When to use the AngularJS `$onInit` Life-Cycle Hook - angularjs

With the release of AngularJS V1.7, the option to pre-assign bindings to has deprecated and removed:
Due to 38f8c9, directive bindings are no longer available in the constructor.
To migrate your code:
If you specified $compileProvider.preAssignBindingsEnabled(true) you need to first migrate your code so that the flag can be flipped to false. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide. Afterwards, remove the $compileProvider.preAssignBindingsEnabled(true) statement.
— AngularJS Developer Guide - Migrating to V1.7 - Compile
Due to bcd0d4, pre-assigning bindings on controller instances is disabled by default. We strongly recommend migrating your applications to not rely on it as soon as possible.
Initialization logic that relies on bindings being present should be put in the controller's $onInit() method, which is guaranteed to always be called after the bindings have been assigned.
— AngularJS Developer Guide - Migrating from v1.5 to v1.6 - $compile
What are the use cases when code has to be moved to the $onInit Life-Cycle Hook? When can we just leave the code in the controller construction function?

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

Related

Constructor in component runs code before data is available [duplicate]

With the release of AngularJS V1.7, the option to pre-assign bindings to has deprecated and removed:
Due to 38f8c9, directive bindings are no longer available in the constructor.
To migrate your code:
If you specified $compileProvider.preAssignBindingsEnabled(true) you need to first migrate your code so that the flag can be flipped to false. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide. Afterwards, remove the $compileProvider.preAssignBindingsEnabled(true) statement.
— AngularJS Developer Guide - Migrating to V1.7 - Compile
Due to bcd0d4, pre-assigning bindings on controller instances is disabled by default. We strongly recommend migrating your applications to not rely on it as soon as possible.
Initialization logic that relies on bindings being present should be put in the controller's $onInit() method, which is guaranteed to always be called after the bindings have been assigned.
— AngularJS Developer Guide - Migrating from v1.5 to v1.6 - $compile
What are the use cases when code has to be moved to the $onInit Life-Cycle Hook? When can we just leave the code in the controller construction function?
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

How to use bindings in controller in an AngularJs 1.6 component

