Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change Attribute From within Directive

Tags:

angularjs

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?

like image 776
Justin Noel Avatar asked Sep 06 '13 22:09

Justin Noel


People also ask

Which directive lets you modify the behavior of another directive?

Attribute directives - change the appearance or behavior of an element, component, or another directive.

What is directive attribute?

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.

What is the difference between structural directive and attribute directive?

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.

How do you create an attribute directive?

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.


2 Answers

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.

$observeshould 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.

like image 22
Michael Benford Avatar answered Sep 22 '22 13:09

Michael Benford


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)

like image 136
Denison Luz Avatar answered Sep 19 '22 13:09

Denison Luz