Prevent service from multiple server loads - angularjs

I've got an angular service MyService
There is get method inside it. It get's info from server and sets it to local variable if variable is undefined, overwise return variable
export class MyService{
private userSettings: UserSettings;
private updateProcessing: boolean = false;
private deferred : any;
constructor(
private $http: ng.IHttpService,
private $q: ng.IQService,
private $log: ng.ILogService) {
}
public get(fromServer: boolean = false) {
var self = this;
if (self.updateProcessing) {
return self.deferred.promise;
}
else if (!self.userSettings || fromServer) {
return self.getFromServer();
} else
return self.$q.resolve(self.userSettings);
}
private getFromServer(): ng.IPromise<any> {
var self = this;
self.updateProcessing = true;
self.deferred = self.$q.defer();
var url = self.getSettingsUrl();
self.$http.get(url).then(
(result: any) => {
self.userSettings = result.data;
self.updateProcessing = false;
self.deferred.resolve(result.data);
},
error => {
this.$log.error(error);
self.updateProcessing = false;
self.deferred.reject(error);
}
);
return self.deferred.promise;
}
}
when I pass this service to 3 different controllers, they all get variable value from server.
I'm trying to save promise and if request is already prosessing whait while it resolves and do not create new one.
Right now with code I posted I do in my controllers
this.MyService.get().then(()=>{
});
and never get inside then callback.
If it's important, I use version: "1.5.8" of angular library.

You can share the same promise. Also note that $http already returns promise so using $q to create a new one is an anti-pattern
private getFromServer(): ng.IPromise < any > {
var self = this;
self.updateProcessing = true;
// if promise not stored need to create it
if (!self.storedPromise) {
var url = self.getSettingsUrl();
self.storedPromise = self.$http.get(url).then(
(result: any) => {
self.userSettings = result.data;
self.updateProcessing = false;
return self.userSettings;
},
catch => {
this.$log.error(error);
self.updateProcessing = false;
return self.$q.reject(error);
}
);
}
// return the stored promise
return self.storedPromise;
}
Now the first call to this method will create the promise and subsequent calls will return the same one

Related

ngShow expression is evaluated too early

I have a angular component and controller that look like this:
export class MyController{
static $inject = [MyService.serviceId];
public elements: Array<string>;
public errorReceived : boolean;
private elementsService: MyService;
constructor(private $elementsService: MyService) {
this.errorReceived = false;
this.elementsService= $elementsService;
}
public $onInit = () => {
this.elements = this.getElements();
console.log("tiles: " + this.elements);
}
private getElements(): Array<string> {
let result: Array<string> = [];
this.elementsService.getElements().then((response) => {
result = response.data;
console.log(result);
}).catch(() => {
this.errorReceived = true;
});
console.log(result);
return result;
}
}
export class MyComponent implements ng.IComponentOptions {
static componentId = 'myId';
controller = MyController;
controllerAs = 'vm';
templateUrl = $partial => $partial.getPath('site.html');
}
MyService implementation looks like this:
export class MyService {
static serviceId = 'myService';
private http: ng.IHttpService;
constructor(private $http: ng.IHttpService) {
this.http = $http;
}
public getElements(): ng.IPromise<{}> {
return this.http.get('./rest/elements');
}
}
The problem that I face is that the array elements contains an empty array after the call of onInit(). However, later, I see that data was received since the success function in getELements() is called and the elements are written to the console.
elements I used in my template to decide whether a specific element should be shown:
<div>
<elements ng-show="vm.elements.indexOf('A') != -1"></elements>
</div>
The problem now is that vm.elements first contains an empty array, and only later, the array is filled with the actual value. But then this expression in the template has already been evaluated. How can I change that?
Your current implementation doesn't make sense. You need to understand how promises and asynchronous constructs work in this language in order to achieve your goal. Fortunately this isn't too hard.
The problem with your current implementation is that your init method immediately returns an empty array. It doesn't return the result of the service call so the property in your controller is simply bound again to an empty array which is not what you want.
Consider the following instead:
export class MyController {
elements: string[] = [];
$onInit = () => {
this.getElements()
.then(elements => {
this.elements = elements;
});
};
getElements() {
return this.elementsService
.getElements()
.then(response => response.data)
.catch(() => {
this.errorReceived = true;
});
}
}
You can make this more readable by leveraging async/await
export class MyController {
elements: string[] = [];
$onInit = async () => {
this.elements = await this.getElements();
};
async getElements() {
try {
const {data} = await this.elementsService.getElements();
return data;
}
catch {
this.errorReceived = true;
}
}
}
Notice how the above enables the use of standard try/catch syntax. This is one of the many advantages of async/await.
One more thing worth noting is that your data services should unwrap the response, the data property, and return that so that your controller is not concerned with the semantics of the HTTP service.

Why my array is still empty even though I had assign value to this array in my controller? (I am using angularjs, not angular)

