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
Related
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
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
I have an angular component:
module.exports = {
bindings: {
vulnerability: '<',
},
controller: ['$scope', function($scope){
//want to get value of vulnerability from above
$scope.vulnerability // doesn't work
}],
};
which i reference in an ng-repeat
<div ng-repeat="vulnerability in vulnerabilities" class="vulnerability-item">
<vulnerability-item vulnerability="vulnerability"> </vulnerability-item>
</div>
The value of vulnerability isn't going to change once the directive is called so I don't need to watch that value, just to reference it once it gets set.
I did put a $scope.$watch on the property just to see if it worked and it did trigger but I couldn't see the new or old value in the handler of the watch so it didn't help me anyway.
I tried $scope.vulnerability and $ctrl.vulnerability which is how I'd reference that property in the directives template but neither worked. How do I get the bound value that is passed in?
adding this watch:
$scope.$watch('vulnerability', function (oldV, newV) {
console.log('old', oldV)
console.log('new', newV)
})
I get a new undefined and old undefined in the console for each instance of the component although if i change $scope.$watch('vulnerability' to $scope.$watch('nonsense') It still fires the logs once for each component.
If the value isn't supposed to be changed, it should be bound once, vulnerability="::vulnerability". This gives the context to the question (and also saves some time on optimization).
In this case $onInit hook may be used:
...
controller: function() {
this.$onInit = function () {
this.vulnerability
}
},
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.
I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.