Dynamic injection of directive inside another directive template - javascript

Hello I'm new to AngularJS and I think I misunderstood something. Im trying to inject dynamically a directive inside another directive template.
For instance I have 2 directives "a-tag" and "b-tag" and I would like to add one of these 2 directives inside another directive "container".
I have something like this:
<body ng-app="myApp">
<container item="a-tag" a-color="#f00"></container>
</body>
I declared my "container" directive to get the item attribute (a-tag, b-tag, or any other directive) and inject it.
angular.module("Container", []).directive("container", function($compile){
return {
restrict: "EA",
scope: {
item: "#"
},
link: function(scope, element, attrs){
var template = '<div id="container">';
var item = '';
if(scope.item !== undefined){
item = '<' + scope.item;
item += ' ></' + scope.item + '>';
}
template += item + '</div>';
element.html(template);
$compile(element.contents())(scope);
}
};
});
It is working but i dunt know how to broadcast to my child directive (a/b,etc.) his attributes (for instance a-color="#f00" like used in the first piece of code).
My child directives look like this:
angular.module("A", []).directive("aTag", function(){
return {
restrict: "EA",
templateUrl: "template/a.html",
replace: true,
link: function(scope, element, attrs){
element.css("color", attrs.bColor);
}
};
});
It is a simple example. Actually I designed a modal popup (my container) and I would like to use it for several things such as displaying a form, a loader (my directives a-tag, b-tag, etc).
Any idea is welcome.
Ty,
RĂ©mi

Related

Adding a dynamic directive inside another directive template in AngularJS

I want to add dynamic directive inside another directive tempalte.
As you see I want to add another directive inside a directive template
How do add those dynamic directive there
Please help
return {
restrict: 'AE',
require :'^awkGrid',
templateUrl: 'views/shutter.html',
link : function(scope, element, attr, controllerInstance){
//Set the header
scope.items = [];
angular.forEach(scope.rowData, function(value, key) {
var obj = {
key : key,
value : value
};
template = <country name="'+value.country+'" id="'+key+'"></country>;
scope.items.push(template);
});
};
//Inside shutter.html file
<div data-ng-repeat="item in items" class="ag-row action-row"
ng-class-odd="'ag-row-even'"
ng-class-even="'ag-row-odd'"
ng-class="{'ag-row-selected':$index == selectedRow}"
ng-click="setClickedRow($index,$event)">
<div class="ag-cell">
{{item}} //Not working ,Prinitng the string
<country name="india" id="-1"></country> //Working
</div>
To dynamically switch out the entire template of a directive you have to set the element's html to the desired new template and then pass the element's contents into $compile to bind it with the $scope.
element.html('<h1>Dynamic Content</h1>').show();
$compile(element.contents())($scope);
This should all be defined in the link function of the directive in question.
You must have to recompile your directive. Add folowing code at the end of link function:
$compile(element.contents())(scope);
Answer is in here.
Of course, you have to add service $compile to your directive as a dependency.

Pass data to transcluded element

I want to create a directive that organizes a displays data grouped by date. I also want to be able to specify a directive that will display the individual rows. In a perfect world it would look something like this (but nice and pretty)
Friday, Oct 28
[some directive html]
[some directive html]
[some directive html]
Saturday, Oct 29
[some directive html]
Sunday, Oct 30
[some directive html]
[some directive html]
...
This obviously doesn't work, so if you have a better approach please tell me, but I was hoping to be able to do something along these lines:
app.directive('dateOrganized', [function(){
return {
template:
'<div>' +
'<div ng-repeat="organizedDate in organizedDate">' +
'<div>{{organizedDate.date | date}}</div>' +
'<div ng-repeat="item in organizedDate.items">' +
'{{rowDirectiveHtml}}' +
'</div>' +
'</div>' +
'</div>',
scope: {
organizedDates: '=',
rowDirectiveHtml: '='
}
...
};
}])
app.directive('itemRow', [function(){
return {
template: '<div>{{item.data}}</div>',
scope: {
item: '='
}
};
}]);
then use it like this:
<div data-organized organized-dates="stuff" row-directive-html="<div item-row item=\"item\" />" />
I know this is super ugly (and doesn't work, but I'm sure I could get it working with a few tweaks) so what I am really asking, is there a better way to do this?
This question is more complicated than might appear, so let's break it down.
What you are building is a directive that accepts a partial template - <div item-row item="item" /> - and that template uses (or linked against a scope with) an inner variable - item - that is not defined in the outer scope by the user; its meaning is defined by your directive and your user "discovers" it by reading the documentation of your directive. I typically name such "magic" variables with a prefixed $, e.g. $item.
Step 1
Instead of passing a template as an HTML-as-string via attribute binding, pass it as contents and transclude that content. Transcluding allows you to bind the transcluded content against an arbitrary scope:
<foo>
<div>my item is: {{$item}}</div>
</foo>
.directive("foo", function(){
return {
scope: {},
transclude: true,
template: "<h1>I am foo</h1><placeholder></placeholder>",
link: function(scope, element, attrs, ctrls, transclude){
scope.$item = "magic variable";
transclude(scope, function(clonedContent){
element.find("placeholder").replaceWith(clonedContent);
});
}
};
});
The above will place the template <div>my item is: {{$item}}</div> (could be any template you specify) where the directive foo decides, and will link against a scope that has $item defined.
Step 2
But the added complexity of your directive is that it uses ng-repeat, which by itself accepts a template, and the template your directive receives needs to be used as a template of ng-repeat.
With just the approach above, this would not work, since by the time link runs, ng-repeat will have already transcluded its own content before you had a chance to apply yours.
One way to address that is to manually $compile the template of foo instead of using the template property. Prior to compiling, we will have a chance to place the intended template where needed:
.directive("foo", function($compile){
return {
scope: {},
transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
scope.items = [1, 2, 3, 4];
var template = '<h1>I am foo</h1>\
<div ng-repeat="$item in items">\
<placeholder></placeholder>\
</div>';
var templateEl = angular.element(template);
transclude(scope, function(clonedContent){
templateEl.find("placeholder").replaceWith(clonedContent);
$compile(templateEl)(scope, function(clonedTemplate){
element.append(clonedTemplate);
});
});
}
};
});
Demo

