Controller doesn't find resolve method - angularjs

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;
});
}]
}
})

Related

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

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.

AngularJS ui-router resolve of REST resource

I am working on an application that photographers can use to upload photos. The frontend is AngularJS and there is a RESTfull api backend.
Because of some issues and the fact that ui-router seems better then ngRouter, I decided to change the $routeprovider to $stateProvider of ui-router.
However, my resolve doesn't work anymore (I guessed it would break but I cannot find the solution to my situation).
So here is the original $routeprovider code:
.when('/photographer', {
templateUrl : '/static/partials/photographer/photographer_dash.html',
controller : 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos();
}
}
})
The PhotogService is a $resource service that has the following $resource objects:
return $resource(apiHost, {}, {
'ownPhotos': {
url: apiHost + '/photographer_own_photos/',
method: 'GET',
interceptor: ResponseInterceptor
}
});
In the controller I would then do the following (photogPrepService being injected because of the resolve):
var promise = photogPrepService;
promise.then(
function (data) {
$scope.ownPhotos = data.data.photos;
});
This all worked well and I would get the photos in the scope.
However as said with ui-router it doesn't work and I cannot seem to get it working...
According to the docs (https://github.com/angular-ui/ui-router/wiki#resolve) the following should work:
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos();
}
}
However, when the resolve is injected in the controller and I use console.log() to print the response, I get the following:
Resource(value)
I somehow cannot seem to get the values (JSON response {"photos": ...}) injected into the controller.. I tried various solutions that have been suggested here on stackoverflow and read guides and the API of ui-router, but I cannot wrap my head around what is going wrong... I hope someone can guide me in the right direction..
Thanks!
I think you can use the code below:
//the PhotogService will keep the same as before
//inject the 'PhotogService' into the controller photographerController
PhotogService.ownPhotos().then(function(data) {
$scope.ownPhotos = data.data.photos;
});
//instead of injecting the photogPrepService through 'resove', inject the PhotogService into the controller
//for the $stateProvider
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
}
You need to give a promise to the resolve, your ressource should look more like :
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos().$promise;
}
}
});
Or modify your ressources to make them promises.

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.

routeprovider using resolve to pass value to controller

I'm looking for a way to pass a value to my controller from my appRoutes. With the idea for it to call a function and do some magic. heres some code so you get an idea:
appRoutes.js
$routeProvider
.when('/students/some/path/:id', {
templateUrl: 'views/studentRecord.html',
controller: 'StudentsController',
resolve: { myVar: 'test' }
});
studentsCtrl.js
angular.module('StudentsCtrl', [])
.controller('StudentsController', function($scope, $http, $routeParams,
$location, myVar, Students) {
/* ... */
});
Ideally, I'd like to call a given function within this controller - but parsing a value would be just as good. The idea is that the controller handles all pages to do with 'students' and will make some http calls so my node server will do some calls to mongodb. Ive tried a few variations on the internet and with no luck. I got an error:
Error: $injector:unpr Unknown Provider
but I'm not sure how to resolve it.
EDIT: I've half resolved this now by using this; http://plnkr.co/edit/mSb58e8cGDNYU27xSizk?p=preview
ideally i'd like to separate the app.js into controllers and services - currently working on this, any edit of the plnkr would be great.
Question still stands of is it possible to hit a function within the controller first, rather than resolving one through a service?
In each resolve property, you can have a function that lets Angular inject services for you to use:
resolve: {
myVar1: function(testService) { return testService.fetchList1(); },
myVar2: function(anotherService, $http) {
// call service functions to mongo-db or what have you
return result;
}
}
Then, your controller, just inject the properties:
// myVar1 and myVar2 are now here
app.controller('StudentsController', function($scope, myVar1, myVar2) {
/* ... */
});
If the returned value from the function inside resolve is a promise, then it will be resolved before controller code is called (hence, the name resolve). This is actually the recommended approach as it makes service code (such as testService) reusable across many controllers.
Passing a function that returns the value
.state('tab2', {
url: '/tab2',
templateUrl: 'templates/tab2.html',
controller: 'Tab2Controller as tab2Ctrl',
//promise
resolve: {
lastName: function () { return 'makarov'}
}
});
Then in the controller
function Tab2Controller(lastName) {
console.log("Tab2", lastName);
}

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 + "!"
}; }, // ... })

Resources