I'm new to angular and I have a user route which I'm attempted to resolve the user object for before rendering the view. I've injected $q and deferred the promise, however, the view is still loading before the promise is returned.
Route:
.when('/user/:userId', {
templateUrl: 'user/show.html',
controller: 'UserController',
resolve: {
user: userCtrl.loadUser
}
})
Controller
var userCtrl = app.controller('UserController', ['$scope',
function($scope){
$scope.user = user; // User is undefined
// This fires before the user is resolved
console.log("Fire from the controller");
}]);
userCtrl.loadUser = ['Restangular', '$route', '$q',
function(Restangular, $route, $q) {
var defer = $q.defer();
Restangular.one('users', $route.current.params.userId).get().then(function(data) {
console.log("Fire from the promise");
defer.resolve(data);
});
return defer.promise;
}];
After looking through the Github issues, I found a similar problem and resolved it with the following:
userCtrl.loadUser = ['Restangular', '$route',
function(Restangular, $route) {
return Restangular.one('users', $route.current.params.userId).get();
}];
Related
I am currently working on an Angular app, but I am having difficulty implementing a promise with resolve. What I want to accomplish is in the following:
Get a users geolocation
Use the users geolocation as parameters for an API call to SongKick
After the data has been received from the API call successfully I want the home.html page to load with the data found in q.resolve
All want all of this to happen in order. Essentially, there is data I need to obtain before displaying my home page. The problem is that when I console log getLocation in my homeCtrl it is undefined. Anyone know why or have a better way to approach this kind of thing?
FYI:assignValues is a success callback after geolocation values have been defined.
routes.js
angular.module('APP', ['ui.router',
'APP.home',
'uiGmapgoogle-maps'
])
.config(function($urlRouterProvider, $stateProvider, uiGmapGoogleMapApiProvider) {
$stateProvider.state("home", {
url:"/",
templateUrl: '/home.html',
controller: 'homeCtrl',
resolve: {
getLocation: function(dataFactory, $q){
var q = $q.defer();
navigator.geolocation.getCurrentPosition(assignValues);
function assignValues(position) {
dataFactory.getMetroArea(position.coords.latitude, position.coords.longitude).then(function(data){
q.resolve(data);
return q.promise;
})
}
}
}
})
HomeCtrl.js
angular.module('APP.home',['APP.factory'])
.controller('homeCtrl', ['$rootScope', '$scope', '$http', '$location', 'dataFactory', 'artists','uiGmapGoogleMapApi', 'getLocation', homeCtrl])
function homeCtrl($rootScope, $scope, $http, $location, dataFactory, artists, uiGmapGoogleMapApi, getLocation){
$scope.googleMapsData = getLocation
}
dataFactory.js(left out rest of factory)
dataFactory.getMetroArea = function(lat, lon){
return $http.get('http://api.songkick.com/api/3.0/search/locations.json?location=geo:'+ lat + ',' + lon + '&apikey=APIKEY')
}
Resolve methods need to either return a promise, or actual data. Here's a cleaned up resolve method which include rejections (you don't want to leave your request hanging).
angular.module('APP', ['ui.router', 'APP.home', 'uiGmapgoogle-maps'])
.config(function($urlRouterProvider, $stateProvider, uiGmapGoogleMapApiProvider) {
$stateProvider.state("home", {
url: "/",
templateUrl: '/home.html',
controller: 'homeCtrl',
resolve: {
getLocation: function(dataFactory,$q) {
var q = $q.defer();
navigator.geolocation.getCurrentPosition(function(position){
dataFactory.getMetroArea(position.coords.latitude, position.coords.longitude).then(function(data){
q.resolve(data);
},function(err){
q.reject(err);
})
},function(err){
q.reject(err);
});
return q.promise;
}
}
});
});
I think your getLocation function should be
getLocation: function(dataFactory, $q){
var q = $q.defer();
navigator.geolocation.getCurrentPosition(assignValues);
function assignValues(position) {
dataFactory.getMetroArea(position.coords.latitude, position.coords.longitude)
.then(function(data){
q.resolve(data);
});
}
return q.promise;
}
Goal:
In my application every controller should be initialized after a user has a session / is logged in, because in this controller I use the data of logged in user.
Code:
app.js
app.run(function($q, $rootScope, AuthSvc){
$rootScope.ajaxCall = $q.defer();
AuthSvc.reloadSession().then(
function(response){
if(response!=null && response!=undefined){
$rootScope.activeUserSession = response;
$rootScope.ajaxCall.resolve();
}else{
$rootScope.activeUserSession = null;
$rootScope.ajaxCall.reject();
}
});
return $rootScope.ajaxCall.promise;
});
routes.js
.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/timeTracking', {
templateUrl: 'partials/timeTracking/projectView.html',
controller: 'timeTrackingController',
resolve: {
response: function($rootScope, $q) {
var defer = $q.defer();
$rootScope.ajaxCall.promise.then(
function(){
defer.resolve();
return defer.promise;
});
}
}
}).
Problem: Controller gets initialized sometimes before the user has a session, I do not understand why.
Sorry I am new to Angular and my english is also crap, so I hope nevertheless you can understand what is my problem.
I think placing your session reload into the app.run is not the right place. Add it directly to resolve and checkout the docs for $q to learn how promises are working.
Because you can't call a promise or defer. You need to call a function that's returning a promise then you can add your then method to do your stuff after the promise is resolved.
Please have a look at the demo below or here at jsfiddle.
It's just an asynchronous method with $timeout to simulate the auth because I don't have a backend to add in the demo.
You can add your AuthSvc directly into resolve.
angular.module('demoApp', ['ngRoute'])
.controller('timeTrackingController', function($scope, response) {
$scope.data = response;
})
.factory('authService', function($q, $timeout) {
return {
reloadSession: function() {
var deferred = $q.defer();
$timeout(function() {
// just simulate session reload
// real api would to the job here
console.log('resolved defer now!!');
deferred.resolve({dummyData: 'hello from service'});
}, 1000);
return deferred.promise;
}
}
})
.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/timeTracking', {
templateUrl: 'partials/timeTracking/projectView.html',
controller: 'timeTrackingController',
resolve: {
response: function(authService) {
return authService.reloadSession().then(function(data) {
return data;
})
}
}
})
.otherwise('/timeTracking');
}]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular-route.js"></script>
<div ng-app="demoApp">
<script type="text/ng-template" id="partials/timeTracking/projectView.html">
project view: {{data | json}}
</script>
<div ng-view=""></div>
</div>
I am using route resolver in angularjs,for user will be redirect to login if user is not logged in as follows,
$routeProvider
.when('/', {
templateUrl: 'app/components/main/dashboard.html',
controller: 'dashboardController',
resolve: {
login: function ($rootScope, $location) {
if (!$rootScope.currentUser) {
$location.path('/login');
}
}
}
})
Here I want use this login function in many other routes,So i can copy paste same resolve function to every where as follows,
.when('/items', {
templateUrl: 'app/components/item/list.html',
controller: 'itemController',
resolve: {
login: function ($rootScope, $location) {
if (!$rootScope.currentUser) {
$location.path('/login');
}
}
}
})
It is working fine,my question is,is there any way to avoid this duplication of codes or is there any other better method ?
I set up a github repository yesterday which is a starting point for a web app and contains this feature here
If you look in public/app/app-routes.js you will see I have added resolve functions as variables, then you can simply do this rather than writing a whole function each time:
Function
var checkLoggedIn = function($q, $timeout, $http, $window, $location, $rootScope) {
// Initialize a new promise
var deferred = $q.defer();
// Make an AJAX call to check if the user is logged in
$http.get('/loggedin').success(function(user) {
// Authenticated
if (user !== '0') {
$rootScope.loggedInUser = user;
$window.sessionStorage['loggedInUser'] = JSON.stringify(user);
deferred.resolve();
}
// Not Authenticated
else {
$window.sessionStorage['loggedInUser'] = null;
$rootScope.loggedInUser = null;
deferred.reject();
$location.url('/login');
}
});
return deferred.promise;
};
checkLoggedIn.$inject = ["$q", "$timeout", "$http", "$window", "$location", "$rootScope"];
Route
.when('/profile', {
title: 'Profile',
templateUrl: '/app/templates/profile.html',
controller: 'ProfileController',
resolve: {
loggedIn: checkLoggedIn
}
})
Should be easily adaptable for your app. Hope that helps!
According to AngularJS, my $http call through a service from my controller is returning undefined?
What seems to be the issue here? I am trying to return the data called, but once passed to the controller the data becomes undefined?
JavaScript
var myStore = angular.module('myStore', [])
.controller('StoreController', ['$scope', 'dataService', function ($scope, dataService) {
$scope.products = dataService.getData();
}])
.service('dataService', ['$http', function($http) {
this.getData = function() {
$http.get('assets/scripts/data/products.json')
.then(function(data) {
return data;
});
};
}]);
HTML
<div class="content">
<ul>
<li ng-repeat="product in products.products">{{product.productName}}</li>
</ul>
</div>
I understand that $http, $q, and $resource all return promises, but I thought I had covered that with .then.
The problem could be that you are not returning the promise created by $http.get in your dataService.getData function. In other words, you may solve your undefined issue by changing what you have to this:
.service('dataService', ['$http', function($http) {
this.getData = function() {
return $http.get...
};
}
If you had multiple calls to $http.get within dataService.getData, here is how you might handle them.
.service('dataService', ['$http', function($http) {
this.getData = function() {
var combinedData, promise;
combinedData = {};
promise = $http.get(<resource1>);
promise.then(function (data1) {
combinedData['resource1Response'] = data1;
return $http.get(<resource2>);
});
return promise.then(function (data2) {
combinedData['resource2Response'] = data2;
return combinedData;
});
};
}]);
A much cleaner way, however, would be to use $q.all
.service('dataService', ['$http', '$q', function($http, $q) {
this.getData = function() {
var combinedData, promises;
combinedData = {};
promises = $q.all([
$http.get(<resource1>),
$http.get(<resource2>)
]);
return promises.then(function (allData) {
console.log('resource1 response', allData[0]);
console.log('resource2 response', allData[1]);
return allData;
});
};
}]);
You're problem does lie in the fact that you are not returning a promise but as you stated in #maxenglander's post you may have multiple http calls involved which means you should start creating and resolving your own promise using $q:
.service('dataService', ['$http', '$q', function($http, $q) {
return $http.get('assets/scripts/data/products.json')
.then(function(data) {
//possibly do work on data
return <<mutated data>>;
});
}];
or if you have multiple http calls and need to do some combination work you can do something $q.all:
.service('dataService', ['$http', '$q', function($http, $q) {
var p1 = $http.get('assets/scripts/data/products.json');
var p2 = $http.get('assets/scripts/data/products2.json');
return $q.all([p1, p2]).then(function(result){
//do some logic with p1 and p2's result
return <<p1&p2s result>>;
});
}];
then in your controller you will need to do:
.controller('StoreController', ['$scope', 'dataService', function ($scope, dataService) {
dataService.getData().then(function(result){
$scope.products = result;
});
}]);
What this allows in your service is now you can do complex calls like say call two webservices inside and wait till they are both complete then resolve the promise.
What I'm trying to express here is that you don't need to return the promise provided by the $http.get function, but since you are doing an async action you DO need to return some promise that will be later fulfilled and acted on.
Following the main answer here, I've tried to do the same, with the exception that my controller is isolated.
I get this:
Uncaught Error: [$injector:modulerr] Failed to instantiate module myApp due to:
ReferenceError: myController is not defined
I only get this when the resolve: parameter is present.
How can I work around this one ?
Route config:
.state("my.jobs", {
url: "/my/:jobId",
templateUrl: "Views/my/index.htm",
controller: "myController",
resolve: myController.resolve // the root of all evil here
})
controller:
(function (ng, app) {
"use strict";
var ctrl = app.controller(
"myController",
['$scope', 'job',
function ($scope, job) {
$scope.job = job;
}]);
ctrl.resolve = {
job: function ($q, $stateParams, batchService) {
var deferred = $q.defer();
jobService.loadJob($stateParams.jobId, true)
.then(deferred.resolve, deferred.reject);
},
delay: function ($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
};
})(angular, myApp);
I don't want to make the controller a global function, I like it isolated as it is.
In your case you could create one service, that you can consume inside your resolve function.
app.factory('resolveService', ['$q', '$stateParams', 'batchService','jobService',function($q, $stateParams, batchService,jobService ) {
return {
job: function($q, $stateParams, batchService) {
var deferred = $q.defer();
jobService.loadJob($stateParams.jobId, true).then(deferred.resolve, deferred.reject);
return delay.promise;
},
delay: function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
}
}]);
Then the config code will be
.state("my.jobs", {
url: "/my/:jobId",
templateUrl: "Views/my/index.htm",
controller: "myController",
resolve: {
resolveService: "resolveService" //this resolves to a service
}
});
For more info look at this reference