This question took me one day to debug it, but still no luck.
Problem: this.gridOptions.data = this.allTemplatesFromClassificationRepo ;
**this.allTemplatesFromClassificationRepo ** is still a empty array. Before this line of code executes, I have call activate() function to assign array to **this.allTemplatesFromClassificationRepo **. Please see this line this.allTemplatesFromClassificationRepo = templateReorderProperties;
P.S. Although I cannot get the value of allTemplatesFromClassificationRepo in controller, but I can get the value of it in html.
namespace app.admin {
'use strict';
class FooController {
static $inject: Array<string> = [];
constructor() {
this.activate();
}
activate() {
this.getData();
this.classificationFoo = this.data[0];
this.getTemplateFromGivenRepo(this.classificationFoo.id, this.classificationFoo.displayName);
this.populateData();
}
data: any;
classificationFoo: any;
allDataFromclassificationFoo: any = [];
// demo grid
gridOptions = {
enableFiltering: true,
},
data: []
};
populateData() {
this.gridOptions.data = this.allTemplatesFromClassificationRepo ;
}
getData() {
this.fooService.getUserData();
this.data = this.fooService.userdata;
}
getTemplateFromGivenRepo(fooId: string, fooName: string) {
switch (fooId) {
case 'FOO':
this.TemplateApi.templatesAvaiableForRepoIdGET(fooId).then(data => {
data.forEach(element => {
element.fooName = fooName;
});
let templateReorderProperties = data
this.allTemplatesFromClassificationRepo = templateReorderProperties;
}, error => {
});
break;
default:
break;
}
};
}
class Bar implements ng.IDirective {
static $inject: Array<string> = [];
constructor() {
}
bindToController: boolean = true;
controller = FooController;
controllerAs: string = 'vm';
templateUrl: string = 'app/foo.html';
static instance(): ng.IDirective {
return new Bar();
}
}
angular
.module('app.admin')
.directive('bar', Bar.instance);
}
getTemplateFromGivenRepo is async operation.
Move this.populateGridData(); call inside getTemplateFromGivenRepo after
this.allTemplatesFromClassificationRepo = templateReorderProperties;
getTemplateFromGivenRepo(repoId: string, repoName: string) {
switch (repoId) {
case 'CLASSIFICATION':
this.TemplateApi.templatesAvaiableForRepoIdGET(repoId).then(data => {
this.allTemplatesFromClassificationRepo = templateReorderProperties;
this.populateGridData(); // call here
}, error => {
});
}
};
OR
You can return promise from getTemplateFromGivenRepo and in then able success callback,call
this.populateGridData();
I think the problem is in this instance that different inside Promise resolve.
Try to write in beginning something like:
var self = this;
and after that change all this to self key.
a.e.:
self.gridOptions.data = self.allTemplatesFromClassificationRepo;
// and so on ...
By this way you will guarantee that you use same scope instance
Hope it will work

Can't access this from $http callback

I'm using angular 1.5 with typescript, I can't access this property from the callback being returned from $http promise.
When I'm trying to access a private method from the callback 'this' is undefined
I have the following ServerAPI service:
export class ServerAPI implements IServerAPI {
static $inject:Array<string> = ['$http', '$q'];
constructor(private $http:ng.IHttpService,
private $q:ng.IQService) {
}
postHandler(partialUrl:string, data?:any, config?:any):ng.IPromise<any> {
let url = this.buildUrl(partialUrl);
var result:ng.IPromise< any > = this.$http.post(url, data, config)
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data))
.catch((error:any):ng.IPromise<any> => this.handlerError(error, data));
return result;
}
private handlerResponded(response:any, params:any):any {
response.data.requestParams = params;
return response.data;
}
private handlerError(error:any, params:any):any {
error.requestParams = params;
return error;
}
}
Which been consumed by user.service:
export class UserService implements IUserService {
static $inject:Array<string> = ['$q', 'serverAPI'];
constructor(private $q:ng.IQService,
private serverAPI:blocks.serverAPI.ServerAPI) {
var vm = this;
$rootScope.globals = $rootScope.globals || {};
$rootScope.globals.currentUser = JSON.parse($window.localStorage.getItem('currentUser')) || null;
this.getUserPermissions();
}
private getUserPermissions:() => IPromise<any> = () => {
var promise = this.serverAPI.postHandler('MetaDataService/GetUserPermissions',
{userID: this.getUser().profile.UserID})
.then((res) => {
this.updateUser('permissions', res.GetUserPermissionsResult); // NOT WORKING, this is undefined
})
.catch((response:any):ng.IPromise<any> => {
this.updateUser('permissions', res.GetUserPermissionsResult); // NOT WORKING, this is undefined
});
return promise;
};
private updateUser:(property:string, value:any) => void = (property, value) => {
};
}
The issue is this line:
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data))
While your lexical scope is maintained in order to find the handlerResponded method the scope is not fully preserved in the output.
you can get around this in 2 ways:
inline your handler function rather than have it as a function on your class
you can bind the call to handlerResponded to the instance
example of binding:
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data).bind(this))

Chaining AngularJS Promises in controller. Is this workaround valid?

