Angularjs binding array element ng-repeat in directive - javascript

I'm trying to display the elements of an array using ng-repeat and a directive. The directive part is important to the solution. However the element of the array is not getting bound and displays an empty value.
The fiddle can be found at http://jsfiddle.net/qrdk9sp5/
HTML
<div ng-app="app" ng-controller="testCtrl">
{{chat.words}}
<test ng-repeat="word in chat.words"></test>
</div>
JS
var app = angular.module('app', []);
app.controller("testCtrl", function($scope) {
$scope.chat = {
words: [
'Anencephalous', 'Borborygm', 'Collywobbles'
]
};
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
template: "<li>{{word}}</li>",
replace: true,
link: function(scope, elm, attrs) {}
}
});
OUTPUT
["Anencephalous","Borborygm","Collywobbles"]
•
•
•
Expected output
["Anencephalous","Borborygm","Collywobbles"]
•Anencephalous
•Borborygm
•Collywobbles
Appreciate your help

You didn't bind word.
You have used isolate scope. If you don't bind with it's scope property,it won't work.
scope: {
word: '='
},
Try like this
<test word="word" ng-repeat="word in chat.words"></test>
DEMO

var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.chat= {words: [
'Anencephalous', 'Borborygm', 'Collywobbles'
]};
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
priority: 1001,
template: "<li>{{word}}</li>",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
Your directive needs to run before ng-repeat by using a higher priority, so when ng-repeat clones the element it is able to pick your modifications.
The section "Reasons behind the compile/link separation" from the Directives user guide have an explanation on how ng-repeat works.
The current ng-repeat priority is 1000, so anything higher than this should do it.

Related

AngularJS Directive two way binding in isolated scope not reflecting in parent scope

I am trying to pass a scope array element to a directive and changing the value of that element inside the directive but when I print the values of the scope element the changes that made inside the directive is not affected in the parent scope. I created Isolated scope and provided two way binding using '=' in scope but It is not giving any change in the parent scope.
Attaching the code
Index.html
<div ng-app="dr" ng-controller="testCtrl">
<test word="word" ng-repeat="word in chat.words"></test>
<button ng-click="find();">
click
</button>
</div>
Javascript Part
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.chat= {words: [
'first', 'second', 'third'
]};
$scope.find = function(){
alert(JSON.stringify($scope.chat, null, 4));
}
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
template: "<input type='text' ng-model='word' />",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
Most of my search returned that putting '=' in directive scope will solve the issue, But no luck with that. can anyone point what is the issue, and how can I reflect the value in parent scope.
You pass a string to your directive, and this string isn't referenced because its not related to your array anymore
i guess you have to change your array properly
Something like the following should work:
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.word = 'test';
$scope.chat= {words: [
{'name':'first'}, {'name': 'second'}, {'name' : 'third'}
]};
$scope.find = function(){
alert(JSON.stringify($scope.chat, null, 4));
}
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
template: "<input type='text' ng-model='word.name' />",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
<div ng-app="dr" ng-controller="testCtrl">
<pre>{{chat.words}}</pre>
<test word="word" ng-repeat="word in chat.words"></test>
<button ng-click="find();">
click
</button>
</div>
The directive can be made more efficient by using one-way (<) binding:
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
̶w̶o̶r̶d̶:̶ ̶'̶=̶'̶
word: '<'
},
template: "<input type='text' ng-model='word.name' />",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
One-way (<) binding has the additional advantage that it works with the $onChanges life-cyle hook.

How to pass transclusion down through nested directives in Angular?

