RouteProvider uses resolve property from another route definition - angularjs

I've got stuck with the following route configuration:
app.config(function ($routeProvider) {
$routeProvider
.when('/common/query/:query', {
templateUrl: 'common.html',
controller: 'UsualResultsController',
resolve: {
UsualResults: usualCntrl.performSearch
}
})
.when('/people/query/:query', {
template: 'people.html',
controller: 'PeopleResultsController',
resolve: {
PeopleResults: peopleCntrl.performSearch
}
})
.when('/people/query/:query/department/:department', {
template: people.html',
controller: 'PeopleResultsController',
resolve: {
PeopleResults: peopleCntrl.performSearch
}
})
.otherwise({
redirectTo: '/'
});
});
and it appears that every route switching is resolved with the 'resolve' object from the last definition.
Here is a simplified plunkr.
Actually, I understand that that in routing order matters and all regex paths should be defined after static paths, but couldn't apply it to my situation.
Moreover, I suppose that the last two route definitions could be combined into one, but again I couldn't grasp, how to achieve it.
I'll be grateful for your help, guys!

app.controller does not return the controller, but the module (same as angular.module).
In your example the following:
peopleCntrl.performSearch = function($timeout, $q) { ... };
Will replace the the function defined by:
usualCntrl.performSearch = function($timeout, $q) { ... };
Since both peopleCntrl and usualCntrl refer to the same module object.
While probably not the cleanest solution, it will work if you don't use the same name for the functions.
Another solution would be the following:
var usualCntrl = function UsualResultsController($scope) { ... };
app.controller('UsualResultsController', ['$scope', usualCntrl]);
usualCntrl.performSearch = function($timeout, $q) { ... };
Demo: http://plnkr.co/edit/jx6HYDXggsaq3qMOdF8c?p=preview

Related

angular-ui: how to get data from "resolve" option when using $stateProvider.state

