defer.promise printing out object and not data - angularjs

I have this code:
app.factory('loadDependencies', function ($q, $timeout) {
return {
load: function () {
console.log("start 1");
var defer = $q.defer();
$timeout(function () {
defer.resolve({ resolve: "got dependencies" });
}, 3000);
return defer.promise;
}
}
});
But the problem is that defer.promise doesn't wait until timeout ends, and it also just print out the object properteis and not the data, it prints out :
Object { then: qFactory/defer/deferred.promise.then(), catch: qFactory/defer/deferred.promise.catch(), finally: qFactory/defer/deferred.promise.finally() }
I'm new to angular and trying to understand what did i do wrong?
edit
Some more info about what i was trying to accomplish.
I have a resolve in route that supposed to dynamically load controllers and css files.
.when('/url', {
templateUrl: 'someview',
controller: 'somecontroller',
resolve: {
load: function (loadDependencies) {
loadDependencies.load(); // here i need to know get the result of what's inside $timeout of 'load'
}
}
})

That's because defer.promise does not return your data and also does not wait until your timeout is completed. So what do you do with it is assign some handlers and wait for it to complete/fail:
function success(data) {
//this is called after defer.resolve({ resolve: "got dependencies" }); gets executed
//here you can access your data
}
function error(error) {
//this is called if the promise gets rejected
}
loadDependencies.load().then(success, error);
Now when your promise completes, it automatically calls one of those handlers. There are more possibilities to apply callbacks, such as the .finally() method. Have a look at the documentation.
EDIT:
Just return the promise in your resolver:
.when('/url', {
templateUrl: 'someview',
controller: 'somecontroller',
resolve: {
load: function (loadDependencies) {
return loadDependencies.load(); // here i need to know get the result of what's inside $timeout of 'load'
}
}
})
Now the controller will be called after the promise is resolved and angular will pass the resolved value to it like so:
app.controller("CtrlName", function($scope, load){
//access your load value here
});

Related

AnguarJS: using 'this' pointer or return statement

Which is the right way to declare a service ?
.service('myService', function(myFactory) {
myFactory.LoadData("...",function(newData){
this.data= newData;
});
}
or
.service('myService', function() {
var data= {};
myFactory.LoadData("...",function(newData){
data= newData;
return data ;
});
}
I don't want to make http calls everytime I use it but only once, and then access the data whenever I want.
I've tried the first example but i'm getting 'undefined' value when I try to use it in my controller.
EDIT:
ok here is my code:
the service :
.service('ClassService', function(ClassFactory) {
ClassFactory.query(function(rawClasses){
this.classes= [];
rawClasses.forEach(function(classe) {
var classeRow={};
classeRow.grade=classe.grade;
classe.branch.forEach(function(branch) {
classeRow._id=branch._id;
classeRow.branch= branch.name;
classeRow.fees= branch.fees;
branch.sub_class.forEach(function(subClass) {
classeRow.label=subClass.label;
classeRow.name= _getGradeName_(classeRow.grade)+(classeRow.branch || '')+ (subClass.label || '');
console.info('ClasseService hihihi!');
this.classes.push(_byValue_(classeRow));
console.log( this.classes);
}, this);
}, this);
}, this);
});
})
and the controller:
.controller('StudentController', function(StudentFactory, ClassService, $scope, $stateParams) {
//...
$scope.classes= ClassService.classes;
//but here console.log($scope.classes) gives 'undefined'
//...
});
The query in your service is running asynchronously so you need to use a promise to wait for the value to become available.
If you make ClassFactory.query() return a promise instead of using a callback it will all become much simpler. Use thePromise.then() in ClassService to handle the completion, but that also returns a promise which you should expose to the controller. Then in the controller use another .then() to know when the data is available.
So assuming you've updated ClassFactory.query() to return a promise:
.service('ClassService', function(ClassFactory) {
var promise =ClassFactory.query();
this.dataLoaded = promise.then(... the code to convert rawClasses to this.classes goes here...);
})
and:
.controller('StudentController', function(StudentFactory, ClassService, $scope, $stateParams) {
//...
ClassService.dataLoaded.then(function () {
$scope.classes= ClassService.classes;
});
});

'invocables' must be an object

I have a state as following :
.state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
I want when I enter to this state to check something before loading the template, in this case I want to call an api that checks for something if the promise is successful it will continue and display the template, otherwise it will redirect the user to another state.
I tried to do this on the top of the controller but I always see the template for a moment then it redirects me, so I tried to use resolve as in this post :
AngularJS | handle routing before they load
As following :
.state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
resolve: function(recoverAccountService, $location, $state, $q) {
var deferred = $q.defer();
deferred.resolve();
recoverAccountService.get({email:$location.search().email, verificationKey:$location.search().verificationKey})
.$promise.then(function (result) {}).catch(function (err) {
$state.go("candidature.pre");
});
return deferred.promise;
},
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
but it didn't work and I'm getting this error in the browser's console :
Error: 'invocables' must be an object
How can I solve this ?
You're not using the correct syntax, uiRouter is expecting as entry for resolve an object, which keys it will try to evaluate.
Lets abbreviate your resolving function as aimadResolver, such that
var aimadResolver = function(recoverAccountService, $location, $state, $q) {
var deferred = $q.defer();
deferred.resolve();
recoverAccountService.get({ email: $location.search().email, verificationKey: $location.search().verificationKey })
.$promise.then(function(result) {}).catch(function(err) {
$state.go("candidature.pre");
});
return deferred.promise;
}
Of course, this is not mandatory, but I'm doing it for the sake of readability. Then, your state definition should be as follows:
state('core.recover', {
url: '/recover',
controller: 'RecoverPasswordCtrl',
resolve: {'yourResolverName': aimaidResolver}
},
templateUrl: 'views/tmpl/recoverAccount/recover-password.html'
})
Don't forget to inject yourResolverName in RecoverPasswordCtrl, or else your controller will be instantiated without waiting anyway. Source: look for the resolve examples
On the side
I'd like to point out that your use of deferred objects doesn't make sense. You're immediately resolving your deferred object on the second line within your function, which means that recoverAccountservice.get() could still be pending while RecoverPasswordCtrl is already being instantiated. Assuming that recoverAccountservice.get() returns a promise (and if it doesn't, you should change it such that it does), you can more efficiently write:
var aimadResolver = function(recoverAccountService, $location, $state, $q) {
return recoverAccountService.get({... })
.then(function(result) {
// do nothing? Apparently you only want to $state.go on a rejection of the promise
})
.catch(function(err) {
$state.go("candidature.pre");
return $q.when()
})
}
More on the use of $q.when() versus deferred can be found here.

