Firing angular js animation within a directive - angularjs

I have a simple directive along these lines:
angular.module('application').directive( 'sdTitle', sdTitleDirective );
sdTitleDirective.$inject = ['$animate'];
function sdTitleDirective($animate) {
var directive = {
template: '<div class="title-container">some content</div>',
link: link,
replace: true
};
return directive;
function link(scope, element, attrs) {
element.bind("click", function() {
$animate.leave(element);
});
}
}
And an animation similar to this:
angular.module('application').animation('.title-container', titleAnimation);
function titleAnimation() {
return {
leave: leaveAnimation
};
function leaveAnimation(element, done) {
console.log('animate leave', element);
element.hide().fadeOut(800, done);
}
}
I can't seem to get the leaveAnimation to actually fire when the directive's element is clicked. I must be missing something with how $animate works or how javascript animations are called, but I'm at a loss.
How do I correctly use the $animate service's animation methods within a directive and with javascript rather than CSS3 animations?

DOM event handlers attached by for example addEventListener() or the jqLite/jQuery methods bind and on are executed outside of the current Angular context and will not trigger the digest loop.
You will have to do it manually by using for example $apply:
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
Example:
element.bind("click", function() {
scope.$apply(function() {
$animate.leave(element);
});
});
Demo: http://plnkr.co/edit/OepqBsCW25Lf2qCenbtK?p=preview
It's not apparent from your posted code if you aren't, but you need to use the ngAnimate module.
Note that in the demo I also changed:
element.hide().fadeOut(800, done);
To:
element.fadeOut(800, done);

Related

AngularJS Passing function with arguments to event directive

To start off, I know there are loads of similar questions but none that I found which supports execution of arbitrary methods with event listeners.
I have this directive which executes functions passed when the window resizes.
app.directive('onResize', function() {
var directive = {
'link': function(scope, element, attrs) {
var onResizeHandler = scope.$eval(attrs.onResize);
angular.element(window).on('resize', onResizeHandler);
angular.element(window).on('$destory', function() {element.off();});
}
};
return directive;
});
I can trigger the above directive with
<div data-on-resize="stickyHeader">...</div>
Which runs my method, inside my controller.
app.controller('myController', [$scope, function($scope) {
$scope.stickyHeader = function() {
console.log('event triggered')
};
}]);
All the above code works fine, but I need to pass some arguments to stickyHeader as in data-on-resize="stickyHeader(arg1, arg2)" When I try that, I get Cannot read property 'call' of undefined at ng (angular.js:3795) in the console. Not sure what I can do to make my directive support arbitrary methods with arguments.
The directive needs to evaluate the AngularJS expression defined by the on-resize attribute every time the events occurs:
app.directive('onResize', function($window) {
var directive = {
link: function(scope, elem, attrs) {
angular.element($window).on('resize', onResizeHandler);
scope.$on('$destroy', function() {
angular.element($window).off('resize', onResizeHandler);
});
function onResizeHandler(event) {
scope.$eval(attrs.onResize, {$event: event});
scope.$apply();
}
}
};
return directive;
});
Also since the resize event comes from outside the AngularJS framework, the event needs to be brought into the AngularJS execution context with $apply().
Further, to avoid memory leaks, the event handler needs to be unbound when the scope is destroyed.
Usage:
<div data-on-resize="stickyHeader($event)">...</div>
For more information, see AngularJS Developer Guide - $event.

Angular class change directive doesn't work

I want to add a directive that will do something when an element class is changed.
My directive is:
(function (angular) {
angular.module('myApp')
.directive('myClassWatch', myClassWatch);
function myClassWatch() {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
scope.$watch(function () {
return element.attr('class');
}, function (newValue, oldValue) {
debugger;
if(oldValue !== newValue)
console.log('class changed from ' + oldValue + ' to ' + newValue);
});
}
}
}
})(angular);
The html is:
<div class="top-icons-item popup-container popup-link-container" my-class-watch>
</div>
I do some actions elsewhere that toggles the class "open" in my div element - it is visible in the html - yet the debugger is never called (also no console logs of course). I can see that the link function is called on page load and the debugger also stops, but thats only on page load and not afterwards when I actually do actions that adds another class.
I have read several issued here before including Directive : $observe, class attribute change caught only once but I can't understand why I don't get the same result. What can I do to try check why this occures?
Update: The class change is made using jQuery not in a controller but in an old jquery watches code. May this be cause? could angular be unaware of class change when its not done from an angular code?
Wrap your jQuery code into $apply.
It similar to you are making changes to $scope out of angular context(jquery ajax, setTimeout, etc). Use $apply to make angular know about the changes done.
angular.element(document.getElementById('app')).injector().invoke(['$compile', '$rootScope', function($compile, $rootScope) {
$rootScope.$apply(function() {
//your jquery code goes here...
var a = document.getElementById('abc');
angular.element(a).addClass('hello');
});
}]);

When does Angulars link function run?

