I am trying to console.log myobject but I keep getting undefined. I tried using $scope.watch and $attrs.$observe. Can someone tell me what I'm doing wrong.
here is the html
<my-component some-data="$ctrl.data"></my-component>
here is the component and controller
module.component('myComponent', {
bindings: {
needThisStuff: '<someData'
},
controller: Ctrl,
templateUrl:
requirejs.toUrl('path/to/templ.html')
});
function Ctrl(){
var self = this;
console.log(self.needThisStuff);
}
how can I access needThisStuff using $watch or $observe or anything else, I am using angular 1.5
I'm pretty sure the object is undefined because your console.log fires as soon as the component loads. The data hasn't been returned yet, so it is still undefined.
As a basic way to test this, you could wrap your console.log in a $timeout just to test.
To fix this, use angular component lifecycle hooks. Change your Ctrl to be something like this:
function Ctrl {
var self = this;
this.$onChanges = function(changesObj) {
console.log(changesObj.needThisStuff);
}
}
$onChanges is called whenever one-way bindings are updated, which in this case is your needThisStuff variable.
Related
I have an AngularJS 1.5 component inside an html page (not parent component)
and the component won't invoke $onChanges upon change.
HTML
<my-comp standards-data="standardsData"></my-comp>
Component
angular.module("bla").component('myComp', {
templateUrl: '/my-comp.tpl.html',
controller: myController,
bindings: {
standardsData: '<'
}
});
function myController() {
var self = this;
self.$onInit = function () {
self.standardsData = {};
}
self.$onChanges = function (changes) {
self.standardsData.something = changes.standardsData.currentValue.something;
};
}
When I fetch new data in the ctrl of the html that contains my component,
it won't influence the component. I get only into the $onInit of the component
and after $scope.standardsData changes (in the containing html/ctrl),
the $OnChanges of my component won't invoke.
Hope I described the problem correctly,
Thanks !
Your component binding to standardsData is a binding to an object reference, which does not change even when one of its properties is modified. As a result, $onChanges is not triggered. This is due to the fact that $onChanges uses a shallow $watch.
In other words, $onChanges is triggered only if we use primitives (i.e. non-objects) or change the reference of the javascript object bound into the component
So, you need to either: 1) Bind manually to the property/properties you want, rather than the object reference, or 2) Change the entire standardsData object when its data changes. You could also 3) Rewrite $onChanges functionality, I suppose (not recommended).
Option 1: Bind only to the property
Use if the parent controller/component merely changes a property or properties.
<my-comp standards-data-something="standardsData.something"></my-comp>
and
bindings: {
standardsDataSomething: '<'
}
Option 2: Change the reference
Use if the parent controller/component obtains completely new standardsData. You would keep your current HTML for this option and set:
standardsData = {newData}; //Reset the new data from http or wherever
to change the reference.
Some further reading you may find interesting:
http://blog.kwintenp.com/the-onchanges-lifecycle-hook/
http://www.codelord.net/2016/12/20/replacing-angulars-deep-watches-with-the-%24docheck-lifecycle-hook/
Is there a difference between using Controller1 vs Controller2?
angular.module('app', [])
.component('foo', {
templateUrl: 'foo.html',
bindings: {
user: '<',
},
controller: Controller1, //Or Controller2
});
function Controller1(){
this.$onInit = function(){
this.user = angular.copy(this.user);
};
this.$onChanges = function(changes){
if(changes.user && !changes.user.isFirstChange()){
this.user = angular.copy(changes.user.currentValue);
}
};
}
function Controller2(){
this.$onChanges = function(changes){
if(changes.user){
this.user = angular.copy(changes.user.currentValue);
}
};
}
Why should I bother with $onInit when I can just do the same this in $onChanges and save some rows?
Is this type of initialization better in $onChanges and $onInit better for some other kind of initialization?
$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
The $onChanges lifecycle hook gets called for a few reasons. The first, is on component initialisation, it passes down that initial changes Object at runtime, so we can grab our data straight away. The second reason it gets called is only when changes occur to '<' (one-way databinding) and '#' (for evaluated DOM attribute values) that are being bound to from the parent component. Once $onChanges gets called, you’ll get a special changes Object back that you can hook into, which we’ll explore in the upcoming sections.
The primary practical difference is $onInit will be called only on the directive initialisation but $onChanges will be called during initialisation and when < and # variables changes.
I have an Angular app where I'm using ui-grid. I want to have a custom action on a cell of the grid that calls a method from my app. So basically, this means calling a method that's somewhere up in the parent hierarchy, from a directive.
This would be achieved by calling something like: $scope.$parent.$parent.$parent.$parent.foo(). But that doesn't seem too nice.
One option would be to create a recursive function that goes up the ancestry of the $scope. That's nicer, but still seems a bit weird.
Also... Is it good practice to try to achieve something like this?
You're correct that $parent.$parent.$parent is definitely not a good practice.
If the method you're calling is another directive, you can require that directive in your child directive and then, the parentDirective's controller function will be injected as the fourth parameter to your link function:
In your DDO:
return {
require : '^parentDirective',
restrict : 'E',
link : function (scope, elem, attrs, parentDirectiveController) {}
}
If what you're trying to call is on a factory/service, you can inject that factory/service into your directive, although this sometimes is a code smell, depending on what you're trying to inject.
Finally, another way to do it is to use event propagation. From your directive, you can use $scope.$emit to send information up to parent controllers:
From the directive:
$scope.$emit('directiveDidStuff', {
data : 'blah'
});
In the parent controller:
$scope.$on('directiveDidStuff', function (evt, params) {
this.data = params.data; // equals blah
});
You can achieve the same by using "&" through one of the scope variable in directive.Like this, you can bind your event to the controller method and from the method, you could do your desired things or if the original business logic which you wants to achieve on onClick of the grid is used across many modules than you can bisect it in service and make it reusable and call the service from the event method. Let me know if you do have any doubts with the approach.
Key Code of example:
Html
<my-component attribute-foo="{{foo}}" binding-foo="foo" isolated-expression- foo="updateFoo(newFoo)" >
Directive
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
So recently I've been updating something that looks like the below:
$scope.arrayOfThings = [];
function setArrayOfThings() {
thingsService.get().then(function (things) {
$scope.arrayOfThings = things;
});
}
$scope.$on('arrayOfThings.add', setArrayOfThings);
To look more like this (using the lesser-known promise integration into bindings...):
$scope.arrayOfThings = thingsService.get();
But how do I force arrayOfThings to update (or re-resolve?) when the collection is changed from another $scope?
arrayOfThings can be seen only inside a child scope, so any change of arrayOfThings in a child scope will maintain data-binding anyway. The data-binding has to be resolve manually by $scope.$apply if the arrayOfThing is changed from an event (DOM event, $broadcast, etc)
You need to put a watch on the service call thingsService.get in the controller that you want to be notified of a change. Like this:
$scope.$watch(thingsService.get, function(newVal, oldVal){
$scope.arrayOfThings2 = newVal;
} );
This is assuming you are injecting that service into the new controller.
I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.