while unit testing ui-router config, ui-router's resolve function returns promise during expectation even after the service promise was resolved

Below is the sample route configuration that I have for myApp using ui-router
angular.module('myApp', ['ui.router']);
angular.module('myApp').config(stateConfig);
stateConfig.$inject = ['$stateProvider','$urlRouterProvider'];
function stateConfig($stateProvider, $urlRouterProvider) {
$stateProvider.state('view1', {
url: '/view1/:id?',
templateUrl: 'app/view1/view1.html',
resolve:{
init: ['$stateParams', 'view1Service', function($stateParams, view1Service){
if($stateParams.id !== ''){
return view1Service.getIdData($stateParams.id)
.then(function(response){
return { data: response.data, responseStatus: response.status };
}, function(response){
return { data:{}, responseStatus: response.status };
});
}
else{
return { data:{}, responseStatus: 200 };
}
}]
},
controller: 'View1Controller as controllerOne'
})
//some other routes with similar configuration
$urlRouterProvider.otherwise('/view1/');
}
Here is the spec for the above code that I have for now. Since resolve function for view1 state is dependent on view1Service I have mocked view1Service and also made it to return a promise(if promise was not returned from mocked service then infinite digest() loop was occuring).
describe('ui router config', function() {
var $rootScope, $state, $injector, myServiceMock, state = 'view2', deferred, mockedService;
beforeEach(function() {
angular.mock.module('myApp');
angular.mock.module('ui.router');
angular.mock.module(function($provide){
$provide.factory('view1Service', function($q){
function getIdData(id){
deferred = $q.defer();
return deferred.promise;
}
return {getIdData: getIdData}
});
});
inject(function(_$rootScope_, _$state_, _$injector_, $templateCache, _$stateParams_, view1Service) {
$rootScope = _$rootScope_;
$state = _$state_;
$injector = _$injector_;
$stateParams = _$stateParams_;
$templateCache.put('app/view1/view1.html', '')
})
});
it('should respond to URL', function() {
expect($state.href(state, { id: 1 })).toEqual('#/view1/1');
});
it('should resolve data', function() {
$state.go(state, {id: '9999'});
deferred.resolve({
data: 'some data',
status: 666
});
$rootScope.$digest();
expect($state).toBe('checking');
expect($state.current.name).toBe(state+'q');
// Call invoke to inject dependencies and run function
expect($injector.invoke($state.current.resolve.init)).toBe('findAll+1');//this assertion fails with below error
});
});
I'm currently able to assert on the current state. I would like to test the resolve function's success and failure callback as well.
However I keep getting following error:
Expected Promise({ $$state: Object({ status: 0 }) }) to be 'findAll+1'.
Any idea why resolve block keeps returning Promise object as above. First of all it shouldn't be returning a promise since view1Service was resolved. And to my understanding even if resolve block invocation returns a promise doesn't expect statement wait till its resolved? I tried even using .then on invocation call, that didn't work either.
Any help is much appreciated.
You are transitioning to the state, which calls the resolve functions, and then you "resolve()" your deferred. All good here. But then you invoke the init function later, which returns a promise, which is an good. But you want to resolve the deferred after this, now that it's been setup.
Basically, I think you are calling "init" twice, once when you call "$state.go", and again explicitly afterwards.
You should be able to first do a $state.get('view1').resolve.init, to grab the init function that you want to test directly. Otherwise, calling "$state.go" will run it automatically.
Hope that helps!

