How to use ocLazyLoad resolved file in ui-router resolve property - angularjs

I want to resolve an api call before view showing it's straightforward by using ui-router resolve property but my resolve property dependent on ocLazyLoad resolved file. So, I getting this error Error: [$injector:unpr] Unknown provider: SavedFactoryProvider <- SavedFactory
This is my code
$stateProvider.state('app.saved', {
url: '/saved',
templateUrl: 'app/modules/saved/views/saved.html',
controller: 'SavedSearchCtrl',
resolve: {
loadFiles: ['$ocLazyLoad', function($ocLazyLoad) {
return $ocLazyLoad.load([{
name: 'app.saved',
files: [
'app/modules/saved/controller.js',
'app/modules/saved/factory.js',
],
cache: false
}]);
}],
searches: ['loadFiles', 'SavedFactory', function(loadFiles, SavedFactory) {
return SavedFactory.getSavedSearches();
}]
}
});
Thank you!

Route resolvers are resolved in parallel with $q.all. Since $ocLazyLoad.load(...) is asynchronous, it surely won't be completed at the moment when searches is called.
$ocLazyLoad.load(...) returns a promise which is can be chained in order to avoid race conditions, something like:
searches: function($ocLazyLoad, $injector) {
return $ocLazyLoad.load([
{
name: 'app.saved',
files: [
'app/modules/saved/controller.js',
'app/modules/saved/factory.js',
],
cache: false
}
])
.then(function () {
var SavedFactory = $injector.get('SavedFactory');
return SavedFactory.getSavedSearches();
});
}
As opposed to ngRoute, UI Router supports a hierarchy of resolvers; a graph of dependencies is being built on state change. The order in which they are resolved can be determined by their dependencies. So searches should list loadFiles as its dependency:
searches: function(loadFiles, $injector) {
var SavedFactory = $injector.get('SavedFactory');
return SavedFactory.getSavedSearches();
}
$injector.get is apparently necessary due to the fact how UI Router invokes resolver functions internally.

Related

Ui-router: inject service from resolved module in next resolve

I have a state that needs to load an entire module asynchronously. The following code does that for me, and it works great (for the sake of clarity, I removed anything not needed, including annotations for automated injections):
$stateProvider.state('root.lazy', {
url: '/lazy',
resolve: {
loadModule: function($ocLazyLoad) {
return getModuleAsync().then(function(module) {
return $ocLazyLoad.load({
name: module.name
});
});
}
});
Now, this module also register a service - say lazyService which gets data asynchronously. I'd like to resolve these data once the module is loaded. So I tried:
$stateProvider.state('root.lazy', {
url: '/lazy',
resolve: {
loadModule: function($ocLazyLoad) {
return getModuleAsync().then(function(module) {
return $ocLazyLoad.load({
name: module.name
});
});
},
data: function(loadModule, lazyService) { // So ui-router knows it needs the previous resolve; but it fails trying to inject lazyService
return lazyService.getData();
}
});
But ui-router gives me the following error: Unknown provider: lazyServiceProvider <- lazyService.
Using some debugging logs, I found out that ui-router may try to inject everything needed in the resolve functions before actually resolving anything. Indeed, the error pops before any logs I put in the loadModule function. But loadModule resolves eventually, and then everything is available but the data.
Is there any way to accomplish what I want without using a super state resolving the module and a nested state resolving the data? Given the current app context, it makes sens for us to resolve both things in the same state.
EDIT: I'm using ui-router 1.0.4 with AngularJS 1.5

Controller doesn't find resolve method

I'm trying to get some config settings from an API before loading my view so I don't get a billion interpolation errors from trying to use undefined data. The app doesn't crash because of the errors but I need to get rid of the errors nontheless.
I figured I'd use a resolve and wait for the config method to resolve before loading the view but I'm unable to get the data to my controller so that I can use it there. I've been looking at other questions and apparently this is the way you should do it.. What am I doing wrong exactly?
The issue is not in the api factory, the api.getConfig() returns a promise of the $http.get request to the external API just fine so I won't include the function snippet here.
In my routing:
.when('/:category/top_rated', {
templateUrl: 'views/pages/results.html',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'getConfig': ['api', function(api) {
api.getConfig().then(function(data) {
return data;
});
}]
}
})
Then in my controller I inject it like this:
core.controller('MainCtrl', ['getConfig', function(getConfig) {
getConfig.then(function(data) {
console.log(data);
});
}]);
However this throws a $injector:unpr error. Why can't it find the resolve method?
Your controller should be declared in the router, otherwise the controller has no context where 'getConfig' is coming from, thus an injector error.
.when('/:category/top_rated', {
templateUrl: 'views/pages/results.html',
controller: 'MainCtrl',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'getConfig': ['api', function(api) {
api.getConfig().then(function(data) {
return data;
});
}]
}
})

How to access resolved date within root scope

