do promises behave differently in services vrs controllers? - angularjs

So I am pretty stuck. I have used angular promises within controllers before similar to this:
thisCtrl.someFunction = function (data){
//...
}
ApiCall.get.something().then(function(data){
thisCtrl.newData = data;
thisCtrl.someFunction(thisCtrl.newData);
});
and I have been able to then use thisCtrl.newData (created within the promise) throughout the controller. I have also been able to use functions defined outside of the promise, within the promise - as shown in the above ex.
For some reason when trying to do this in a service I cant seem to make it work. Trying to use the same pattern as above:
this.someFunction = function (data){
//...
}
ApiCall.get.something().then(function(data){
this.newData = data; // Cannot read property of 'newData' of undefined
this.someFunction(thisCtrl.newData); //error: Cannot read property of 'someFunctions' of undefined
});
So it seems that once I do the same thing in the service, this, is out of scope - rendering me kind of helpless in passing that data around to the rest of the service, or using any data from the rest of the service inside the promise. I have tried returning the data as an object:
ApiCall.get.something().then(function(data){
return {
data: data
}
});
But it too returns a promise, leaving me in the same boat. I have actually tried a LOT of things, and googled quite a bit. I want this to be available in a service to prevent repeating large blocks of code in multiple controllers. Can I not create promises inside of services? If so, any help is appreciated as to how to properly do it.... Thanks!

When you use "this", it may not be what you expect because "this" depends on the function context.
To make sure you're binding to the right function context use "fn.bind(this)"
ApiCall.get.something().then(function(data){
this.newData = data;
this.someFunction(...);
}.bind(this));
Alternatively, for wider cross-browser compatibility, you can save "this" as "self" in the outer function scope, so that you can use it from your inner function:
var self = this;
ApiCall.get.something().then(function(data){
self.newData = data;
self.someFunction(...);
});
For more information, check out this reference: https://codereview.stackexchange.com/questions/49872/using-var-self-this-or-bindthis

Related

Return value from service once $resource promise is resolved

