What is the lifecycle of an AngularJS Controller?
Well, actually the question is what is the life cycle for a ngView
controller.
Controllers are not singletons. Anyone can create a new controller and they are never auto-destroyed. The fact is that it's generally bound to the life cycle of its underlying scope. The controller is not automatically destroyed whenever its scope is destroyed. However, after destroying an underlying scope, its controller is useless (at least, by design, it should be).
Answering your specific question, a ngView
directive (as well for ngController
directive) will always create a new controller and a new scope every time a navigation happens. And the last scope is going to be destroyed as well.
The life cycle "events" are quite simple. Your "creation event" is the construction of your controller itself. Just run your code. To know when it gets useless ("destruction event"), listen to scope $destroy
event:
$scope.$on('$destroy', function iVeBeenDismissed() {
// say goodbye to your controller here
// release resources, cancel request...
})
For ngView
specifically, you are able to know when the content gets loaded through the scope event $viewContentLoaded
:
$scope.$on('$viewContentLoaded', function readyToTrick() {
// say hello to your new content here
// BUT NEVER TOUCHES THE DOM FROM A CONTROLLER
});
Here is a Plunker with a concept proof (open your console window).
Lifecycle of angular controller
It is expected behaviour in AngularJS, {{}}
(interpolation) directive will get call on each digest cycle and evaluates there expression. Like interpolation directive most of the angular directive gets evaluated when digest cycle run eg. ng-bind
, ng-show
, ng-class
, ng-if
, etc.
If you want to execute you binding code only once then you need to use bindonce directive which ::
& your code would be
<div ng-controller="MyCtrl">
{{::logToConsole()}}
</div>
Detailed explaination How Binding work in Angularjs?
AngularJS pure ng-controller lifecycle hooks
According to the section Scope Life Cycle in the scope documentation (v1.6.10) there are not such hooks (using the ng-controller
approach).
The scope lifecycle would go like this:
- Creation
The root scope is created during the application bootstrap by the $injector
. During template linking, some directives create new child scopes.
- Watcher registration
During template linking, directives register watches on the scope. These watches will be used to propagate model values to the DOM.
- Model mutation
For mutations to be properly observed, you should make them only within the scope.$apply()
. AngularJS APIs do this implicitly, so no extra $apply
call is needed when doing synchronous work in controllers, or asynchronous work with $http
, $timeout
or $interval
services.
- Mutation observation
At the end of $apply
, AngularJS performs a $digest
cycle on the root scope, which then propagates throughout all child scopes. During the $digest
cycle, all $watched
expressions or functions are checked for model mutation and if a mutation is detected, the $watch
listener is called.
- Scope destruction
When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy()
API. This will stop propagation of $digest
calls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.
Of course, alternatively, you can always use $rootScope.Scope#$on
for listening for changes.
Examples:
$scope.$on('my-custom-event', function () {
// some code to execute when my-custom-event is fired
});
$scope.$on('$destroy', function () {
// some code to execute when the scope is destroyed
});
When to use the AngularJS `$onInit` Life-Cycle Hook
Code has to be moved in the $onInit
function, when it depends on bindings, because these bindings are not available within this
in the constructor. They get assigned AFTER instantiation of the component class.
Example:
You have a state definition like this:
$stateProvider.state("app", {
url: "/",
views: {
"indexView": {
component: "category"
}
},
resolve: {
myResolve: (someService) => {
return someService.getData();
}
}
});
You can bind the result of myResolve
to your component like this:
export const CategoryComponent = {
bindings: {
myResolve: "<"
},
controller: Category
};
If you now log out this.myResolve
in the constructor
and in $onInit
you will see something like this:
constructor() {
console.log(this.myResolve); // <-- undefined
}
$onInit() {
console.log(this.myResolve); // <-- result of your resolve
}
So, your constructor should only contain constructing code like:
constructor() {
this.myArray = [];
this.myString = "";
}
Every angular specific initialisation and binding or dependency usage should be in $onInit
AngularJS directive/controller lifecycle and unbinding $watch and $on listeners
Angular handles that for you.
When scope is destroyed (for example when new view is loaded via ng-view directive old view's scope is destroyed ) all it's child scopes are destroyed and theirs $watchers and listeners registered via $on as well.
$rootScope isn't destroyed at all during lifetime of your application, so you have to manages it's listeners manually, but generally you register there stuff which should be permanent.
When you register listeners via addEventListener you have to remove them manually as it's not managed via angular.
What is the Angular ui-router lifecycle? (for debugging silent errors)
After some experimenting, I figured out how to see into the lifecycle well enough to debug my app and get a feel for what was happening. Using all the events, including onEnter, onExit, stateChangeSuccess, viewContentLoaded from here, gave me a decent picture of when things are happening in a way that's more both more flexible and specific to my code than a written out lifecycle. In the app module's "run" function, I placed:
This code would have saved me days of time and confusion if I started using it when I first started with Angular and UI-router. UI-router needs a "debug" mode that enables this by default.
$rootScope.$on('$stateChangeStart',function(event, toState, toParams, fromState, fromParams){
console.log('$stateChangeStart to '+toState.name+'- fired when the transition begins. toState,toParams : \n',toState, toParams);
});
$rootScope.$on('$stateChangeError',function(event, toState, toParams, fromState, fromParams, error){
console.log('$stateChangeError - fired when an error occurs during transition.');
console.log(arguments);
});
$rootScope.$on('$stateChangeSuccess',function(event, toState, toParams, fromState, fromParams){
console.log('$stateChangeSuccess to '+toState.name+'- fired once the state transition is complete.');
});
$rootScope.$on('$viewContentLoading',function(event, viewConfig){
console.log('$viewContentLoading - view begins loading - dom not rendered',viewConfig);
});
/* $rootScope.$on('$viewContentLoaded',function(event){
// runs on individual scopes, so putting it in "run" doesn't work.
console.log('$viewContentLoaded - fired after dom rendered',event);
}); */
$rootScope.$on('$stateNotFound',function(event, unfoundState, fromState, fromParams){
console.log('$stateNotFound '+unfoundState.to+' - fired when a state cannot be found by its name.');
console.log(unfoundState, fromState, fromParams);
});
AngularJS initialize controller variables
Controller lifecycle hooks were introduced in AngularJS 1.5. $onInit
hook is supposed to play exactly this role:
this.$onInit = function () { ... }
$onInit
hook is a replacement for pre-link function. It is executed by the compiler.
It may not be executed if a controller doesn't belong to a directive (ng-controller
is a directive, too) but is instantiated directly with $controller
, like route controller. In this case this.$onInit()
should be called explicitly in constructor.
It should be noticed that in original snippet function init() {...}
doesn't play significant role. It isn't exposed as a method, so it cannot help a controller to be more testable or extensible.
Related Topics
How to Disable Browser Developer Tools
Ruby Array to JavaScript Array
How to Geocode 20 Addresses Without Receiving an Over_Query_Limit Response
How to Include HTML in a Js Rails Response
Ruby on Rails 3.1 - Assets Pipeline - Assets Rendered Twice
JavaScript Equivalent of Rails Try Method
Jquery Clone() Not Cloning Event Bindings, Even with On()
Encrypting Data with Ruby Decrypting with Node
How to Prevent Closing Browser Window
Using Queryselectorall to Retrieve Direct Children
How to Add/Remove a Class in JavaScript
How to Run JavaScript Inside Swift Code
JavaScript in Wkwebview - Evaluatejavascript VS Adduserscript
Javascriptcore Call Function with Callback Swift
Drawing a Line with Three.Js Dynamically
How to Wait in Node.Js (Javascript)? L Need to Pause for a Period of Time