Why can I not chain promises in a route 'resolve'?

I am trying to chain the promise that fetches 'dtaDashboard' to use a value from the fetched dashboard, to execute a second call. For some reason the promise chain does not seem to work inside a 'resolve'. Why not?
state('uploadServiceTxData', {
url: '/dashboard/:id/upload',
templateUrl: 'views/dashboard-upload.html',
controller: 'DashboardUploadController',
resolve: {
dtaRefData: function(RefDataService) {
return RefDataService.getRefData();
},
dtaDashboard: function(DashboardService, $stateParams) {
return DashboardService.get({ id: $stateParams.id });
},
dtaUploads: function(TransactionSummaryUploadService, dtaDashboard, $q) {
return dtaDashboard.then(function(dashboard) { return TransactionSummaryUploadService.findByExample({ service: dashboard.service.name }) });
}
}
});
Some more information on the code above. The functions on the DashboardSerivce and TransactionSummaryUploadService, are implemented with $resource. So "get" and "findByExample" use $resource to make REST calls.
You are very close.
dtaDashboard has already been resolved when it's injected into datUploads. So you use the resolved result (meaning it's no longer a promise).
state('uploadServiceTxData', {
url: '/dashboard/:id/upload',
templateUrl: 'views/dashboard-upload.html',
controller: 'DashboardUploadController',
resolve: {
dtaRefData: function(RefDataService) {
return RefDataService.getRefData();
},
dtaDashboard: function(DashboardService, $stateParams) {
return DashboardService.get({ id: $stateParams.id });
},
dtaUploads: function(TransactionSummaryUploadService, dtaDashboard, $q) {
return TransactionSummaryUploadService.findByExample({ service: dtaDashboard.service.name });
}
}
});
The reason it did not work is that the services were implemented with $resource. The methods of $resource do not return promises, they return an object which contains a promise, and some other things. To get to the promise itself, I needed to use dtaDashboard.$promise.
So code like this works:
dtaDashboard: function(DashboardService, $stateParams) {
return DashboardService.get({ id: $stateParams.id });
},
dtaUploads: function(dtaDashboard) {
dtaDashboard.$promise.then(function(result) { ... /* result contains the data now. */ });
}
return DashboardService.get({ id: $stateParams.id });
Does not return the promise from the $resource service, it returns an object containing the promise (and eventually the data); that object is resolved, but not the promise it contains. To ensure that the route resolves to the data, this can be done instead:
dtaDashboard: function(DashboardService, $stateParams) {
return DashboardService.get({ id: $stateParams.id }).$promise;
}
Now dtaDashboard will be fully resolved to the data wherever it is injected.

Loading data from angular service on startup

