So to summarise I am using angular-ui router resolve function to retrieve state specific data. However it doesn't seem to full wait for the promise to resolve:
state('parent.ChildState', {
url: '/myUrl?param1¶m1',
templateUrl: 'views/list.view.html',
controller: 'MyController',
resolve: {
data: resolveData
}
}).
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
DataService.myDataObj = DataService.get(filters, function(result, headers) {
DataService.myDataObj = result;
});
return DataService.myDataObj;
// Note I have also tried returning directly the DataService.get call however this makes all the below console log statements as undefined (see below for the controller code to know what I mean). So I do the assignment first and then return that.
}
Now in the controller I had a function that executes on load like so:
function ExpensesController(DataService) {
$scope.viewData = DataService;
initData();
function initData() {
// this generally logs a ngResource and shows the full data obj on console
console.log($scope.viewData.myDataObj);
// this gets undefined on console
console.log($scope.viewData.myDataObj.someField1);
// this log fine, however why do I need to do promise
// resolve? should be resolved already right?
$scope.viewData.myDataObj.$promise.then(function() {
console.log($scope.viewData.myDataObj.someField1);
});
As your required data to resolve is async, you need to return a promise and add return statement inside your callback function.
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
return DataService.get(filters, function(result, headers) {
DataService.myDataObj = result;
return DataService.myDataObj
});
}
You can read ui-router resolve docs more about how resolver works and when they should return promise or pure values.
I don;t know if I have got your problem :), but here is what I feel is wrong
1) in the resolve return a promise, it should not be resolved
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
return DataService.get(filters);
}
2) In the controller you should inject the data that is declared in resolve not the DataService so your controller should be
function ExpensesController(data) {
$scope.viewData = data;
}
Related
I have a service function that I need to call each time a route is visited or refreshed. The function returns an Angular promise. The result of the promise needs to be loaded into the controller's scope each time the function is called.
I'm currently using a resolve argument on the state definition to call the function.
.state('app.theState', {
url: '/theRoute/:theRouteId',
views: {
'menuContent': {
templateUrl: 'templates/theRoute.html',
controller: 'TheRouteCtrl'
}
},
resolve: {theResolvedResult: function(theService){
return theService.theAsyncCall().then(function(result){
return result;
},function(errMsg){
return errMsg;
});}
})
The data is passed into the controller as an argument.
.controller( 'TheRouteCtrl', function($scope, $state, $log, theResolvedResult)
I am updating the controller $scope in a watch on the global that holds theResolvedResult. (Using the workaround described at the end of this post). I tried a watch on the argument itself, but it never gets triggered.
$scope.$state=$state;
$scope.$watch('$state.$current.locals.globals.theResolvedResult', function (result) {
$scope.aValue = result.aValue;
});
Unfortunately, I guess because the watch is on a global, the watch is triggered when any route runs, and all other routes throw an error for every route except the one where the resolve is defined.
ionic.bundle.js:26794 TypeError: Cannot read property 'aValue' of undefined
at route_ctrl.js:234
How can I fix these errors, or is there a better way to do this?
Maybe just guard against the case where result is not defined:
$scope.$watch('$state.$current.locals.globals.theResolvedResult', function (result) {
if (result) {
$scope.aValue = result.aValue;
}
});
Breaking change for Angular V1.6
Move initialization logic into $onInit function
/* REPLACE
$scope.$state=$state;
$scope.$watch('$state.$current.locals.globals.theResolvedResult', function (result) {
$scope.aValue = result.aValue;
});
*/
//WITH
this.$onInit = function() {
$scope.aValue = $state.$current.locals.globals.theResolvedResult.aValue;
};
Initialization logic that relies on bindings being present should be put in the controller's $onInit() method, which is guaranteed to always be called after the bindings have been assigned.
— AngularJS Migration Guide (V1.6 $compile breaking change bcd0d4)
Erroneous rejection handler
The resolve is converting a rejected promise to a fulfilled promise:
/* ERRONEOUS
resolve: {theResolvedResult: function(theService){
return theService.theAsyncCall().then(function(result){
return result;
},function(errMsg){
//ERRONEOUS converts rejection
return errMsg;
});}
*/
// BETTER
resolve: {
theResolvedResult: function(theService){
return theService.theAsyncCall().then(function(result){
console.log(result);
return result;
},function catchHandler(errMsg){
console.log(errMsg);
//return errMsg;
//IMPORTANT re-throw error
throw errMsg;
});
}
It is important to re-throw the error in the catch handler. Otherwise the $q service converts the rejected promise to a fulfilled promise. This is similar to the way a try...catch statement works in vanilla JavaScript.
The $watch method take a function as its first argument:
/* HACKY
$scope.$state=$state;
$scope.$watch('$state.$current.locals.globals.theResolvedResult', function (result) {
$scope.aValue = result.aValue;
});
*/
//BETTER
//$scope.$state=$state;
//$scope.$watch('$state.$current.locals.globals.theResolvedResult',
$scope.$watch(
function () {
return $state.$current.locals.globals.theResolvedResult;
},
function (result) {
if (result) {
$scope.aValue = result.aValue;
};
});
When the first argument of the $watch method is a string, it evaluates it as an Angular Expression using $scope as its context. Since the $state service is not a property of $scope a function should be used instead.
The best bet is to avoid the $watch hack in the first place. Making sure that the value is good should happen in the Service, or at the very least in the resolve.
Check out this Plunker for a demo of how resolve should work.
The general idea is that resolve has one job; to make sure that the data is ready when we want it. The docs has a few examples on how ui.router handles resolve resolutions. They're a little limited, but generally show that "good" results are expected. Error handling is ultimately left up to your own choice.
Enjoy!
I have below code
$scope.init = function (){
console.log($scope.languageFilePath);
$http.get($scope.languageFilePath) //languageFilePath contain url
.then(function(res){
console.log('scueese');
$scope.translate = res.data;
console.log($scope.translate.SERVICE);
// $scope.eqipment = $scope.translate['COLOR'];
//console.log("Data String "+equip);
//$scope.eqipment="ghsh"
});
};
$scope.init();
Now what is my issue this method calling properly but before initializing $scope.translate its executing other part of code.
What changes i have to do in the code so first its initialize $scope.translate then only it call some other methods of file .
Note:- $http.get() method calling when this method invoked after that other methods are executing and code inside then executing after those method called.
If your problem is to execute the code after $scope.init() invocation, you could try to use the http.get promise, like this:
$scope.init = function (){
console.log($scope.languageFilePath);
return $http.get($scope.languageFilePath) //languageFilePath contain url
.then(function(res){
console.log('scueese');
$scope.translate = res.data;
console.log($scope.translate.SERVICE);
});
};
$scope.init().then(function(){
//this code will be executed after the http get...
});
EDIT 1
after receiving some more code from OP, you should modify your .js file as shown here: http://pastebin.com/mFUWSU0N
this way, ALL the code below init() will be executed after the $http.get completion.
may be you should out the wanted function in your init:
$scope.init = function (){
console.log($scope.languageFilePath);
return $http.get($scope.languageFilePath) //languageFilePath contain url
.then(function(res){
console.log('scueese');
$scope.translate = res.data;
console.log($scope.translate.SERVICE);
myFunc();
myFunc2();
});
};
What you also could do is make it a dependency of your controller.
Lets say for instance you are using the routeprovider to navigate to your page you could then do this:
myApp.config(['$routeProvider',function ($routeProvider) {
$routeProvider.when('../../goto/yourpage', {
templateUrl: '/yourpage.html',
controller: 'myController',
resolve: {
initializeFunction: function($http) {
// do some logic you want executed before anything else
return $http.get("blablabla").$promise;
});
}
}
});
}]);
then in your controller that is active on yourpage.html you can do:
myApp.controller('myController',function ($scope,initializeFunction) {
$scope.possibleReturnFromTheFunction = initializeFunction;
});
I have a problem with outputting the values of a promise function.
$scope.getTeacher = function(teacherAbr) {
var promise = dataService.getTeacher(teacherAbr);
var testje = promise.then(function(payload) {
return payload;
});
return testje; // outputs an d object, not the direct payload values
};
The output of this controller function is this:
However, when I'm doing testje.$$state it returns undefined. How can I output the value object? I can't output the payload variable in a new $scope, because this data is dynamic.
Here is a simplified version of this on Plunker.
You should change the way you think about things when you work with asynchronous code. You no longer return values, instead you use Promise or callback patterns to invoke code when data becomes available.
In your case your factory can be:
.factory('dataService', function($http, $log, $q) {
return {
getTeacher: function(teacher) {
// Originally this was a jsonp request ('/teacher/' + teacher)
return $http.get('http://echo.jsontest.com/key/value/one/two').then(function(response) {
return response.data;
}, function() {
$log.error(msg, code);
})
}
};
});
And usage in controller:
dataService.getTeacher('Lanc').then(function(data) {
$scope.teacher = data;
});
Also $http.get already returns promise, so you don't have to create one more with $q.defer().
Demo: http://plnkr.co/edit/FNYmZg9NJR7GpjgKkWd6?p=preview
UPD
Here is another effort for combining lessons with teachers.
Demo: http://plnkr.co/edit/SXT5QaiZuiQGZe2Z6pb4?p=preview
//in your services file
return {
getTeacher: function (teacher) {
// Originally this was a jsonp request ('/teacher/' + teacher)
return $http.get('http://echo.jsontest.com/key/value/one/two')
})
//change the controller to this
dataService.getTeacher(teacherAbr).then(function(msg){
$scope.getdata=msg.data;
console.log("From server"+msg.data);
});
I am trying to run the following code before any of my AngularJS app controllers, directives run, but unfortunately the app main page controller loads before this code finish executing, so I was wondering if there is a way to ensure that all my app controllers, directives won't run / load before this code finish completely? Thanks
myApp.run(['TokenSvc',function (TokenSvc) {
TokenSvc.getToken().then(function(serverToken){
console.log('Got it...');
}, function(status){
console.log(status);
});
}]);
Most commonly you'll see resolve in the ng-route or ui-router $state definition used for this concern, but that can be problematic. If the resolution takes a while, the user will just be staring at a blank screen. Of course, you can mitigate this problem by using an interceptor to display a loader, but I'd argue that that's outside the intended utility of interceptors.
I like to use something to manage the initialization promise(s), and inject that thing into top-level Controllers (i.e. either a Mediator or Observer pattern):
(function () {
function UserInfoLoader($q, facebookService, githubService) {
var _initPromise = null;
function initialization() {
var deferred = $q.defer(),
_initPromise = deferred.promise,
facebookLoading = facebookService.somePromiseFunc(),
githubLoading = githubService.somePromiseFunc();
$q.all([facebookLoading, githubLoading])
.then(function (results) {
// do something interesting with the results
deferred.resolve();
// set the promise back to null in case we need to call it again next time
_initPromise = null;
});
return promise;
}
this.initialize() {
// if there's already an initialization promise, return that
return _initPromise ? _initPromise : initialization();
}
}
angular.module('myApp').service('userInfoLoader', UserInfoLoader);
}());
This is great, because you can have multiple Controllers depend on the same workflow logic and they'll only produce one promise.
(function () {
function UserProfileController($scope, userInfoLoader) {
$scope.loading = true;
function load() {
userInfoLoader.initialize().then(function () {
$scope.loading = false;
});
}
load();
}
function UserMessagesController($scope, userInfoLoader) {
// same sort of loading thing
}
angular.module('myApp')
.controller('userProfileController', UserProfileController)
.controller('userMessagesController', UserMessagesController)
;
}());
To borrow from Mr. Osmani's book linked above, the loader service is like an air traffic controller. It coordinates the schedules of and passing information between multiple "airplanes", but they never have to talk to each other.
Another approach that I've seen is to use a FrontController, usually added on the body element, that manages a global loader, showing it during long-running async operations. That one's pretty simple, so I won't write it all out.
Do the fowllowing in each route:
$routeProvider.when("/your/path", {
templateUrl: "template/path",
controller: "controllerName",
resolve: {
getToken: ['TokenSvc',function (TokenSvc) {
return TokenSvc.getToken();
}]
}
});
You need that the getToken method return always the same object. Something like this:
obj.token = null;
obj.getToken = function(){
if(!obj.token){
var deferred = $q.defer();
obj.token = deferred;
deferred.promise.then(function(serverToken){
console.log("Got it. The token is ",serverToken);
}, function(status){
console.log("something is wrong ", status);
});
$http.get("url/to/token")
.success(function(data){
deferred.resolve(data);
})
.error(function(data, status){
deferred.reject(status);
});
}
return obj.token.promise;
}
I have a factory that returns an object from $http.get
app.factory( 'myFactory', function($http) {
return {
get: function() {
return $http.get('/data').success(function(data) {
return data
})
}
}
})
then
$scope.listings = myFactory.get()
In index.html, I use ng-repeat on listings.data and there is no problem finding each object.
Then from a directive I call .getListings() that's in my controller and in the Chrome Javascript console $scope.listings =
Object {then: function, success: function, error: function, $$v: Object}
Why can't I just use this?
$scope.listings.data
And is it ok to use this?
$scope.listings.$$v.data
What is $$v?
You are making little mistake in your code with get in service you return a promise object which will be filled when you sucessfully complete response from the server so kindly modify your code below to make it working.
app.factory( 'myFactory', function($http) {
return {
get: function() {
return $http.get('/data')
}
}
})
myFactory.get().then(function(data){
$scope.listings=data;
});