Injector returns undefined value? - angularjs

I'm trying to get the service from my legacy code and running into a weird error with injector() returning undefined:
Check this plnkr
Also, I'm trying to set back the new property value back to the service, will that be reflected to the scope without the use of watch?
Thank you very much, any pointer or suggestion is much appreciated.

You're trying to get the element before the DOM has been constructed. It's basically the same issue as running javascript outside of a $(document ).ready(). So this line has no element to get:
var elem = angular.element($('#myCtr'));
Also, by the way, instead of using jQuery, another Angular option for doing the above is:
var elem = angular.element(document.querySelector('#myCtr'))
Angular provides an equivalent to $(document ).ready() called angular.element(document).ready() which we can use.
But you'll also need to grab scope and execute your change within a scope.$apply() so that Angular is aware that you've changed something that it should be aware of.
Combining the two we get:
angular.element(document).ready(function () {
var elem = angular.element($('#myCtr'));
//get the injector.
var injector = elem.injector();
scope= elem.scope();
scope.$apply(function() {
injector.get('cartFactory').cart.quantity = 1;
});
});
updated plnkr

Related

Updating 'this' Context Property Inside of a $Promise In An Angular JS Service

I have a function being used in my service that is defined as:
var getData = function() {
return anotherService.getData().$promise;
};
and a this property that I manipulate throughout the service.
this.someProperty = 'a string';
I call the above function inside the return section of my service:
return{
updateProperty: function(){
getData().then(function(data){
this.someProperty = data;
});
}
}
In the above, I get an this is undefined related error in my browser console. I assume this is because the resolved $promise is an AJAX call and this is used out of context. What's the best way to manipulate a this property using the returned data from an AJAX call in this instance?
if you're manipulating this throughout your service, assign it to a variable like var self = this. The problem is that this is the context of a function and can be changed by other code using fn.call(context) or fn.apply(context, args). So it's liable to be different inside of the scope of any given function.
So just assign it to some variable and use it:
var self = this;
return {
updateProperty: function(){
getData().then(function(data){
self.someProperty = data;
});
}
};
The simplest way would be to use the bind function. This function sets the 'this' context for the function call. So, in your case you'd have to use it twice so that the proper 'this' populates in.
return{
updateProperty: function(){
getData().then((function(data){
this.someProperty = data;
}).bind(this));
}
}
This comes to ensure that the handler you passed to the promise is executed with the original 'this' (passed to updateProperty). Now, to pass the correct 'this' value to the updateProperty function, you should, in your controller do:
(myService.updateProperty.bind(this))();
There are numerous versions of binding, including binding the entire service. Also, have a look at lodash for function extensions.
I prepared a small pen to demonstrate this. It covers what I listed above, plus another important thing to note. When you use setTimeout, the handler is invoked with in the global context (in this case, 'window'), this is why I added a third bind, to make sure 'this' is relevant inside the timeout handler. I also added various count increment calls to demonstrate that 'this' is the same value along the way.
If this is a repeating scenario, you might want to pass either the target object (and then use the handler just to know it was updated), or a handler (which also needs binding). I added examples for these scenarios as well.
One last word, call, apply and bind are key to javascript and worth learning. Put some time into it and work your way out of context hell.

Auto-updating scope variables in angularjs

