Angular Directive Passing Template Name in as an attribute - angularjs

if I wanted to pass into angular an attribute which is a template name stored on the templateCache, would doing something like the following be a good approach?
app.directive('myPopup', function() {
var directive = {
link: link,
scope {
'template': '#'
}
template: $templateCache.get(template);
restrict: 'E'
};
return directive;
function link(scope, element, attrs) {
}
});
Or should it be done on the compile statement, I guess so for performace??
I'm looking to ultimately wrap a bootstrap popover and then be able to provided html content through a template attribute allowing me to reuse popovers by providing different templates.
Can anyone offer any suggestions for the best course of action?
Thanks

Related

How to make this call in an angular scenario?

I'm using a youtube player called YTPlayer.
https://github.com/pupunzi/jquery.mb.YTPlayer
In this code he makes a JQuery call which works fine.
$(document).ready(function () {
$(".player").mb_YTPlayer();
});
How can i make such a call from my controller without using JQuery?
Thanks.
You create a directive. You can think of directives as extending html.
Your directive will look something like this:
.directive('ytPlayer', function() {
return {
scope: {
pathToVideo: '&'
},
link(scope, element, attr) {
//at this point, the DOM is ready and the element has been added to the page. It's safe to call mb_YTPlayer() here.
//also, element is already a jQuery object, so you don't need to wrap it in $()
element.mb_YTPlayer();
//scope.pathToVideo() will return '/video.mpg' here
}
}
}
And you'll add it to your page with this markup:
<yt-player path-to-video="/video.mpg"></yt-player>
It's OK to use jQuery inside of a directive if your video player is dependent on it. You should never need to use jQuery in an angular controller. If you find yourself doing so, you're not "thinking angular".
Many times, video players and other components require specific markup to work, so you can customize your template for the directive with the template property:
.directive('ytPlayer', function() {
return {
scope: {
pathToVideo: '&'
},
replace: true,
template: '<div><span></span></div>'
link(scope, element, attr) {
element.mb_YTPlayer();
//scope.pathToVideo() will return '/video.mpg' here
}
}
}
These two lines:
replace: true,
template: '<div><span></span></div>'
will cause angular to replace the yt-player markup with the markup in the template property.

Angularjs: access template in directive

What I want to do is to append compiled template to some other DOM element in Angualrjs directive. Is it possible? How?
I know you can use transclude to include the template and keep content in the tag, but how do I attach my compiled template to other DOM element?
angular.module("my.directive")
.directive('passwordStrength', [function(){
return {
templateUrl: '/tpl/directive/passwordStrength.html',
restrict: 'A',
link: function postLink(scope, iElement, iAttrs){
console.log('password strength is running');
iElement.append($template) // problem here!!!
}
};
}]);
I'm not sure what you're trying to accomplish overall. You may want to use ng-include to pull in markup from a url, rather than using the templateUrl property, or you may want to use the templateUrl property on one directive, then make another directive and include that directive in the second directive. I made some sample directives to help give you ideas.
.directive('myDirective', function($compile) {
return {
scope: {
path: '=myDirective'
},
link: function(scope, element) {
// rather than use `templateUrl`, I'll get markup from the path using `ng-include`
var html = '<div ng-include="path"></div>';
// manipulate the markup however you want to
html+= '<p>More stuff from "myDirective"!</p>';
// append the markup
element.append(html);
// compile the markup so that Angular will know about it (or use the directive `compile` rather than `link`)
$compile(element.contents())(scope);
}
};
})
// this is sort of like "extending" the other directive.
.directive('myOtherDirective', function() {
return {
scope: {
path: '=myOtherDirective'
},
template: '<p>Stuff from "myOtherDirective"></div><div my-directive="path"></div>'
};
})
Here's a demo you can mess around with.

wrapping inputs in directives in angular

I had the idea to wrap inputs into custom directives to guarantee a consistent look and behavior through out my site. I also want to wrap bootstrap ui's datepicker and dropdown. Also, the directive should handle validation and display tooltips.
The HTML should look something like this:
<my-input required max-length='5' model='text' placeholder='text' name='text'/>
or
<my-datepicker required model='start' placeholder='start' name='start'/>
in the directives i want to create a dom structure like:
<div>
<div>..</div> //display validation in here
<div>..</div> //add button to toggle datepicker (or other stuff) in here
<div>..</div> //add input field in here
</div>
I tried various ways to achieve this but always came across some tradeoffs:
using transclude and replace to insert the input into the directives dom structure (in this case the directive would be restricted to 'A' not 'E' like in the example above). The problem here is, that there is no easy way to access the transcluded element as I want to add custom attributes in case of datepicker. I could use the transclude function and then recompile the template in the link function, but this seems a bit complex for this task. This also leads to problems with the transcluded scope and the toggle state for the datepicker (one is in the directives scope, the other in the transcluded scope).
using replace only. In this case, all attributes are applied to the outermost div (even if I generate the template dom structure in the compile function). If I use just the input as template, then the attributes are on the input, but I need to generate the template in the link function an then recompile it. As far as I understand the phase model of angular, I would like to avoid recompiling and changing the template dom in the link function (although I've seen many people doing this).
Currently I'm working with the second approach and generating the template in the link function, but I was wondering if someone had some better ideas!
Here's what I believe is the proper way to do this. Like the OP I wanted to be able to use an attribute directive to wrapper an input. But I also wanted it to work with ng-if and such without leaking any elements. As #jantimon pointed out, if you don't cleanup your wrapper elements they will linger after ng-if destroys the original element.
app.directive("checkboxWrapper", [function() {
return {
restrict: "A",
link: function(scope, element, attrs, ctrl, transclude) {
var wrapper = angular.element('<div class="wrapper">This input is wrappered</div>');
element.after(wrapper);
wrapper.prepend(element);
scope.$on("$destroy", function() {
wrapper.after(element);
wrapper.remove();
});
}
};
}
]);
And here's a plunker you can play with.
IMPORTANT: scope vs element $destroy. You must put your cleanup in scope.$on("$destroy") and not in element.on("$destroy") (which is what I was originally attempting). If you do it in the latter (element) then an "ngIf end" comment tag will get leaked. This is due to how Angular's ngIf goes about cleaning up its end comment tag when it does its falsey logic. By putting your directive's cleanup code in the scope $destroy you can put the DOM back like it was before you wrappered the input and so ng-if's cleanup code is happy. By the time element.on("$destroy") is called, it is too late in the ng-if falsey flow to unwrap the original element without causing a comment tag leak.
Why not doing a directive like that?
myApp.directive('wrapForm', function(){
return {
restrict: 'AC',
link: function(scope, inputElement, attributes){
var overallWrap = angular.element('<div />');
var validation = angular.element('<div />').appendTo(overallWrap);
var button = angular.element('<div />').appendTo(overallWrap);
var inputWrap = angular.element('<div />').appendTo(overallWrap);
overallWrap.insertBefore(inputElement);
inputElement.appendTo(inputWrap);
inputElement.on('keyup', function(){
if (inputElement.val()) {
validation.text('Just empty fields are valid!');
} else {
validation.text('');
}
});
}
}
});
Fiddle: http://jsfiddle.net/bZ6WL/
Basically you take the original input field (which is, by the way, also an angularjs directive) and build the wrappings seperately. In this example I simply build the DIVs manually. For more complex stuff, you could also use a template which get $compile(d) by angularjs.
The advantage using this class or html attribute "wrapForm": You may use the same directive for several form input types.
Why not wrap the input in the compile function?
The advantage is that you will not have to copy attributes and will not have to cleanup in the scope destroy function.
Notice that you have to remove the directive attribute though to prevent circular execution.
(http://jsfiddle.net/oscott9/8er3fu0r/)
angular.module('directives').directive('wrappedWithDiv', [
function() {
var definition = {
restrict: 'A',
compile: function(element, attrs) {
element.removeAttr("wrapped-with-div");
element.replaceWith("<div style='border:2px solid blue'>" +
element[0].outerHTML + "</div>")
}
}
return definition;
}
]);
Based on this: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
This directive does transclusion, but the transcluded stuff uses the parent scope, so all bindings work as if the transcluded content was in the original scope where the wrapper is used. This of course includes ng-model, also min/max and other validation directives/attributes. Should work for any content. I'm not using the ng-transclude directive because I'm manually cloning the elements and supplying the parent(controller's) scope to them. "my-transclude" is used instead of ng-transclude to specify where to insert the transcluded content.
Too bad ng-transclude does not have a setting to control the scoping. It would make all this clunkyness unnecessary.
And it looks like they won't fix it: https://github.com/angular/angular.js/issues/5489
controlsModule.directive('myWrapper', function () {
return {
restrict: 'E',
transclude: true,
scope: {
label: '#',
labelClass: '#',
hint: '#'
},
link: link,
template:
'<div class="form-group" title="{{hint}}"> \
<label class="{{labelClass}} control-label">{{label}}</label> \
<my-transclude></my-transclude> \
</div>'
};
function link(scope, iElement, iAttrs, ctrl, transclude) {
transclude(scope.$parent,
function (clone, scope) {
iElement.find("my-transclude").replaceWith(clone);
scope.$on("$destroy", function () {
clone.remove();
});
});
}
});

how to use custom directive to add other angularjs directive

I am a angularjs newbie, and I must miss something simple.
What I am trying to do is to create tooltip by using angular ui. I create a customer directive, in which it will add 3 angular directives to the attribute of the element based on the placeholder value:
myApp.directive('ngTooltip', function () {
return{
restrict: 'A',
link: function (scope, element, attrs) {
attrs.$set('tooltip', attrs['placeholder']);
attrs.$set('tooltip-placement', 'bottom');
attrs.$set('tooltip-trigger', 'focus');
}
}
});
In my markup, I have
and it got rendered as expected:
<input name="test" placeholder="this is test" tooltip="this is test" tooltip-placement="bottom" tooltip-trigger="focuse" />
However, the tooltip does not work. If I directly apply the 3 tooltip attributes to the markup, the tooltip works.
Looks like the 3 directives added by the custom directive do not get evaluated by angularjs.
Any ideas?
You can't dynamically add directives without recompiling the element with $compile, which will cause an infinite loop unless you resort to some workaround. There is an easier way to take care of this: declare a directive template and AngularJS will handle the directives properly.
myApp.directive('ngTooltip', function () {
return{
restrict: 'A',
template: '<input tooltip tooltip-placement="bottom" tooltip-trigger="focus">',
replace: true,
link: function (scope, element, attrs) {
attrs.$set('tooltip', attrs['placeholder']);
}
}
});
Working example: plunker.

Is it ok watch the scope inside a custom directive?

I'm trying to write a directive to create a map component so I can write something like:
<map></map>
Now the directive looks like this:
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.$watch('selectedCenter', function() {
renderMap(scope.selectedCenter.location.latitude, scope.selectedCenter.location.longitude, attrs.zoom?parseInt(attrs.zoom):17);
});
function renderMap(latitude, longitude, zoom){
GoogleMaps.setCenter(latitude, longitude);
GoogleMaps.setZoom(zoom);
GoogleMaps.render(element[0]);
}
}
};
});
The problem is that 'watch' inside the directive doesn't looks very well thinking in the reusability of the component. So I guess the best thing is being able to do something like:
<map ng-model="selectedCenter.location"></map>
But I don't know if it's even a good thing using angular directives inside custom directives or how can I get the object indicated in the ng-model attribute in the custom-directive's link function.
You will need to do something like that
angular.module('myApp')
.directive('map', function (GoogleMaps) {
return {
restrict: 'E',
scope: {
ngModel: '=' // set up bi-directional binding between a local scope property and the parent scope property
},
as of now you could safely watch your scope.ngModel and when ever the relevant value will be changed outside the directive you will be notified.
Please note that adding the scope property to our directive will create a new isolated scope.
You can refer to the angular doc around directive here and especially the section "Directive Definition Object" for more details around the scope property.
Finally you could also use this tutorial where you will find all the material to achieve a directive with two way communication form your app to the directive and opposite.
Without scope declaration in directive:
html
<map ng-model="selectedCenter"></map>
directive
app.directive('map', function() {
return {
restrict: 'E',
require: 'ngModel',
link: function(scope, el, attrs) {
scope.$watch(attrs.ngModel, function(newValue) {
console.log("Changed to " + newValue);
});
}
};
});
One easy way you can achieve this would be to do something like
<map model="selectedCenter"></map>
and inside your directive change the watch to
scope.$watch(attrs.model, function() {
and you are good to go

Resources