UPDATE
I am currently doing this, and I'm not sure why it works, but I don't think this is the correct approach. I might be abusing digest cycles, whatever those are.
First, I want to have the array navigationList be inside a service so I can pass it anywhere. That service will also update that array.
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigationList = [];
var getNavigation = function() {
ExtService.getUrl('navigation.json').then(function(data) {
angular.copy(data.navigationList, navigationList);
});
}
return{
getNavigation: getNavigation,
navigationList: navigationList,
}
}]);
Then in my controller, I call the service to update the array, then I point the scope variable to it.
ChapterService.getNavigation();
$scope.navigationList = ChapterService.navigationList;
console.log($scope.navigationList);
But this is where it gets weird. console.log returns an empty array [], BUT I have an ng-repeat in my html that uses $scope.navigationList, and it's correctly displaying the items in that array... I think this has something to do with digest cycles, but I'm not sure. Could anyone explain it to me and tell me if I'm approaching this the wrong way?
I have a main factory that runs functions and calculations. I am trying to run
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData : function () {
ExtService.getUrl('navigation.json').then(function(data) {
return data;
});
}
}
return: {
navigation: navigation
}
I did a console.log on the data before it gets returned and it's the correct data, but for some reason, I can't return it..
The ExtService that has the getUrl method is just the one that's typically used (it returns a promise)
In my controller, I want to do something like
$scope.navigation = ChapterService.navigation.getNavigationData();
in order to load the data from the file when the app initializes,
but that doesn't work and when I run
console.log(ChapterService.navigation.getNavigationData());
I get null, but I don't know why. Should I use app.run() for something like this? I need this data to be loaded before anything else is done and I don't think I'm using the best approach...
EDIT
I'm not sure if I should do something similar to what's being done in this jsfiddle, the pattern is unfamiliar to me, so I'm not sure how to re purpose it for my needs
My code for ExtService is
app.factory('ExtService', function($http, $q, $compile) {
return {
getUrl: function(url) {
var newurl = url + "?nocache=" + (new Date()).getTime();
var deferred = $q.defer();
$http.get(newurl, {cache: false})
.success(function (data) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
});
EDIT 2
I'd like to separate the request logic away from the controller, but at the same time, have it done when the app starts. I'd like the service function to just return the data, so I don't have to do further .then or .success on it...
You are using promises incorrectly. Think about what this means:
var navigation = {
getNavigationData : function () {
ExtService.getUrl('navigation.json').then(function(data) {
return data;
});
}
}
getNavigationData is a function that doesn't return anything. When you're in the "then" clause, you're in a different function so return data only returns from the inner function. In fact, .then(function(data) { return data; }) is a no-op.
The important thing to understand about promises is that once you're in the promise paradigm, it's difficult to get out of it - your best bet is to stay inside it.
So first, return a promise from your function:
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
return ExtService.getUrl('navigation.json');
}
}
return {
navigation: navigation
}
}])
Then use that promise in your controller:
app.controller('MyController', function($scope, ExtService) {
ExtService
.navigation
.getNavigationData()
.then(function(data) {
$scope.navigation = data;
});
})
Update
If you really want to avoid the promise paradigm, try the following, although I recommend thoroughly understanding the implications of this approach before doing so. The object you return from the service isn't immediately populated but once the call returns, Angular will complete a digest cycle and any scope bindings will be refreshed.
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
// create an object to return from the function
var returnData = { };
// set the properties of the object when the call has returned
ExtService.getUrl('navigation.json')
.then(function(x) { returnData.nav = x });
// return the object - NB at this point in the function,
// the .nav property has not been set
return returnData;
}
}
return {
navigation: navigation
}
}])
app.controller('MyController', function($scope, ExtService) {
// assign $scope.navigation to the object returned
// from the function - NB at this point the .nav property
// has not been set, your bindings will need to refer to
// $scope.navigation.nav
$scope.navigation = ExtService
.navigation
.getNavigationData();
})
You are using a promise, so return that promise and use the resolve (.then) in the controller:
app.factory('ChapterService', [ 'ExtService', function(ExtService) {
var navigation = {
getNavigationData: function () {
return ExtService.getUrl('navigation.json'); // returns a promise
});
}
return: {
navigation: navigation
}
}
controller:
ChapterService
.navigation
.getNavigationData()
.then(function (data) {
// success
$scope.navigation = data;
}, function (response) {
// fail
});
This is a different approach, I don't know what your data looks like so I am not able to test it for you.
Controller
.controller('homeCtrl', function($scope, $routeParams, ChapterService) {
ChapterService.getNavigationData();
})
Factory
.factory('ChapterService', [ 'ExtService', function(ExtService) {
function makeRequest(response) {
return ExtService.getUrl('navigation.json')
}
function parseResponse(response) {
retries = 0;
if (!response) {
return false;
}
return response.data;
}
var navigation = {
getNavigationData: function () {
return makeRequest()
.then(parseResponse)
.catch(function(err){
console.log(err);
});
}
}
return navigation;
}])

Resources