Beginner angularjs - including directive within directive - javascript

This is probably as basic as it gets. I have some simple markup that's suppose to represent a list:
<unordered-list>
<list-item></list-item>
<list-item></list-item>
</unordered-list>
I also have two directives defined for unordered-list and list-item:
.directive('unorderedList', function() {
return {
restrict: 'E',
replace: true,
template: '<ul></ul>'
};
})
.directive('listItem', function() {
return {
restrict: 'E',
replace: true,
template: '<li>Test</li>'
}
});
Yet when I run this (http://jsfiddle.net/esWUD/), the only thing rendered is the <ul>. No <li>Test</li> elements render within the <ul>.
Why not don't they render?

You'll need to use ngTransclude to allow directives to have sub-elements. Set transclude: true in unorderedList.
transclude: true,
template: '<ul ng-transclude></ul>'
Working fiddle.

Related

AngularJs: Iterating over array inside directive in order to create nested set of directives

Inside angularJS directive I'm trying to iterate over array and based on values I would like to create nested list of directives.
Current version of directive
Directive type
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div></div>",
link: function(scope, element, attrs){
if (angular.isArray(scope.type)){
angular.forEach(scope.type, function(value, index){
$log.error(value);
element.append("<type type='scope.type['"+index+"]'></type>");
});
} else if (angular.isObject(scope.type)){
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
};
})
I also tried to use above directive with next version of link function:
if (angular.isArray(scope.type)) {
element.append("<div ng-repeat='element in scope.type'><type type='element'></type></div>");
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
None of provided codes solve my issue.
Below you will find example explaining on specific case:
Let's say that I had next object in the scope.type = [null,"int"]. Now I would like to use <type type='type'><type> and as a result of first evaluation I want to have sth like:
<type type='type[0]'></type><type type='type[1]'></type>
Further evaluation of those values should lead to some simpler form but right now it is not important.
How I can achieve sth like this?
Edit
I tried even to exctract part of the code responsible for iteration to the seperate directive but it still does not work. Code:
Update link function in type directive:
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='scope.type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
New directive:
.directive("typeb", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div ng-repeat='t in type'>{{t}}</div>",
};
})
Problem still occurs but generated html contains only next pieces as a result of typeb directive:
<!-- ngRepeat: t in type -->
The problem you are getting is <!-- ngRepeat: t in type --> this is because your type didn't contains any value, when it is inside typeb directive
Your directive shouldn't be use scope.variable on view.
Scope variable will be directly accessible by their name like
{{type}} or <typeb type='type'></typeb>
Change your link code to below.
Directive link
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{type}}</div>")
}
$compile(element.contents())(scope)
}
Thanks.
Use an ng-repeat in the template
<your-directive attr="item.someattr" ng-repeat="item in items"></your-directive>
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "NG REPEAT HERE",
...
})

Proper way to nest a directive in angular

Here is my plunkr: http://plnkr.co/edit/A0s3kafWD1WIaFcwimIT?p=preview
I am trying to nest a directive inside another directive but it is not working.
Angular:
app.directive('test', function() {
return {
restrict: 'E',
template: '<div>Hello Parent</div>'
}
})
app.directive('childtest', function() {
return {
restrict: 'E',
template: '<div>Hello Child</div>'
}
})
HTML:
<test>
<childtest>
</childtest>
</test>
How do I correctly do this?
I think it very much depends on your actual use-case, but you have some options:
Don't give the parent directive a template:
app.directive('test', function() {
return {
restrict: 'E'
}
});
app.directive('childtest', function() {
return {
restrict: 'E',
template: '<div>Hello Child</div>'
}
});
HTML
<test>
<childtest>
</childtest>
</test>
Put the children in the template of the parent:
app.directive('test', function() {
return {
restrict: 'E',
template: '<div>Hello parent <childtest></childtest></div>'
}
});
app.directive('childtest', function() {
return {
restrict: 'E',
template: '<div>Hello Child</div>'
}
});
HTML
<test></test>
Give the parent a template, and use transclusion to move the child directive to inside the template
app.directive('test', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>Hello Parent <div ng-transclude></div></div>'
}
});
app.directive('childtest', function() {
return {
restrict: 'E',
template: '<div>Hello Child</div>'
}
});
HTML
<test>
<childtest>
</childtest>
</test>
Basically I think what you are looking for here is ng-transclude. What it does is it allows you to keep any child elements inside of your custom directive - all the while still allowing you to add elements either before or after those child elements.
Including this, basically your parent directive changes to:
app.directive('test', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>Hello Parent<div ng-transclude></div></div>'
}
});
The <div ng-translcude></div> tells angular exactly where you want the child included at and adding the transclude: true tells angular that you want to have this behavior. Take a look at your updated plunker here: http://plnkr.co/edit/HL9PD6U4V7p56T3Cqx5F?p=preview

AngularJS: multiple directives with transclusion on same element

