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.
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.
You can consider using transclusion inside a custom directive, to achieve the behavior you are looking for without using ng-repeat.
Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
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.
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';
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With