I'm trying to use angular-ui-router with angularAMD and there is one thing I cannot get working.
I have one abstract state (named "first") and another nested state (named "second") that inherits from "first" and that contains a views key with 2 views, each view having its own controller and template.
On angularAMD repo (github.com/marcoslin/angularAMD/issues/62), marcoslin show a way to do that, but I can't make it work, thus I think it's not exactly my situation :(
$stateProvider
.state('root', {
url: '',
abstract: true,
views: {
'header': angularAMD.route({
templateUrl: 'app/root/header.tpl.html',
controller: 'HeaderCtrl',
controllerUrl: 'HeaderCtrl'
}),
'footer': angularAMD.route({
templateUrl: 'app/root/footer.tpl.html',
controller:'FooterCtrl',
controllerUrl: 'FooterCtrl'
})
}
})
I was converting some big project in a lazy load way, so I thought the problem could come from a still not found side-effect. I made a simple plunker to check if it works better in a simpler environment, but it still doesn't work.
Essentially, my problematic code is this one:
$stateProvider
.state('first',
angularAMD.route({
abstract: true,
controllerUrl: 'first-controller',
templateUrl: 'first.tmpl.html',
controllerAs: 'first'
})
)
.state('first.second', {
url: '/introduction',
views: {
second1: angularAMD.route({
templateUrl: 'second1.tmpl.html',
controllerUrl: 'second1-controller',
controllerAs: 'second1'
}),
second2: angularAMD.route({
templateUrl: 'second2.tmpl.html',
controllerUrl: 'second2-controller',
controllerAs: 'second2'
})
}
});
Please note that I use the possibility provided by angularAMD to avoid using controller key in angularAMD.route() method. You can do this if your controller file return an anonymous function.
My full example can be found here: http://plnkr.co/edit/5WBtd3R7k20yRkMazIuk?p=preview
EDIT: I've forked my Plunker to show what happens when I try to use the more traditionnal angular syntax, without returning an anonymous function when loading my controllers.
So the controller looks like:
define(['app'], function (app) {
'use strict';
app.controller('Second1Controller', function () {
var vm = this;
vm.value = "Hello you ! I'm Number 2.1";
});
});
and the call to angularAMD in the ui-router is changed like this (notice the controller key):
.state('first.second', {
url: '/introduction',
views: {
second1: angularAMD.route({
templateUrl: 'second1.tmpl.html',
controllerAs: 'second1',
controllerUrl: 'second1-controller',
controller: 'Second1Controller'
}),
second2: angularAMD.route({
templateUrl: 'second2.tmpl.html',
controllerUrl: 'second2-controller',
controllerAs: 'second2',
controller: 'Second2Controller'
})
}
});
See the new Plunker here: http://plnkr.co/edit/kPFBo7ssAtqAuiKdwnQ9?p=preview
This time I get the usual error that fires when you have not loaded what you try to use:
Error: [ng:areq] Argument 'Second1Controller' is not a function, got undefined
END EDIT
Anybody to help me ? :)
Cheers !
Joel
angularAMD adds a promise to the ui-router native 'resolve' property of the state config object so that it looks somewhat like this:
$stateProvider.state('login', {
url : '/login',
controller : 'loginCtrl',
resolve : {
...
},
views : {
...
}
...
});
The promise is resolved when the script is loaded via RequireJS. Only after all promises of the resolve object are fulfilled will the controller be instantiated. This is great since it allows lazy loading of scripts.
The resolve property on the view is not interpreted by angular-ui-router. The JavaScript file containing the controller is not loaded at all. Angular can therefore not find the function ('Second1Controller' is not a function, got undefined).
While this works perfectly fine with regular (not nested) views, there are issues with nested views. The resolve object is not allowed on individual views but must be placed directly inside the state config (see Angular UI Router Docs). We thus somehow have to define the controllerUrls on the state config's highest level instead of on the individual view. This works with tiny changes in angularAMD and our config.
// angularAMD.js around line 146
if (load_controller) {
var resolve = config.resolve || {};
resolve['__AAMDCtrl'] = ['$q', '$rootScope', function ($q, $rootScope) { // jshint ignore:line
var defer = $q.defer();
// NOTE: It is now possible to load an array of controllerUrl's.
// Only the first array element will be injected as ctrl (room for improvement here)
require(load_controller instanceof Array ? load_controller : [load_controller], function (ctrl) {
defer.resolve(ctrl);
//console.log('loaded ' + load_controller);
$rootScope.$apply();
});
return defer.promise;
}];
config.resolve = resolve;
}
Our config must be changed, too:
$stateProvider.state('login', {
url : '/login',
controllerUrl : [
'template1Ctrl.js',
'template2Ctrl.js'
]
views : {
'viewContainer1' : {
templateUrl : 'template1.html',
controller : 'template1Ctrl'
},
'viewContainer2' : {
templateUrl : 'template2.html',
controller : 'template2Ctrl'
}
}
...
});