Is there a way to detect directive destruction in AngularJS? - angularjs

I want to use a $timeout in a directive in AngularJS. But I can't find a way in the directive documentation to detect when it is destroyed, in case it happens before my timeout finishes and I need to cleanup the timeout.
Is there an event I can bind to or some built in function (similar to $destroy for controllers) that I can use to detect when my directive will be destroyed? Or am I missing a fundamental concept about directives?

The $destroy event you mentioned can also be used in a directive:
app.directive('myDirective', function() {
return {
link: function(scope) {
scope.$on('$destroy', function() {
// Clean up
});
}
};
});

Related

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?

Firing angular js animation within a directive

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);

Directive inside ng-repeat

I'm trying to get a directive inside an ng-repeat to work.. it worked when it was hardcoded in HTML but after switching to an ng-repeat certain aspects of it stopped working.
<div ng-repeat="section in filterSections" filter-tray>
Test {{section.label}}
</div>
I have a module with a controller that emits events:
controller: function($scope, $element) {
this.activateTray = function(trayID) {
$scope.$emit('filterTray::show', {
tray: trayID
});
};
};
I have a directive on the page - it should receive events from the controller. Since switching to using ng-repeat receiving the event has stopped working. It still initialises, it just doesn't listen for the event.
.directive('filterTray', function() {
return {
restrict: 'A',
require: '^filter',
link: function($scope, $element, attrs, filterNavController) {
console.log('this debug statement works');
$scope.$on('filterTray::show', function(e, data) {
console.log('this debug statement never runs');
});
}
};
})
Since adding the repeat has the $scope variable been affected? Perhaps $on isn't listening to the correct thing anymore? Any ideas / tips would be appreciated!
Yes - ngRepeat creates a new scope.
Not sure if events are the right design choice, but if so, use broadcast instead of emit, and the events will reach your directive's scope.
controller: function($scope, $element) {
this.activateTray = function(trayID) {
$scope.$broadcast('filterTray::show', {
tray: trayID
});
};
};
emit shoots events up the scope tree to all ancestors
broadcast reaches all descendants.
When creating a directive that will be reused (this includes ngRepeat) it is best practice to create an isolate scope for your directive. This way you can send the trayID without having to use events. Check out the section on isolate scope in the AngularJS docs on directives here.

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).

Can someone provide an example of a $destroy event for scopes in AngularJS?

Can someone please provide an example of scope's $destroy event? Here is the reference documentation from http://docs.angularjs.org/api/ng.$rootScope.Scope#$destroy
$destroy()
Removes the current scope (and all of its children) from the parent
scope. Removal implies that calls to $digest() will no longer
propagate to the current scope and its children. Removal also implies
that the current scope is eligible for garbage collection.
The $destroy() is usually used by directives such as ngRepeat for
managing the unrolling of the loop.
Just before a scope is destroyed a $destroy event is broadcasted on
this scope. Application code can register a $destroy event handler
that will give it chance to perform any necessary cleanup.
Demo: http://jsfiddle.net/sunnycpp/u4vjR/2/
Here I have created handle-destroy directive.
ctrl.directive('handleDestroy', function() {
return function(scope, tElement, attributes) {
scope.$on('$destroy', function() {
alert("In destroy of:" + scope.todo.text);
});
};
});
$destroy can refer to 2 things: method and event
1. method - $scope.$destroy
.directive("colorTag", function(){
return {
restrict: "A",
scope: {
value: "=colorTag"
},
link: function (scope, element, attrs) {
var colors = new App.Colors();
element.css("background-color", stringToColor(scope.value));
element.css("color", contrastColor(scope.value));
// Destroy scope, because it's no longer needed.
scope.$destroy();
}
};
})
2. event - $scope.$on("$destroy")
See #SunnyShah's answer.

Resources