How do I update a scope attributes in the link method? - angularjs

This code is just a test that I'm putting together so I can understand the logic.
Basically what I want to happen is to get a value passed into the directive. Based on that value, I want to add several attributes to the scope.
This first test is just to update the attribute that was already passed in.
Directive:
angular.module('bd.common')
.directive('bdPopoverLegend', function () {
return {
restrict: 'AE',
replace: true,
scope: {
legendInfo: '=info'
},
templateUrl: '/bd/common/ctl/bdPopoverLegend.html',
link: function (scope, element, attrs) {
scope.legendInfo.test = 'hello world 4';
}
};
});
Template:
<div>{{legendInfo.test}}</div>
Calling the Directive:
<div bd-popover-legend="" info="{test: 'hello world 3'}">
The value never changes, it stays as 'hello world 3' and not 'hello world 4'

Try this:
.directive('bdPopoverLegend', function () {
return {
restrict: 'AE',
replace: true,
scope: {
legendInfo: '=info'
},
templateUrl: '/bd/common/ctl/bdPopoverLegend.html',
link: function (scope, element, attrs) {
scope.$watch('legendInfo', function (watchInfo) {
watchInfo.test = 'hello world 4';
});
}
};
});

Related

Passing arguments to nested directives in Angular

I am trying to pass arguments from a parent directive to a child directive but the arguments seem to be undefined. Is there a right way to do this?
}).directive('flipper', function ($timeout) {
return {
template: '<div class="flipper" ng-class="{big: big}"><div class="inner animated " ng-style="rotatorTop()"><ul><li ng-repeat="app in apps">{{app.name}}</li></ul></div></div>',
restrict: 'E',
replace: false,
scope: {
apps: "=",
big: "=",
sizes: "="
},
link: function ($scope, $element, $attrs) {
// undefined!
alert($scope.apps);
...
}
};
}).directive('slogan', function ($window) {
return {
template: '<div class="message"><div class="message-big"><div class="black">{{lines[0]}}<flipper apps="apps" class="big" sizes="flipperSize"></flipper>' +
'</div><div class="black">{{lines[1]}}</div><div class="black">{{lines[2]}}</div></div></div>',
restrict: 'E',
replace: false,
transclude: true,
scope: {
apps: "=",
lines: "="
},
link: function ($scope, $element, $attrs) {
...
}
};
});
Yes there is. As your code is quite incomplete, I've made this simple directive to demonstrate how you can pass values from the controller to a parent directive and a child directive.
myApp.directive('cdParent', function() {
return {
restrict: "AE",
template: "<div cd-child option=\"option\" text=\"text\">",
scope: {
option: "="
},
link: function(scope, elem, attrs) {
console.log("inside parent directive: " + scope.option); // will log the color for the controller
scope.text = "Hello nested directives";
}
}
});
myApp.directive('cdChild', function() {
return {
restrict: "AE",
template: "<pre style=\"color: {{option}}\">{{text}} ({{option}})</pre>",
scope: {
option: "=",
text: "="
},
link: function(scope, elem, attrs) {
console.log("inside child directive: " + scope.option);
}
}
});
also, the link function doesn't do dependency injection as a controller does, so it's arguments should be scope, element, attrs not $scope, $element, $attrs, argument order makes a difference here

AngularJS - How to pass html attribute variable value to directive?

...
...
UPDATE
HTML
<my-directive ng-repeat="item in items = ( data | filter: {isExists: true})">
something
</my-directive>
<my-second-directive counter="{{items.length}}"></my-second-directive>
JS
angular.module('directives')
.directive('myDirective', function () {
...
})
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '#'
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
Excuse me I've not described my question well. My first directive should be ngRepeated, with filter. But in my second directive, I would like to allow to show a counter, how many first directive is currently instantiated, because the user will be able to add and remove instances. So I would like to get the value of the items.length by fly with the second directive. But the link() method of the second directive is fired prior the ngRepeat, so the value of the counter will be an empty string.
Thanks in advance
UPDATE 2
.directive('cardGroupHeader', function($templateCache){
return {
restrict: 'EA',
transclude: true,
replace: true,
require: '^cardGroup',
scope: {
cbiscounter: '=?',
cbcounter: '=?',
cbisarrow: '#?'
},
template: $templateCache.get('card-group-header-tpl.html'),
link: function(scope, $element, $attrs, cardGroupController) {
scope.rowId = cardGroupController.getCurrentId();
console.log(scope.cbcounter);
scope.toggle = function () {
cardGroupController.toggle(scope.rowId)
}
angular.element(document).ready(function () {
console.log(scope.cbcounter);
});
scope.$watch('scope.cbcounter', function (n, o) {
if(n && n != o) {
console.log(n);
}
});
//scope.cbcounter++;
}
};
})
HTML
<card-group-header cbiscounter="true" cbarrow="true" cbcounter="data.length">Waiting for Approval</card-group-header>
<card-group-row cbCollapsed="false">
<card ng-repeat="approveItem in data = (approveItems | filter: {isApproved: false, isRejected: false})">
TEMPLATE
$templateCache.put('card-group-header-tpl.html', '<div class="card-group-header" ng-click="toggle()"><span ng-transclude></span><span class="card-group-counter" ng-if="cbiscounter">{{cbcounter}}</span></div>');
When I change the data.length to 2, this is transferred well. If I use the data.length the scope.cbcounter is always undefined. In case of 2 I've got it back on the console.log(scope.cbcounter);
The counter: '#' means that you are accepting a string value. If you wanted to pass an expression you could either use:
<my-second-directive counter="{{ items.length }}"></my-second-directive>
Or:
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
EDIT:
I finally quite understand the problem! It's because of attributes are not interpolated until after the link phase. You have two following options:
The first option is wrapping every in the link inside $timeout to take it away from the event loop and be executed after DOM finished manipulating:
.directive('mySecondDirective', function ($timeout) {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
$timeout(function() {
alert(scope.counter);
});
}
});
Secondly, using $observe:
attrs.$observe('counter', function(value){
console.log(value);
});
or using $watch as #jusopi suggested.
I think this would be what you want.
Html
<div ng-app="testapp" ng-controller="testctrl">
<div ng-repeat="item in filtereditems">
{{item}}
</div>
<testdir counter="filtereditems.length" />
</div>
Javascript
angular.module('testapp', [])
.directive('testdir', function(){
return {
restrict: 'E',
scope:{
counter: '='
},
link: function(scope, element, attrs) {
alert(scope.counter);
}
}
})
.controller('testctrl', function($scope, $filter){
$scope.items = [
{name: 'A', isExists: true},
{name: 'B', isExists: false},
{name: 'C', isExists: true},
{name: 'D', isExists: false}
];
$scope.filtereditems = $filter('filter')($scope.items, {isExists: true});
})
My jsfiddle is here.
In addition to #LVarayut's answer about the scope's binding expression, the reason the alert is undefined is because linking is not part of the $digest cycle. So binding and data hasn't be effected yet (don't quote me on that, it's the best way I could verbalize what I'm showing in the code below).
Instead you need to use a watcher to trigger the alert
link: ($scope, elem, attrs)->
#undefined because linking isn't part of the $digest cycle
#alert $scope.count
$scope.$watch 'count', (n, o)->
if n and n isnt o
true
#alert n
http://plnkr.co/edit/xt95gb3cTXfUEHgpWK1W?p=preview

