Is it possible to populate directives in a page dynamically - angularjs

I have a list directive selectors in an array I need to populate them dynamically in a page at runtime how do I proceed I am using angularjs 1.5.8

You should use $compile service to do that. For example, you have the following array of directives:
var myDirectives = [
"<my-app-directive-one></my-app-directive-one>",
"<my-app-directive-two></my-app-directive-two>",
"<my-app-directive-three></my-app-directive-three>"
];
Then you can compile them individually or in a loop and insert to you html like this:
TestDirective.$inject = ["$compile"];
function TestDirective($compile) {
return {
restrict: "E",
link: link
};
function link(scope, element) {
element.append($compile(myDirectives[0])(scope));
}
}
I used the scope of a parent directive (used as a directive sandbox), but you can create a child scope (scope.$new()) or an isolated scope (scope.$new(false)) for it.
If you array looks like this:
var myDirectives = ["my-app-directive-one", "my-app-directive-two"];
then you can simply do the following transformation:
myDirectives = myDirectives.map(
function(directive) {
return "<" + directive + "></" + directive + ">";
}
);
Simpler solution
You can just use ng-if instead of ng-show, if you can place all the directives on the same page before runtime. It will solve you perfomance issues, since ng-if removes all the watchers when destroying a directive.

Related

Angular dynamic templating with compile VS template function?

I already know what is the purpose of each item in : compile vs link(pre/post) vs controller
So let's say I have this simple code :
HTML
<body ng-controller="mainController">
{{ message }}
<div otc-dynamic=""></div>
</body>
Controller
app.controller("mainController", function($scope) {
$scope.label = "Please click";
$scope.doSomething = function() {
$scope.message = "Clicked!";
};
});
Directive
app.directive("otcDynamic", function($compile) {
var template = "<button ng-click='doSomething()'>{{label}}</button>";
return {
compile: function(tElement, tAttributes) {
angular.element(tElement).append(template);
for (var i = 0; i < 3; i++) {
angular.element(tElement).append("<br>Repeat " + i + " of {{name}}");
}
return function postLink(scope, element, attrs) {
scope.name = "John";
}
}
}
});
So as we can see , I modify the template (at the compile function - which is where it should be actually)
Result ( plnker):
But
I didn't know that template:... can also take a function.
So I could use the template function instead (plunker) :
app.directive("otcDynamic", function() {
var template1 = "<button ng-click='doSomething()'>{{label}}</button>";
return {
template: function(element, attr) {
element.append(template1);
for (var i = 0; i < 3; i++)
element.append("<br>Repeat " + i + " of {{name}}");
},
link: function(scope, element) {
scope.name = "John";
}
}
});
Question
If so - When should I use the template function vs compile function ?
Let me try to explain what I understood so far.
Directives is a mechanism to work with DOM in Angular. It gives you leverage of playing with DOM element and it's attribute. So it also gives you callbacks to make your work easy.
template , compile and link are those examples. Since your question is specific with compile and template I would like to add about link as well.
A) Template
Like it state, it is a bunch of HTML tags or files to represent it on DOM directly as the face of your directive.
Template can be a file with specific path or inline HTML in code. Like you stated above. template can be wrap in function but the sole use of template is the final set of HTML which will be placed on DOM. Since you have the access to element and its attributes, you can perform as many DOM operation here as well.
B) Compile
Compile is a mechanism in directive which compiles the template HTML or DOM to do certain operation on it and return final set of HTML as template. Like given in Angular DOC
Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.
Which clearly says that, this is something on top of template. Now like I said above you can achieve similar operations in template as well but when we have methods for its sole purpose, you should use them for the sake of best practice.
You can read more here https://docs.angularjs.org/api/ng/service/$compile
C) Link
Link is used to register listeners like $watch, $apply etc to link your template with Angular scope so that it will get binded with module. When you place any directive inside controller, the flow of scope goes through the link that means the scope is directly accessible in link. Scope is sole of angular app and thus it gives you advantage of working with actual model. Link is also useful in dom manipulations and can be used to work with any DOM element using jQlite
So collecting all above in one
1. Template is the primary source of DOM or HTML to directive. it can be a file or inline HTML.
2. Compile is the wrapper to compile HTML into final template. It is used to gather all the HTML element and attribute to create template for directive.
3. Link is the listener wrapper for various scope and watchers. It binds scope of current controller with html of template and also do manipulation around it.
Hope this helps a bit to understand. Thanks

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

AngularJs: Directive with function for template property. How to get scope value?

