Yair Tavor
Yair Tavor

Reputation: 2538

Angular directive with two way binding, filter and ng-repeat

I'm trying to create a directive that accepts an array of objects and run ng-repeat to render them.

app.directive("rockers", function(){
    return {
        restrict : "E",
        replace : true,
        scope : {
            items : '='
        },
        template : '<div>'+
                       '<span ng-repeat="item in items">{{item.name}} Rocks!</span>'+
                    '</div>'

    };
});

In the controller, I set the scope with opjects:

app.controller("appController", function($scope){
    $scope.scopedItems = [{name:"Aanny"}, {name:"Bonny"}, {name:"Danny"}];
});

And then, to call the directive I pass the scopedItems with filter, like this:

<div ng-app="myApp" ng-controller="appController">
    Rockers: 
    <rockers items="scopedItems | filter:{name:'Bonny'}"></rockers>
</div>

Without using the filters in the HTML, everything works fine. When passing the filter I still get the results I want, but I'm also getting this error: "Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!"

What am I doing wrong?

Here is a jsFiddle link to recreate the error.

Thanks!

Upvotes: 3

Views: 5241

Answers (4)

Daniel W.
Daniel W.

Reputation: 563

I found a very simple solution involving adding another two-way bound scope variable to the directive and filtering inside the directive template instead of inside the html:

<div ng-app="myApp" ng-controller="appController">
    <input type="text" ng-model="find"><br>
    Rockers: 
    <rockers items="scopedItems" filter="find"></rockers>
</div>

Angular gives digest errors when a filtered array expression is passed in as a two-way bound directive scope variable. By using the filter attribute, you can filter anything you type in the input field, or use a $scope object to defined your filter e.g. $scope.find = {name: 'Bonny'}; You could even use a scope filter function or pre-defined filter, anything you would pass into a real filter expression will work because of the two-way binding.

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

app.controller("appController", function($scope){
    $scope.scopedItems = [{name:"Aanny"}, {name:"Bonny"}, {name:"Danny"}];
});

app.directive("rockers", function($filter){
    return {
        restrict : "E",
        replace : true,
        scope : {
            items : '=',
            filter: '='
        },
        template : '<div>'+
                       '<span ng-repeat="item in items | filter:filter">{{item.name}} Rocks!</span>'+
                    '</div>'
    };
});

Working example: http://jsfiddle.net/2jhswna6/

Upvotes: 0

tazmaniax
tazmaniax

Reputation: 416

You could also use memoization in a custom filter to return the calculated value unless the input value changes.

See this example: Memoizing Angular filters to stop digest errors

Upvotes: 0

Goodnickoff
Goodnickoff

Reputation: 2267

Also you can pass filtered data to directive like a string:

<rockers items="{{ scopedItems | filter:{name:'Bonny'} }}"></rockers>

and parse it value to object it directive:

app.directive("rockers", function(){
    return {
        restrict : "E",
        replace : true,
        scope : {},
        link:function(scope, elem, attr){
            scope.items = JSON.parse(attr.items);
        },
        template : '<div>'+
                       '<span ng-repeat="item in items">{{item.name}} Rocks!</span>'+
                    '</div>'

    };
});

http://jsfiddle.net/34ag7/4/

Upvotes: 3

Goodnickoff
Goodnickoff

Reputation: 2267

You can pass rocker name into directive by attribute and filter it there:

<div ng-app="myApp" ng-controller="appController">
    Rockers: 
    <rockers name="Bonny" items="scopedItems"></rockers>
</div>

And in directive:

app.directive("rockers", function(){
    return {
        restrict : "E",
        replace : true,
        scope : {
            items : '='
        },
        link:function(scope, elem, attr){
            scope.filteredItems = function(filterItemName){
                return scope.items.filter(function(item){
                    return item.name == filterItemName;
                })
            };
            scope.filteredItem = scope.filteredItems(attr.name);
        },


        template : '<div>'+
                       '<span ng-repeat="item in filteredItem">{{item.name}} Rocks!</span>'+
                    '</div>'

    };
});

http://jsfiddle.net/b3dc9/39/

Upvotes: 1

Related Questions