In one of my UI-router states, I have the following link '/users', which points to a page that shows a list of users. Below is the code I wrote to resolve the list of users by using a $resource call, however, the data doesn't seem resolved when the page is loaded:
.state('users', {
url: '/users',
templateUrl: '/assets/angularApp/partials/users.html',
controller: 'UserListCtrl as vm',
resolve: {
users: function ($resource) {
return $resource('http://localhost:8080/api/users').query().$promise.then(function(data) {
return data;
});
}
}
})
In the UserListCtrl, I have the following to assign the resolved users to vm.users so that the users can be displayed on the partial page:
function UserListCtrl($log, users) {
var vm = this;
vm.users = users;
}
The page, however, only displays a few empty rows without any data filled in. So I think the resolve is not working properly on my url /users, could anyone spot where might be problematic? Thanks
Change it to:
users: function ($resource) {
return $resource('http://localhost:8080/api/users').query().$promise;
}
Because that way you're returning the promise, and by using then(...), you're resolving the promise within and just returning data thus it won't pass it to the controller because it's returning them after the controller has loaded.
Using $q service will help you
var userVal = $resource('http://localhost:8080/api/users').query()
retrun $q.resolve(userVal.$promise).then(function(r){ return r;});
Inside you controller, you are good to go
vm.users = users
Related
In the angular ui-router documentation it is stated, that a controller will be instantiated only after all the promises have been resolved, including fetching data from a remote server.
I have a $resource instance:
angular.module('app')
.factory('Item', item);
item.$inject = ['$resource'];
function item($resource) {
return $resource('/items/:id/', {id: '#id'}, {
update: {method: 'PATCH'}
});
}
And I define a state which takes an id from url and retrieves data from a REST API:
$stateProvider.state('item', {
url: '/item/:itemId',
templateUrl: '../components/item/item.html',
controller: 'ItemController',
resolve: {
item: function ($stateParams, Item) {
return Item.get({id: $stateParams.itemId});
}
}
})
In the controller I try to use an Item parameter, assuming that if the controller works, then all the data is already fetched and I can freely use it:
angular.module('app')
.controller('ItemController', ItemController);
ItemController.$inject = ['$scope', 'item', 'Item'];
function ItemController($scope, item, Item) {
$scope.item = item;
$scope.similarItems = Item.query({item_type__id: $scope.item.item_type_detail.id});
}
But the browser gives me this error:
TypeError: Cannot read property 'id' of undefined
And pointing to this exact line, which means that $scope.item.item_type_detail is still undefined, in other words - not resolved.
But if i wrap my second Item call (in the controller) in a $timeout for, say, 5000ms, it would work fine, meaning that the problem is not with the ajax call or improper dependency injection, but that the controller somehow gets instantiated before the promise resolves.
How can this be explained and what can I do to fix it?
Return a promise to the resolver function:
$stateProvider.state('item', {
url: '/item/:itemId',
templateUrl: '../components/item/item.html',
controller: 'ItemController',
resolve: {
item: function ($stateParams, Item) {
//return Item.get({id: $stateParams.itemId});
return Item.get({id: $stateParams.itemId}).$promise;
//return promise --------------------------^^^^^^^^^
}
}
})
It is important to realize that invoking a $resource object method immediately returns an empty reference. Once the data is returned from the server the existing reference is populated with the actual data.
By returning the $promise property, the router will wait for the data from the server.
Not sure if this is the best way to state the question but I only want the resolve to run once when I change the state. This is because I have multiple tabs and I need to use an AJAX call to get the data for the template. So when you click on a tab, I will store that information into the $scope. But I don't want to keep making this call if the user switches to another tab and goes back.
This is what my $stateProvider looks like right now:
state('something', {
views: {
'filters.form': {
templateUrl: '<%= asset_path('my_path.html') %>',
controller: function($scope, sources){
$scope.sources = sources;
}
}
},
resolve: {
sources: function($http) {
return $http.get('/sources').success(function(data){
return JSON.parse(data.sources);
});
}
}
})
Which works great but everytime I switch tabs and come back, it makes another call which I don't want.
I tried to pass in the $scope into the resolve and checked to see if $scope.sources existed before I made an AJAX call but that didn't work.
I would just move it in a service and cache it and inject it in the resolve.
app.service('SourceService',['$http', function(){
var _cachedPromise;
this.getSources = function(){
return _cachedPromise || _cachedPromise = $http.get('/sources').then(function(result){
//return result.data.sources //If the data is already an object which most possibly is just do
return JSON.parse(result.data.sources);
});
}
}]);
Now in the resolve do:-
resolve: {
sources: function(SourceService) {
return SourceService.getSources();
}
}
or just do it in the resolve itself.
I'm pretty new to Angular and I've been going round in circles on this one for a while now.
A bit of background first, I'm using the MEAN stack from mean.io which uses Angular UI Router.
I have a Post model in my DB which can have a category id assigned to it.
When I create a new post I want to load the existing categories from the DB and display them in a select box.
From what I can see I need to use resolve, first of all it doesn't feel right having logic in the resolve property which is in a file called config.js - so far I've placed the call to a service in there and im getting the categories back using the following code:
.state('create post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
resolve: {
loadCategories: function (Categories) {
Categories.query(function(categories) {
return categories;
});
}
}
})
The first problem is that I can't access the returned data in my controller or view.
Secondly I only want to load Categories that belong to a certain Organisation. I will be assigning an organisation id to each user so how can I access the currently signed in user when I'm in config.js - again this doesn't feel like the right place to be doing this sort of logic though.
Any help would be really appreciated.
Thanks
config.js:
register post state :
.state('post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
resolve: PostsController.resolve
})
register posts controller:
.controller({
PostsController: ['$scope', 'loadCategories', PostsController],
...
})
controller function:
function PostsController($scope, loadCategories){
$scope.categories = loadCategories;
};
PostsController.resolve = {
loadCategories: ['dependencies', function(dependencies){
return dependencies.query(...)
}]
};
Angular manage your dependency injection
Assuming Categories is an angular resource, you should be able to just
loadCategories: function (Categories) {
return Categories.query();
}
And then in your controller:
.controller('PostsController', function ($scope, loadCategories) {
$scope.categories = loadCategories;
});
Ok, reading your comments, it sounds like you'll have some issue because you want to inject this into the controller, but only in certain states. You could try:
.state('create post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
data: {
categories: Categories.query()
}
})
and then
.controller('PostsController', function ($scope, $state){
console.log($state.current.data.categories);
});
Which should work...
I've built a table with a filter, sorting, limit and pagination system.
With normal array of objects from the javascript works as expected.
My problem comes when I try to retrieve data from the server and to use it with my client-side pagination and filter. When the page is load for a few seconds the $scope.data is undefined until $http is resolved. I don't know how to solve it and I hope you can help me.
This is the pagination filter which I think is initialized before $http is resolve.
.filter('pagination', function () {
return function (inputArray, currentPage, pageSize) {
var start = (currentPage - 1) * pageSize;
return inputArray.slice(start, start + pageSize);
};
})
I will leave a fiddle here of my problem so you can see it there better.
Fiddle
I don't know if I should edit my post or I do it correctly posting an answer but I came up with a solution and I want to share it.
My problem was that the $scope.data (request from the server) was undefined when controller was initialized so what I did is to set up a route and wait for the promise to resolve before initializing the controller.
One problem I came up with is the fact that it's not possible to inject a .factory directly into the .config so I had to inject $provide to my .config and provide it with my service.
This is the a part of the code I'm talking about :
.config(['$routeProvider','$provide', function($routeProvider, $provide) {
$provide.factory('select', ['$http','$q', function($http, $q) {
return {
getItems : function () {
var deferred = $q.defer();
$http.get('php/select_diarios.php', {cache : true}).success(function (data) {
//Passing data to deferred's resolve function on successful completion
deferred.resolve(data);
}).error(function () {
//Sending an error message in case of failure
deferred.reject("An error occured while fetching items");
});
//Returning the promise object
return deferred.promise;
}
}
}]);
$routeProvider
.when('/', {
templateUrl: 'modulos/home/home.html',
controller: 'AppController',
resolve: {
**data**: function (select) {
return select.getItems();
}
}
}).
when('/genericos', {
controller: 'genericosController'
})
.otherwise({ redirectTo: '/' });
}])
Finally I only had to inject the data parameter of the resolve into my controller like so:
.controller('AppController', ['$scope', '$location','filterFilter','**data**', function ($scope, $location, searchFilter, **data**)
... more code
}]);
Also because of the otherwise redirect you must set a <a href="#/></a> in the pagination cause if not, each time you click to switch a page it doesn't know where to go and redirects to the first one and never changes the page.
this is how the pagination look like.
<ul class="pagination pull-right">
<li>«</li>
<li ng-repeat="n in range" ng-class="{active: n === (currentPage -1)}" ng-click="setPage(n+1)">{{n+1}}</li>
<li>»</li>
</ul>
I developed an angularjs app and I've a REST API for fetching app resources from my AngularJS app. When the user logs himself, I save his access token in a cookie. If he refreshes the page, I want to get back user information from the token like:
mymodule.run(function ($rootScope, $cookies, AuthService, Restangular) {
if ($cookies.usertoken) {
// call GET api/account/
Restangular.one('account', '').get().then( function(user) {
AuthService.setCurrentUser(user);
});
}
});
AuthService:
mymodule.factory('AuthService', function($http, $rootScope) {
var currentUser = null;
return {
setCurrentUser: function(user) {
currentUser = user;
},
getCurrentUser: function() {
return currentUser;
}
};
});
But if the user accesses a controller which needs a user variable:
mymodule.controller('DashboardCtrl', function (AuthService) {
var user = AuthService.getCurrentUser();
});
the controller code was executed before the end of the API call so I've a null var. Is there a good solution to wait on user data loading before starting controllers?
I've found this link but I'm looking for a more global method to initialize app context.
Here's one way I like to handle this, assuming that Restangular returns the new promise from then (I haven't used Restangular). The promise can be stored somewhere like on AuthService and then used later in the controller. First, add a property to AuthService to hold the new promise:
return {
authPromise: {}, // this will hold the promise from Restangular
// setCurrentUser, getCurrentUser
// ...
When calling Restangular, save the promise and be sure to return the user data so that the controller can access it later, like this:
AuthService.authPromise = Restangular.one('account', '').get()
.then( function(user) {
AuthService.setCurrentUser(user);
return user; // <--important
});
Lastly, create a new promise in the controller that will set the user variable.:
mymodule.controller('DashboardCtrl', function (AuthService) {
var user;
AuthService.authPromise.then(function(resultUser){
user = resultUser;
alert(user);
// do something with user
});
});
Demo: Here is a fiddle where I've simulated an AJAX call with $timeout. When the timeout concludes, the promise resolves.
You could try have the authentication method on a parent controller. Then in the resolve method in the routing you could go resolve:DashboardCtrl.$parent.Authenticate().