Proper way to nest a directive in angular - javascript

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

Related

How to make two way binding between angular directives without using "dots"

Suppose, I have controller:
angular.module('tf').controller('Ctrl', function($scope){
$scope.params = {
orderBy: null
};});
And a directive "common":
angular.module('tf').directive("common", function() {
return {
restrict: 'E',
replace: true,
template: '<div><outer order-by="orderBy"><inner order-by-field="name1"></inner><inner order-by-field="name2"></inner></outer></div>',
controller: function ($scope) {
},
scope: {
orderBy: '='
},
link: function (scope, element, attrs) {
}
}});
Controller is using directive within it's template:
<div ng-app="tf">
<div ng-controller="Ctrl">
<common order-by="params.orderBy"></common>
<div style="color:red">{{params.orderBy}}</div>
</div>
This directive is using directive "outer":
angular.module('tf').directive("outer", function() {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-transclude></div>',
controller: function ($scope) {
this.order = function (by) {
$scope.orderBy = by
};
},
scope: {
orderBy: '=',
}
}});
Which is parent for the directive "inner":
angular.module('tf').directive("inner", function() {
return {
require: '^outer',
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-click="onClicked()">{{orderByField}}</div>',
controller: function ($scope) {
$scope.onClicked = function () {
$scope.outer.order($scope.orderByField);
}
},
scope: {
orderByField: '#'
},
link: function (scope, element, attrs, outer) {
scope.outer = outer;
}
}});
The directive "outer" shares "order" method with directive "inner" by it's controller. The directive "inner" is accessing it by using "require" mechanism.
For some reason, this is not working as expected (Property of the controller isn't updated each time it's changed by directive). If I place "orderBy" into object (e.g. {"order": {"by": null }} ) and use object instead of string value, everything is working as expected ( controller scope is properly updated by the directive). I know about "always use dots" best practices principle, but I don't wanna use it here, because it would make my directive's API less intuitive.
Here is jsfiddle:
http://jsfiddle.net/A8Vgk/1254/
Thanks

Communicate action thru directive attributes

I'm probably missing something here on the way $observe works inside directive. I have to child directive that needs to communicate thru their parent directive:
<parent>
<button>Change text</button>
<child text="child element"></child>
</parent>
app.directive('button', function() {
return {
require: '^parent',
scope: true,
restrict: 'E',
link: function(scope, element, attrs, parentCtrl) {
element.bind('click', function() {
parentCtrl.changeText();
});
},
};
});
app.directive('parent', function() {
return {
restrict: 'E',
scope: true,
controller: function($scope, $element, $attrs) {
var child = $element.find('child');
this.changeText = function() {
child.attr('text', 'new text');
};
}
};
});
app.directive('child', function() {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
attrs.$observe('text', function(text) {
element.html(text);
});
}
};
});
That code is only to illustrate an problem I have in the app i'm developing. All directive needs to have a isolated scope, so I cannot communicate with it. I made a plunker. Feel free to let me know if there's a better way to communicate to the child directive from the parent one.
Thank's a lot, as always!
I didnt understand why the parent and child has to have isolated scope but if you just want to communicate between parent and child you can use $emit and $on.
app.directive('parent', function($rootScope) {
return {
restrict: 'E',
scope: true,
controller: function($scope, $element, $attrs) {
var child = $element.find('child');
this.changeText = function() {
child.attr('text', 'new text');
};
$rootScope.$emit('FromParent', somedata);
}
};
});
app.directive('child', function($rootScope) {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
attrs.$observe('text', function(text) {
element.html(text);
});
$rootScope.$on('FromParent', function(event, somedata){
//Do something with the data
});
}
};
});

How to bind variables into transcluded part

How to bind variables from directive's scope into transcluded template?
app.directive('foo', function(){
return {
restrict: 'E',
transclude: true,
template: '<div ng-transclude></div>',
link: function (scope) {
scope.num = 5;
}
}
})
<div ng-app="app">
<foo>
{{num}}
</foo>
</div>
You're missing an app module. Also I added a modified class so that you could see that the template is being applied:
var app = angular.module("app", []);
app.directive('foo', function(){
return {
restrict: 'E',
transclude: true,
template: '<div class="modified" ng-transclude></div>',
link: function (scope) {
scope.num = 5;
}
}
});
See plnkr: http://plnkr.co/edit/x9NE6A4kkqspKbO08yhq?p=preview

