Chaining AngularJS Promises in controller. Is this workaround valid? - angularjs

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

Related

What happens when I use angular.module(...).controller(...).directive(...)?And why? [duplicate]

This question already has answers here:
How to implement chained method calls like jQuery?
(4 answers)
Closed 5 years ago.
I think this equals to
var module = angular.module(...);
module.controller(...);
module.directive(...);
But I'm not sure. And I don't know what happens in angular and why I could write code this way.
I try to debug and trace it but it's so confused.
This is called a fluent API.
Each method will return the module instance, so that another method may be called.
To illustrate we can create a class that does something similar.
class Module {
controller() {
console.log('controller');
return this;
}
directive() {
console.log('directive');
return this;
}
}
When each method has finished, it will return the module instance this so that another method can be chained.
So now we can use this class and chain the methods like this:
new Module().controller().directive();
Or
const module = new Module();
module.controller();
module.directive();
What happens when I use angular.module(…).controller(…).directive(…)?And why?
Short answer
This is good way to write your code in one file.
If you want to split Angular project to different files, use 2nd approach:
var app = angular.module(...);
app.controller(...);
app.directive(...);
Long answer
Also take a look on this angular code snippets (took from https://code.angularjs.org/1.5.6/angular.js):
You can see controller, directive, module, filter, factory, value, provider, decorator, animation, config, component ,run returns moduleInstance
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** #type {Object.<string, angular.Module>} */
var modules = {};
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
/** #type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** #type {!Array.<Function>} */
var configBlocks = [];
/** #type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
/** #type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_configBlocks: configBlocks,
_runBlocks: runBlocks,
requires: requires,
name: name,
provider: invokeLaterAndSetModuleName('$provide', 'provider'),
factory: invokeLaterAndSetModuleName('$provide', 'factory'),
service: invokeLaterAndSetModuleName('$provide', 'service'),
value: invokeLater('$provide', 'value'),
constant: invokeLater('$provide', 'constant', 'unshift'),
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
function invokeLaterAndSetModuleName(provider, method) {
return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
}
Why is it better?
Both approaches do the same so developer will decide what is better for his project structure
for efficiency?
There is no efficiency value measurement, both has same efficiency. No performance penalty.
for what?
In project I want to write each directive each controller each ... in separate file so I use app.controller(...);, app.service(...); ,...
However common directives I want to put in one file so I use:
app.directive(…).directive(…).directive(…).directive(…).directive(…)
Hope it will spread the light on your understanding :)

Prevent service from multiple server loads

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

How can I monitor the value of some scope variables for changes?

I have this code in my appController. The code sets the value of $scope.cursorWait to true when there's an HTTP in progress:
$scope.$on('cfpLoadingBar:started', function (event, data) {
$scope.cursorWait = true;
});
$scope.$on('cfpLoadingBar:completed', function (event, data) {
$scope.cursorWait = false;
});
I also have this in my connect service. The functions get called when the internet is disconnected:
isConnectedHandler = (): void => {
var self = this;
self.$rootScope.connected = true;
self.$rootScope.disconnected = false;
self.connectMessage = null;
self.minutes = 0;
}
isNotConnectedHandler = (): void => {
var retry = 0;
var self = this;
self.$rootScope.connected = false;
self.$rootScope.disconnected = true;
How could I monitor the value of $rootScope.disconnected and cursorWait to then set the value of a rootScope variable waiting to true if either $rootScope.disconnected or cursorWait were true?
Assuming your controller has several ViewModels it would like to monitor, let's say cursorWait and connected for example. In this case, Angular provide you with the watchGroup ability. This way you can monitor several variables and in the case one of them change, you may react accordingaly.
Sample Code (Using Typescript for demonstration)
$scope.$watchGroup([()=> { return this.cursorWait }, ()=> { return this.connected}],
(oldValues, newValues)=> {
/* The callback gets an array of `oldValues` and an array of `newValues`,
the index according to the variables you were watching */
});
For more information, refer to Angular documentation.

How return `$resource` promise response?

