Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS directive with ng-repeat not rendering

The problem is that I have to manage a list of gum balls that is retrieved from a service. The directive I've created seems to work when I hardcode the elements in the HTML, but when I attempt to dynamically allocate the gum balls using a ng-repeat.

HTML

<div ng-controller="GumballsCtrl">

<h1>Working</h1> 
    <ul>
        <li ng-repeat="gumball in Gumballs">
            <div class="gumballColor{{gumball.color}}">{{gumball.color}}</div>
        </li>
    </ul>

<h1>Problem - Expecting the same result at the work version</h1>

    <ul>
        <li ng-repeat="gumball in Gumballs">
            <mygumball id={{gumball.id}} color="{{gumball.color}}">{{gumball.color}}</mygumball>
        </li>
    </ul>
</div>

JavaScript

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

function GumballsCtrl($scope, Gumballs) {
    $scope.Gumballs = Gumballs;
}

myApp.factory('Gumballs', function () {
    return [{
        id: '1',
        color: 'R'
    }, {
        id: '2',
        color: 'G'
    }, {
        id: '3',
        color: 'B'
    }, {
        id: '4',
        color: 'Y'
    }, {
        id: '5',
        color: 'G'
    }];
});

myApp.directive('mygumball', function ($scope) {
    return {
        restrict: 'E',

        scope: {},

        link: function (scope, element, attrs) {
            if (attrs.color !== '' && attrs.color !== undefined) {
                scope.color = attrs.color;
            } else {
                scope.color = 'U';
            }
        },

        replace: true,

        template: "<div class='gumballColor{{color}}'>{{color}}</div>"
    };
});

CSS

.gumballColorR {
    font-size: 12px;
    text-align: center;
    padding: 2px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: solid 1px #CC0000;
    background-color: #FF0000;
    width: 15px;
    height: 15px;
    margin-left: 5px;
    margin-top: 5px;
}
.gumballColorG {
    font-size: 12px;
    text-align: center;
    padding: 2px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: solid 1px #00CC00;
    background-color: #00FF00;
    width: 15px;
    height: 15px;
    margin-left: 5px;
    margin-top: 5px;
}
.gumballColorB {
    font-size: 12px;
    text-align: center;
    padding: 2px;
    color: #FFFFFF;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: solid 1px #0000CC;
    background-color: #0000FF;
    width: 15px;
    height: 15px;
    margin-left: 5px;
    margin-top: 5px;
}
.gumballColorY {
    font-size: 12px;
    text-align: center;
    padding: 2px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: solid 1px #CCCC00;
    background-color: #FFFF00;
    width: 15px;
    height: 15px;
    margin-left: 5px;
    margin-top: 5px;
}
.gumballColorU {
    font-size: 12px;
    text-align: center;
    padding: 2px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: solid 1px #CCCCCC;
    background-color: #DDDDDD;
    width: 15px;
    height: 15px;
    margin-left: 5px;
    margin-top: 5px;
}

http://jsfiddle.net/i3sik/NGB9v/22/

The id and color attributes when passed into the directive end up being undefined when passed using the ng-repeat, but work when hardcoded in the HTML.

like image 741
user2247790 Avatar asked Apr 05 '13 06:04

user2247790


People also ask

What is directive in AngularJS explain ng-repeat directive with an example?

AngularJS ng-repeat Directive The ng-repeat directive repeats a set of HTML, a given number of times. The set of HTML will be repeated once per item in a collection. The collection must be an array or an object. Note: Each instance of the repetition is given its own scope, which consist of the current item.

What can I use instead of NG-repeat?

You can consider using transclusion inside a custom directive, to achieve the behavior you are looking for without using ng-repeat.

Does ng-repeat create a new scope?

Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.

How do I get the index of an element in NG-repeat?

Note: The $index variable is used to get the Index of the Row created by ng-repeat directive. Each row of the HTML Table consists of a Button which has been assigned ng-click directive. The $index variable is passed as parameter to the GetRowIndex function.


1 Answers

The problem here is your isolate scope. By using scope: {} you created a new, isolate scope to act on that element. Isolate scopes don't inherit from the parent scope. All attributes and content on directives with isolate scopes are evaluated within the context of the isolate scope. gumball doesn't exist in the isolate scope, so everything comes up as undefined.

You have two choices to fix this: (1) remove the isolate scope (e.g. scope: true to create a child scope); or (2) bind the values in your isolate scope.

To bind your attributes to scope variables, you simply need to specify the scope and the kind of binding you want:

scope: {
  id: '@',
  color: '@'
},

This says that the attributes id and color are to be interpolated in the context of the parent scope and then added to the scope. You can remove all that logic inside your link function - this will do it for you.

But this still leaves the problem of the content inside the directive. To interpolate that in the context of the parent scope, you need transclusion:

transclude: true,
template: "<div class='gumballColor{{color}}' ng-transclude></div>"

Transclusion takes the contents of the element and interpolates relative to a new child of the parent scope, e.g. where gumball would still be defined.

With these two changes, your directive will work as desired.

If you're confused about which scope to use, here's another SO question that might help: When writing a directive, how do I decide if a need no new scope, a new child scope, or a new isolate scope?


Side note: Even without the isolate scope, the logic in your link function to determine attribute values wouldn't work. Order of execution is the important part here, which is roughly: compiler -> controller -> link -> interpolation. Until the interpolation is done, there is no value for your attributes. So your checks won't work.

That said, you can set up an $observe on interpolated attributes; the $observe will always fire the first time, even if there was no value passed. You can use this to set your default. $observe is also very efficient.

attrs.$observe( 'attr1', function(val) {
  if ( !angular.isDefined( val ) ) {
    scope.attr1 = 'defaultValue';
  }
});
like image 93
Josh David Miller Avatar answered Oct 17 '22 13:10

Josh David Miller