I am trying to figure out how to pass a transclusion down through nested directives and bind to data in the inner-most directive. Think of it like a list type control where you bind it to a list of data and the transclusion is the template you want to use to display the data. Here's a basic example bound to just a single value (here's a plunk for it).
html
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data"><div>{{ source.name }}</div></outer>
</body>
javascript
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
ctrl.welcomeMessage = 'Welcome to Angular';
}])
.directive('outer', function(){
return {
restrict: 'E',
transclude: true,
scope: {
model: '='
},
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template :'<div class="inner" my-transclude></div>'
};
})
.directive('myTransclude', function() {
return {
restrict: 'A',
transclude: 'element',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
element.after(clone);
})
}
}
});
As you can see, the transcluded bit doesn't appear. Any thoughts?
In this case you don't have to use a custom transclude directive or any trick. The problem I found with your code is that transclude is being compiled to the parent scope by default. So, you can fix that by implementing the compile phase of your directive (this happens before the link phase). The implementation would look like the code below:
app.directive('inner', function () {
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template: '<div class="inner" ng-transclude></div>',
compile: function (tElem, tAttrs, transclude) {
return function (scope, elem, attrs) { // link
transclude(scope, function (clone) {
elem.children('.inner').append(clone);
});
};
}
};
});
By doing this, you are forcing your directive to transclude for its isolated scope.
Thanks to Zach's answer, I found a different way to solve my issue. I've now put the template in a separate file and passed it's url down through the scopes and then inserting it with ng-include. Here's a Plunk of the solution.
html:
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data" row-template-url="template.html"></outer>
</body>
template:
<div>{{ source.name }}</div>
javascript:
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
}])
.directive('outer', function(){
return {
restrict: 'E',
scope: {
model: '=',
rowTemplateUrl: '#'
},
template: '<div class="outer"><inner my-data="model" row-template-url="{{ rowTemplateUrl }}"></inner></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
scope: {
source: '=myData',
rowTemplateUrl: '#'
},
template :'<div class="inner" ng-include="rowTemplateUrl"></div>'
};
});
You can pass your transclude all the way down to the third directive, but the problem I see is with the scope override. You want the {{ source.name }} to come from the inner directive, but by the time it compiles this in the first directive:
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
the {{ source.name }} has already been compiled using the outer's scope. The only way I can see this working the way you want is to manually do it with $compile... but maybe someone smarter than me can think of another way.
Demo Plunker

Pass a value from parent directive template to child directive

Problem:
I'm attempting to pass a value from an ng-repeat into a child-directive but when I try to access my passed variable in directive 2 I get "undefined".
Here's an illustration of what I am attempting. Basically directive 1 represents an array of widgets while directive 2 represents a single widget. I am attempting to pass an item from the ng-repeat loop into my child directive.
My Attempt:
Here's a simplified version of my directive 1 template:
<li ng-repeat="item in widgets">
<directive2 item="item"></directive2>
</li>
Here's a simplified version of directive 2:
angular.module('directive2').directive(
['$compile', '$rootScope',
function ($compile, $rootScope) {
return {
restrict: 'E',
scope: { item: '=' },
templateUrl: 'ext-modules/tile/widgetTemplate.html',
link: function (scope, element, attrs) {
console.log(scope.item); // undefined
}
};
}]);
The ng-repeat on widgets creates two items and I have verified that the data exists. The application works fine and doesn't throw an error but my console.log returns : undefined.
My Question:
How can I pass a value from a directive template's ng-repeat into a child-directive?
here's a fiddle: http://jsfiddle.net/3znEu/112/
Yet another solution proposal:
HTML:
<div ng-controller="MyCtrl">
<directive1></directive1>
</div>
JavaScript:
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.widgets = [
'a', 'b', 'c', 'd'
];
}])
.directive('directive1', function () {
return {
restrict: 'E',
scope: false,
template:
'<li ng-repeat="item in widgets">' +
'<directive2 item="item"></directive2>' +
'</li>'
}
})
.directive('directive2', ['$compile', '$rootScope',
function ($compile, $rootScope) {
return {
restrict: 'E',
scope: { item: '=' },
template:
'<div>elem = {{item}}</div>',
link: function (scope, element, attrs) {
console.log(scope.item);
}
}
}]);
Fiddle: http://jsfiddle.net/masa671/dfn75sp3/
It works fine when you put directive2 as directive name, not module:
http://jsfiddle.net/3znEu/113/
'use strict';
var app = angular.module('myApp', [])
.controller('myController', ['$scope', function($scope) {
$scope.greeting = 'Hello World!';
$scope.widgets = ["111","222","333"]
}]);
app.directive('directive1',
['$compile', '$rootScope',
function ($compile, $rootScope) {
return {
restrict: 'E',
scope: { item: '=' },
template: '<div>{{item}}</div>',
link: function (scope, element, attrs) {
console.log(scope.item); // undefined
}
};
}]);
I have modified your fiddler a bit http://jsfiddle.net/3znEu/115/. Few changes
1. Added a restrict to your directive.
2. Added a template to render the Items (only for testing and demo)
3. Changed items in scope from '#' to '='
angular.module("myApp").directive("directive1", function(){
return {
restrict: 'E',
scope: {
item: "="
},
template: "{{item}}"
}
});