I require the user details for multiple areas. I tried to return the details using a generic function. But i am getting the result as undefined. i understand that, I require to use the $differed to get the reuslt. But I don't have any idea about my current scenario.
anyone help me here please?
here is my function:
$scope.currentUserInfo = function () {
var userDetails;
server.getProfile.get().$promise.then(function ( response ) {
if( response.Message.toUpperCase().indexOf('SUCCESS') != -1) {
return userDetails = response;
}
return "Not able to get the user details this time"
})
return userDetails;
}
$scope.currentUser = $scope.currentUserInfo();
console.log( $scope.currentUser ) //undefined.
var function1 = function () {
$scope.currentUserInfo(); //once user details available do further
$scope.age = $scope.currentUser.age;
}
var function2 = function () {
$scope.currentUserInfo(); //once user details available do further
$scope.name = $scope.currentUser.name;
}
server.getProfile.get() is an asynchronous call.
The return userDetails; line in $scope.currentUserInfo function will get executed even if the server.getProfile.get() call is not yet finish.
Try this:
$scope.currentUserInfo = function () {
server.getProfile.get().$promise.then(function ( response ) {
if( response.Message.toUpperCase().indexOf('SUCCESS') != -1) {
$scope.currentUser = response;
}
$scope.message = "Not able to get the user details this time";
})
}
$scope.currentUser will be populated after the ajax call is finish. I assume that you are using $scope.currentUser in your html bindings so when the value changes it will automatically reflect in your view

Angular - Organise controller, factory and "class"

I would like to understand how to have a nice organisation in my angular project.
[see code below]
Does it makes sense to have the getFireList function into the Factory ? Or should i put it into the controller ?
Does the "class" Fire makes sense ? Should i remove it ? Should i move it to the controller ? Should i move it the the factory ?
If you see anything wrong in this code i'm really interested to learn more.
For now, i've got this :
A class "Fire" to create new object of type Fire.
function Fire (p_power) {
// ATTRIBUTES
this.id = null;
this.power = p_power;
this.position = {
x: null,
y: null
}
// GETTERS/SETTERS
// id
this.getId = function() {
return this.id;
}
this.setId = function(p_id) {
this.id = p_id;
}
// power
this.getPower = function() {
return this.power;
}
this.setPower = function(p_power) {
this.power = p_power;
}
// position
this.getPosition = function() {
return this.position;
}
this.setPosition = function(p_position) {
this.position = p_position;
}
// METHODS
this.increasePower = function(p_plus) {
this.power += p_plus;
}
this.decreasePower = function(p_minus) {
this.power -= p_minus;
}
}
A controller
simuApp.controller('FireController', function($scope, FireFactory) {
// ...
});
And a factory
simuApp.factory('FireFactory', function() {
return {
fire_list: [],
getFireList : function() {
return $http.get(site_url+'fire/fireList').
then(
function(success) {
var data = success.data;
var fires = [];
var fire_tmp;
for (i=0 ; i<data.length ; i++) {
fire_tmp = new Fire( data[i].power );
fire_tmp.setId( data[i].idFire );
fires.push( fire_tmp );
}
fire_list = fires;
return fire_list;
}, function(err) {
// ...
}
);
}
}
});
Thanks for your help.
First, let's get the terminology right. .factory is a method to register a function that generates an instance of the service - hence "factory". What it generates, though, is a singleton service instance.
So, the service you create would be more properly named as FireSvc (as opposed to FireFactory), whereas the function that creates it could have the word "factory" in it (although, in the case below, that function name is not really needed - it could just be an anonymous function):
.factory("FireSvc", function FireSvcFactory(){
});
It is a good practice to use a Service to abstract away any domain/business logic from the controller. Keep the controller thin, responsible only to define the ViewModel, and react to events by changing the ViewModel or invoking functions on the Model.
So, having FireSvc.getFireList() makes sense.
Now, whether the list is a collection of plain objects, or instances of Fire is completely independent of Angular and is entirely up to you. In any case, it is too broad of a topic to discuss in a SO answer.

Resources