I'm currently playing with AngularJS. I'd like to return, from a service, a variable that will let the scope know when it has changed.
To illustrate this, have a look at the example from www.angularjs.org, "Wire up a backend". Roughly, we can see the following:
var projects = $firebase(new Firebase("http://projects.firebase.io"));
$scope.projects = projects;
After this, all updates made to the projects object (through updates, be it locally or remotely) will be automatically reflected on the view that the scope is bound to.
How can I achieve the same in my project? In my case, I want to return a "self-updating" variable from a service.
var inbox = inboxService.inboxForUser("fred");
$scope.inbox = inbox;
What mechanisms let the $scope know that it should update?
EDIT:
In response to the suggestions, I tried a basic example. My controller:
$scope.auto = {
value: 0
};
setInterval(function () {
$scope.auto.value += 1;
console.log($scope.auto.value);
}, 1000);
And, somewhere in my view:
<span>{{auto.value}}</span>
Still, it only displays 0. What am I doing wrong ?
UPDATE:
I made a demo plunker: http://plnkr.co/edit/dmu5ucEztpfFwsletrYW?p=preview
I use $timeout to fake updates.
The trick is to use plain javascript references:
You need to pass an object to the scope.
You mustn't override that object, just update or extend it.
If you do override it, you lose the "binding".
If you use $http it will trigger a digest for you.
So, whenever a change occurs, the scope variable reference to same object that gets updated in the service, and all the watchers will be notified with a digest.
AFAIK, That's how $firebase & Restangular work.
If you do multiple updates you need to have a way of resetting properties.
Since you hold a reference to an object across the application, you need to be aware of memory leaks.
For example:
Service:
app.factory('inboxService', function($http){
return {
inboxForUser: function(user){
var inbox = {};
$http.get('/api/user/' + user).then(function(response){
angular.extend(inbox, response.data);
})
return inbox;
}
};
});
Controller:
app.controller('ctrl', function(inboxService){
$scope.inbox = inboxService.inboxForUser("fred");
});
It depends on how the object is updating. If it gets updated "within" angular, a digest cycle will be triggered (See http://docs.angularjs.org/guide/scope), and the view will update automatically. That is the beauty of Angular.
If the object gets updated "outside" of angular (e.g. a jQuery plugin), then you can manually trigger a digest cycle by wrapping the code that's doing the updating in an $apply function. Something like this:
$scope.$apply(function() {
//my non angular code
});
See http://docs.angularjs.org/api/ng.$rootScope.Scope for more info.

Force "soft" update of fields with Angularjs

I have an application, call it a "form-filler" that works with many, many sites using Jquery to automatically update fields.
Pseudo Code:
Inject Jquery into the webpage
Discover the required form.
Update the values, e.g.,
$(document).ready(function) {
$('#id').val("some value");
}
I have a new customer who is using Angularjs and this model breaks down as the $scope is obviously being updated "out-of-band". I don't have access to the third party source to make changes, so I was wondering if it is possibly to get a jQuery update to trigger an Angularjs update?
You can use angular.element() to get a hold of the scope and the ngModelController:
var value = 'theNewValue';
var el = angular.element($('#name'));
el.scope().$apply(function(){
el.val(value);
el.controller('ngModel').$setViewValue(el.val());
});
Here is a simple example: http://plnkr.co/edit/OJQQmanwQoFQSgECuqal?p=preview
Agreeing on the other responses, I'd suggest to use $timeout instead of $apply to avoid problems with the digest phase.
Like in #liviu-t response, get hold of the $timeout service by means of the $element's injector. Then use it as it was a nextTick() function. It is in fact (with second argument 0 or missing) almost equivalent to nextTick(), with the difference that it always runs its argument in the digest phase, unlike $apply, which must be called outside of the digest.
It's a tad complicated depending on the actual case. My solution assumes that the elements are available on dom ready and not loaded by angular using partials. DEMO
JS
function setAngularValue($elem, value) {
var scope = $elem.scope();
var ngModelName = $elem.attr('ng-model');
if(ngModelName) {
var $injector = $elem.injector();
//get the parse service to use on the ng-model
var $parse = $injector.get('$parse');
var ngModel = $parse(ngModelName);
scope.$apply(function() {
//this will allow any ng-model value ex: my.object.value
ngModel.assign(scope, value);
});
}
else {
//fallback if there is no model, weird case imho
$elem.val(value);
}
}
$(document).ready(function() {
var $elem = angular.element('#myJqueryId');
var value = 'some value';
setAngularValue($elem, value);
});
HTML
<p>Hello {{my.angular.model}}!</p>
<input id="myJqueryId" ng-model="my.angular.model"></input>
LINKS
$injector
$parse

Angular JS inject new instance of class

I want to inject instances of a class. From this thread it looks like services return new serviceArgFunction, which is what I want. https://groups.google.com/forum/#!msg/angular/56sdORWEoqg/VxECXKbn3gsJ
But I can't seem to get it to work. For some reason, my two instances of share state. Can anyone help me figure out what I'm doing wrong?
http://jsfiddle.net/kTjMy/5/
var Klass = function() {
this.count = 0;
this.getAndIncCount = function() {
this.count += 1;
return this.count;
};
};
app.service('service', Klass);
You can use a factory that returns the constructor for your class:
app.factory('myKlass', function() {
return Klass
});
Even more simply, if Klass is defined elsewhere, you can use value:
app.value('myKlass', Klass);
Either way, inject it as normal.
function CtrlA($scope, myKlass)
{
new myKlass();
}
See this jsFiddle.
[Edit] See also this Google Groups post and this associated example.
To make it clear I created a little Plunker.
It has a Service and a Factory.
Service and Factory have a value and setters and getters.
Both are injected into Controllers A and B.
If You change the value of the Service in Controller A, this change takes also place in Controller B, because the service is a Singleton.
But when You change the value in the Factory in the Controller B, this affects only B because the change takes only place in the instance that is created in B.
Enjoy!
Maximilian - thank you for the great example.
I converted this to JSFiddle, as a place to experiment with another design pattern that I wanted to dig into.
Why is it that swapping the provider type from "service" to "factory" still seems to work?
It seems more about how the provider is created i.e. using 'new' versus just referencing it directly.
Also, within the service, is there a design pattern as to why a service should implement the "this" keyword, as opposed to using using return { myAccessor : function () { return val; } }
See
http://jsfiddle.net/jeffsteinmetz/XF69p/
In this jsfiddle, you can change one line of the code (line 23), and it still works, the line of code can be either:
App.service('MyService', function() {
or
App.factory('MyService', function() {
Why do these both work?
It seems more about how you reference the service with "new"
var myInstance = new MyFactory();
Which is different than just calling it directly
MyService.getVal()
Any angular folks care to lay out the best practices and reasoning behind this:
when to use 'new' and when you don't need new
why use the 'this' reference approach to define your provider instead of return { accessorName : function}
why do both factory or service work in the above example?

How do I access the $scope variable in browser's console using AngularJS?

I would like to access my $scope variable in Chrome's JavaScript console. How do I do that?
I can neither see $scope nor the name of my module myapp in the console as variables.
Pick an element in the HTML panel of the developer tools and type this in the console:
angular.element($0).scope()
In WebKit and Firefox, $0 is a reference to the selected DOM node in the elements tab, so by doing this you get the selected DOM node scope printed out in the console.
You can also target the scope by element ID, like so:
angular.element(document.getElementById('yourElementId')).scope()
Addons/Extensions
There are some very useful Chrome extensions that you might want to check out:
Batarang. This has been around for a while.
ng-inspector. This is the newest one, and as the name suggests, it allows you to inspect your application's scopes.
Playing with jsFiddle
When working with jsfiddle you can open the fiddle in show mode by adding /show at the end of the URL. When running like this you have access to the angular global. You can try it here:
http://jsfiddle.net/jaimem/Yatbt/show
jQuery Lite
If you load jQuery before AngularJS, angular.element can be passed a jQuery selector. So you could inspect the scope of a controller with
angular.element('[ng-controller=ctrl]').scope()
Of a button
angular.element('button:eq(1)').scope()
... and so on.
You might actually want to use a global function to make it easier:
window.SC = function(selector){
return angular.element(selector).scope();
};
Now you could do this
SC('button:eq(10)')
SC('button:eq(10)').row // -> value of scope.row
Check here: http://jsfiddle.net/jaimem/DvRaR/1/show/
To improve on jm's answer...
// Access whole scope
angular.element(myDomElement).scope();
// Access and change variable in scope
angular.element(myDomElement).scope().myVar = 5;
angular.element(myDomElement).scope().myArray.push(newItem);
// Update page to reflect changed variables
angular.element(myDomElement).scope().$apply();
Or if you're using jQuery, this does the same thing...
$('#elementId').scope();
$('#elementId').scope().$apply();
Another easy way to access a DOM element from the console (as jm mentioned) is to click on it in the 'elements' tab, and it automatically gets stored as $0.
angular.element($0).scope();
If you have installed Batarang
Then you can just write:
$scope
when you have the element selected in the elements view in chrome.
Ref - https://github.com/angular/angularjs-batarang#console
This is a way of getting at scope without Batarang, you can do:
var scope = angular.element('#selectorId').scope();
Or if you want to find your scope by controller name, do this:
var scope = angular.element('[ng-controller=myController]').scope();
After you make changes to your model, you'll need to apply the changes to the DOM by calling:
scope.$apply();
Somewhere in your controller (often the last line is a good place), put
console.log($scope);
If you want to see an inner/implicit scope, say inside an ng-repeat, something like this will work.
<li ng-repeat="item in items">
...
<a ng-click="showScope($event)">show scope</a>
</li>
Then in your controller
function MyCtrl($scope) {
...
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
}
Note that above we define the showScope() function in the parent scope, but that's okay... the child/inner/implicit scope can access that function, which then prints out the scope based on the event, and hence the scope associated with the element that fired the event.
#jm-'s suggestion also works, but I don't think it works inside a jsFiddle. I get this error on jsFiddle inside Chrome:
> angular.element($0).scope()
ReferenceError: angular is not defined
One caveat to many of these answers: if you alias your controller your scope objects will be in an object within the returned object from scope().
For example, if your controller directive is created like so:
<div ng-controller="FormController as frm">
then to access a startDate property of your controller, you would call angular.element($0).scope().frm.startDate
To add and enhance the other answers, in the console, enter $($0) to get the element. If it's an Angularjs application, a jQuery lite version is loaded by default.
If you are not using jQuery, you can use angular.element($0) as in:
angular.element($0).scope()
To check if you have jQuery and the version, run this command in the console:
$.fn.jquery
If you have inspected an element, the currently selected element is available via the command line API reference $0. Both Firebug and Chrome have this reference.
However, the Chrome developer tools will make available the last five elements (or heap objects) selected through the properties named $0, $1, $2, $3, $4 using these references. The most recently selected element or object can be referenced as $0, the second most recent as $1 and so on.
Here is the Command Line API reference for Firebug that lists it's references.
$($0).scope() will return the scope associated with the element. You can see its properties right away.
Some other things that you can use are:
View an elements parent scope:
$($0).scope().$parent.
You can chain this too:
$($0).scope().$parent.$parent
You can look at the root scope:
$($0).scope().$root
If you highlighted a directive with isolate scope, you can look at it with:
$($0).isolateScope()
See Tips and Tricks for Debugging Unfamiliar Angularjs Code for more details and examples.
I agree the best is Batarang with it's $scope after selecting an object (it's the same as angular.element($0).scope() or even shorter with jQuery: $($0).scope() (my favorite))
Also, if like me you have you main scope on the body element, a $('body').scope() works fine.
Just assign $scope as a global variable. Problem solved.
app.controller('myCtrl', ['$scope', '$http', function($scope, $http) {
window.$scope = $scope;
}
We actually need $scope more often in development than in production.
Mentioned already by #JasonGoemaat but adding it as a suitable answer to this question.
Inspect the element, then use this in the console
s = $($0).scope()
// `s` is the scope object if it exists
You can first select an element from the DOM that's within the scope you want to inspect:
Then you can view the scope object by querying the following in the console:
angular.element($0).scope()
You can query any property on the scope, e.g.:
angular.element($0).scope().widgets
Or you can inspect the controller attached to the scope:
angular.element($0).scope().$myControllerName
(Another option that can work is to put a breakpoint in your code. If the $scope is currently in the current 'plain old JavaScript' scope, then you can inspect the value of $scope in the console.)
I've used angular.element($(".ng-scope")).scope(); in the past and it works great. Only good if you have only one app scope on the page, or you can do something like:
angular.element($("div[ng-controller=controllerName]")).scope(); or angular.element(document.getElementsByClassName("ng-scope")).scope();
I usually use jQuery data() function for that:
$($0).data().$scope
The $0 is currently selected item in chrome DOM inspector.
$1, $2 .. and so on are previously selected items.
Say you want to access the scope of the element like
<div ng-controller="hw"></div>
You could use the following in the console:
angular.element(document.querySelector('[ng-controller=hw]')).scope();
This will give you the scope at that element.
At the Chrome's console :
1. Select the **Elements** tab
2. Select the element of your angular's scope. For instance, click on an element <ui-view>, or <div>, or etc.
3. Type the command **angular.element($0).scope()** with following variable in the angular's scope
Example
angular.element($0).scope().a
angular.element($0).scope().b
Chrome's console
This requires jQuery to be installed as well, but works perfectly for a dev environment. It looks through each element to get the instances of the scopes then returns them labelled with there controller names. Its also removing any property start with a $ which is what angularjs generally uses for its configuration.
let controllers = (extensive = false) => {
let result = {};
$('*').each((i, e) => {
let scope = angular.element(e).scope();
if(Object.prototype.toString.call(scope) === '[object Object]' && e.hasAttribute('ng-controller')) {
let slimScope = {};
for(let key in scope) {
if(key.indexOf('$') !== 0 && key !== 'constructor' || extensive) {
slimScope[key] = scope[key];
}
}
result[$(e).attr('ng-controller')] = slimScope;
}
});
return result;
}
in angular we get jquery element by angular.element()....
lets c...
angular.element().scope();
example:
<div id=""></div>
For only debugging purposes I put this to the start of the controller.
window.scope = $scope;
$scope.today = new Date();
And this is how I use it.
then delete it when I am done debugging.
Also, we can access the scope by name of HTML element like this:
angular.element(document.getElementsByName('onboardingForm')[0]).scope()
Just define a JavaScript variable outside the scope and assign it to your scope in your controller:
var myScope;
...
app.controller('myController', function ($scope,log) {
myScope = $scope;
...
That's it! It should work in all browsers (tested at least in Chrome and Mozilla).
It is working, and I'm using this method.

Resources