Search code examples
javascriptangularjsangularjs-directiveangularjs-scopeangularjs-controller

AngularJS: how to bind a constant object to a directive


I've created a directive with a binding using "scope". In some cases, I want to bind a constant object. For instance, with HTML:

<div ng-controller="Ctrl">
    <greeting person="{firstName: 'Bob', lastName: 'Jones'}"></greeting>
</div>

and JavaScript:

var app = angular.module('myApp', []);

app.controller("Ctrl", function($scope) {

});

app.directive("greeting", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            person: "="
        },
        template:
        '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
    };
});

Although this works, it also causes a JavaScript error:

Error: 10 $digest() iterations reached. Aborting!

(Fiddle demonstrating the problem)

What's the correct way to bind a constant object without causing the error?


Solution

  • Here's the solution I came up with, based on @sh0ber's answer:

    Implement a custom link function. If the attribute is valid JSON, then it's a constant value, so we only evaluate it once. Otherwise, watch and update the value as normal (in other words, try to behave as a = binding). scope needs to be set to true to make sure that the assigned value only affects this instance of the directive.

    (Example on jsFiddle)

    HTML:

    <div ng-controller="Ctrl">
        <greeting person='{"firstName": "Bob", "lastName": "Jones"}'></greeting>
        <greeting person="jim"></greeting>
    </div>
    

    JavaScript:

    var app = angular.module('myApp', []);
    
    app.controller("Ctrl", function($scope) {
        $scope.jim = {firstName: 'Jim', lastName: "Bloggs"};
    });
    
    app.directive("greeting", function () {
        return {
            restrict: "E",
            replace: true,
            scope: true,
            link: function(scope, elements, attrs) {
                try {
                    scope.person = JSON.parse(attrs.person);
                } catch (e) {
                    scope.$watch(function() {
                        return scope.$parent.$eval(attrs.person);
                    }, function(newValue, oldValue) {
                        scope.person = newValue;
                    });
                }   
            },
            template: '<p>Hello {{person.firstName}} {{person.lastName}}</p>'
        };
    });