I'm trying to inject 2 templates into an element and operate on them:
<div
ic-first="foo"
ic-second="bar"
ic-third="baz"
ic-fourth="qux"
>
</div>
icFirst should inject via a template an empty div as a child of its element. icSecond should inject a second div (with a bunch of content) as the second child of its element, so the resulting html would look like:
<div
ic-first="foo" // priority: 100
ic-second="bar" // priority: 50
ic-third="baz" // priority: 0
ic-fourth="qux" // priority: 0
>
<div id="foo"></div>
<div> <!-- a bunch of stuff from the templateUrl --> </div>
</div>
Both icFirst and icSecond will inject other elements into the newly created containers.
When I specify a directive template property on both directives, I get an error:
Error: Multiple directives [icFirst, icSecond] asking for template on: <div ic-first…
When I add transclude: true to both directives, icFirst executes just fine…but then the other directives on the same element are not executed. When I set transclude: 'element', the other directives execute but I get an error that the first child ($scope.firstObj) is undefined.
All four directives need access to each other's scope, so I'm doing most of my work in their controllers:
app.directive('icFirst', ['ic.config', function (icConfig) {
return {
restrict: 'A',
priority: 100,
template: '<div id="{{firstId}}"></div>',
replace: false,
transclude: 'element',
controller: function icFirst($scope, $element, $attrs) {
// …
$scope.firstId = $scope.opts.fooId;
$scope.firstElm = $element.children()[0];
$scope.firstObj = {}; // this is used by the other 3 directives
},
link: function(scope, elm, attrs) { … } // <- event binding
}
);
app.directive('icSecond', ['ic.config', function (icConfig) {
return {
restrict: 'A',
priority: 0,
templateUrl: 'views/foo.html',
replace: false,
transclude: 'element',
controller: function icSecond($scope, $element, $attrs) {
// …
$scope.secondElm = $element.children()[1];
$scope.secondObj = new Bar( $scope.firstObj );
// ^ is used by the remaining 2 directives & requires obj from icFirst
},
link: function(scope, elm, attrs) { … } // <- event binding
}
);
Note I have corrected the behaviour of replace: false to match the documented behaviour, as described in pull request #2433.
I tried instantiating $scope.firstObj in the controller, and setting it in the linkFn (hoping the transclusion would have completed by the time the linkFn executes), but I get the same problem. It appears first-child is actually a comment.
The only reason I can come up with that explains throwing this error is that the AngularJS team was trying to avoid needless overwrites/DOM manipulation:
Considering the actual behaviour of replace: false vs the documented behaviour, I think the actual is in fact the intended behaviour. If this is true, then allowing multiple templates/templateUrls to be used on the same element will cause subsequent templates to overwrite previous ones.
Since I already modified the source to match the documented behaviour, as a quick fix†, I modified the source again (/src/ng/compile.js:700) to remove the assertNoDuplicate check (which corresponds to angular.js:4624). Now I return the following 2 objects, and it works, and I can't find any negative repercussions:
// directive icFirst
return {
restrict: 'A',
priority: 100,
replace: false,
template: '<div id="{{firstId}}"></div>',
require: ["icFirst"],
controller: Controller,
link: postLink
};
// directive icSecond
return {
restrict: 'A',
require: ['icFirst'],
replace: false,
templateUrl: 'views/bar.html',
priority: 50,
controller: Controller,
link: postLink
};
† If made permanent, the check should probably be
if (directive.templateUrl && directive.replace)
(and similar for directive.template)

Angular JS - Is there a way to pass the directive's attribute to the template

on the Page
<rn-text-edit rn-scope="profile.first_name"></rn-text-edit>
on the js
app.directive("rnTextEdit", function () {
return {
restrict: 'E',
replace: true,
template:'<span>{{'+rn-scope+'}}</span>'
}
});
I know I can replace the DOM and access the attribute through link. I wonder if there is a way of passing the directive's attribute to a template.
If you are just displaying the value:
<rn-text-edit rn-scope="{{profile.first_name}}"></rn-text-edit>
-
app.directive("rnTextEdit", function () {
return {
restrict: 'E',
replace: true,
scope: {
rnScope: '#'
},
template: '<span>{{rnScope}}</span>'
}
});
If the directive needs to modify the value, you could use '=' and skip the double curlies.
fiddle
more info on scope and '#' in the Angular Directives page

Wrapping Foundation 4 reveal in Angular

New to Angular, just trying to get some harmony with Zurb Foundation 4. A case in point; I am trying to make use of the http://foundation.zurb.com/docs/components/reveal.html component.
Straight-forward approach seemed to be to wrap as directives:
directive('modal', function() {
return {
template: '<div ng-transclude id="notice" class="reveal-modal">' +
'<a close-modal></a>' +
'</div>',
restrict: 'E',
transclude: true,
replace: true,
scope: {
'done': '#',
},
transclude: true,
link: function(SCOPE, element, attrs, ctrl) {
SCOPE.$watch('done', function (a) {
// close-modal
});
}
}
}).
directive('closeModal', function() {
return {
template: '<a ng-transclude href="#" class="close-reveal-modal">x</a>',
restrict: 'A',
transclude: true,
replace: true
}
}).
directive('showModal', function() {
return {
template: '<a ng-transclude class="reveal-link" data-reveal-id="notice" href="#"></a>',
restrict: 'A',
transclude: true,
replace: true,
}
});
This works fine up to a point, for example, I can use the modal to show different notices from a template:
<modal done="">
<div ng-include src="'partials/notices/' + notice + '.html'"></div>
</modal>
<select ng-model="notice" ng-options="n for n in ['notice-1', 'notice-2']">
<option value="">(blank)</option>
</select>
<a show-modal>show modal</a>
However, where it gets sticky is if I want to trigger close-modal/ show-modal from a controller/ on a certain event (e.g. within $watch). I'm assuming my directive needs a controller to trigger click, but would be good Angular practise?
This question is very old and I don't know if it works with Reveal. But I've wrapped the dropbox library into Angular only by calling the .foundation() method on the .run() method of angular:
app.run(function ($rootScope) {
$rootScope.$on('$viewContentLoaded', function () {
$(document).foundation();
});
});
That works for me. I guess that you can also create a directive to handle user interaction.
Controllers should not be triggering UI events directly neither manipulate UI elements directly. All that code is supposed to go in Directives.
What you can do is:
Bind a bool in Directive Scope to the Parent scope and add a watch on that. I think you already did that. OR
Do a scope.$broadcast on the controller and add scope.$watch on the directive to close then.

Categories