AngularJS - communication between directives not working properly

I have the following html:
<div ng-controller="collapseController">
<div><breadcrumb-visualiser breadcrumbs="breadcrumbs" /></div>
<div id="partialViewContainer">
<div id="personContainer" partial-view-loader view="person" parent="" breadcrumbs="breadcrumbs"></div>
</div>
</div>
Template breadcrumb-visualiser
<div style="width: 100%; background-color: #eee">
<div ng-repeat="breadcrumb in breadcrumbs">
<span class="customBreadcrumb"><a ng-href="" ng-click="">{{breadcrumb}}</a></span>
</div>
</div>
partial-view-loader
Loads an MVC partial view into the containing div. The loading partial view will be able to add yet another (new) view to the screen, while hiding the previous screen.
As you may see by the above html, this directive shares the breadcrumbs binding, provided by the collapseController.
The directive adds the latest breadcrumb (associated to the just loaded partial view) to the existing breadcrumb list like so:
$scope.AddBreadCrumb = function (breadcrumb) {
$scope.breadcrumbs.push(breadcrumb);
}
That's a function in the directive controller.
The issue
The collapseController initialises breadcrumbs with this value ['A', 'B'].
So breadcrumbs A and B are displayed right away.
The first load of partial-view-loader will add breadcrumb C, resulting in:
['A', 'B', 'C'].
As I click the button that causes a new view to be added, I will once again trigger partial-view-loader, but now for breadcrumb D.
The problem is that it does not seem to update the breadcrumb. There's no visual change. Internally though, changes have been done but incorrectly.
If I add logging to AddBreadCrumb like so:
$scope.AddBreadCrumb = function (breadcrumb) {
console.log($scope.breadcrumbs);
$scope.breadcrumbs.push(breadcrumb);
console.log($scope.breadcrumbs);
}
I get the following output:
before: ['A', 'B']
- push -
after: ['A', 'B', 'D']
Question
How come the added breadcrumb 'C' isn't preserved, and why is the new breadcrumb list not displayed (even if it's incorrect)?
In response to Divya:
self.AddChildByDirective = function (viewIdentifier, parentViewIdentifier) {
var html = '<div id="' + viewIdentifier + 'Container" fvl-partial-view-loader view="' + viewIdentifier + '" parent="' + parentViewIdentifier + '" breadcrumbs="breadcrumbs" /></div>';
var target = $('#partialViewContainer');
var linkFunc = $compile(html);
var content = linkFunc($scope);
target.append(content);
chainedScreensService.CollapsePartialByIdentifier(parentViewIdentifier);
}
That builds, compiles and appends the directive code for the new screen (view and parent are different).
Extra info:
I've changed the scope of both directives to scope: false. The idea is to make sure I'm using the variables declared by the controller, not something in an isolated scope. Zero difference.
This is the current code of the directives:
breadcrumbVisualiser
angular.module('directives.api').directive("breadcrumbVisualiser", [
function () {
return {
restrict: 'E',
scope: false,
templateUrl: 'Templates/Directives/BreadcrumbVisualiser.html',
controller: function () {
},
link: function (scope, element, attrs) {
}
}
}
]);
partialViewLoader
angular.module('directives.api').directive("partialViewLoader", [
'$compile',
'chainedScreensService',
function (
$compile,
chainedScreensService) {
return {
restrict: 'A',
scope: false,
controller: ['$scope', function ($scope) {
}],
link: function (scope, element, attrs) {
chainedScreensService.GetPartialView(scope.activeView).then(function (viewData) {
$.post(viewData.Url, function(view) {
var linkFunc = $compile(view);
var content = linkFunc(scope);
element.append(content);
scope.AddBreadCrumb(viewData.Subject);
});
});
}
}
}
]);
scope.AddBreadCrumb(viewData.Subject) is a function defined in the controller.