Angularjs 1.2.9 Scope Isolation

I'm using Scope Isolation in one of my directives. However, this doesn't seem to work:
<div ng-controller="MyCtrl">
Hello, {{name}}!
<dir info='spec'>
{{ data }}
</dir>
</div>
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.spec = 'Super';
}
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
}
}
});
Fiddle: enter link description here
Here is a fiddle: http://jsfiddle.net/WA5t5/
Since this commit(1.2) Child elements that are defined either in the application template or in some other
directives template do not get the isolate scope.
You can do this instead:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
template: "{{ data }}"
}
});
If you want to alter this behavior check my other answer: Why I can't access the right scope?
Try:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
template:"<div>{{data}}</div>"
}
});
DEMO
Another solution using transclusion to bind the scope yourself:
myApp.directive('dir', function(){
return {
restrict: 'AE',
scope:{
data: '=info'
},
transclude:true,
compile: function (element, attr, linker) {
return function (scope, element, attr) {
linker(scope, function(clone){
element.append(clone); // add to DOM
});
};
}
}
});
You can still use the same html as before:
<div ng-controller="MyCtrl">
Hello, {{name}}!
<dir info='spec'>
{{data}}
</dir>
</div>
DEMO
You should have a template defined in your directive where you show the data scope variable. The html code does not know what the data scope variable is, it's only known in the directive's template. See this demo
myApp.directive('dir', function () {
return {
restrict: 'AE',
scope: {
data: '=info'
},
link: function (scope, element, attrs) {
console.log(scope.data);
},
template: 'Hello {{data}}'
}
});

Rendering AngularJS directives from array of directive names in a parent directive or controller

I'm attempting to dynamically render directives based on a configuration array of directive names. Is this possible in angular? I also want these rendered directives to live within a single parent dom element rather than each getting a new wrapper (as you would with ng-repeat)
http://jsfiddle.net/7Waxv/
var myApp = angular.module('myApp', []);
myApp.directive('one', function() {
return {
restrict: 'A',
template: '<div>Directive one</div>'
}
});
myApp.directive('two', function() {
return {
restrict: 'A',
template: '<div>Directive two</div>'
}
});
function MyCtrl($scope) {
$scope.directives = ['one', 'two'];
}
<div ng-controller="MyCtrl">
<div ng-repeat="directive in directives">
<div {{directive}}></div>
</div>
</div>
EDIT:
Since posting this, I've also tried:
.directive('parentDirective', function () {
return {
restrict: 'A',
replace: true,
link: function (scope, element) {
scope.directives = ['one', 'two'];
for (var i = 0; i < scope.directives.length; i++) {
element.prepend('<div ' + scope.directives[i] + '></div>')
}
}
};
});
<div parent-directive></div>
With this, the templates from the prepended directives are not rendered.
Here what I came up with (took a long time)... The solution is pretty versatile though, you can modify $scope.directives array at will and the directives will be fabricated dynamically. You can also point to any particular property in the current scope to retrieve the directive list from.
Demo link
app.js
var myApp = angular.module('myApp', []);
myApp.directive('one', function() {
return {
restrict: 'E',
replace: true,
template: '<div>Directive one</div>'
}
});
myApp.directive('two', function() {
return {
restrict: 'E',
replace: true,
template: '<div>Directive two</div>'
}
});
myApp.directive('dynamic', function ($compile, $parse) {
return {
restrict: 'A',
replace: true,
link: function (scope, element, attr) {
attr.$observe('dynamic', function(val) {
element.html('');
var directives = $parse(val)(scope);
angular.forEach(directives, function(directive) {
element.append($compile(directive)(scope));
});
});
}
};
});
function MyCtrl($scope) {
$scope.directives = ['<one/>', '<two/>'];
$scope.add = function(directive) {
$scope.directives.push(directive);
}
}
index.html
<div ng-controller="MyCtrl">
<div dynamic="{{directives}}"></div>
<button ng-click="add('<one/>')">Add One</button>
<button ng-click="add('<two/>')">Add One</button>
</div>
So the second attempt would have worked had I used $compile on the prepended directives like so:
.directive('parentDirective', function($compile)
....
element.prepend($compile('<div ' + scope.directives[i] + '"></div>')(scope));

Categories