So consider the following fragment from my angularUI routing setup. I am navigating to the route /category/manage/4/details (for example). I expect 'category' to be resolved before the relevant controller loads, and indeed it is to the extent that I can put a breakpoint inside the resolve function that returns the category from the category service and see that the category has been returned. Now putting another breakpoint inside the controller itself I can see that 'category' is always undefined. It is not injected by UI router.
Can anyone see the problem? It may be somewhere other than in the code I've provided but as I have no errors when I run the code, it's impossible to tell where the source of the issue might lie. Typical js silent failures!
.state('category.manage', {
url: '/manage',
templateUrl: '/main/category/tree',
controller: 'CategoryCtrl'
})
.state('category.manage.view', {
abstract: true,
url: '/{categoryId:[0-9]*}',
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId).then(returnData); //this line runs before the controller is instantiated
}]
},
views: {
'category-content': {
templateUrl: '/main/category/ribbon',
controller: ['$scope', 'category', function ($scope, category) {
$scope.category = category; //category is always undefined, i.e., UI router is not injecting it
}]
}
},
})
.state('category.manage.view.details', {
url: '/details',
data: { mode: 'view' },
templateUrl: '/main/category/details',
controller: 'CategoryDetailsCtrl as details'
})
The concept is working. I created working plunker here. The changes is here
instead of this
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
//this line runs before the controller is instantiated
return CategoryService.getCategory($stateParams.categoryId).then(returnData);
}]
},
I just returned the result of the getCategory...
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId); // not then
}]
},
with naive service implementation:
.factory('CategoryService', function() {return {
getCategory : function(id){
return { category : 'SuperClass', categoryId: id };
}
}});
even if that would be a promise... resolve will wait until it is processed...
.factory('CategoryService', function($timeout) {return {
getCategory : function(id){
return $timeout(function() {
return { category : 'SuperClass', categoryId: id };
}, 500);
}
}});
Related
I am using ui-router to load differents subviews on a given state. Some views require resources which take a long time to resolve so I'd like to display my other views as soon as they are ready.
Here is the way I am fetching my resources:
.config(['$stateProvider', '$routeProvider',
function ($stateProvider, $routeProvider) {
$stateProvider
.state('test', {
abstract: true,
url: '/test',
views: {
'main': {
template: '<h1>Hello!!!</h1>' +
'<div ui-view="view1"></div>' +
'<div ui-view="view2"></div>'
}
}
})
.state('test.subs', {
url: '',
views: {
'view1#test': {
template: "Im View1: {{ item }}",
controller: 'View1Ctrl',
resolve: {
test1: function () { return 'yo' }
}
},
'view2#test': {
template: "Im View2: {{ item }}",
controller: 'View2Ctrl',
resolve: {
// This takes a lot of time to resolve
test2: function ($q, $timeout) {
var deferred = $q.defer()
$timeout(function () { deferred.resolve('boom') }, 5000)
return deferred.promise
}
}
}
}
});
}])
I created a JSFiddle to exemplify my issue:
http://jsfiddle.net/o76uq5oe/6/
Is there a way not to wait for all the promises to be resolved?
This happens because resolve promises should be resolved in order for the state transition to be completed, that's why resolve dependencies are there. It doesn't matter if they were defined for the state itself or for its particular view. From the manual:
If all the promises are resolved successfully, the $stateChangeSuccess
event is fired and the values of the resolved promises are injected
into any controllers that reference them. If any of the promises are
rejected the $stateChangeError event is fired.
It can be done if the views belong to separate states. The workaround is possible with sticky states from ui-router-extras, like that:
...
.state('test.subs', {
views: {
'view1#test': {
template: "Im View1: {{ item }}",
controller: 'View1Ctrl',
resolve: {
test1: function () { return 'yo' }
}
},
},
sticky: true,
onEnter: function ($state) {
$state.transition.then(function () {
$state.go('test.subs.view2')
});
}
})
.state('test.subs.view2', {
views: {
'view2#test': {
template: "Im View2: {{ item }}",
controller: 'View2Ctrl',
resolve: {
test2: function ($q, $timeout) {
var deferred = $q.defer()
$timeout(function () { deferred.resolve('boom') }, 1000)
return deferred.promise
}
}
}
}
});
Otherwise the dependency should be resolved in view controller, like it would be done without router and resolve.
I have controller that needs to receive two id values. One id value identifies a vendor and the second identifies an event (eventId and VendorId).
My current state is as such.
.state('/vendordetail', {
url: '/vendordetail/:vendorId',
templateUrl: '/home/vendordetail',
controller: 'VendorDetailController',
resolve: {
vendorId: ['$stateParams', function ($stateParams) {
return $stateParams.vendorId;
}],
vendorPreload: ['$stateParams', 'vendorDetailRepo', '$state', function ($stateParams, vendorDetailRepo, $state) {
if ($stateParams.vendorId != "")
return vendorDetailRepo.get({ id: $stateParams.vendorId }).$promise;
}]
}
});
At the moment I am able to take the vendorId. But I would like to include an eventId in the state. Can anyone help me modify my state to take the second parameter (eventId) ?
Thank you.
First oft all I would advice you to put your parameters as url-parameters if you don't have good reasons against it.
Following this advice your state may look as follows:
state('/vendordetail', {
url: '/vendordetail?:vendorId:eventId',
templateUrl: '/home/vendordetail',
controller: 'VendorDetailController',
resolve: {
vendorId: ['$stateParams', function ($stateParams) {
return $stateParams.vendorId;
}],
eventId: ['$stateParams', function ($stateParams) {
return $stateParams.eventId;
}],
vendorPreload: ['$stateParams', 'vendorDetailRepo', '$state', function ($stateParams, vendorDetailRepo, $state) {
if ($stateParams.vendorId != "")
return vendorDetailRepo.get({ id: $stateParams.vendorId }).$promise;
}]
}
});
By the way: you don't need to resolve the parameters. You can simply inject $stateParams into your controller.
url: '/vendordetail/:vendorId'/:eventId,
Not sure why you have a vendorId in the resolve as well as the url part. As long as $stateParams is injected into your controller, you can access them that way.
I am working on an application and I'd like to use the controllerAs syntax to not rely only on $scope. I am using $resource to get data from the API and the problem I encounter is that in the success/error callbacks I can use only $scope, since this is not defined.
Here is some code to better explain the problem.
This is my main module where among other things I configure the router:
angular
.module('app', ['ngRoute', 'ngResource', 'LocalStorageModule', 'app.users', 'app.auth'])
.config(configure)
.controller('MainController', ['$scope', '$location', MainController]);
function configure($routeProvider, localStorageServiceProvider, $resourceProvider) {
// configure the router
$routeProvider
.when('/', {
templateUrl: 'app/homepage.html',
controller: 'MainController',
controllerAs: 'vm',
data: { authRequired: true }
})
.when('/users', {
templateUrl: 'app/users/main.html',
controller: 'UserController',
controllerAs: 'vmu',
data: { authRequired: true }
})
.otherwise({redirectTo: '/'});
}
// the MainController is not relevant here
In the user module I get some info about the users from the API. Here is a simplified example:
angular
.module('app.users', ['ngResource'])
.controller('UserController', ['UserService', UserController])
.factory('UserService', ['$resource', UserService]);
function UserController(UserService) {
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
this.groupRules = UserService.users.rules({ id: success.userGroupId });
// I have to use $scope instead but it is not what I want
// $scope.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}
function UserService($resource) {
var userResource = {};
userResource.users = $resource('https://my.api.com/users/:action',
{},
{
list: { method: 'GET', isArray: true, params: { action: 'list' } }
single: { method: 'GET', params: { action: 'single', id: '#id' } }
rules: { method: 'GET', params: { action: 'getRules', id: '#id' } }
});
return userResource;
}
I'd like to be able to use 'this' in the callback of the $resource, but of course I'll get an error since 'this' is 'undefined' inside the callback.
Using $scope solves the problem, but I need to refactor some code and I'd like to avoid using $scope all the time.
Any workaround? Maybe I should use a different approach?
Thanks in advance for your help and explanations!
You should look into how to use this in javascript and into javascript scopes and closures.
This should work better:
function UserController(UserService) {
var _this = this;
this.users = UserService.users.list();
this.getUserInfo = function(userId) {
_this.user = UserService.users.single({ id: userId },
function(success) {
// here I'd like to use 'this' but the following line will trigger an error
_this.groupRules = UserService.users.rules({ id: success.userGroupId });
} );
}
}
I have got a ui -router state here
var AccountParentState = {
url: "/Account",
views: accountrootview,
stickA: true,
},
AccountAddState = {
url: "/add",
views: addaccountview,
controller: "account-controller",
resolve: {
Name: function () {
return "Joydeep";
}
}
};
$stateProvider
.state('account', AccountParentState)
.state("account.add", AccountAddState)
And this is my controller :
angular.module('ngApp').controller('account-controller', ['$scope', '$state', "account", "plugin-factory", "Name", function ($scope
, $state
, account
, plugins, Name) {
console.log(Name);
}]);
When I am trying to resolve the Name within the account-controller . Its throwing the error as :
Unknown provider: NameProvider <- Name <- account-controller
My question is how to resolve this situation . Or resolve the data within the ui-router state using resolve property .
To help you to see the issue, I created working plunker, which does use similar definition to the above states:
var AccountParentState = {
url: "/Account",
views: { '' :
{ template: 'Account state <hr/><div ui-view=""></div>',} },
stickA: true,
},
AccountAddState = {
url: "/add",
//views: addaccountview,
templateUrl: 'tpl.html',
controller: "account-controller",
resolve: {
Name: function() {
return "Joydeep";
}
}
};
$stateProvider
.state('account', AccountParentState)
.state("account.add", AccountAddState)
Factories and controller:
.factory('account', function(){return {}})
.factory('plugin-factory', function(){return {}})
.controller('account-controller',
['$scope', '$state', "account", "plugin-factory", "Name"
, function($scope, $state, account, plugins, Name) {
console.log(Name);
}
])
And that is working, because controller was instantiated by the UI-Router, as a part of the state transition.
If, on the other hand, we will extend that example with this (see forked broken version) line in the index.html (outside of the state def)
<div ng-controller="account-controller"></div>
<div ui-view=""></div>
The issue will appear. Why? Because now the controller is not managed by the UI-Router. It is not injected by its infrastructure, not provided with configured value 'Name'. It is "plain angular" style..
Check the working solution here and the broken here
My state definition looks like:
.state('repository', {
url: '/repository/:host/:owner/:repository',
views: {
appView: {
templateUrl: '/templates/app/repository.html'
},
'repositoryView#repository': {
templateUrl: '/templates/app/_repositoryAnalytics.html'
}
},
resolve: {
resolvedRepository: [
'$stateParams', 'RepositoryService', function($stateParams, RepositoryService) {
console.log(RepositoryService.find($stateParams.host, $stateParams.owner, $stateParams.repository));
return 1;
}
]
},
controller: 'AnalyticsController'
});
However, it hands and never gets to the AnalyticsController. If I remove the resolve, then it works just fine (except, of course, I don't have the resolved value).
I did return 1 for now just to see if it would return something without worrying about promises. I did the console.log to see if it hit, and it does not.
I created update of the prvious plunker here. All is working if all the parts are as well. Your resolve code is ok (it could really return just a value). The only thing different is:
The controller belongs to the view === { views : { viewName : { template, controller}}}
Controller should not (cannot) be defined at the state level. It simply is there to do the MVC stuff...
$stateProvider.state('repository', {
url: '/:host/:owner/:repository',
views: {
appView: {
template: '<div><h1>Repository</h1><div ui-view="repositoryView" ></div>',
controller: 'AnalyticsController', // here is new position
}
},
// removed from here
// controller: 'AnalyticsController',
resolve: {
resolvedRepository: [
'$stateParams', 'RepositoryService', function($stateParams, RepositoryService) {
console.log(RepositoryService.find($stateParams.host, $stateParams.owner, $stateParams.repository));
return 1;
}
]
},
Check it in action here
If you want to return a value for testing purposes, you need to do it in an object, see the first example here: https://github.com/angular-ui/ui-router/wiki#resolve
resolve: {
resolvedRepository: [
'$stateParams',
'RepositoryService',
function($stateParams, RepositoryService) {
return {'value': 1};
}
]
}
If that won't work, try leaving out your RepositoryService to check if that's the problem:
resolve: {
resolvedRepository: [
'$stateParams',
function($stateParams) {
return {'value': 1};
}
]
}