In my app I have states that fetch various resources from server using resolve clause.
I also have a global controller that need access to these resources. For example, consider a breadcrumb that needs to show some data of these resource.
How can I access these resources from the global controller? Even if I have the state object at hand, I couldn't figure how to extract the resolved properties.
EDIT: I'm using ui-router package for routing.
As I understand it you have something like this:
.when('/', {
controller: 'HomeCtrl',
templateUrl: 'home.html',
resolve: {data: someDataFetcher}
})
And you need to access the data in a global controller. I would have to see a code example to perfectly understand what you want, but I would suggest using a shared service.
function HomeCtrl (data, myDataService) {
myDataService.data = data;
}
function GlobalCtrl (myDataService) {
myDataService.data // => The data
}
A service has only one instance so it is easy to use that to share state and resources across controllers.
Hope this helps.
update:
Create a resolve closure:
var myResolveClosure = function (resolver) {
// Return a wrapper around the resolver
return function (globalController, $q) {
var deferred = $q.defer();
resolver($q).then(function (data) {
globalController.addData(data);
deferred.resolve(data);
}).catch(function () {
deferred.reject();
});
return deferred.promise;
}
}
.when('/', {
controller: 'HomeCtrl',
templateUrl: 'home.html',
resolve: {
data: myResolveClosure(function ($q) {
var deferred = $q.defer();
// do something and resolve with the data
return deferred.promise;
})
}
})
So basically you pass in the specific resolver you need to fetch that data. The "myResolveClosure" will wrap a a function around the resolver, passing in $q to the specific resolver to do some fetching. It will then take the data resolved from the specific resolver and pass it into the global controller, then resolve itself so that any controller you specified also will get the data.
Hope this makes sense :-)
The idea is to add a resolve key that depends on the required data (see breadcrumb in code below). When the function of this key is invoked, we can be assured that our data item is ready for use. Then we put it on the state (data property).
.state('some.item.detail.state', {
url: '/:itemId',
resolve: {
item: function($stateParams, MyServerResource) {
return MyServerResource.get({itemId: $stateParams.itemId}).$promise;
},
breadcrumb: function(item) {
this.data = item;
}
},
templateUrl: 'myTemplate.html'
}
To consume the data in the global controller, we simple examine $state.current.data.

Getting A Route Parameter for Resolve

I'm getting killed by my inability to grok Angular-UI UI-Router. I have a state defined as follows:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
// From examples for testing
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$state) {
return FamilyService.getMember($state.current.params.membersId);
}
},
controller: 'MemberController'
});
Since the docs say $stateParams aren't available, I'm using $state.current.params. Should be fine. Instead, I get dead air. I can't access the membersId to save my life.
To test my service and make sure it's not the problem, I hard coded in a membersId and the controller gets the memberDetails result as well as the simpleObj result. So, Service and Controller and working great.
Example of that hardcoded:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
// From examples for testing
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$state) {
return FamilyService.getMember('52d1ebb1de46c3f103000002');
}
},
controller: 'MemberController'
});
Even though the docs say you can't use $stateParams in a resolve, I've tried it anyway. It doesn't work either.
return FamilyService.getMember($stateParams.membersId);
How in the world do I get the membersId param to get passed into the FamilyService for the resolve?
I don't have much hair left to pull out; so, someone save me please.
I finally figured this out. It was quite simple and in the docs. Despite reading several times, I overlooked it each time. I needed to inject $stateParams into the resolve:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$stateParams) {
return FamilyService.getMember($stateParams.membersId);
}
},
controller: 'MemberController'
});
I still don't understand why the documentation says this is not possible.
Two Important $stateParams Gotchas
The $stateParams object is only populated after the state is activated
and all dependencies are resolved. This means you won't have access to
it in state resolve functions. Use $state.current.params instead.
$stateProvider.state('contacts.detail', { resolve: {
someResolve: function($state){
//* We cannot use $stateParams here, the service is not ready //
// Use $state.current.params instead *//
return $state.current.params.contactId + "!"
}; }, // ... })

Angular ui-router fails to resolve named dependencies

I recently migrated from ui-router 0.0.1 to 0.2.0. Since the migration, ui-router fails to resolve named dependencies that needs to be injected into a view's controller. Here's the sample code which works fine with ver 0.0.1 but fails in ver 0.2.0
angular.module( 'sample.test', [
'ui.router',
'i18nService'
])
.config(function config($stateProvider) {
$stateProvider.state( 'mystate', {
url: '/mystate',
resolve: {i18n: 'i18nService'},
views: {
'main': {
controller: 'MyCtrl',
templateUrl: 'templates/my.tpl.html'
}
}
});
})
.controller('MyCtrl', ['i18n', function(i18n) {
// fails to resolve i18n
}]);
i18nService is a simple service that return a promise
angular.module('i18nService', [])
.factory('i18nService', ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
$http.get('..').then(..);
return deferred.promise;
}]);
I get the error "Unknown provider: i18nProvider <- i18n" when using v0.2.0
If i change the resolve config to:
resolve: {
i18n: function(i18nService) {
return i18nService
}
},
everything works fine. Is this an expected behaviour, or am I missing some configuration?
Here's the plunker: http://plnkr.co/edit/johqGn1CgefDVKGzIt6q?p=preview
This is a bug that was fixed last month:
https://github.com/angular-ui/ui-router/commit/4cdadcf46875698aee6c3684cc32f2a0ce553c45
I don't believe it's in any currently released version, but you could either get the latest from github or make the change yourself in your js file. It's simply changing key to value in that one line (you can see it in the github commit).
A workarround is to just not change the name for now.... do
resolve :{
i18nService: 'i18nService'
}
Then inject i18nService to your controller instead of i18n. It's a bit of a hack, but it does work (it injects the resolved service not the promise).

Resources