简体   繁体   中英

AngularAMD + ui-router multiple views does not load controllers

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

How angularAMD Works

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.

Why the Error is Thrown

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).

How to Adapt to Multiple Views

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'
        }
    }
    ...
});

I was trying to use angularAMD a week or two ago for something similar, and figured out how to get it to load there. I went ahead and got your code working on plunkr.

define([
  'angular',
  'angularAMD',
  'angular-ui-router',
  'second1-controller',
  'second2-controller',
  'first-controller'
], function (angular, angularAMD) {
  'use strict';

  var app = angular.module('myApp', ['ui.router'])
    .config(function ($stateProvider, $urlRouterProvider) {
      $stateProvider
        .state('first',
            angularAMD.route({
                abstract: true,
                controllerUrl: 'first-controller',
                templateUrl: 'first.tmpl.html',
                controller: 'first-controller'
            })
        )
        .state('first.second', {
          url: '/introduction',
            views: {
                second1: angularAMD.route({
                    templateUrl: 'second1.tmpl.html',
                    controllerUrl: 'second1-controller',
                    controller: 'second1-controller'
                }),
                second2: angularAMD.route({
                    templateUrl: 'second2.tmpl.html',
                    controllerUrl: 'second2-controller',
                    controller: 'second2-controller'
                })
            }
        });

      $urlRouterProvider.when('', '/introduction');
      $urlRouterProvider.when('/', '/introduction');
    });

    return angularAMD.bootstrap(app);
});

and setup the controllers as such:

define(['angularAMD'], function (angularAMD) {
  'use strict';
  angularAMD.controller('first-controller',['$scope',function($scope){
    $scope.foo='bar';
  }]);
});

, http://plnkr.co/edit/bvaNDXhhelmo0smEDPWh?p=preview

FYI with AngularAMD and using angular-ui-router (for those who may hit this via google trying to solve a problem - like me)

I found that angular-ui-router Version v0.2.15 - does NOT work as intended with angularAMD

When I downgraded to this version : //cdn.jsdelivr.net/angular.ui-router/0.2.12/angular-ui-router.min

Version 0.2.12 worked just fine and all that was lost was a good deal of sanity and time. BUT - still have to appreciate other people's work they give away for free.

(I do not know however if you will run into other issues with .2.12 - just know my app is routing and I'm not getting a "'..Controller' function, is undefined" error any longer)

I have an open issue on github: https://github.com/marcoslin/angularAMD/issues/154

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM