4

I have a directive that takes an array of objects. When declaring the directive in markup, the scope has an array of objects that wrap the ones needed by the directive. So I need to apply a map function on the array. What's the right way to do this so updates made to the original array are reflected inside the directive?

Here's a Plunker with a naive approach (which I was surprised to see mostly "work" except for lots of $digest errors): http://plnkr.co/edit/GUCZ3c

2 Answers 2

4

You should avoid calling a function from an Angular expression unless, among other things, that function does some very lightweight work (a quick computation, for instance). This question has more details on that matter.

In your case, you should cache the name list and bind it to the directive. Here's an example:

app.controller('MainCtrl', function($scope) {  
    $scope.people = [
        { name: 'Alice'}, 
        { name: 'Bob' },
        { name: 'Chuck' }
    ];

    $scope.addName = function(name) {
        $scope.people.push({name: name});
    };

    $scope.$watch(function() { return $scope.people.length; }, function() {
        $scope.names = $scope.people.map(function(p) { return p.name; });
    });
});

Your directive code remains the same. Here's a fork of your Plunker.

Update: I've changed the code so it uses a $watch to update the name list automatically, following @KrisBraun advice.

Sign up to request clarification or add additional context in comments.

3 Comments

Good point about caching. Using a $watch on the underlying array to update the cache would make it cleaner, too, and handle changes outside that controller.
You're right about using a $watch to update the cache automatically. I'll update the post to use it.
Nice idea using $scope.people.length for the $watch which will be more space and speed efficient than the whole array. There is a risk other code might add and remove elements yielding a different list with the same length, but knowing my current code that shouldn't be an issue for me.
1

I believe you need to treat expressions a little differently when binding them to your isolated scope. Here is a forked plunker.

app.directive('myDirective', function($compile) {
  return {
    restrict: 'E',
    scope: {
      names: '&namesFunc'
    },
    template: '<div><p>JSON: {{names() | json}}</p><p>Names:</p><ul><li ng-repeat="name in names()">{{name}}</li></ul></div>',
    replace: true,
    link: function(scope, elem, attr, ctrl) {
    }
  };
});

6 Comments

Ah, I should have tried that. For some reason I had thought & bindings weren't updated automatically, but clearly they are.
Any idea if there are efficiency differences between the two binding methods?
& bindings are not updated. They just provide a way to execute an expression in the context of the parent scope. In this case, the directive is calling getName() defined in the parent scope.
Here is some information on when expressions can cause performance/integrity issues. thenittygritty.co/angularjs-pitfalls-using-scopes Read "Pitfall #1: Scope digester and expressions"
That article has good insights, but I disagree with 'DO NOT use functions in expressions". Instead, it should've said "DO NOT use functions that aren't lightweight in expressions". I asked a question precisely on how bad is to call functions in Angular expressions a while ago, and I got one answer that pretty much sums it up, IMO.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.