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

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.

Related

RouteProvider uses resolve property from another route definition

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

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

AngularJS - Show route only after all promises are resolved

I try to load route only after promises are resolved
var app = angular.module("thethaoso", ["ngRoute"]);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider) {
$routeProvider
.when('/', {
resolve: {
message: function (repoService) {
return repoService.getMsg();
}
}
});
}]);
app.factory('repoService', function ($http) {
return {
getMsg: function () {
return "hihihi";
}
};
});
app.controller('teamLoadCtrl', function ($scope,message) {
$scope.message= message;
});
View:
<div ng-app='thethaoso' ng-controller='teamLoadCtrl'>
{{message}}
</div>
Always get the error Error: [$injector:unpr]http://errors.angularjs.org/1.3.7/$injector/unpr?p0=messageProvider%20%3C-%20message%20%3C-%20teamLoadCtrl
full code at http://jsfiddle.net/c0y38yp0/5/
Am I missing something ?
Thanks all.
The problem is that you have not specified a template and a controller to resolve the message object to. If you used the following syntax, it will work.
.when("/", {
templateUrl: "yourView.html",
controller: "yourController",
resolve: {
message: function(yourService){
return yourService.get();
}
}
Here is a working jsfiddle: http://jsfiddle.net/c0y38yp0/10/
You can also resolve the promise manually in your controller like so:
repoService.getMsg()
.then(function (msg) {
$scope.message = msg;
}
When the promise is resolved onto the scope as I did above, the ui will update. You can show a loading bar and use ng-hide to make the pages feel fluent while the loading occurs.
When you resolve, service have to return promise not value.
Here is example service
app.factory('repoService', function ($http, $q) {
var user = {};
var q = $q.defer();
$http.get('https://api.github.com/users/Serhioromano')
.success(function(json){
user = json;
q.resolve();
}).error(function(){
q.reject();
});
return {
promise: function() {
return q.promise;
},
get: function() {
return user;
}
};
});
The point here is that you return promise only. You handle how you save result. And then you can use this result like in get(). You know that by the time you call get() the user variable already set because promise was resolve.
Now in router.
app.controller('MainCtrl', function($scope, repoService) {
$scope.user = repoService.get();
});
app.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: '/view.html',
controller: 'MainCtrl',
resolve: {
message: function (repoService) {
return repoService.promise();
}
}
})
.otherwise({ redirectTo: '/' });
});
You return promise by repoService.promise()
In controller repoService.get() is triggered only after that promise resolved.
So you get your data.
Another thing in your code, you used ng-controller. But that thing is not binded to router and thus it avoid if it is resolved or not. You have to delete ng-controller and use controller router controller: 'MainCtrl',.
This affect your HTML
<body ng-app="myapp">
<ng-view></ng-view>
<script type="text/ng-template" id="/view.html">
<p>Hello {{user.name}}!</p>
</script>
<body>
You have to use <ng-view> to include subtemplate there and then in sub template you can use scope of the controller.
See plunker.
There are a few things wrong with the code you posted, in contrast to the code you are attempting to draw inspiration from.
When you resolve a route with the $routeProvider, the results are applied against an element <ng-view></ngview>, not a base element <div> as you have specified. Without the <ng-view> element, there is no way for the $routeProvider to bind the correct controller to the correct html fragment. Using ng-controller instantiates a controller instance when the dom element is rendered, and does not allow passing parameters to the controller as you have tried. Thus your resolution error due to an unknown message object. Effectively, message is not available outside the $routeProvider instance.

AngularAMD + ui-router + dynamic controller name?