I have such working code:
Service (factory?):
myApp.factory('MyService', ['$q','$resource', 'MyResource',
function ($q, $resource, MyResource) {
return {
getRoles: function () {
return MyResource.getRoles(); //MyResource makes API calls using $resource
} } });
Controller:
$scope.Roles = MyService.getRoles();
$scope.Roles.$promise.then(function () { $scope.RolesCount = $scope.Roles.length; });
What I'd like to do is to create such function in MyService that will return number of roles once getRoles is resolved so I can use it in controller like this:
$scope.RolesCount = MyService.getRolesCount();
and then in html
{{RolesCount}}
Unfortunately, I have no idea how to do this since my getRolesCount() method needs to return something so I can't use $promise.then() inside MyService. I'll try to update my question's title once I come up with something more accurate.
If server response needs to be transformed in a service, then then should be moved there. However, it's not practical or possible if then is supposed to unwrap a promise and assign a value to scope, like in the code above.
$resource returns an object that is filled on promise resolution. The way a resource is supposed to be used in view is:
{{Roles.length}}
When an object is updated, it will be updated in view, too. Assigning the value to another variable is inefficient.
It's impossible to do something like
$scope.RolesCount = MyService.getRolesCount();
because getRolesCount is asynchronous, and the value it resolves with is scalar. It is possible to flatten it with `async..await, (TypeScript or ES2017), but will require additional measures to synchronize control flow with AngularJS digests, as explained in this answer.

Using an AngularJS controller's scope in Typescript

The situation:
I recently started using Typescript and am currently integrating it into my AngularJS web application. I have been using the 'controller as' syntax instead of $scope where possible.
The issue: I can't seem to find a proper way to replace var self = this;
I started replacing vm with this everywhere which soon got me to a dead-end when inside of forEach functions, click events etc.
My temporary solution: casting var self = this; in each function, since I can't cast it globally as far as I understand. It also doesn't work in all cases.
My question: have I missed something obvious? Is there a better way to access the controller's scope?
Fat Arrow functions to the rescue!!!
There is really no need to use var self = this in TypeScript...
Fat arrow functions preserve the (lexical) scope (meaning of this) inside of callback functions:
var self = this;
fooService.doSomething()
.then(result => {
console.assert(this === self); // this is valid and true
});
For your specific example:
items.forEach((item) => {
this.somethingElse = item; // (no need for self)
});
Note This is not really a feature of TypeScript, but more so a function of ES6/ES2015
Further Explanation

How do I properly set $scope properties from services that return a promise?

I am trying to figure out the proper way to set properties in my controller's $scope when those properties must be retrieved from some service. Typically I assign scope variables as follows:
$scope.firstName = 'John';
$scope.lastName = 'Doe';
So how do I perform the same operation when I want to retrieve the property value from some service method?
Here is my factory service method:
var getUserDetails = function () {
return $q.when($resource('/api/v1/common/userdetails').get());
};
I would like to assign user details in my controller's $scope like so:
$scope.userDetails = commonService.getUserDetails();
However, that does not work. I end up having to do something like this which feels very unnatural:
commonService.getUserDetails().then(function (user) {
$scope.user = user;
});
Not to mention the above will return a promise, not just the pure user data result I would like. I feel as though I'm not using promises, services, or assigning scope values from services in my controller appropriately.
Can someone please let me know if this is correct, and if not please show the proper way to do this?
Contrary to the answers $resource still does what you expect it to - although automatic unwrapping was removed from promises it still works with $resource. The problem is that you explicitly wrapped it in a regular $q promise when you called $q.when on it. Instead, do:
var getUserDetails = function () {
return $resource('/api/v1/common/userdetails').get(); // no $q.when
};
And you will get the behavior you're expecting:
$scope.userDetails = commonService.getUserDetails(); // will work.
Although personally - I like the explicit notation that doesn't hide the fact an http request is being made better.

AngularJS, sharing data between controllers

So, this isn't the typical question of HOW to do it. I know it can be done with a service or a factory but I was wondering if someone could share what the advantages/disadvantages would be of just creating a basic service, injecting it into each controller, and then extending the service with functions from each controller. Something similar to the following example..
app.service('HelperService', function() {
return {};
});
app.controller('Controller1', function($scope, HelperService) {
$scope.somefunc = function() {
//do stuff here
};
HelperService.somefunc = $scope.somefunc;
});
app.controller('Controller2', function($scope, HelperService) {
HelperService.somefunc();
});
This works, and works well. I feel a bit stupid for asking this but it just seems like I'm missing something here as to why this isn't used or recommended?
It may work, but its a bad idea.
Controller2 HelperService.somefunc() won't exist until Controller1 has been instantiated. So theres an implicit dependency of Controller2 on Controller1
the code on HelperService isn't in one place where it can be understood together
if you are doing some sort of data manipulation in that function, it really should be operating on data encapsulated by the HelperService.
The service is a singleton and it will be instantiated once calling new on the function itself -- the function you pass in is essentially a constructor. This will create the empty object you are returning for use everywhere, but if you want to return an object in such a way it makes more sense to use .factory, but this is not a big deal.
At any rate, you can consider your code to conceptually do this:
var HelperService = function () {}
var helperService = new HelperService;
function Controller1() {
helperService.someFunc = function () {}
}
function Controller2() {
helperService.someFunc();
}
I would consider this a dangerous thing to do for a couple of reasons:
Controller1 must be instantiated before Controller2 or else somefunc won't be available to Controller2. Ideally the controllers would have no knowledge of each other.
You are coupling Controller/ViewModel (since you're using scope) with service level logic, but these should be decoupled. HelperService shouldn't know about the controllers either. Instead, you should be injecting a service that has an API that the controllers expect to use. This doesn't always have to be HelperService, it just has to look like HelperService to the controllers and its API shouldn't change.
Without knowing specifics about what you're trying to do, it's hard to advise. In general you may rethink what you want to do, but you can extend functionality of services with other services. Consider services to be in their own layer.

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.

Resources