The docs and stuff I've seen show how to use the "resolve" option but how do you get the data back?
I'm using meanjs.org v0.3.x
My route:
//Setting up route
angular.module('mymodule').config(['$stateProvider',
function($stateProvider) {
//start routing
$stateProvider.
state('some-path', {
url: '/some-url',
templateUrl: 'modules/mymodule/views/my-controller.client.view.html',
resolve: {
DataService: 'DataService',
promiseToResolve: function(DataService){
return DataService.get({});
}
}
});
}
]);
Now how exactly to I access the "promiseToResolve" data in my controller? Docs don't seem to mention this.
Also, please let me know if the above code would break when it is minified.
angular.module('mymodule').config(['$stateProvider',
function($stateProvider) {
//start routing
$stateProvider.
state('some-path', {
url: '/some-url',
templateUrl: 'modules/mymodule/views/my-controller.client.view.html',
controller: mySpecialController,
resolve: {
promiseToResolve: ['DataService', function(DataService) {
return DataService.get({});
}]
}
});
}
]);
You don't need the specific DataService line, so it's removed, and now in your controller (which you've specified in the above code) you can directly access the resolved data:
angular.module('mymodule').controller('mySpecialController', ['$scope', 'promiseToResolve',
function ($scope, promiseToResolve) {
$scope.dataResults = promiseToResolve.data;
}
]);
As for minification, here we use ngInject which is great.

ui-router resolve is not working with the index page controller

I want to resolve some value before I load the first page of my application, but it kept telling me
Unknown provider: programClassSummaryProvider <- programClassSummary <- HomeCtrl
I pretty sure I did it correctly, because I did the same thing for any other controller and routing. but it is not working for my homepage controller.
It seems like it load the controller first, before it is resolved in the routing. Anything wrong with my code?
In routing.js
$stateProvider
.state('home', {
url: '/home',
controller: 'HomeCtrl',
controllerAs: 'vm',
templateUrl: 'index_main.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
ncyBreadcrumb: {
skip: true
}
});
in controller.js
angular
.module('issMccApp')
.controller('HomeCtrl', homeCtrl);
homeCtrl.$inject = ['$scope', '$location', '$state', '$auth', 'programClassSummary'];
/* #ngInject */
function homeCtrl($scope, $location, $state, $auth, programClassSummary) {
var vm = this;
vm.isAuthenticated = isAuthenticated;
vm.programClassSummary = programClassSummary;
if (!$auth.isAuthenticated()) {
$state.go('login');
return;
}
function isAuthenticated() {
return $auth.isAuthenticated();
}
}
in factory.js
function getProgramClassSummary(showAll) {
var query = "";
if (showAll)
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary?all=1';
else
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary';
return $http.get(query)
.success(function (result) {
return result;
})
.error(function (err) {
return err;
})
}
I'd say, we really have to distinguish the UI-Router state world, and angular itself. Reason why is clearly defined here (extracted $resolve from UI-Router API documentation):
$resolve
resolve(invocables, locals, parent, self)
Resolves a set of invocables. An invocable is a function to be invoked via $injector.invoke(), and can have an arbitrary number of dependencies. An invocable can either return a value directly, or a $q promise. If a promise is returned it will be resolved and the resulting value will be used instead. Dependencies of invocables are resolved (in this order of precedence)
from the specified locals
from another invocable that is part of this $resolve call
from an invocable that is inherited from a parent call to $resolve (or recursively
from any ancestor $resolve of that parent).
There is a wroking plunker, which uses this index.html
<body ng-controller="RootCtrl">
a summary for a root:
<pre>{{summary}}</pre>
<ul>
<li>home
<li>other
</ul>
<div ui-view=""></div>
So, here we use some RootCtrl, which won't go through state machine UI-Router, it is angular basic stuff
The root controller must be defined as
.controller('RootCtrl', ['$scope', 'GroupDataFactory', function ($scope, groupDf) {
$scope.summary = groupDf.getProgramClassSummary();
}])
For a state home, we can use different approach, in fact the same as above (simplifed version below)
.state('home', {
url: "/home",
templateUrl: 'tpl.home.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
controller: 'HomeCtrl',
})
And its controller is now able to consume the locals
.controller('HomeCtrl', ['$scope', 'programClassSummary', function ($scope, summary) {
$scope.summaryForHome = summary;
}])
Check it in action here

Controlling multiple views in one controller in AngularJS

Following the MVC pattern, one controller should be able to handle multiple views in AngularJS.
E.g. I have one view for showing all users and one for creating a new user. My $routeProvider (excerpt) looks like this:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/showusers', {
templateUrl: 'partials/showusers.html',
controller: 'userController'
}).
when('/createuser', {
templateUrl: 'partials/showusers.html',
controller: 'userController'
})
}]);
Both views share several methods such as retrieving all user attributes. These shared methods are accessed through a factory.
The userController (excerpt) currently looks like this where Users is a factory:
angular.module('supportModule').
controller('userController', function($scope, Users) {
var init = function(){
$scope.users = Users.getAll();
$scope.attributes = Users.getAttributes();
}
init();
})
The problem is: I don't need to request Users.getAll(); when I'm on the createuser view.
Of course, I could easily parse the route by using $location, the $routeProvider or pure JS and then conditonally call methods - but I figure there is a more elegant and efficient way in Angular :)
Something like in typical MVC frameworks where one controller has many actions - one for each view.
So my question:
How to elegantly call methods based on the view in a controller which controls more than one view?
You could use resolve: when setup $routeProvider to pass values to a controller depending on the matched route.
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/showusers', {
templateUrl: 'partials/showusers.html',
controller: 'userController',
resolve: {
ctrlOptions: function () {
return {
getAllUsers: true,
};
}
}
}).
when('/createuser', {
templateUrl: 'partials/showusers.html',
controller: 'userController',
resolve: {
ctrlOptions: function () {
return {
getAllUsers: false,
};
}
}
})
}]);
and then in the controller, you can inject item specified in resolve: like this:
app.controller('userController', function ($scope, Users, ctrlOptions) {
var init = function () {
if (ctrlOptions.getAllUsers) {
$scope.users = Users.getAll();
}
$scope.attributes = Users.getAttributes();
};
init();
});
Hope this helps.

