How to make this call in an angular scenario? - angularjs

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.

Related

How can I be notified when DOM elements are added to my directive?

I've got a simple directive that draws a few elements, like in this example. I want to programatically set some style properties but in the link function, the elements are apparently not there yet.
Here's a fiddle.
What I think is happening is that when I call the colorSquares function, there are no squares yet in the DOM. Wrapping it in a $timeout, it works, but that just feels so wrong.
Is there any way I can be notified when the elements exist? Or is there a place that I can put the code which will access them that is guaranteed to run after they exist?
myApp.directive('myDirective', ['$timeout', function ($timeout) {
return {
restrict: 'E',
replace: false,
link: function (scope, elem, attr) {
scope.squares = [1,2,3,4,5];
function colorSquares() {
var squaresFromDOM = document.getElementsByClassName('square');
for (var i = 0; i < squaresFromDOM.length; i++) {
squaresFromDOM[i].style['background-color'] = '#44DD44';
}
}
// this does not work, apparently because the squares are not in the DOM yet
colorSquares();
// this works (usually). It always works if I give it a delay that is long enough.
//$timeout(colorSquares);
},
template: '<div><div ng-repeat="s in squares" class="square"></div></div>'
};
}]);
You should work with Angular rather than against it which is to say you should use data bindings to do what you are trying to do rather than events/notifications in this context.
http://jsfiddle.net/efdwob3v/5/
link: function (scope, elem, attr) {
scope.squares = [1,2,3,4,5];
scope.style = {"background-color": "red"};
},
template: '<div><div ng-repeat="s in squares" class="square" ng-style="style"></div></div>'
That said there's no difference in doing the above and just using a different class that has that red background color or even just doing style="background-color: red;"
you put the answer in your qeustion, "It always works if I give it a delay that is long enough.".
So just make the delay long enough, in this situation that can be achieved by adding an onload event because when the elements get added to the DOM it calls that event.
So instead of just colorSquares(); you could use:
window.addEventListener("load", colorSquares);
Though this may not be the ideal solution since it will also trigger when something else triggers the onload event.
Answering your question directly. To know if an element is added to a directive or to the DOM in general, you can simply put a directive on that element, since the directive will run only when the element on which it "sits" is already in the DOM.
Using part of your code as an example:
myApp.directive('myDirective', function () {
return {
...
//put custom directive that will notify when DOM is ready
template: '<div><div ng-repeat-ready ng-repeat="s in squares" class="square"></div></div>'
};
});
And here is the custom ng-repeat-ready directive:
myApp.directive('ngRepeatReady', function () {
return {
link: function (scope) {
if (scope.$last) {
//do notification stuff here
//for example $emit an event
scope.$emit('ng-repeat is ready');
}
}
}
});
This directive will run when the element on which is sits is already in the DOM and check if the element has $last property on the scope (ng-repeat sets this flag for the last element of the iterated object) which means that the ng-repeat directive is done and you can now operate on the DOM safely.

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.

How can I act on a html document once processed by angular?

I am relatively new to Angular.
I have a html document in which angular creates a html table with ng-repeat. When this table has been built, I would like to apply to it a Jquery function. How can I do that ?
function : $("#creneaux").footable()
If I apply the function in the controller when it is instantiated, nothing happens. when I apply it in the javascript console when the page has been displayed, it works.
Firstly, I would move the $("#creneaux").footable() into a directive.
Solution:
Use $timeout without a delay to (a bit simplified) put the action at the end of the browser event queue after the rending engine:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
$timeout(function() {
$("#creneaux").footable();
});
}
};
});
Demo: http://plnkr.co/edit/b05YKhipeVmrVHu2Xzsm?p=preview
Good to know:
Depending on what you need to perform, you can instead use $evalAsync:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$evalAsync(function() {
$("#creneaux").footable();
});
}
};
});
The difference is that now the code will run after the DOM has been manipulated by Angular, but before the browser re-renders.
This can in certain cases remove some flickering that might be apparent between the rendering and the call to for example the jQuery plugin when using $timeout.
In the case of FooTable, the plugin will run correctly, but the responsiveness will not kick in until the next repaint, since the correct dimensions are not available until after rendering.
Try writing a directive.
app.directive('sample', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// your jquery code goes here.
},
};
});
Learn to write everything in angular instead jquery. This may help you "Thinking in AngularJS" if I have a jQuery background?

Angular Directive Passing Template Name in as an attribute

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

How to move code from App.js to Directive

I have a small amount of js in the app.js file that I needed in order to manipulate the DOM in this Angular Grid:
http://plnkr.co/PXRgUA
You can see it in app.js.
$('.userRow ').live('click', function(e) {
$(this).find('span.userDetailRow.blueRow').show().animate({height:200},500);
});
$('.closeDetails').live('click', function(e) {
$(this).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
e.stopPropagation();
});
How can I move this to a directive?
Does it have to be moved to a directive?
It does not seem right here.
Yes, you can (and should) move it to a directive. For the sake of clarity I'll include your old code here:
$('.userRow ').live('click', function(e) {
$(this).find('span.userDetailRow.blueRow').show().animate({height:200},500);
});
$('.closeDetails').live('click', function(e) {
$(this).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
e.stopPropagation();
});
This (binding event listeners with jquery) is what people are chomping at the bit to describe as 'not the angular way.' Instead, you can use ng-click (which is just an inbuilt directive) to call javascript functions:
<tr row ng-click="expandRow()" ng-repeat="user in users" class="item-in-list el userRow" animate="fadeIn">
<span class="userDetailRow blueRow" style="display:none;"><span close ng-click="closeDetails(); $event.stopPropagation()">x</span>
You can see here there are two custom attributes defined on these elements. These link to the directives below. These directives have custom functions defined in their link function which you can then call with ng-click (though note that this is putting these functions on the global scope).
.directive('close', function() {
return {
restrict: 'A',
replace: false,
link: function($scope, element, attrs) {
$scope.closeDetails = function() {
$(element).parent('span').animate({height:0}, 500).animate({height:0},500).hide();
}
}
}
})
.directive('row', function() {
return {
restrict: 'A',
replace: false,
link: function($scope, element, attrs) {
$scope.expandRow = function() {
$(element).find('span.userDetailRow.blueRow').show().animate({height:200},500);
}
}
}
});
jQuery is still being used to here to locate and modify the elements for the sake of simplicity, so you can see where your old logic has gone. However you should ideally change this to use angular's inbuilt animation functionality. (more info on how animation works in the new angular version: http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
Plunker here:
http://plnkr.co/edit/UMvYnx?p=preview

Resources