I have an AngularJS 1.6.3 app and am having an issue with $q.all() not correctly resolving. It seems that the resolve function in my config (sessionService.initializeApp()) is not even getting called at all.
The console.log() statements are not being run, and the network requests are not being made. However, if I change sessionService.initializeApp() to simply be a variable instead of a function call with a return (i.e. var initializeApp = return $q.all(...)), and just call sessionService.initializeApp in the resolve, the network requests are made, but the console.log() statements do not run, and the controller does not instantiate.
I feel like I am returning the promises correctly, but obviously something is wrong since the resolve is not actually resolving. Any ideas here?
app.config.js
//more code above...
.when('/dashboard', {
slug: 'dashboard',
templateUrl: 'app/dashboard/dashboard.html',
controller: 'DashboardController',
options: {
title: 'Dashboard'
},
resolve: {
appReady: function(sessionService) {
console.log('resolving...');
return sessionService.initializeApp();
}
}
})
//more code below...
session.service.js
//more code above...
var state = {
appReady: false,
loading: false,
data: {}
};
var service = {
initializeApp: initializeApp,
getData: getData,
setData: setData
};
function initializeApp() {
return $q.all({
user: ajaxService.get('SupplierService.svc/me', {}),
states: ajaxService.get('SupplierService.svc/states', {}),
utilityTypes: ajaxService.get('SupplierService.svc/utilities/types', {}),
roles: ajaxService.get('SupplierService.svc/contacts/types', {})
}).then(function(response) {
setData('user', response.user['data']);
setData('states', response.states['data']);
setData('utilityTypes', response.utilityTypes['data']);
setData('roles', response.roles['data']);
}).catch(function(e) {
console.log(e);
}).finally(function() {
state.appReady = true;
});
}
function getData(key) {
if (key) {
return state.data[key];
} else {
return state.data;
}
}
function setData(key, val) {
state.data[key] = val;
}
return service;
//more code below...
dashboard.controller.js
//more code above...
function DashboardController($q, ajaxService, sessionService) {
console.log('dashboard...');
});
//more code below...
catch the promise from controller instead of the service. in the service just return the $q.all
//more code above...
var service = {
initializeApp: initializeApp
};
function initializeApp() {
return $q.all({
user: ajaxService.get('SupplierService.svc/me', {}),
states: ajaxService.get('SupplierService.svc/states', {}),
utilityTypes: ajaxService.get('SupplierService.svc/utilities/types', {}),
roles: ajaxService.get('SupplierService.svc/contacts/types', {})
})
}
return service;
catch the promise in controller
function DashboardController($q, ajaxService, sessionService,setData) {
sessionService..then(function(response) {
setData('user', response.user['data']);
setData('states', response.states['data']);
setData('utilityTypes', response.utilityTypes['data']);
setData('roles', response.roles['data']);
}).catch(function(e) {
console.log(e);
}).finally(function() {
state.appReady = true;
});
});
For now, I've given up on using resolve and just modified sessionService.initializeApp() to check for whether the app is ready, and if so, just return a resolved promise immediately so I don't have to refetch resources from the server.
function initializeApp() {
if (isAppReady()) {
return $q.when('appReady');
}
return $q.all({
user: ajaxService.get('SupplierService.svc/me', {}),
states: ajaxService.get('SupplierService.svc/states', {}),
utilityTypes: ajaxService.get('SupplierService.svc/utilities/types', {}),
roles: ajaxService.get('SupplierService.svc/contacts/types', {})
}).then(function(response) {
setData('user', response.user['data']);
setData('states', response.states['data']);
setData('utilityTypes', response.utilityTypes['data']);
setData('roles', response.roles['data']);
}).catch(function(e) {
console.log(e);
}).finally(function() {
setAppReady(true);
});
}
And then in my controllers, I just do:
sessionService.initializeApp().then(function() {
//do stuff
});
Related
I have two form components, which have a common JS validator.
import { validateInput } from './validateInput.js'
export default {
data () {
return {
email: 'a#a.com',
validEmail: false
}
},
methods: {
validateInput
},
watch: {
'email': function (val) {
this.validEmail = validateInput('email', val)
// ^ makes async request
}
}
}
The validator uses jQuery.ajax - async HTTP request to server. The problem is that, since validateInput() is asynchronous, it returns before it gets a response from the server (this.validEmail is undefined)
How do I update the Vue instance (this.validEmail) after the async request in this JS imported function is completed?
In simple terms - how do I access the Vue instance from inside validateInput in a way that works asynchronously?
I've tried passing the Vue instance as an argument, doesn't update:
validateInput('email', val, this)
----
// validateInput.js
email() {
$.ajax({
...
success(response) {
vueInstance.validEmail = response // doesn't work
}
})
vueInstance.validEmail = false
// ^^^^ this works
}
Try this
watch: {
'email': function (val) {
validateInput('email', val,this)
}
}
and
validateInput('email', val, ref)
----
// validateInput.js
email() {
$.ajax({
...
success(response) {
ref.validEmail = response // doesn't work
}
})
// ^^^^ this works
}
Or the traditional way to deal with asynchronous calls is pass a callback that has a closure over the variable that needs edited.
watch: {
'email': function (val) {
validateInput('email', val,function(response){this.validEmail=response})
}
}
//...
validateInput('email', val, cb)
//...
success(response) {
cb(response);
}
I am stuck on why my promises aren't working. From the other articles I have looked at, I think I am doing it correctly. Here is the code I have currently:
Factory code
factory.isLoggedIn = function() {
$http.get('/api/v1/fitbit/auth')
.success((data) => {
return data.status;
})
.error((error) => {
console.log('Error: ' + error);
});
}
Controller code
$scope.fitbitAuth = function() {
FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
return $scope.fitbitStatus;
};
From my understanding of promises, the return $scope.fitbitStatus should be populating the $scope.fitbitAuth, but it isn't. I am also returning a boolean in the Factory, which should be populating $scope.fitbitStatus.
You have to return something (the promise), or it is undefined.
Factory code:
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}
Controller code:
$scope.fitbitAuth = function() {
return FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
};
The complete success/error block in your factory is not necessary and should be removed. I'm also unsure why you return $scope.fitbitStatus; as it is undefined at the time of return.
Edit: Edited the answer to actually return the promise.
Currently you haven't return anything from isLoggedIn factory method and you are calling .then method over it.
To make it working return $http promiseobject from service method. In your case you could simply return$http.getmethod call which return promise object itself and then you can easily chain them up by callingFitbitFactory.isLoggedIn().then`
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}
Here is my code:
Interface
export interface IConsignmentDataService {
getConsignmentListData(): ng.IPromise<IConsignmentListViewModel>;
filterConsignments(filters: Filter[]): ng.IPromise<IConsignmentListViewModel>;
}
Implementation
export class ConsignmentDataService implements IConsignmentDataService {
static $inject: string[] = ["$http", "$q"];
constructor(private $http: ng.IHttpService, private $q: ng.IQService) {
}
getConsignmentListData(): ng.IPromise<IConsignmentListViewModel> {
var defer = this.$q.defer();
var apiUrl = "/Api/Consignments/AllConsignments";
this.$http.get(apiUrl)
.success((data: IConsignmentListViewModel) => {
defer.resolve(data);
})
.error((reason: string) => {
defer.reject(reason);
});
return defer.promise;
}
filterConsignments(filters: Filter[]): ng.IPromise<IConsignmentListViewModel> {
var defer = this.$q.defer();
var url = "/Api/Consignments/FilterConsignments";
this.$http.post(url, { "filters": filters })
.success((data: IConsignmentListViewModel) => {
defer.resolve(data);
})
.error((reason: string) => {
defer.reject(reason);
});
return defer.promise;
}
}
and this is how I am calling this:
refresh(): void {
this.consignmentDataService.filterConsignments(this.currentFilters).then((data: IConsignmentListViewModel) => {
console.log("success");
}, () => {
console.log("failure");
});
}
The problem is, when I call getConsignmentListData() its working properly but when I call filterConsignments its giving error:
Error: this.consignmentDataService.filterConsignments is not a function
Please be ensured that the solution is building properly and the parameters are also populated nicely. I also tried to use a console.log() inside filterConsignments to check if it is getting called but no luck. It is always giving the error.
Detailed Error Message:
Error: this.consignmentDataService.filterConsignments is not a function
ConsignmentListController.prototype.refresh#http://localhost:8044/app/consignment/List/consignmentList.controller.js:145:21
ConsignmentListController#http://localhost:8044/app/consignment/List/consignmentList.controller.js:139:21
invoke#http://localhost:8044/Scripts/angular.js:4478:14
instantiate#http://localhost:8044/Scripts/angular.js:4486:27
$ControllerProvider/this.$get</<#http://localhost:8044/Scripts/angular.js:9151:18
ngViewFillContentFactory/<.link#http://localhost:8044/Scripts/angular-route.js:977:26
invokeLinkFn#http://localhost:8044/Scripts/angular.js:8789:9
nodeLinkFn#http://localhost:8044/Scripts/angular.js:8289:1
compositeLinkFn#http://localhost:8044/Scripts/angular.js:7680:13
publicLinkFn#http://localhost:8044/Scripts/angular.js:7555:30
createBoundTranscludeFn/boundTranscludeFn#http://localhost:8044/Scripts/angular.js:7699:1
controllersBoundTransclude#http://localhost:8044/Scripts/angular.js:8316:18
update#http://localhost:8044/Scripts/angular-route.js:935:25
$RootScopeProvider/this.$get</Scope.prototype.$broadcast#http://localhost:8044/Scripts/angular.js:16311:15
commitRoute/<#http://localhost:8044/Scripts/angular-route.js:619:15
processQueue#http://localhost:8044/Scripts/angular.js:14745:28
scheduleProcessQueue/<#http://localhost:8044/Scripts/angular.js:14761:27
$RootScopeProvider/this.$get</Scope.prototype.$eval#http://localhost:8044/Scripts/angular.js:15989:16
$RootScopeProvider/this.$get</Scope.prototype.$digest#http://localhost:8044/Scripts/angular.js:15800:15
$RootScopeProvider/this.$get</Scope.prototype.$apply#http://localhost:8044/Scripts/angular.js:16097:13
done#http://localhost:8044/Scripts/angular.js:10546:36
completeRequest#http://localhost:8044/Scripts/angular.js:10744:7
requestLoaded#http://localhost:8044/Scripts/angular.js:10685:1
<div data-ng-animate="1" class="ng-scope" ng-view="">
I am building routes/states and the menu based on what the user is authorized to see. I've looked around and tried a few different things, but i'm hitting a brick wall. The SessionService object in the RoleService Factory is empty whenever RoleService.validateRole() is called. No route is added and the app is effectively dead. Why is the injected factory empty and the methods undefined.
Here is a simplified layout of the app starting in order of dependencies.
In app.run(), I am adding the states to the app instead of doing it in the config.
$stateProviderRef.state(value.stateName, state);
The states come from (a factory) AppConfig.getStates(), which returns an array.
var states = AppConfig.getStates();
In getStates() we validate each route's role.
if(RoleService.validateRole(routes[i].role))
The RoleService depends on the SessionService and the validateRole function does this check:
if(SessionService.currentUser.role === role)
The SessionService depends on the AuthenticationService which is just a factory that returns a promise using $http (the user object). The SessionService.currentUser is a function that .then()s the returned promise from the AuthenticationService.
return {
currentUser: function(){
AuthenticationService.then(function(result){
return result;
});
}
};
I'm not sure of a better way to explain the code without including the entire files.
Based on the plunker (mentioned in comment), I updated/cloned it to another, which is working
I. simple - when static data are returned (no $http)
Because the service SessonService was defined like this:
return {
currentUser: function() {
...
we cannot call it as a property:
...
return {
validateRoleAdmin: function () {
if (SessionService.currentUser.role === 'admin') {
...
},
validateRole: function (role) {
if(SessionService.currentUser.role === role){
...
it is a function it must be called as a function currentUser():
return {
validateRoleAdmin: function () {
if (SessionService.currentUser().role === 'admin') {
...
},
validateRole: function (role) {
if(SessionService.currentUser().role === role){
...
II. waiting for async calls
The adjusted example
Next, if we in example create a static result of the service AuthenticationService:
angular.module('daedalus').factory('AuthenticationService',
function() {
return {"idsid": "ad_jdschuma","role": "user","id": "33333"}
}
)
we cannot expect there will be some then method:
currentUser: function() {
//AuthenticationService.then(function(result) {
// return result;
//});
return AuthenticationService;
}
And to make it really async we can replace it with this:
angular.module('daedalus').factory('AuthenticationService',
['$timeout', function($timeout) {
return {
getData: function() {
return $timeout(function() {
return {
"idsid": "ad_jdschuma",
"role": "user",
"id": "33333"
}
})
}
};
}])
And then use even the .then() - Session service:
angular.module('daedalus').factory('SessionService', ['AuthenticationService',
function(AuthenticationService) {
return {
currentUser: function(){
return AuthenticationService
.getData()
.then(function(result){
return result;
});
}
};
}]
)
And the RoleService:
return {
...
validateRole: function(route) {
console.log('SessionService currentUser: ' + JSON.stringify(SessionService))
return SessionService
.currentUser()
.then(function(userRole) {
if (userRole.role === route.role) {
return route;
} else {
return null;
}
})
}
And with this in place in appConfig
getStates: function(){
var items = [];
var deffered = $q.defer();
var validatedCount = routes.length;
for(var i=0,len=routes.length; i<len; i++){
var route = routes[i];
RoleService
.validateRole(route)
.then(function(route){
if(route) {
items.push(route.stateConfig)
}
if(--validatedCount === 0 ){ // all processed
deffered.resolve(items)
}
})
}
return deffered.promise;
}
We can do that in run:
AppConfig
.getStates()
.then(function(states) {console.log(states)
angular.forEach(states, function(value, key) {
var state = {
"url": value.url,
"templateUrl": value.templateUrl,
"controller": value.controller
};
$stateProviderRef.state(value.stateName, state);
});
// Configures $urlRouter's listener *after* your custom listener
$urlRouter.sync();
});
$urlRouter.listen();
Check it here
The concept of the second solution (async) is too .thenified(). I just intended to show that all is working. Better approach how to get security data is completely covered here:
Confusing $locationChangeSuccess and $stateChangeStart
I have moved some common code to factory. but the controller is executing before factory get loaded. In this case i am getting the blank response(zero results)
can anyone suggest the best solution.
here is my angular factory,
app.factory('TabsFactory', function($resource){
var activetabs = {};
activetabs.getDepositAccountDetails = function() {
return $resource('xxxx/:number', {}, {
getDepositAccountDetailsService: {
method: 'GET',
isArray: false
}
});
}
activetabs.getAccountInfo = function(){
return accountinit.accountInfo;
}
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
return activetabs;
});
controller,
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo);
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
You should use chain promise to update scope variable, because your accountInfo variable is updated inside $resource promise.
Code
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo).then(function(data){
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
});
Update
Service method should return promise inorder to continue promise chain
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
//added return below
return activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
return accountinit.accountInfo;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
Yes, this will happen because of JavaScript executing asynchronous operations but your controller in such a way that it expects things to be synchronous operations.
When you call TabsFactory.getAccountInfo() its possible that your $resource('xxxx/:number') is still not completed and response ready for you to process!!
So, what to do? You have make use of promise. I usually have a repository (A factory with method that return promise) to handle server communications. Here is an example:
app.factory('accountRepository', ["$http","$q",function($http,$q){
return {
getDepositAccountDetails : function(id) {
var deferred = $q.defer();
$http.ger('xxx').success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
};
}] );
My repository will have more operations like add account, update account info etc..
my controller/service then calls these methods as follows:
accountRepository.getDepositAccountDetails(123).then(function(response) {
// Process the response..
}, function(error) {
// Some error occured! handle it
});
doing so, my code gets executed only after I get response from server and data is ready for consumption or display. Hope this helps..
Update: You might want to have a look at this to get the idea ;)