properties from resolve object not passing to controller, unknown provider error

Before voting this down, yes this question has been asked here already, and answered, but the answers are not satisfactory. They all correctly suggest to add the controller in the route configuration, but this is not the case here.
The expected behavior for the routeProvider's resolve object xxx is to be injected into the controller:
var app = angular.module('X', [])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/',{
controller:'XCtrl',
templateUrl: 'x.html',
resolve: {
xxx: function () {
return 'XXX from routing config.';
}
}
})
}])
.controller('XCtrl', function($scope, xxx) {
console.log('xxx = '+xxx);
});
The console should get a xxx = XXX from routing config. entry.
Instead, the code extract above fails with:
Error: [$injector:unpr] Unknown provider: xxxProvider <- xxx
http://errors.angularjs.org/1.2.10/$injector/unpr?p0=xxxProvider%20%3C-%20xxx
.. etc.
XCtrl is not declared in the HTML with a ng-controller directive but only defined in the routing configuration.
If you have several entry in the routing configuration using the same controller, all properties that are injected in the controller must appear in all instances of the resolve object:
var app = angular.module('X', [])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/',{
controller:'XCtrl',
templateUrl: 'x.html',
resolve: {
xxx: function () {
return 'XXX from routing config.';
}
another: // ...
}
})
.when('/page2',{
controller:'XCtrl',
templateUrl: 'x/p2.html',
resolve: {
xxx: function () {
return 'XXX from routing config.';
}
}
})
}])
.controller('XCtrl', function($scope, xxx) {
console.log('xxx = '+xxx);
});
The resolve objects don't have to be promises, straight data returning from a function as above also works. Promises are useful to prevent the routing if one of them is rejected.

Using resolve in $routeProvider causes 'Unknown provider ...'