Dynamically Create and Load Angular Directive

In my application i have a list of custom directive names.
$scope.data =["app-hello","app-goodby","app-goodafter"];
each name in this array is one directive that im created.
var app = angular.module('app',[]).controller('mainCtrl',function($scope){
$scope.data =["app-hello","app-goodby","app-goodafter"];
}).directive('appHello',function(){
return {
restrict:'EA',
template:'<h1>Hello Directive</h1>'
};
}).directive('appGoodbye',function(){
return {
restrict:'EA',
template:'<h1>GoodBye</h1>'
};
}).directive('appGoodafter',function(){
return{
restrict:'EA',
template:'<h1>Good Afternoon</h1>'
};
});
now i want to load directive with ng-repeat in the view for example because i used EA restrict for directive can create directive in ng-repeat like this :
<div ng-repeat="d in data" >
<div {{d}}></div>
</div>
but this way it doesn't work. so the real question is if i have list of directive how to load this directive with ng-repeat.for this scenario i create a jsbin .
thanks.
You need a "master" directive that $compiles the HTML (optionally containing directives) into an Angular-aware template and then links the compiled element to a $scope:
app.directive('master', function ($compile) {
return {
restrict: 'A',
link: function postLink(scope, elem, attrs) {
attrs.$observe('directive', function (dirName) {
if (dirName) {
var compiledAndLinkedElem =
$compile('<div ' + dirName + '></div>')(scope);
elem.html('').append(compiledAndLinkedElem);
}
});
}
};
});
<div master directive="{{dir}}" ng-repeat="dir in ['dir1', 'dir2', 'dir3']"></div>
See, also, this short demo.
You can do it in this way:
Directive:
app.directive('compile',function($compile){
return{
restrict:'A',
template: '<div></div>',
link:function(scope,elem,attrs){
scope.name = attrs.compile;
elem.children('div').attr(scope.name,'');
$compile(elem.contents())(scope);
}
};
});
HTML:
<div ng-repeat="d in data" compile="{{d}}">
</div>
Jsbin: http://jsbin.com/wofituye/4/edit
I actually prefer to create templates, that just contain the directive. Then you can use ng-include this then enables you to easily pass scope variables into the dynamically chosen directives too.
Here is my widget code fore example:
<div ng-repeat="widget in widgets track by $index" ng-include="widget.url" class="widget-container" ng-class="widget.widget_type.config.height +' ' + widget.widget_type.config.width">
</div>
Then I set the widget.url to a template containing just the right directive.
I can then in my directive do this:
<custom-widget ng-attr-widget="widget"></custom-widget>
Then I have access to the dynamic variable too, so I can access configuration specifics too, without having to dynamically generate HTML strings and compile them. Not a perfect solution, but personally I used to use the other approach mentioned, and discovered that this fit my needs much better.

karma testing for $scope item not working / item not fired by $compile

I created a directive for my AngularJS app which checks the $scope to see if a profile image is present. If it is the directive shows the image by appending a new DIV, if not it shows a default via a css class. The directive looks like so... and it works in the app.
.directive('profileImage',function() {
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="profileImage" class="profileImage">');
}
}
};
})
;
now I wish to write a unit test for this (I know I should have done that first but I am just learning about tests). I have written the following:
it('should display a specific image', inject(function($rootScope, $compile) {
//$rootScope.profileImage = "srcOfImage";
$scope = $rootScope;
$scope.profileImage = "srcOfImage";
var element = angular.element('<div profile-image="profileImage"></div>')
element = $compile(element)($scope)
console.log(element);
var imgElem = element.find('img');
expect(imgElem.length).toBe(1);
expect(imgElem.eq(0).attr('src')).toBe('srcOfImage');
}));
Now the test fails in this scenario (other tests where no $scope.profileImage is available has passed). When I out put the element I have created I get the following:
LOG: {0: <div profile-image="profileImage" class="ng-scope ng-isolate-scope"><img ng-src="profileImage" class="profileImage"></div>, length: 1}
So everything works but the profile-image="profileImage" in the var element = angular.element('<div profile-image="profileImage"></div>') is being taken as a literal string. It is not showing the value 'srcOfImage'. What am I doing wrong?
The error looks like so:
PhantomJS 1.9.2 (Mac OS X) Profile Image Directive should display a specific image FAILED
-src="prExpected undefined to be 'srcOfImage'.>, length: 1}
Big thanks for any advice / help / explanations
This is expected behavior because:
element.append('<img ng-src="profileImage" class="profileImage">');
appends a normal DOM image without compilation, so the properties are added as they are.
Try:
element.append('<img ng-src="profileImage" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
Remember to declare the $compile service in your directive:
.directive('profileImage',function($compile) {
One more problem is you have to use ng-src with {{}} like this:
ng-src="{{profileImage}}"
Your final directive looks like this:
.directive('profileImage',function($compile) { //declare $compile service.
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="{{profileImage}}" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
}
}
};
})
;

Categories

Resources