Link function not getting run in AngularJS directive

I'm having trouble with an angular directive. It doesn't seem to run the link function.
I feel like it's something obvious, but I just can't figure it out.
The directive is required as seen from below
angular.module('test').requires // ["injectedModule"]
Code below. Fiddle.
Any help would be amazing.
angular
.module('test', ['injectedModule'])
.controller('tester', [
function() {
this.test = function(data) {
alert(data);
}
}
]);
angular
.module('injectedModule', [])
.directive('testing', [
function() {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
alert(scope, element, attrs);
}
};
}
]);
<div ng-app="test">
<div ng-controller="tester as t">
<video id="test" ng-src="https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/11726387_1613973172221601_1804343601_n.mp4" testing="t.test(el)" />
</div>
</div>
Looks to me like
restrict: 'E',
should be
restrict: 'A',
Your directive isn't being called at all as it is.
I think the error is in the restriction you are giving to your directive.
You are restricting your directive to match only element (in other words tag). You should restrict to match attribute 'A'. Here's angular official documentation https://docs.angularjs.org/guide/directive
and here's your fiddle working
Code sample:
angular
.module('injectedModule', [])
.directive('testing', [
function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
alert(scope, element, attrs);
}
};
}
]);

Alternative approach for eliminating $watch

As mentioned in the title in my angular application due to the below approach it leads to creation of many watch, i want to find some alternative methods for this.
<div ng-app="myapp">
<first></first>
</div>
var myApp = angular.module('myapp', []);
myApp.directive('first', [
function() {
return {
restrict: 'AE',
replace: true,
transclude: true,
template: '<div id="first"><second id="second" param="paramData"></second></div>',
scope: {
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
}
],
link: function(scope, element, attrs, ctrl,$timeout) {
scope.paramData = "Test";
scope.updateParamData = function(){
scope.paramData = "TimeOut";
};
//$timeout(scope.updateParamData,5000);
}
};
}
]);
myApp.directive('second', [
function() {
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
scope: {
param: '=param'
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
console.log("inside controller",$scope.param);
}
],
link: function(scope, element, attrs) {
console.log("inside link",scope.param);
scope.$watch(scope.param,function(){
console.log("inside watch",scope.param);
element.innerHTML = scope.param;
});
}
};
}
]);
In the above example the param which is passed from first directive to the second directive is controlled by first directive so the para can change at any time so in the second directive i am using the watch to update the second directive HTML based on the param update.
So now the problem is if i used same kind of approach in my application at many places it leads to multiple watch, so i want to check is this approach is correct or is there is any other alternative approach for this.?
There must be a $watch somewhere to detect the change in the value.
One way to reduce the number of watches is not to use two-way binding scope: {param: "="} in the second directive, and instead use one-way binding of "&".
.directive("second", function(){
return {
scope: { param: "&" }, // this does not create a watch on the parent
template: "<div>{{param()}}</div>" // {{ }} creates a watch
}
})
Of course, you can also explicitly add a $watch in the link/controller (although in your particular example where you use element.innerHTML) it can easier be done with the template approach above):
link: function(scope, element){
scope.$watch(function(){ return scope.param(); },
function(newValue, oldValue){
console.log(newValue, oldValue);
});
}
So, the number of watches is 1 in each case.
I see no way to improve this. Since you need to actually listen for changes for param I do not see a way different from watchers in this case.

Categories

Resources