I am trying to do an asynchronous http request to load some data before my app loads and so I am using a resolve in $routeProvider which is an http request in my MainController. For some reason, I keep getting Error: [$injector:unpr] Unknown provider: appDataProvider <- appData where appData is where I do my http request. I am using AngularJS v 1.2.5.
Here is the code and two methods that I tried that both give the same error:
Method #1
MainController.js
var MainController = ['$scope','$location','appData',
function($scope, $location, appData){
console.log(appData.data);
}
];
MainController.loadData = {
appData: function($http, $location, MainFactory){
var aid = MainFactory.extractAid($location);
return $http({method: 'GET', url: URL_CONST + aid});
}
};
app.js
var app = angular.module('HAY', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/', {
redirectTo: '/pages/alerts'
})
.when('/pages/:pageName', {
templateUrl: function(params) {
return 'views/pages/' + params.pageName + '.html';
},
controller: MainController,
resolve: MainController.loadData
})
.otherwise({
redirectTo: '/pages/alerts'
});
});
I tried changing the name in case it was a conflicting system reserved keyword but with no luck. For some reason, appData is never recognized
Method #2
I also tried changing it around like so:
app.js
var app = angular.module('HEY', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/', {
redirectTo: '/pages/alerts'
})
.when('/pages/:pageName', {
templateUrl: function(params) {
return 'views/pages/' + params.pageName + '.html';
},
controller: MainController,
resolve: {
appData: ['$http', '$location','MainFactory', function($http, $location, MainFactory) {
var aid = MainFactory.extractAid($location);
return $http({method: 'GET', url: URL_CONST + aid});
}]
}
})
.otherwise({
redirectTo: '/pages/alerts'
});
});
MainController.js
var MainController = ['$scope','$location','appData',
function($scope, $location, appData){
console.log(resolvedData);
}
];
However, the result was exactly the same. Does this have something to do with angular 1.2.5 ?
Here is a working version from someone else
http://mhevery.github.io/angular-phonecat/app/#/phones
And here is the code:
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone) {
return Phone.query();
},
delay: function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
}
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl, resolve: PhoneListCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
Here's an example of the code I've used in the application I'm working on, not sure it will help much because its not much different than how you have it already.
Routing
.when('/view/proposal/:id',{
controller : 'viewProposalCtrl',
templateURL : 'tmpls/get/proposal/view',
resolve : viewProposalCtrl.resolveViewProposal
})
Controller
var viewProposalCtrl = angular.module('proposal.controllers')
.controller('viewProposalCtrl',['$scope','contacts','details','rationale',
function($scope,contacts,details,rationale){
$scope.contacts = contacts;
$scope.details = details;
$scope.rationale = rationale;
// [ REST OF CONTROLLER CODE ]
});
// proposalSrv is a factory service
viewProposalCtrl.resolveViewProposal = {
contacts : ['$route','proposalSrv',function($route,proposalSrv){
return proposalSrv.get('Contacts',$route.current.params.id)
.then(function(data){
return data.data.contacts;
},function(){
return [];
});
}],
details : ['$route','proposalSrv',function($route,proposalSrv){
return proposalSrv.get('Details',$route.current.params.id)
.then(function(data){
return data.data.details;
},function(){
return {};
});
}],
rationale : ['$route','proposalSrv',function($route,proposalSrv){
return proposalSrv.get('Rationale',$route.current.params.id)
.then(function(data){
return data.data.rationale;
},function(){
return {};
]
}]
};
Now that I think about it, when I was developing my application I did have a problem and not sure why when I named my resolve function "resolve." This gave me a problem:
.when('/path',{
// stuff here
resolve : myCtrlr.resolve
})
but this did not:
.when('/path',{
//stuff here
resolve : myCtrlr.myResolveFn
})
Another Possibility
The only other thing I can think of, is that you're returning the promise from the $http call and then trying to use appData.data Try using the .then function or one of the other functions (.success,.error) to retrieve the information from the promise.
The problem was NOT due to previously using different version of AngularJS.
Here are the fixes using the code that I have above.
In app.js, you need to declare the controller as controller: 'MainController' and NOT as controller: MainController even though you have var MainController = app.controller('MainController', ....).
Second and biggest thing was that in my index.html I declared my controller already like so:
index.html
body ng-app="HEY" controller="MainController" /body
This was causing the whole Unknown provider error Apparently angular wont tell you that you have already declared the controller that you are using to do the resolve it and that that will cause a weird error that have nothing to do with the resolve.
I hope this helps someone who may have the same problem.
One thing I noticed in angular 1x docs is that YOU DO NOT SPECIFY THE RESOLVED PARAMETER AS AN ANNOTATED DEPENDENCY
So this:
.when('/somewhere', {
template: '<some-component></some-component>',
resolve: {
resolvedFromRouter: () => someService.fetch()
}
})
export default [
'$scope',
'someService',
'resolvedFromRouter'
Controller
]
function Controller($scope, someService, resolvedFromRouter) {
// <= unknown provider "resolvedFromRouter"
}
is wrong. You don't specify the resolved parameter as a dependency, in the docs:
For easier access to the resolved dependencies from the template, the resolve map will be available on the scope of the route, under $resolve (by default) or a custom name specified by the resolveAs property (see below). This can be particularly useful, when working with components as route templates.
So just do this instead:
.when('/somewhere', {
template: '<some-component></some-component>',
resolve: {
resolvedFromRouter: () => someService.fetch()
}
})
export default [
'$scope',
'someService',
Controller
]
function Controller($scope, someService) {
$scope.$resolve.resolvedFromRouter; // <= injected here
}

Resources