I'm trying to write a generalized route in my application and resolve the view and controller names on the fly based on the route params.
I have the following code that works:
$stateProvider.state('default', angularAMD.route({
url: '/:module/:action?id',
templateUrl: function (params) {
var module = params.module;
var action = module + params.action.charAt(0).toUpperCase()
+ params.action.substr(1);
return 'app/views/' + module + '/' + action + 'View.html';
},
controller: 'userController',
}));
However, I'm unable to figure out a way to resolve the controller name dynamically. I tried using resolve as described here, but ui-router seems to handle resolve differently than angular-route.
Any pointers?
EDIT: I've already tried using controllerProvider but it doesn't work for me (for instance, the following code just returns a hard coded controller name to test whether it actually works):
controllerProvider: function () {
return 'userController';
}
Gives me the following error:
Error: [ng:areq] Argument 'userController' is not a function, got undefined
http://errors.angularjs.org/1.3.3/ng/areq?p0=userController&p1=not%20aNaNunction%2C%20got%20undefined
This is a link to working plunker.
solution
We need two features of the UI-Router:
resolve (to load the missing pieces of js code)
controllerProvider (see cites from documentation below)
angularAMD - main.js definition
This would be our main.js, which contains smart conversion controllerName - controllerPath:
require.config({
//baseUrl: "js/scripts",
baseUrl: "",
// alias libraries paths
paths: {
"angular": "angular",
"ui-router": "angular-ui-router",
"angularAMD": "angularAMD",
"DefaultCtrl": "Controller_Default",
"OtherCtrl": "Controller_Other",
},
shim: {
"angularAMD": ["angular"],
"ui-router": ["angular"],
},
deps: ['app']
});
controllers:
// Controller_Default.js
define(['app'], function (app) {
app.controller('DefaultCtrl', function ($scope) {
$scope.title = "from default";
});
});
// Controller_Other.js
define(['app'], function (app) {
app.controller('OtherCtrl', function ($scope) {
$scope.title = "from other";
});
});
app.js
Firstly we would need some method converting the param (e.g. id) into controller name. For our test purposes let's use this naive implementation:
var controllerNameByParams = function($stateParams)
{
// naive example of dynamic controller name mining
// from incoming state params
var controller = "OtherCtrl";
if ($stateParams.id === 1) {
controller = "DefaultCtrl";
}
return controller;
}
.state()
And that would be finally our state definition
$stateProvider
.state("default", angularAMD.route({
url: "/{id:int}",
templateProvider: function($stateParams)
{
if ($stateParams.id === 1)
{
return "<div>ONE - Hallo {{title}}</div>";
}
return "<div>TWO - Hallo {{title}}</div>";
},
resolve: {
loadController: ['$q', '$stateParams',
function ($q, $stateParams)
{
// get the controller name === here as a path to Controller_Name.js
// which is set in main.js path {}
var controllerName = controllerNameByParams($stateParams);
var deferred = $q.defer();
require([controllerName], function () { deferred.resolve(); });
return deferred.promise;
}]
},
controllerProvider: function ($stateParams)
{
// get the controller name === here as a dynamic controller Name
var controllerName = controllerNameByParams($stateParams);
return controllerName;
},
}));
Check it here, in this working example
documentation
As documented here: $stateProvider, for a state(name, stateConfig) we can use controller and controllerProvider. Some extract from documentation:
controllerProvider
...
controller (optional) stringfunction
Controller fn that should be associated with newly related scope or the name of a registered controller if passed as a string. Optionally, the ControllerAs may be declared here.
controller: "MyRegisteredController"
controller:
"MyRegisteredController as fooCtrl"}
controller: function($scope, MyService) {
$scope.data = MyService.getData(); }
controllerProvider (optional) function
Injectable provider function that returns the actual controller or string.
controllerProvider:
function(MyResolveData) {
if (MyResolveData.foo)
return "FooCtrl"
else if (MyResolveData.bar)
return "BarCtrl";
else return function($scope) {
$scope.baz = "Qux";
}
}
...
resolve
resolve (optional) object
An optional map<string, function> of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them ALL to be resolved before the controller is instantiated...
I.e. let's use controllerProvider:
... to resolve the controller name dynamically...
In case, that you managed to get here, maybe you'd like to check another similar solution with RequireJS - angular-ui-router with requirejs, lazy loading of controller

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