How to make a template repeat

I have a directive, I want that template to show all the rows from data set
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<tr><td>{{Name}} </td><td>{{Surname}}</td></tr>',
replace: true,
link: function(scope, elm, attrs) {
var dat= TestFactory.dataReturn();
for (var i = 0; i < dat.length; i++) {
scope.Name = dat[i].Name;
scope.Surname = dat[i].Surname;
console.log(dat[i]);
}
// alert("hah");
}
};
}]);
How can I make it repeat like ng-repeat ?
Assuming your service returning a promise. Here is the simple code to repeat your data in table.
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<table ng-repeat="person in persons"><tr><td>{{person.Name }} </td><td>{{person.Surname}}</td></tr></table>',
replace: true,
link: function(scope, elm, attrs) {
TestFactory.dataReturn().then(function(resp){
scope.persons = resp.data;
});
}
};
}]);
Alternatively, you could redefine your element directive to represent a single object, and then ng-repeat the directive itself. Obviously, this would require moving the location where you use the factory. Your application architecture may or may not be able to accommodate this change.
directive:
app.directive('exampleDirective', [function () {
return {
restrict: 'E',
template: '<tr><td>{{person.Name}} </td><td>{{person.Surname}}</td></tr>',
replace: true,
scope: {
person: '='
}
};
}]);
And usage:
<example-directive ng-repeat="data in dataFromFactory" person="data"></example-directive>

ngModel passed through wrapper to child directive

Let's imagine, we have one child directive <child/>, that takes ng-model and ng-change and does some actions with them. And we have two kind of wrappers <w1/> and <w2/>, that contain <child/>.
W1 should pass ng-model through directly to <child/>
W2 should pass some inner object as model to <child/>
in first case i would use require: '^ngModel'
in second : require: 'ngModel' but i need them to work simultaneously
so model is simple object that can be passed easily through.
<wrapper ng-model="foo"></wrapper>
Wrapper:
module
.directive('wrapper', function () {
return {
restrict: 'E',
template: "<child ng-model='ngModel'></child>",
scope: {
ngModel:'='
},
link: function ($scope) {
}
};
});
Child:
module
.directive('child', function () {
return {
restrict: 'E',
require: 'ngModel',
template: "<div>some wierd stuff</div>",
scope: {
},
link: function ($scope, iElem, iAttr, ngModel) {
var a = ngModel.$viewValue;
}
};
});

What's the right way to use filters with transclusion in directives?

I'm trying to apply a filter to the transcluded text in my directive but I'm not sure what's the best way to do this. A working copy of idea is at the following link but I feel I'm sorting of cheating by using the compile function to get at the transcluded text. See JSFiddle.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
phrase: '#',
text: '#'
},
template: '<span ng-bind-html-unsafe=" text | highlight:phrase:false "></span>',
compile: function (elem, attr, transclude) {
transclude(elem, function (clone) {
// grab content and store in text attribute
var txt = clone[0].textContent;
attr.text = txt;
});
}
};
});
Other way to do would be http://jsfiddle.net/3vknn/, I guess.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
scope: {
phrase: '#'
},
controller: function($scope, $filter) {
$scope.highlight = $filter('highlight');
},
link: function (scope, elem, attr) {
scope.$watch('phrase', function(phrase) {
var html = scope.highlight( elem.text(), phrase );
elem.html( html );
});
}
};
});

Resources