MyController has clickedvalue as false in its scope.
the click changes it to true
the dir directive watches for change on the clicked attribute.
How is the $watch seeing change - or how is the clickvalue changing without being interpolation markup {{clickvalue}}
right now click="clickvalue" triggers the $watch which would mean it is 'bound' to the MyContoller's clickvalue
<div ng-controller='MyController'>
<button ng-click='click()'>Call function inside directive</button>
<dir clicked="clickedvalue"></dir>
</div>
app.js
app.controller('MyController', ['$scope', function($scope) {
$scope.clickedvalue = false;
$scope.click = function() {
$scope.clickedvalue = !$scope.clickedvalue;
};
}
]);
appDirectives = angular.module('app.directives', []);
appDirectives.directive('dir', [function() {
return {
restrict: 'E',
scope: {
clicked: '='
},
link:function(scope, element, attrs ) {
scope.$watch('clicked', function() {
console.log('innerFunc called');
})
}
};
}]);
If I understood you well, the '=' does not require you to use {{interpolation}}, '#' would.
Moreover, take note that you set up the $watch on attribute, not on the controller's clickedvalue.
In angular js {{}} means one way binding & when you are using directive and declaring isolated scope with using = that means two way binding of the scope variables. If one of variable changes then the relative scope updates.
scope: {
clicked: '='
},
In your case clicked has been mapped with clickedvalue.As value of clicked gets update, it will also update the value of clickedvalue which is isolated scope directive
Related
I want to pass object (reference - two way binding) through ATTRIBUTE not by isolated scope. How can I do this? Because code bellow passing string instead of object:
HTML
<tr ng-form="rowForm" myDirective="{{row.data}}">
Directive
angular.module("app").directive("myDirective", function () {
return {
require: ["^form"],
restrict: "A",
link: function (scope, element, attrs, ctrls) {
scope.$watch(function () {
return attrs.myDirective;
}, function (newValue, oldValue) {
// .....
Directives can do two-way data binding without parsing or compile anything manually, sorry for not delivering the plunker but it's rebelius and won't save for me
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myObj = {name: 'Tibro', age: 255}
})
.directive('myDirective', function(){
return {
scope: {
'myAttribute': '='
},
template: '{{myAttribute}}',
link: function(scope){
scope.myAttribute.age= 31
}
}
})
HTML
<body ng-controller="MainCtrl">
controller: {{myObj}} <br/>
directive: <my-directive my-attribute="myObj"></my-directive>
</body>
OUTPUT
controller: {"name":"Tibro","age":31}
directive: {"name":"Tibro","age":31}
you can see from the output that passed object has been binded two-way and change made in directive is reflected on controller level
The result of {{ }} interpolation is a string. An object can't be passed like that.
Bindings are idiomatic here and thus preferable. The whole thing becomes messy when the directive is forced to use parent scope. However, it can be done by parsing scope properties manually with $parse:
$scope.$watch(function () {
var myDirectiveGetter = $parse($attrs.myDirective);
return myDirectiveGetter($scope);
}, ...);
This is a job for binding (< or =, depending on the case). If isolated scope isn't desirable, this can be done with inherited scope and bindToController:
scope: true,
bindToController: {
myDirective: '<'
},
controllerAs: `vm`,
controller: function ($scope) {
$scope.$watch('vm.myDirective', ...);
}
Notice that directive attribute is my-directive, not myDirective.
Couple of things:
myDirective in the tr should be my-directive, as per Angular's
conventions.
{{row.data}} prints the variable, you need to pass it without the
{{}} for it to go through as an object.
Changes to my scope variable foo are getting updated in the html. When that value is change inside the scope of a directive's controller, it isn't updating in the html.
What do I need to do to make it update?
I have a simple example:
app.js
var app = angular.module('app', []);
app.controller('ctrl', function($scope) {
$scope.foo = 99;
$scope.changeValue = function() {
$scope.foo = $scope.foo + 1;
}
});
app.directive('d1', function(){
return {
restrict: 'E',
scope: {
theFoo: '='
},
templateUrl: 'd1.html',
controller: 'd1Ctrl',
}
});
app.controller('d1Ctrl', function($scope) {
$scope.test = $scope.theFoo;
});
d1.html
<div>
<p>The value of foo is '{{theFoo}}'.</p>
<p>The value of test is '{{test}}'.</p>
</div>
inside index.html
<d1 the-foo='foo'>
</d1>
<button ng-click='changeValue()'>change value</button>
So in summary, {{theFoo}} is updating, but {{test}} isn't. Why?
The reason is that $scope.foo value is a primitive.
In the directive controller you only assign $scope.test once when controller initializes. Primitives have no inheritance the way objects do so there is nothing that would change $scope.test after that initial assignment
If you used an object instead to pass in ... inheritance would be in effect and you would see changes...otherwise you would need to watch $scope.theFoo and do updates to $scope.test yourself
The code you have in your controller only initializes to that value if it is indeed set at the time the controller is linked. Any subsequent changes are not going to work.
If you want to bind any subsequent changes, then you need to set a $watch statement either in your controller or a link function.
$scope.$watch( 'theFoo', function(val){ $scope.test = val; })
updated plunker - http://plnkr.co/edit/eWoPutIJrwxZj9XJu6QG?p=preview
here you have isolated the scope of the directive, so test is not visible to the d1.html, if you need to change test along with the theFoo you must first make it visible to the directive by
app.directive('d1', function(){
return {
restrict: 'E',
scope: {
theFoo: '=',
test : '=' //getting test as well
},
templateUrl: 'd1.html',
controller: 'd1Ctrl',
}
});
and in index.html you should pass the value to the test by
<d1 the-foo='foo' test='foo'></d1>
in the above code your controller is not much of a use , code will work fine even without this part controller: 'd1Ctrl'.
with this example you dont have to use $watch.
I am currently working on extending the UX of Angular Typeahead. I made it possible for the results list not to get cleared on input .blur and remain in the DOM until the query changes.
But this way the results do not disappear. And I want to bind the input.blur() event to the variable, that is later on passed to the scope that has control over the typeahead-popup.html module.
Here's the adapted relevant-code of what I currently have:
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
.directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$position', 'typeaheadParser',
function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) {
return {
link: function(originalScope, element, attrs, ctrls) {
var isBlurred;
//some irrelevant library code
//on this event I change isBlurred;
element.bind('blur', function() {
//irrelevant functionality
isBlurred = true;
});
element.bind('focus', function() {
isBlurred = false;
});
}
}
}
}])
.directive('typeaheadPopup', function() {
return {
scope {
//<irrelevant other variables>
isBlurred: '='
}
},
replace: true,
//<irrelevant code>
link: function(scope, element, attrs) {
//<irrelevant other functions that have access to the scope and apply params at the DOM element>
scope.$watch('isBlurred', function() {
console.log(isBlurred)
});
});
})
});
Directive typeaheadPopup has control over the relevant DOM elements. Therefore the variable change from the .blur and .focus events on the input. But this doesn't happen. I just get wasBlurred is not defined.
Question:
How do I adjust the code in a way so that variable change that happens in typeahead directive is reflected properly within typeaheadPopup directive?
Here is a working Plunker with ui-bootstrap.
Here is an integration with ui-bootstrap and a close button control. Plunker
Here is a working Plunker with the code you gave and the question about how to communicate between directives.
Your isBlurred variable needs to be assigned to the scope :
originalScope.isBlurred;
Blur and focus are not part of the ngModelOptions that will trigger the scope to automaticaly change. You need to trigger it manualy.
element.on('blur', function() {
originalScope.$apply(function(){
originalScope.isBlurred = true;
});
});
Now your isBlurred is linked to the scope and that it will update with your custom events, you can forward it to your typeaheadPopup directive this way :
<input type="text" ng-model="name" typeahead typeahead-popup="isBlurred">
Define a isolated scope in your typeaheadPopup :
scope : {
isBlurred: '=typeaheadPopup'
}
There it is : your isBlurred variable is accessible and will automatically be updated in your directive. No need for a $watch. But you'll need one if you want to trigger an event when it changes :
scope.$watch('isBlurred', function() {
alert("inside typeaheadPopup directive" + scope.isBlurred)
});
The general answer is for all non-Angular event-handlers (in your case it's element.bind) you should call scope.$apply (or scope.$digest) to run the digest cycle and synchronise the values.
So for your case you either do manual call:
element.on('blur', function() {
originalScope.isBlurred = true;
originalScope.$apply();
});
or to run Angular method that will do it for you, for example $timeout:
element.on('blur', function() {
$timeout(function(){
originalScope.isBlurred = true;
});
});
$timeout sometimes is needed like a hack to integrate 3rd party non-Angular libraries.
If you can nest your directives, or use one as an attribute on the other, then they are able to communicate more cleanly. If they exist on the same level, then I believe some injected share state is you best bet.
If possible, nest the directives and make them communicate as described under "Creating Directives that Communicate" (at the bottom) of the documentation.
Example:
angular.module('foo', [])
.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
controller: function() {
this.someFunction = function(pane) {
};
}
};
})
.directive('child', function() {
return {
require: '^^parent',
restrict: 'E',
transclude: true,
link: function(scope, element, attrs, parentCtrl) {
parentCtrl.someFunction();
}
};
});
I guess You need something like this demo right? So you need to assign a two-way bounded scope in each directive like this
scope: {
model: "="
},
then set both of them with the same model
<body ng-controller='MainController as vm'>
<foo model='vm.foobar'></foo>
<bar model='vm.foobar'></bar>
</body>
so MainController's foobar is set two-way bounded to foo and bar's scope, so technically, they share the same model and have every changes the model have.
please see the demo for the full code
I have a custom directive which is sometimes hidden using ng-hide:
<my-custom-directive ng-show="vm.showBox"
value="vm.objects"
></my-custom-directive>
A snippet from my custom directive code:
function myCustomDirective() {
var directive = {
controller: controller,
controllerAs: 'vm',
///...
scope: {
value: '='
}
};
return directive;
function controller($scope) {
var vm = this;
///...
$scope.value.dates = $scope.value.dates || [];
}
}
The problem: even when the directive isn't supposed to be loaded/displayed (because vm.showBox is false), the custom directive's controller code runs and in such case it fails because $scope.value isn't passed (it's undefined there).
Why does the directive's controller code runs anyway, if the directive is hidden? I want to assume that if the directive is used, it's given valid parameters without having to check whether $scope.value is defined.
ng-show controls visibility of element (change of css property display) but element still exists in DOM. to remove/create element you can use ng-if instead of ng-show
ng-show only controls display property of element. It does not prevent element to render in DOM. To prevent it you can use ng-if.
But the problem with ng-if is that according to condition every it destroy scope and creates new one.
To prevent this problem you should use ng-show.
In your case you can use watch expressin in your directive.
<my-custom-directive ng-show="vm.showBox"
value="vm.objects"
></my-custom-directive>
function myCustomDirective() {
var directive = {
controller: controller,
controllerAs: 'vm',
///...
scope: {
value: '='
}
};
return directive;
function controller($scope) {
var vm = this;
///...
var watchExpression = $scope.$watch('value', function(newValue, oldValue){
if(newValue)
{
$scope.value.dates = $scope.value.dates || [];
watchExpression(); //de-register watch expression
}
});
}
}
This has caught me with a surprise. AngularJS directive is not setting a local scope if it's being used with isolated scope.
I've a amount field which is being validated. The validation message is within a directive which watches for the amount, if greater than 5 it should display an error message. However the error message is not displayed, but the template responses to ng-if which has property declared on local scope.
Below is the code and PLUNKR
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.vm = {};
});
app.directive('errorMessage', function() {
return {
restrict: "E",
replace: true,
templateUrl: 'my-template.tpl.html',
scope: {
amount: '='
},
link: function(scope, element, attrs) {
scope.isAmountError = true;
scope.$watch('amount', function() {
if (scope.amount > 5) {
scope.isAmountError = true;
scope.errorText = 'Amount lesser than 5';
} else {
scope.isAmountError = false;
}
});
}
};
});
Your problem is ng-if. From the API page at https://docs.angularjs.org/api/ng/directive/ngIf
Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored.
So each time ng-if is evaluating to true, a new scope is created which does not reference your errorText. On the other hand, if you use ng-show, it hides/shows but keeps the scope, and all works fine.
<div class="row" ng-show="isAmountError">
Here is the updated plunkr http://plnkr.co/edit/BsSueLNpWA8XveyxblmE?p=preview