From what I understand it only runs once before the page is rendered. But is there ever a case where it runs after the page has been rendered?
I tried testing a few things with this plnkr:
angular
.module('app', [])
.directive('test', function($http) {
return {
restrict: 'E',
template: '<input />',
link: function(scope, el, attrs) {
var input = angular.element(el[0].children[0]);
input.on('change', function() {
console.log('change event');
scope.$apply(function() {
console.log('digest cycle');
});
});
input.on('keyup', function() {
console.log('keyup event');
var root = 'http://jsonplaceholder.typicode.com';
$http.get(root+'/users')
.success(function() {
console.log('http request successful');
})
.error(function() {
console.log('http request error');
});
});
console.log('link function run');
}
};
});
Does typing in the input field cause the link function to run?
Do event listeners cause the link function to run?
Do http requests (made with $http?) cause the link function to run?
Do digest cycles cause the link function to run?
The answer to all of these questions seem to be "no".
The link function runs when an instance of the directive is compiled, in this case, when a <test></test> element is created. That can be when angular's bootstrapping compiles the page, when it comes into being from a ng-if, when a ng-repeat makes it, when it's made with $compile, etc.
link will never fire twice for the same instance of the directive. Notably, it fires right after the template has been compiled in the directive's lifecycle.
1 - No, it causes to change the only ng-model if you have it binded.
2 - No, it will only launch the code inside the event binds.
3 - Again no, the event bind will launch the $http.get(). And please don't put an $http directly on your directive. Use a factory or something like that.
4 - Dunno
As Dylan Watt said, the directive link runs only when the directive is compiled (only once) per element/attr.... You can compile it in different ways. Plain http, $compile, ng-repeat....
You can create a $watch inside your directive to "relaunch" some code on a binded element change.
This maybe can help you: How to call a method defined in an AngularJS directive?

AngularJS: accessing the ngTouch service from a directive?

I really love how the new ng-click directive in Angular now automatically includes functionality for touch events. However, I am wondering if it is possible to access that touch-event service from my custom directive? I have lots of directives that require that I bind a click event to the given element, but I'm simply doing that using the typical jquery syntax (ex: element.on('click', function(){ ... })). Is there a way that I can bind an ng-click event to an element within a directive? Without having to manually put a ng-click tag on my element in the HTML of my view...?
I want to be able to harness the power of both click and touch events. I could obviously import a library (such as HammerJS or QuoJS) but I would prefer not to have to do that, especially since Angular is already doing it.
I can access the $swipe service and bind different elements to that, but is there a similar service for ngTouch?
For reference, this is an example of when I would want to do this:
mod.directive('datepicker', ['$timeout', function($timeout){
return {
link: function(scope, elem, attrs){
var picker = new DatePicker();
elem.on('click', function(e){
picker.show();
});
// I would rather do something like:
// elem.on('ngTouch', function(){ ... });
//
// or even:
// $ngTouch.bind(elem, {'click': ..., 'touch': ...});
}
}
}]);
UPDATE: As noted by below, the source code for the ng-click directive is here. Can anyone see a way to harness that code and turn it into a "bindable" service?
I don't think that's quite the right approach. I'd approach this by using a template within your directive and then using ngTouch within that.
mod.directive('datepicker', ['$timeout', function ($timeout) {
return {
template: '<div ng-touch="doSomethingUseful()"></div>',
link: function (scope, elem, attrs) {
var picker = new DatePicker();
scope.doSomethingUseful = function () {
// Your code.
}
}
}
}]);
UPDATE
Full example with additional attributes on the directive element:
http://codepen.io/ed_conolly/pen/qJDcr

Model inside a template of a custom directive never updated

I have a simple custom directive myElement with a templateUrl that print a simple message:
<p>Message: {{message}}</p>
Here's the definition of my directive:
testapp.directive('myElement', function() {
return {
restrict: 'E',
template: '<p>Message: {{message}}</p>',
link: function(scope, elem, attrs) {
scope.message = 'This message is never updated... :(';
setTimeout(function() {
scope.message = "Why this message is never shown?";
}, 1000);
}
};
});
After 1 second, I expect an update of the message to "Why this message is never shown?". Unfortunately, the message is never updated.
Here is the jsFiddle: http://jsfiddle.net/seyz/SNMfc
Could you explain me why?
Due to how Angular dirty checking works, when you execute code outside of angular scope (e.g. using setTimeout, setInterval, or from within some third-party plugin callback) the changes produced by that code invocation won't be 'recognized' immediately by Angular scope.
For such scenarios you need to wrap your code inside the scope.$apply() method.
In this particular case you may simply use $timeout function to replace your setTimeout(fn, 1000)call with $timeout(fn, 1000)and your code will be wrapped with scope.$apply() call (Plunker).
You will need to use scope.$apply();
setTimeout(function() {
scope.message = "Why this message is never shown?";
scope.$apply();
}, 1000);
From the documentation:
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries).

Resources