Using AngularJS. I have a directive that I want to have two way data binding. The directive will have an attribute called "activate". Initially, the value of "activate" will be "1".
The directive's link function will check if "activate" is equal to "1". If so, it will change "activate" to 0, but do some other stuff.
Later, if I want the directive to do some stuff again, in the controller, I will change "activate" to "1" again. Since the directive has the watch, it will repeat the cycle.
Unfortunately, every time I do this, I get "Expression '0' used with directive 'testDirective' is non-assignable!" or "Non-assignable model expression: 1 (directive: testDirective)".
Here is HTML:
<body ng-app="app">
<test-directive activate="1"></test-directive>
</body>
Here is JS :
var app = angular.module('app', []);
app.directive('testDirective', function() {
return {
restrict: 'E',
scope: {
activate : '='
},
link: function( scope, elem, attrs, controller ) {
var el = elem[0];
var updateContent = function() {
el.innerText = 'Activate=' + scope.activate;
};
updateContent();
attrs.$observe( 'activate', function() {
console.log('Activate=' + scope.activate);
if( scope.activate == '1') {
scope.activate = '0'
updateContent();
}
});
}
}
});
Here it is on jsFiddle : http://jsfiddle.net/justbn/mgSpY/3/
Why can't I change the value stored in the directive's attribute? I'm using 2 way binding.
The docs say " If the parent scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception."
NOTE: The update content properly shows the value of "activate". However, the value of "activate" in "" does not update.
However that makes no sense to me as the parent scope property DOES exist.
Any ideas?
Attribute directives - change the appearance or behavior of an element, component, or another directive.
Attribute directives are used to change the appearance or behavior of an element. Examples of attributes directives are ngStyle , ngClass , ngModel. ngStyle — used to apply styles that will change the appearance. ngClass — used to apply CSS classes to change the appearance.
There are two types of directives in Angular. Attribute directives modify the appearance or behavior of DOM elements. Structural directives add or remove elements from the DOM.
Steps to create a custom attribute directiveAssign the attribute directive name to the selector metadata of @Directive decorator. Use ElementRef class to access DOM to change host element appearance and behavior. Use @Input() decorator to accept user input in our custom directive.
To monitor a property of the scope, you should use scope.$watch
instead of attrs.$observe
:
link: function(scope, elem, attrs, controller) {
var el = elem[0];
var updateContent = function() {
el.innerText = 'Activate=' + scope.activate;
};
scope.$watch('activate', function(value) {
console.log('Activate=', value);
if(value === 1) {
scope.activate = '0'
}
updateContent();
});
}
jsFiddle here.
Notice that I've removed the updateContent
call from the link function, because you can only safely access a property of the scope inside a $watch
callback, unless you can ensure that the value bound to that property is available before the directive is processed by Angular.
$observe
should only be used to observe/watch the value changes of a DOM attribute that contains interpolation (for instance, value="{{ value }}"
). Check out this SO question to understand it better.
Although I agree with the use of $watch
instead of attrs.$observe
that's not the main reason for the error message you are getting.
The problem is that you are trying to assign a value to a non-assignable expression - as the error message tells you : Non-assignable model expression: 1 (directive: testDirective)
The non-assignable expression in this case is the number "1"
<test-directive activate="1">
You manage to pass the initial value (1) to the directive, but when the directive tries to update that value the attribute activate can't be changed - because it's a number.
So what you need to do is to change that to a variable, so you can update the value later.
See the code below where I initialise a variable in the $scope called activate.initialValue via a controller.
And I have also used $watch
instead of attrs.$observe
.
I've added the $timeout
just to simulate an event to change the activate value after 2 seconds.
HTML
<body ng-app="app" ng-controller="appCtrl">
<test-directive activate="activate.initialValue"></test-directive>
</body>
JS
var app = angular.module('app', []);
app.controller('appCtrl', function($scope){
$scope.activate = {
initialValue : 1
}
});
var app = angular.module('app', []);
app.controller('appCtrl', function($scope){
$scope.activate = {
initialValue : 2
}
});
app.directive('testDirective', function($timeout) {
return {
restrict: 'E',
scope: {
activate : '='
},
link: function(scope, elem, attrs) {
scope.$watch('activate', function(newValue, oldValue){
console.log('activate has changed', newValue);
});
$timeout(function(){
scope.activate = 0;
}, 2000);
},
template: "{{activate}}"
}
});
You can also see working here (http://jsfiddle.net/mgSpY/63/).
And here it is AngularJS official documentation about it (http://docs.angularjs.org/error/ngModel:nonassign)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With