2

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

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

7 Comments

While combining logically-related components (controllers, directives, injectors, etc.) into a module within a single JS file makes sense, I don't think it solves my problem. If I do as you suggest above (I have tried it), I then get a similar error with the authentication service: Uncaught TypeError: Cannot read property 'service' of undefined. While I could also combine this into the loginModule JS file, the file would soon become very large.
I still don't get why 'app' is not defined within my AMD modules. I'm following this angularAMD example for the authentication service.
First of all, this doesn't have to be all in one file. You can still separate things and let requirejs load the files on demand. One just has to be careful putting things together. And of course, not everything should be put into the loginModule. Perhaps it makes sense to put your map service into a mapModule? I think restructuring your modules a little bit will solve your problem. Why is app not defined? Because requirejs first looks at the define. Then it will load these files and not before this it will start to run the code below the define line. That is the reason why app is not defined.
The load order: app.js: define([...'controller/login',... { var app = angular.module('cmsApp', ['ngRoute']); The define lets requirejs load the login controller: login.js: define(['app', 'directive/login_form'...], function (app) { The define lets requirejs load your login directive: login_form.js: define(['app'], function (app) { app.directive('loginForm', function (AUTH_EVENTS) { Now app is not defined, because the line var app = angular.module('cmsApp', ['ngRoute']); was not yet executed.
Please have a look at my updated answer. Using dynamically loaded controllers is perhaps the best solution for you.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.