Note: this is in TypeScript / Angular 1.4.x
Plunker in javascript: http://plnkr.co/edit/LCka4CFLcRe0lPEF9AM2?p=preview
I have to chain multiple calls with promises. init3 depends on init2 depends on init1.
Some of the promises need to continue even if they fail. So I had to use the flattened technique here : http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/ To reuse callback code.
The problem is when chaining promises, I completely lose the controller's this instance inside 1st chain then (init2 and init3)
So i modified the internal return to pass the controller in the chain so the bindings (bind to controller) and services are accessible.
Is this Valid/Correct? (passing the controller in the chain and reusing the same callback for success/error)
Comments in code also explain/ask questions.
Code:
export class ControllerX {
private myInformation: string;
private myMoreInformation: string;
public constructor(private $q: ng.IQService, private service1: Service1) {
this.init()
.then(this.init2) //If init() fails dont continue the chain
.then(this.init3, this.init3); //Even if init2() fail, we continue.
//init2() cannot fail now but its a recovery example.
}
private init(): ng.IPromise<ControllerX> {
return this.service1.getMyInformation().then((information: string): ControllerX => {
this.myInformation = information;
return this; //Push this for the next then
}); //Do nothing on error, let it propagate.
}
private init2(ctrl?: ControllerX): ng.IPromise<ControllerX> {
if (!ctrl) { //Are we called from a chain
ctrl = this;
}
return ctrl.service1.getMyMoreInfo().then((information: string): ControllerX => {
ctrl.myMoreInformation = information;
return ctrl;
}, (error: any): ControleurListeCours => {
ctrl.myMoreInformation = DEFAULT;
return ctrl;
});
}
private init3(ctrl?: ControllerX): ng.IPromise<ControllerX> {
//blablabla
}
}
This code won't work as expected.
this.init()
.then(this.init2) //If init() fails dont continue the chain
.then(this.init3, this.init3); //Even if init2() fail, we continue.
//init2() cannot fail now but its a recovery example.
If init fails, the code will skip init2 but will be caught by the second then method, and init3 will be executed.
HTML
<div ng-app="myApp" ng-controller="myVm as vm">
<pre>
init1 status = {{vm.init1Status}}
init2 status = {{vm.init2Status}}
init3 status = {{vm.init3Status}}
</pre>
</div>
JS
angular.module('myApp',[]).controller('myVm', function ($q) {
var vm = this;
vm.init1Status = "not done";
vm.init2Status = "not done";
vm.init3Status = "not done";
function init1() {
vm.init1Status = "init1 failed";
return $q.reject("init1 failed");
};
function init2() {
vm.init2Status = "init2 done";
};
function init3() {
vm.init3Status = "init3 done";
};
init1().then(init2).then(init3,init3);
});
The result
init1 status = init1 failed
init2 status = not done
init3 status = init3 done
The JSFiddle

What type of Typescript return value should I use for a $http call that returns nothing other than success?

I created a service using Typescript:
class ConfigService implements IConfigService {
public admin = {};
public adminBackup = {};
public user = {};
public loaded = false;
constructor(
private $http: ng.IHttpService,
private $q: ng.IQService
) {
}
static $inject = [
'$http',
'$q'
];
put = ():ng.IHttpPromise<> => {
var defer = this.$q.defer();
if (angular.equals(this.admin, this.adminBackup)) {
return defer.resolve();
} else {
this.$http({
data: {
adminJSON: JSON.stringify(this.admin),
userJSON: JSON.stringify(this.user)
},
url: '/api/Config/Put',
method: "PUT"
})
.success(function (data) {
this.adminBackup = angular.copy(this.admin);
this.userBackup = angular.copy(this.user)
return defer.resolve();
});
}
return defer.promise;
};
}
I also created this interface:
interface IConfigService {
put(): ng.IHttpPromise<>;
}
However the code is giving me an error saying:
Error 3 Cannot convert 'void' to 'ng.IHttpPromise<any>'.
Error 4 Cannot convert 'ng.IPromise<{}>' to 'ng.IHttpPromise<any>':
Type 'ng.IPromise<{}>' is missing property 'success' from type 'ng.IHttpPromise<any>'.
Use
ng.IPromise<void>
Also you could let it implicitly type it for you if you don't declare a return type and don't use that interface.
Also there should be no return statement here :
return defer.resolve();
Just :
defer.resolve();
When I ran into this issue I found that I wasn't able to use ng.IPromise<void> as the declarations for ng.IHttpPromise<> demanded that I return an object.
For example:
public saveTechnicianNote = (jobNumber: string, technicianNote: Model.TechnicianNote): ng.IPromise<void> => {
var data = {
jobNumber: jobNumber,
subject: technicianNote.subject,
details: technicianNote.details
};
return this.$http.post(this.urlFactory.saveTechnicianNote, data);
}
threw up an error that Type IHttpPromise<{}> is not assignable to IPromise<void>.
To get around this I made it return ng.IPromise<any>. Not 100% ideal but it was the best solution I could come up with that didn't require me to extend on angular.d.ts
Final Code:
public saveTechnicianNote = (jobNumber: string, technicianNote: Model.TechnicianNote): ng.IPromise<any> => {
var data = {
jobNumber: jobNumber,
subject: technicianNote.subject,
details: technicianNote.details
};
return this.$http.post(this.urlFactory.saveTechnicianNote, data);
}

Resources