Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to watch attribute changes triggered from outside the AngularJS world?

I’m trying to understand interactions between the Angular world and the non-Angular world.

Given a directive that one declares like this:

<dir1 id="d1" attr1="100"/>

If code outside angular changes the directive this way:

$("#d1").attr("attr1", 1000);

How can the directive know that one of its attribute has changed?

like image 990
Sylvain Avatar asked Jun 18 '13 02:06

Sylvain


2 Answers

It would be best to make this change inside the directive instead. If, for whatever reason, that's not possible, then there are a couple of options.

Outside the app, get a reference to any DOM element within the app. Using that reference, you can then get a reference to its scope. You could use your element with id d1. For example:

var domElement = document.getElementById('d1');
var scope = angular.element(domElement).scope();

Here are a couple of options:

Option 1

Modify the model instead of making a direct change to the view. In the link function, store the initial attribute value in a scope variable like:

scope.myvalue = attrs.attr1;

Then you can change the value outside the app (using the above reference to scope) like:

scope.$apply(function(){
    scope.myvalue = 1000;
    console.log('attribute changed');
});

Here is a fiddle

Option 2

If the view is manipulated directly with jQuery, I don't know of any use of $observe, $watch, or an isolate scope binding to the attribute that will work, because they all bind to the attribute expression itself, just once, when the link function is first run. Changing the value will cause those bindings to fail. So you'd have to $watch the attribute on the DOM element itself (rather than through attrs):

scope.$watch(function(){         
    return $(el).attr('attr1');    // Set a watch on the actual DOM value
}, function(newVal){
    scope.message = newVal;
});

Then you can change the value outside the app (using the above reference to scope) like:

scope.$apply(function(){
    $("#d1").attr("attr1",1000);
});

Here is a fiddle

like image 64
Dan Avatar answered Nov 13 '22 22:11

Dan


Use a Web Components library like x-tags by Mozilla or Polymer by Google. This option works without maunally calling $scope.$apply every time the attribute changes.

I use x-tags because of their wider browser support. While defining a new custom tag (directive) you can set the option lifecycle.attributeChanged to a callback function, which will fire every time an argument is changed.

The official docs aren't very helpful. But by trial and error and diving into the code I managed to find out how it works.

The callback function's context (the this object) is the element itself. The one whose attribute has changed. The callback can take three arguments:

  • name – the name of the attribute,
  • oldValue and
  • newValue – these speak for themselves.

So now, down to business:

The code

This will watch the attribute for changes:

xtag.register('dir1', {
    lifecycle: {
        attributeChanged: function (attribute, changedFrom, changedTo) {
            // Find the element's scope
            var scope = angular.element(this).scope();

            // Update the scope if our attribute has changed
            scope.$apply(function () {
                if (attribute == 'attr1') scope.style = changedTo;
            });
        }
    }
});

The attributeChanged callback only fires when the arguments' values actually change. To get their initial values you need to scan the lot manually. The easiest way seems to be while defining the directive:

myApp.directive('dir1', function () {
    return {
        ... ,
        link: function (scope, element, attributes) {
            scope.attr1 = element[0].getAttribute('attr1');
        }
    };
});
like image 1
tomekwi Avatar answered Nov 13 '22 22:11

tomekwi