I'm trying to have different templates for directive. All solutions that I've found were about using $compile service inside linker function.
But we have template property for directive factory that can be a function. Why not to use this option?
So:
return {
template: function(element, attr) {
if(attr.template){
return attr.template;
}
return defaultTemplate;
}
}
and after in html:
<my-directive template="button.template"></my-directive>
Works like charm.
Only one thing is missing: I don't know how to get scope variable button.template inside. Now just getting 'button.template' like a string. Also tried with brackets - same string only with brackets '{{button.template}}'. Is it possible or I should return back to solution with compile service?
The template function is executed before the linking phase. This means that the scope is not yet available when it is evaluated (and this is why it is not injected into the function).
The pattern is to inject $compile into your directive factory and use it in the postLink phase, because at that time, the scope is already available. For example,
app.directive('myDirective', [ '$compile',
function ($compile) {
function postLink(scope, element, attrs) {
// Here, both the DOM and the scope are available, so
// you can extend the DOM with templates compiled against the scope
// if you are using <my-directive template="button.template"></my-directive>
var template = scope.$eval(attrs.template);
// if you are using <my-directive template="{{ button.template }}"></my-directive>
var template = attrs.template; // interpolation is already processed against the scope
// compile the template and append to existing DOM
element.append($compile(template || defaultTemplate)(scope));
}
function template(element, attrs) {
// Here, you cannot evaluate attrs.template against the scope
// because the scope does not yet exist! Only the DOM is
// available (you can use the raw values of attributes if needed)
return '<div></div>';
}
return {
template: template,
link: postLink
}
}
])

Angular: add ng-click inside directive

In my directive i using appendTo and ng-click inside it:
$("<div>" + "<a href='' ng-click='addIt()' + user + "</a></div>").appendTo '.user-class'
And ng-click doesn't work. Should i use $compile? How to use it in that case?
this code is a part of directive that make dropdown menu from json. There is no option to move this to directive template, so need to find solution how to make 'ng-click' works here.
Like Matthew, I am most curious as to which part(s) cannot be moved out into an external template / internal template function. It doesn't make much sense to me to have to re $compile a directive only to add a ng-click handler.
The senseful way would be to add the ng-click directive to a template. Either an external one, or a function returning a string value in the directive definition object.
Running $compile after the link step has finished is not a good idea performance wise, seeing as how it recompiles the entire DOM element your directive is attached to. Avoid doing so for as long as you possibly can.
.directive('myDir', function () {
return {
template: function (/*element, attrs*/) {
return '<div>{{::user}}</div>';
},
link: function (scope, el, attrs) {
scope.user = 'John';
}
};
});
This would result in the following DOM structure:
<div>
John
</div>
jsbin
Yes you will need $compile , inject $compile in directive and you can do it like this :
var link = $compile("<div>" + "<a href='' ng-click='addIt()'" + user + "</a></div>")(scope);
link.appendto('.user-class');

Angular custom directive with filter in attribute

I would like make angular directive where I use filter on data which are passed as argument.
So something like this:
<div class="my-module" data="a in array | orFilter:filter"></div>
Where "data" is attribute of directive "my-module".
I looked into ngRepeat source, but they parse ng-repeat argument and then they evaluate them. I can't use ng-repeat because I'm creating new instance of object (Marker for map) from data parameter.
Is it realy so hard? Is possible do this in custom directive and how?
Small example what i want: http://jsfiddle.net/PjRAr/1/
EDIT
I'm trying extend this map wrapper to render filtered markers.
My potencional solution is holding copy of all markers and visible markers. Add $watch to filter and when filter was changed call $scope.markers = $scope.$eval("allMarkers | orFilter:filter");.
With this solution we need hold two copy of all markers (~500).
You can $eval the filter expression.
in your directive link function:
elem.text( scope.$eval( attrs.data ).join(', ') );
in your template:
<div my-directive data="['Hello', 'xxx', 'World'] | filter:'o'"></div>
and the directive renders (by filtering-out 'xxx') to:
Hello, World
EDIT:
If the values are dynamic, you can of course do:
scope.$watch( attrs.data, function( arr ) {
elem.text( arr.join(', ') );
});
I do not think you can avoid having $watch, though.
I think you're mixing a few things. I'm not sure why you don't want to use ng-repeat, that's what it's made for. Because you isolate the scope you don't have access to the parent scope. The '=' binding tries to bind your isolated scope data attribute to the parent scope's model called what is in the attribute, but you can't bind to something that has been filtered. If you don't want to repeat the div with the attributes, put them on your own element, it just creates the content...
Here's a fiddle showing both using ng-repeat. You can see the binding is 2-way, it adds an updated: true property.
(FIDDLE)
link: function (scope, element, attrs, ctrl) {
element.append('<p>a: ' + scope.data.a + ', b: ' + scope.data.b);
scope.data.updated = true;
}
So, when I use one-way binding i got empty array in $watch (i think it's trying evaluate in local scope [removed "data" from directive scope for one way binding]. When i use two way binding [in directive scope is {data: "=data"}] i got error "Watchers fired in the last 5 iterations" (it's common error for filtering in angular).
So my solution:
Directive:
...
scope: {
data: "=data"
}
...
link: function (scope, element, attrs, ctrl) {
...
scope.$watch("data", function (newValue) {
angular.forEach(newValue, function (v, i) {
model.add(v);
}
}
}
...
Controller:
...
$scope.filter = { a:true, b:false, ... };
$scope.all = [..data..];
$scope.visible = [..data..];
$scope.$watch("filter", function(newValue) {
$scope.visible = $scope.$eval("all | orFilter:filter");
}, true);
...
HTML:
<div my-module data="visible"></div>
Thank you very much guys, you're help me so much. I'm learned many new things about angular binding.

Resources