Search code examples
angularjsrequirejsangular-amd

Using AngularJS with RequireJS via AngularAMD: Cannot read property 'directive' of undefined


Having created a very basic prototype AngularJS project, I wanted to migrate it to use RequireJS to load the modules. I modified my app based on the AngularAMD and AngularAMD-sample projects.

Now, when I access my default route I get:

Uncaught TypeError: Cannot read property 'directive' of undefined

I've been scratching my head as to why the dependency on 'app' is not being satisfied. If anyone can spot what I'm obviously doing wrong, it'd be much appreciated.

I've put the source code of my project here on GitHub, but here's the key parts:

main.js

require.config({

    baseUrl: "js/",

  // alias libraries paths
    paths: {
        'angular': '../bower_components/angular/angular',
        'angular-route': '../bower_components/angular-route/angular-route',
        'angular-resource': '../bower_components/angular-resource/angular-resource',
        'angularAMD': '../bower_components/angularAMD/angularAMD',
        'ngload': '../bower_components/angularAMD/ngload',
        'jquery': '../bower_components/jquery/jquery'

    },

    // Add angular modules that does not support AMD out of the box, put it in a shim
    shim: {
        'angularAMD': ['angular'],
        'ngload': [ 'angularAMD' ],
        'angular-route': ['angular'],
        'angular-resource': ['angular']
    },

    // kick start application
    deps: ['app']
});

app.js

define(['angularAMD', 'angular-route', 'controller/login', 'controller/project_detail', 'controller/project_list'], function (angularAMD) {
  'use strict';

  var app = angular.module('cmsApp', ['ngRoute']);

  app.constant('REMOTE_BASE_URL', "/cms/v2/remote");

  app.constant('SERVER_ERROR_TYPES', {
    authentication: 'Authentication',
    application: 'Application',
    transport: 'Transport'
  });

  app.constant('AUTH_ERROR_TYPES', {
    invalidLogin: "INVALID_CREDENTIALS",
    invalidToken: "INVALID_TOKEN",
    noToken: "NO_TOKEN"
  });

  app.constant('AUTH_EVENTS', {
    loginSuccess: 'auth-login-success',
    loginFailed: 'auth-login-failed',
    logoutSuccess: 'auth-logout-success',
    notAuthenticated: 'auth-not-authenticated'
  });

  app.config(['$routeProvider',
    function($routeProvider) {
      $routeProvider.
        when('/login', {
          templateUrl: 'partials/login.html',
          controller: 'LoginCtrl'
        }).
        when('/projects', {
          templateUrl: 'partials/project-list.html',
          controller: 'ProjectListCtrl'
        }).
        when('/projects/:projectId', {
          templateUrl: 'partials/project-detail.html',
          controller: 'ProjectDetailCtrl'
        }).
        otherwise({
          redirectTo: '/projects'
        });
    }]);

  return angularAMD.bootstrap(app);
});

And the file which the exception is being raised in: login_form.js

define(['app'], function (app) {
   app.directive('loginForm', function (AUTH_EVENTS) {
      return {
        restrict: 'A',
        template: '<div ng-if="visible" ng-include="\'partials/login.html\'">',
        link: function (scope) {
          scope.visible = false;

          scope.$on(AUTH_EVENTS.notAuthenticated, function () {
            scope.visible = true;
          });

          scope.$on(AUTH_EVENTS.loginFailed, function () {
            alert("An error occured while trying to login. Please try again.")
            scope.visible = true;        
          });

          scope.$on(AUTH_EVENTS.logoutSuccess, function () {
            scope.visible = true;
          });
        }
      };
    });
});

Solution

  • You are loading 'controller/login' before the app itself was created.

    Probably it is better to create a separate module like

    define(['directive/login_form', 'service/authentication'], function () {
       'use strict';
       var loginModule = angular.module('loginModule', []);
       loginModule.controller('LoginCtrl', ...
       loginModule.directive('loginForm', ...
    

    and then do something like

       var app = angular.module('cmsApp', ['ngRoute', 'loginModule']);
    

    Does that make sense?

    UPDATE:

    I am just thinking of another solution. Just remove 'controller/login' from your app define. Using angularAMD your controller should not be loaded anyway before you navigate to the specified url. Just remove it and your controller gets loaded on demand. That way, app will be defined! (Although I would still suggest to create multiple modules. It feels better to not have everything in the app module but have multiple modules for different responsibilities. Also much better for testing.)

    angularAMD.route({
        templateUrl: 'views/home.html',
        controller: 'HomeController',
        controllerUrl: 'scripts/controller'
    })
    

    Note the field controllerUrl.

    Have a look here.