I have a directive that transcludes the original content, parses it, and uses the information in the original content to help build the new content. The gist of it looks like this:
.directive('list', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: '...',
scope: true,
controller: function($scope, $element, $attrs, $transclude) {
var items;
$transclude(function(clone) {
clone = Array.prototype.slice.call(clone);
items = clone
.filter(function(node) {
return node.nodeType === 1;
})
.map(function(node) {
return {
value: node.getAttribute('value')
text: node.innerHTML
};
});
});
// Do some stuff down here with the item information
}
}
});
Then, I use it like this:
<list>
<item value="foo">bar</item>
<item value="baz">qux</item>
</list>
This all works fine like this. The problem occurs when I try to use an ng-repeat inside the directive content, like this:
<list>
<item ng-repeat="item in items" value="{{ item.value }}">{{ item.text }}</item>
</list>
When I try to do this, there are no items. Anyone know why this wouldn't work, or if there is a better way of accomplishing the same kind of thing?
You may try:
transcludeFn(scope, function (clone) {
iElem.append(clone);
})
For bit more details:
HTML:
<foo data-lists='[lists data here]'>
<li ng-repeat="list in lists">{{list.name}}</li>
</foo>
Directive:
var Foo = function() {
return {
restrict: 'E',
template: '...'
transclude: true,
scope: { lists: '=?' }
link: function(scope, iElem, iAttrs, Ctrl, transcludeFn) {
transcludeFn(scope, function (clone) {
iElem.append(clone);
}
}
};
};
.directive('foo', Foo);
You should let transcludFn know which scope you are going to use within transcludeFn. And if you don't want to use isolate scope, you may also try transcludeFn(scope.$parent....)
Related
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
I have a Directive:
var ActorDisplayDirective = function() {
return {
replace : false,
restrict : 'AE',
scope : {
actor : "="
},
templateUrl: staticContext + '/angular-app/templates/actor-display-template.html',
link : function(scope, elem, attrs) {
},
}
};
This works fine in some places, but not others. Here is my code to show it where it is not working:
<p>CAP: {{can_approve_for}}</p>
<p>
Actor display template:
<span actor-display actor='can_approve_for'></span>
After template
</p>
The CAP: ... displays the data, the directive's actor value is null. Why? My controller does:
dataFactory.getCanApproveFor().then(function(data) {
$scope.can_approve_for = data;
});
So, I am able to see the value on the page, but the directive does not show it. I'm assuming it's a timing/refresh thing, but this directive works elsewhere in ng-repeat, because the ng-repeat evaluates after hte object is already set, I guess. How do I do it in this case?
You are not actually declaring ActorDisplayDirective as a directive. Its just a plain function that returns an object that sort of looks like a directive.
You have to tell angular that it is a directive like so:
angular.module('someModule', [])
.directive('actorDisplay', function () {
return {
replace: false,
restrict: 'AE',
scope: {
actor: "="
},
templateUrl: staticContext + '/angular-app/templates/actor-display-template.html',
link: function (scope, elem, attrs) {
},
}
})
I have a list and want it to filter by my custom filter. But the value to it I want to put from custom directive with it's own scope. How to do it?
Body:
<body ng-controller="test">
<tr ng-repeat="item in list | myfilter: HowToPuttHereValue? >
Here is my custom filter:
.filter('myfilter', function(){
return function(array, num){
return array.slice(num, num+1);
}
})
And here is my custom directive:
.directive('mydirective', function() {
return {
restrict: "E",
template:"<input ng-model='counter'><button ng-click='getIt(counter)'>PRESS</button>",
scope:{
item: '='
},
link: function(scope, element, attr){
scope.getIt = function(counter){
console.log(counter);
}
}
}
})
Please see the Example:
JsFiddle Example
P.S. I guess I've already found a solution using "scope.$parent" . But is there a possibility to pass the value straight to a "myfilter: here? "
Here it is.
<div ng-app="hello">
<div ng-controller="forExampleController">
<ul>
<li ng-repeat="num in list | myfilter: howToPutHereFromDirective ">{{num}} </li>
</ul>
<mydirective item="list.length" filter-value="howToPutHereFromDirective"></mydirective>
</div>
</div>
function forExampleController($scope){
$scope.list = [1,2,3,4,5,6,7,8,9];
$scope.howToPutHereFromDirective = 3;
}
angular.module('hello', [])
.filter('myfilter', function(){
return function(array, num){
return array.slice(num, num+1);
}
})
.directive('mydirective', function() {
return {
restrict: "E",
template:"<input ng-model='counter'><button ng-click='getIt()'>PRESS</button>",
scope:{
item: '=',
filterValue: '='
},
link: function(scope, element, attr){
scope.getIt = function(){
scope.filterValue = parseInt(scope.counter);
}
}
}
});
Doing scope.$parent within isolated scope isn't too good indeed, because the objective of isolated scope is exactly the opposite.
I'm not sure what purpose item="list.length" two-way binding has to serve, but it is a bad idea.
I'm quite new to AngularJS and I'm trying to understand a few things.
First of all, I have my controller of which I will place a snippet here:
var OfficeUIRibbon = angular.module('OfficeUIRibbon');
// Defines the OfficeUIRibbon controller for the application.
OfficeUIRibbon.controller('OfficeUIRibbon', ['$scope', '$http', function($scope, $http) {
var ribbon = this;
ribbon.setApplicationMenuAsActive = function() {
ribbon.applicationMenuActive = true;
}
}
Then I have a directive:
var OfficeUIRibbon = angular.module('OfficeUIRibbon');
OfficeUIRibbon.directive('ribbonApplicationMenu', function() {
return {
restrict: 'E',
replace: false,
scope: {
data: '#'
},
templateUrl: function(element, attributes) {
return attributes.templateurl;
}
}
});
The directive is called like this:
<ribbon-application-menu templateUrl="/OfficeUI.Beta/Resources/Templates/ApplicationMenu.html" data="/OfficeUI.Beta/Resources/JSon/Ribbon/ribbon.json"></ribbon-application-menu>
This does all work and in my template for the directive, the following is placed:
<div id="application" id="applicationMenuHolder" ng-controller="OfficeUIRibbon as OfficeUIRibbon" ng-show="OfficeUIRibbon.applicationMenuActive"...
From inside another element, when I execute a click a function on my controller is executed:
ng-click="OfficeUIRibbon.setApplicationMenuAsActive()"
Here's the directive from the other element:
OfficeUIRibbon.directive('ribbon', function() {
return {
restrict: 'E',
replace: false,
scope: {
data: '#'
},
templateUrl: function(element, attributes) {
return attributes.templateurl;
}
}
});
This function does change the property "applicationMenuActive" on the ribbon itself, which should make the item in the directive template show up.
However, this is not working. I'm guessing I need to watch this property so the view get's updated accordingly.
Anyone has an idea on how this could be done?
I want to nest two directives and the inner directive has a ng-class bound to a function that takes a scope attribute from inner and outer scopes and return a Boolean
This is the HTML:
<ul my-toolbar disabled-when="myCtrl.isProcessing" >
<li my-action-button action="myCtrl.action()" disable-when="myCtrl.isSad()" />
</ul>
This is my outer directive:
myApp.directive("myToolbar", function() {
return {
restrict: 'A',
scope: {
disabled: '=disabledWhen'
},
transclude: true,
controller: function($scope) {
this.isDisabled = function() {
return $scope.disabled;
}
}
};
});
And this is my inner directive:
myApp.directive("myActionButton", function() {
return {
restrict: 'A',
scope: {
action: '&',
disabled: '=disabledWhen'
},
replace: true,
template: "<li ng-class='{disabled: isDisabled()}'><a ng-click='isDisabled() || action()' /></li>",
link: function(scope, elem, attrs, toolbarCtrl) {
scope.isDisabled = function() {
return toolbarCtrl.isDisabled() || scope.disabled;
};
}
};
});
Now the problem is that the ng-class='{disabled: isDisabled()}' binding is initialized once in the beginning but not updated when myCtrl.isProcessing changes!
Can someone please explain why? and how can I fix this without changing my design?
#Jonathan as requested I put my angular code in a fiddle and (this is part that's irritating me now) it works!
http://jsfiddle.net/shantanusinghal/ST3kH/1/
Now, I'll go back to seeing why it doesn't work for me in my production code!! *puzzled