Hello here is my component :
angular.module('myApp').component('dendroCtrl', {
templateUrl: '/templates/dendro.html',
bindings: {
id: '=',
type: '=',
mini: "="
},
controller: function ($scope, Api) {
//Dendro
var test = mini;
I tried this :
var test = mini;
var test = this.mini;
var test = $scope.mini;
Mini is everytime undefined.
How could I use my bindings in my controller ? Thanks
EDIT
This is how I call the component :
<dendro-ctrl id="149" type="Demand" mini="false"></dendro-ctrl>
From the documentation:
Components have a well-defined lifecycle Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life of the component. The following hook methods can be implemented:
$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller.
$onChanges(changesObj) - Called whenever one-way bindings are updated. The changesObj is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form
So you can just use:
controller: function(Api) {
var ctrl = this;
ctrl.$onInit = function() {
console.log(ctrl.mini);
}
}
if you want to display the initial value of the mini binding.

How to resolve Angular Service directly from an Angular Component

I am currently using Angular 1.5. I am using ui-router as my primary navigation mechanism. I am leveraging Angular components.
I understand that I can use .resolve on my states to instantiate services which are then passed down through my component hierarchy (mostly using one-way bindings).
One of my components is called literatureList and is used in more than one route/state. The literatureList component makes use of a specific service called literatureListService. literatureListService is only used by literatureList. literatureListService takes a while to instantiate, and uses promises etc.
In each of the .state definitions then I need to have a .resolve that instantiates literatureListService. This means that I need to refer to this literatureListService in each of the .state.resolve objects. This doesn't seem very DRY to me.
What I'd really like to do is remove the literatureListService references from the .state.resolve objects and 'resolve' the service from 'within' the literatureList component itself.
How do I code a 'resolve-style' mechanism within the literatureList component that will handle the async/promise nature of literatureListService? What is best practice for doing this?
Code snippets follow:
state snippets:
$stateProvider.state({
name: 'oxygen',
url: '/oxygen',
views: {
'spofroot': { template: '<oxygen booklist="$resolve.literatureListSvc"></oxygen>' }
},
resolve:{
literatureListSvc: function(literatureListService){
return literatureListService.getLiterature();
}
}
});
$stateProvider.state({
name: 'radium',
url: '/radium',
views: {
'spofroot': { template: '<radium booklist="$resolve.literatureListSvc"></radium>' }
},
resolve:{
literatureListSvc: function(literatureListService){
return literatureListService.getLiterature();
}
}
});
literatureListService:
angular.module('literature')
.factory('literatureListService',function($http,modelService){
// Remember that a factory returns an object, whereas a service is a constructor function that will be called with 'new'. See this for a discussion on the difference: http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html
console.log('literatureListService factory is instantiating - this will only happen once for each full-page refresh');
// This is a factory, and therefore needs to return an object containing all the properties that we want to make available
var returnObject = {}; // Because this is a factory we will need to return a fully-formed object (if it was a service we would simply set properties on 'this' because the 'context' for the function would already have been set to an empty object
console.log('service instantiation reporting that modelManager.isDataDirty='+modelService.isDataDirty);
// The getLiterature method returns a promise, and therefore can only be consumed via a promise-based mechanism
returnObject.getLiterature = function($stateParams){
console.log('literatureService.getLiterature will now return a promise (via a call to $http)');
return $http({method: 'GET', url: 'http://localhost:3000/literature/'});
};
return returnObject;
});
oxygen component html:
<div>
This is the OXYGEN component which will now render a literature list, passing in bookList.data as books
<literature-list books="$ctrl.booklist.data"></literature-list>
</div>
oxygen component js
angular.module('frameworks')
.component('oxygen',{
templateUrl:"frontend/framework/frameworks/oxygenComponent.html",
controller:function($http){
var $ctrl = this;
console.log('Hello from the oxygen component controller with literatureListSvc='+$ctrl.booklist); // Bound objects NOT YET AVAILABLE!!!!!!
this.$onInit = function() {
//REMEMBER!!!! - the bound objects being passed into this component/controller are NOT available until just before the $onInit event fires
console.log('Hello from the oxygen component controller onInit event with bookList='+JSON.stringify($ctrl.booklist));
};
}
,bindings:{ // remember to make these lowercase!!!
booklist:'<'
}
});
literatureList component html:
<div>
{{$ctrl.narrative}}
<literature-line ng-repeat="literatureItem in $ctrl.books" wizard="fifteen" book="literatureItem" on-tut="$ctrl.updateItemViaParent(itm)">555 Repeat info={{literatureItem.title}}</literature-line>
</div>
literatureList component js
angular.module('literature')
.component('literatureList',{
templateUrl:'frontend/literature/literatureListComponent.html',
//template:'<br/>Template here33 {{$ctrl.listLocalV}} wtfff',
// controller:function(literatureListService){
controller:function(){//literatureListService){
var $ctrl=this;
this.narrative = "Narrative will unfold here";
this.updateItemViaParent = function(book){
this.narrative = 'just got notified of change to book:'+JSON.stringify(book);
};
this.$onInit = function(){
console.log('literatureList controller $onInit firing with books='+JSON.stringify($ctrl.books));
};
this.$onChanges = function(){
console.log('literatureList controller $onChanges firing');
};
},
bindings: {
books:'<'
}
});
As JavaScript in reference based, you can crete object in your service and access it in all three controllers that you need.
For Example:
function serviceA() {
var vm = this;
vm.testObject = {};
vm.promise1().then(function(response) {
vm.testObject = response;
})
}
function ControllerA($scope, serviceA) {
$scope.testA = service.testObject;
}
In this case, as soon as the promise is resolved, all the controllers will get the value of the response and can be used in the partials respecively

$onInit does not immediately have bindings data

I have this component in my app:
<actions-bar actions="$ctrl.actionsBarData"></actions-bar>
Here is the controller / component definition:
.component('actionsBar', {
controller: 'actionsBarController',
bindings: {
actions: '<'
}
})
.controller('actionsBarController', function() {
var vm = this;
vm.$onInit = function() {
console.log(vm.actions);
setTimeout(function() {
console.log(vm.actions);
}, 500);
};
});
Take note of the two console.log statements within $onInit. In the browser the first logging statement prints undefined. The second statement, wrapped in setTimeout correctly prints out the this.actions object.
According to the Angular docs:
$onInit() - Called on each controller after all the controllers on an
element have been constructed and had their bindings initialized
If the bindings have been initialized, why is the data not immediately available? Why must I set a delay of 500ms before it is available?
If $onInit isn't the solution here, how can I reliably access the bindings data within my controller? It appears that this is correct lifecycle hook to use, and none of the other hooks appear to be what I want in this case.
This is because while the bindings have been initialized in the controller, they haven't necessarily been initialized where they are coming from. If you are populating your binding with an async call, the object is going to be undefined or empty.
$onChanges allows you to inspect changes that happen to your bindings.
vm.$onChanges = function (changesObj) {
console.log(changesObj.actions);
}
If what I described above is happening, this will fix it for you.
EDIT: a word

AngularJS 1.5.x $onChanges Not Working with One-Way Binding Changes

I don't understand why $onChanges isn't kicked off when I change a bound primitive in an input. Can someone see what I've done wrong, and explain this in an uncomplicated way? I made a plunkr of a quick test application after I couldn't get it to work in my actual application either.
angular
.module('test', [])
.component('test', {
template: '<child application="vm.application"></child>',
controller: 'testCtrl as vm'
})
.controller('testCtrl', function() {
var vm = this;
vm.$onInit = function () {
vm.application = {
data: {
name: 'Test'
}
}
};
})
.component('child', {
template: '<input type="text" ng-model="vm.application.data.name">',
bindings: {
application: '<'
},
controller: 'childCtrl as vm'
})
.controller('childCtrl', function() {
var vm = this;
vm.$onChanges = function (changes) {
console.log('CHANGED: ', changes);
};
})
The $onChanges method is not called for changes on subproperties of an object. Default changes to objects generally follow this sequence within a components lifetime:
UNINITIALIZED_VALUE to undefined
undefined to {} or { someAttribute: someValue, .. }
({..} to undefined if you delete the object in a parent scope)
In order to watch subproperties you could use the $doCheck method that was added in 1.5.8. It is called on every digest cycle and it takes no parameters. With great power comes great responsibility. In that method you would put logic that detects whether a certain value has been updated or not - the new value will already be updated in the controller's scope, you just need to find a way to determine if the value changed compared to the previously known value.
You could set a previousValueOfObjectAttribute variable on the controller before you start to expect changes to this specific attribute (e.g. when subcomponent B calls an output binding function in component A, based on which the target object - which is an input binding to B - in A changes). In cases where it is not predictable when the change is about to occur, you could make a copy of the specific atributes of interest after any change observed via the $doCheck method.
In my specific use case, I did not explicitly check between an old and new value, but I used a promise (store $q.defer().promise) with the intention that any change I would 'successfully' observe in the $doCheck method would resolve that promise. My controller then looked something like the following:
dn.$doCheck = function () {
if (dn.waitForInputParam &&
dn.waitForInputParam.promise.$$state.status === 0 &&
dn.targetObject.targetAttribute !== false)
dn.waitForInputParam.resolve(dn.targetObject.targetAttribute);
}
dn.listenToInputChange = function () {
dn.waitForInputParam = $q.defer();
dn.waitForInputParam.promise.then(dn.onInputParamChanged);
}
dn.onInputParamChanged = function (value) {
// do stuff
//
// start listening again for input changes -- should be async to prevent infinite $digest loop
setTimeout(dn.listenToInputChange, 1);
}
(w.r.t. promise.$$state.status, see this post).
For all other intents and purposes, watching changes to primitive data types, you should still use $onChanges. Reference: https://docs.angularjs.org/guide/component
It's $onChanges and not $onChange.
Also, the onChange only updates when the parent value is changed, not the child. Take a look at this plunkr. Note the console.log only fires when you type in the first input.
As others said above, Angular does not watch for changes in object properties, however, you can make Angular believe that your object is changed by reference.
It is sufficient to do a shallow copy of the object in order to trigger an $onChanges event:
vm.campaign = angular.extend({}, vm.campaign);
Credits to #gkalpak
Dealing with $onChanges is tricky. Actually, thats why in version 1.5.8 they introduced the $doCheck, similar to Angular 2 ngDoCheck.
This way, you can manually listen to changes inside the object being listened, which does not occur with the $onChanges hook (called only when the reference of the object is changed). Its the same thing, but it gets called for every digest cycle allowing you to check for changes manually (but better then watches).
For more details, see this blog post.

Resources