RequireJs Angular execution order of dependencies - angularjs

I recently converted my project from bootstrapping angular through the ng-app default method to using RequireJs and doing so manually. Everything works fine most of the time, but some percentage of the time I get errors saying my controllers and services of the login page are undefined, so the controller doesn't properly load and values aren't bound.
I believe the problem stems from the order of execution of dependencies. First, here are the errors:
Error: [ng:areq] http://errors.angularjs.org/1.3.15/ng/areq?p0=NavController&p1=not%20aNaNunction%2C%20got%20undefined
Error: [$injector:unpr] http://errors.angularjs.org/1.3.15/$injector/unpr?p0=UsersServiceProvider%20%3C-%20UsersService%20%3C-%20LoginController
So far I am only seeing this problem in Chrome, and when I have the javascript console open everything seems to work. I have inserted a few console.log() statements to get some visibility into the order of activity within these scripts. For three files, routeConfig.js, nav.js and usersService.js I have a console.log on the first line within the define function and another one on the first line of the angular definition function:
routeConfig.js:
define(['angularAMD',
'angular-route',
'uiBootstrap',
'lib/angularSlideables',
'lib/ng-infinite-scroll',
'lib/angular-modal-service'], function (angularAMD) {
console.log('top of routeConfig');
var app = angular.module('svlPlatform', ['ngRoute', 'ui.bootstrap', 'angularSlideables', 'infinite-scroll', 'angularModalService']).value('THROTTLE_MILLISECONDS', 750);
app.config(['$routeProvider', function ($routeProvider, $httpProvider) {
console.log('inside routeConfig');
// Because angularAMD is built on requireJS, the following paths to controllerUrl follow requireJs's conventions.
// They don't need a .js extention and they are relative to the baseUrl specified in require.config.
$routeProvider
.when('/login', angularAMD.route({
templateUrl: 'views/login.html',
controllerUrl: 'controllers/login'
}))
.when('/me', angularAMD.route({
templateUrl: 'views/me.html',
controllerUrl: 'controllers/me'
})) //this continues and returns app...
nav.js:
define(['config/routeConfig', 'services/authService', 'services/usersService', 'services/menuService'], function(app) {
console.log('top of nav controller');
app.controller('NavController', ['$log', '$anchorScroll', '$location', '$rootScope', 'AuthService', 'UsersService', 'MenuService',
function ($log, $anchorScroll, $location, $rootScope, auth, users, menu) {
console.log('inside nav controller');
var vm = this;
menuScope = this;
function loadMenu() {
menu.items().then(function(response){
vm.menuItems = response.data.menuItems;
$('body').css("background-color","#f2f2f2");
markMenuActive(vm.menuItems);
}, function(error) {
console.log('There was a problem getting the menu data.');
});
} //continues...
usersService.js
define(['config/routeConfig', 'utils/urlBuilder', 'services/menuService'], function (app, _urlBuilder) {
console.log('top of users service');
var urlBuilder = _urlBuilder;
app.factory('UsersService', ['$http', 'MenuService', function ($http, menu) {
console.log('inside users service');
var meCache;
var refreshMeCache = function () {
meCache = $http.get('/api/v1/users/me');
menu.refreshItemsCache();
}; //continues...
Here is my requireJs config:
require.config({
baseUrl: 'javascripts',
paths: {
'angular': '//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min',
'angular-route': '//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.min',
'bootstrapJs': '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min',
'uiBootstrap': '//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.1/ui-bootstrap-tpls.min',
'angularAMD': '//cdn.jsdelivr.net/angular.amd/0.2.0/angularAMD.min'
},
shim: {
'angularAMD': ['angular'],
'bootstrapJs': [],
'uiBootstrap': ['angular'],
'lib/angularSlideables': ['angular'],
'lib/ng-infinite-scroll': ['angular'],
'angular-route': ['angular'],
'lib/angular-modal-service': ['angular']
},
deps: [
'bootstrapJs',
'config/routeConfig',
'services/authService',
'services/usersService',
'services/menuService',
'controllers/nav',
'controllers/main',
'lib/angular-modal-service',
'config/config',
'app'
]
});
When the login page (the first page the app loads) is loaded correctly, i see this after I open the console:
top of routeConfig
top of users service
top of nav controller
inside routeConfig
inside users service
inside nav controller
but when the page doesn't load properly, this is what i'm seeing in the console.
top of routeConfig
inside routeConfig
top of users service
top of nav controller
...then errors
I don't understand the bootstrapping process or requireJs' load process enough to figure out why this is happening and how to fix it. Thank you to anyone who has any ideas. Thanks.

Perhaps this is the best way to solve this problem, perhaps it is not. I gave up trying to use require to specify any kind of load order in the require config or using any of the module definitions. I forced it by having several of my controller and service modules return the app, and have them dependent in series rather than in parallel, this forces require to run the scripts in the order I specify.
So, rather than have nav.js and usersService.js dependent on routeConfig.js, which provides the app object for both of those components, I have the service dependent on routeConfig.js, and the nav.js module dependent on the usersService (which now returns its app object). This method could continue to include other controllers and services which need to be loaded before bootstrapping. I then finally call another file, app.js, which calls the angular.bootstrap() (angularAMD.bootstrap() in my case), and everything works now.

Related

Issue while running browserify on a simple angular app

I'm making this very preliminary attempt of using node/npm/browserify to build my angular app. The ./app/controllers, ./app/directives, ./app/services basically have index.js files which in turn require() the js files! Below is the root js file i.e. public/index.js.
require('./app/controllers/');
require('./app/directives/');
require('./app/services/');
var app = angular.module('myApp', ['ngRoute'])
app.config(function($routeProvider) {
$routeProvider
.when("/movie/:movieId", {
template: require('./views/movie.html'),
controller: 'MovieCtrl as movie'
})
.when("/movie/:movieId/scene/:sceneId", {
template: require('./views/scene.html'),
controller: 'SceneCtrl as scene'
});
});
module.exports = app;
Now after running below command i do get a bundle.js however,
browserify public/index.js -o release/bundle.js
However, the below line in bundle.js throws the error "Uncaught ReferenceError: app is not defined"
app.controller('MainCtrl', function ($route, $routeParams, $location, MyFactory) {
Now, i was assuming because var app is specified in index.js it should be accessible in MainCtrl.js. Could someone suggest how i could make this work?
----- Adding some more info ------
app/controllers/index.js contains below code:-
require('./MainCtrl.js')
require('./MovieCtrl.js')
require('./SceneCtrl.js')
And MainCtrl.js contains below code:-
app.controller('MainCtrl', function ($route, $routeParams, $location, MyFactory) {
//...
})
I don't know where the line in your code is... it isn't clear from the question, but anyway:
Now, i was assuming because var app is specified in index.js it should be accessible in MainCtrl.js.
That assumption is false. You will need to pass in a reference to whatever you need when you instantiate whatever you included.
For example..
var mainCtrl = new MainCtrl(app);
Ok, so i kind of understood what was going on. var app is local and cannot be accessible anywhere else. Once i set app to the global scope (which is obviously a horrible thing!) and required files after declaring app, it worked. This is mostly not the correct way of doing it, but as i mentioned this was a very preliminary attempt.
app = angular.module('myApp', ['ngRoute'])
app.config(function($routeProvider) {
$routeProvider
.when("/movie/:movieId", {
template: require('./views/movie.html'),
controller: 'MovieCtrl as movie'
})
.when("/movie/:movieId/scene/:sceneId", {
template: require('./views/scene.html'),
controller: 'SceneCtrl as scene'
});
});
require('./app/controllers/');
require('./app/directives/');
require('./app/services/');

Using ui-router and ocLazyLoad to load a controller and set the partial to it

I'm a complete Angular noob and trying to do some fancy stuff quickly, so forgive me if this is a dumb question.
I've created a website that uses routing, and I'm using ui-router for the routing instead of the standard Angular router. The theory is still the same - I have an index.html page in the root of my website which is the "master" or "host" page, and loginView.htm, which is a partial, exists in a separate directory.
The mainController for the project is loaded in the index.html page. Referencing this controller does NOT cause an error or problem.
What I'd like to do, in order to keep code manageable and small, is have the custom controller for a partial page lazy load when I load the partial, and then associate that partial page with the newly loaded controller. Makes sense, right? I don't want to load all the controllers by default, because that's a waste of time and space.
So my structure looks like this (if it matters to anyone):
Root
--app/
----admin/
------login/
--------loginView.html
--------loginController.js
--mainController.js
index.html
This is my loginController code. For testing purposes, I have made the mainController code match this exactly.
var loginController = function ($scope, $translate) {
$scope.changeLanguage = function (key) {$translate.use(key); };
};
angular.module('app').controller('loginController', loginController);
Finally, here is my routing code:
function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$urlRouterProvider.otherwise("/admin/login");
$stateProvider
.state('login', {
url: "/admin/login",
templateUrl: "app/admin/login/loginView.html",
controller: loginController,
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load([
{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}
]);
}
}
})
;
}
angular
.module('app')
.config(config)
.run(function ($rootScope, $state) {
$rootScope.$state = $state;
});
Now - if I remove the whole "resolve" section, and change the controller to "mainController", everything works. The page loads, the buttons work, they call the "changeLanguage" function and everything is wonderful.
But I want the "changeLanguage" feature to reside in the loginController because that's the only page that uses it. So when the code looks like it does above, an error fires("Uncaught Error: [$injector:modulerr]") and the partial page fails to load.
I don't understand what I'm doing wrong, and I'm not finding what I need via Google (maybe I just don't know the right question to ask).
Help?
Looking through the docs I cannot find the name property for ocLazyLoad#load.
Try the following:
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load(['app/admin/login/loginController.js']);
}
}
Or, pre configure it in a config block:
app.config(function ($ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
modules: [{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}]
});
});
// then load it as:
$ocLazyLoad.load('loginController');

How to push configuration from one module to another module in AngularJS

I'm looking for a way to "send" or "register" some configuration from several modules to one single module.
The idea is that different modules will have theirs own route configuration. And one single module controller will build the menu based on those configurations.
I want to "send" or "register" configuration instead of querying, because the menu controller cannot knows which modules are available.
Here is a solution to my need:
Each module defines its own routes using $routeProvider with a custom parameter defining if the route should be displayed in the menu and another custom parameter with the name to display. (See above Module A example).
Then the menu controller will loop the $route.routes variable (which contains all defined routes) to build de menu. (See above Module B example).
Module A:
angular
.module('module-a', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/path-a', {
controller: 'ModuleAController',
templateUrl: 'path/to/template.html',
menuDisplay: true, // Custom parameter
displayName: 'Solution Manager' // Custom parameter
});
});
Module B:
angular
.module('module-b', [])
.controller('MenuController', ['$scope', '$route', function ($scope, $route) {
var entries = [];
angular.forEach($route.routes, function (route, path) {
if ( route.menuDisplay == true ) {
this.push({
displayName: route.displayName,
route: '#' + path
});
}
}, entries);
$scope.menu = entries;
}]);
You will probably add some more checks to ensure displayName is provided, etc ...

AngularJS Dynamic loading a controller

I read a lot about lazzy loading, but I am facing a problem when using $routeProvider.
My goal is to load a javascript file which contains a controller and add a route to this controller which has been loaded previously.
Content of my javascript file to load
angular.module('demoApp.modules').controller('MouseTestCtrlA', ['$scope', function ($scope) {
console.log("MouseTestCtrlA");
$scope.name = "MouseTestCtrlA";
}]);
This file is not included when angular bootstap is called. It means, I have to load the file and create a route to this controller.
First, I started writing a resolve function which has to load the Javascript file. But declaring my controller parameter in route declaration gave me an error :
'MouseTestCtrlA' is not a function, got undefined
Here is the call I am trying to set :
demoApp.routeProvider.when(module.action, {templateUrl: module.template, controller: module.controller, resolve : {deps: function() /*load JS file*/} });
From what I read, the controller parameter should be a registered controller
controller – {(string|function()=} – Controller fn that should be associated with newly created scope or the name of a registered controller if passed as a string.
So I write a factory which should be able to load my file and then (promise style!) I whould try to declare a new route.
It gave me something like below where dependencies is an array of javascript files' paths to load :
Usage
ScriptLoader.load(module.dependencies).then(function () {
demoApp.routeProvider.when(module.action, {templateUrl: 'my-template', controller: module.controller});
});
Script loader
angular.module('demoApp.services').factory('ScriptLoader', ['$q', '$rootScope', function ($q, $rootScope) {
return {
load: function (dependencies)
{
var deferred = $q.defer();
require(dependencies, function () {
$rootScope.$apply(function () {
deferred.resolve();
});
});
return deferred.promise;
}
}
}]);
Problem
I still have this javascript error "'MouseTestCtrlA' is not a function, got undefined" which means Angular could not resolved 'MouseTestCtrlA' as a registered controller.
Can anyone help me on this point please ?
Re-reading this article http://ify.io/lazy-loading-in-angularjs/ suggested to keep a $contentProvider instance inside Angular App.
I came up with this code in my app.js
demoApp.config(function ($controllerProvider) {
demoApp.controller = $controllerProvider.register;
});
It enables me to write my controller as expected in a external javascript file :
angular.module("demoApp").controller('MouseTestCtrlA', fn);
Hope this can help !

How do I test controllers with Angular Translate initialized in App Config?

I have an app that uses Angular Translate (https://github.com/PascalPrecht/angular-translate). Translate works great in the application via browser but when I try to test any controller I get Error: Unexpected request: GET locale/locale-en.json. How do I unit test my controllers since translate does a GET request for the language file on startup?
I am using the yeoman angular generator with Karma.
App.js:
angular.module('myApp', ['ngCookies', 'ui.bootstrap', 'pascalprecht.translate'])
.config(function ($routeProvider, $locationProvider, $translateProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.otherwise({
redirectTo: '/'
});
$translateProvider.useStaticFilesLoader({
prefix: 'locale/locale-',
suffix: '.json'
});
$translateProvider.uses('en');
$translateProvider.useLocalStorage();
});
Controller Test:
describe('Controller: DocumentationCtrl', function () {
// load the controller's module
beforeEach(module('myApp'));
var DocumentationCtrl,
scope,
$httpBackend;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
DocumentationCtrl = $controller('DocumentationCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function () {
$httpBackend.whenGET('locale/locale-en.json').respond(200, {
"TITLE": 'My App'
});
expect(scope.awesomeThings.length).toBe(3);
});
});
The Documentation Controller is just a standard generated controller.
You have to specify the preferred language within config phase rather then run phase. So $translate.uses('us') becomes $translateProvider.preferredLanguage('us').
Same goes for useLocalStorage(). These are all methods to configure $translate service.
You should also try to avoid uses() to set a default language. Use preferredLanguage() instead. The reason for this is, that $translate.uses() tries to load a i18n file as soon as possible,
if there's a cookie or similar which uses another language key, uses() will load two files, that why we introduced preferredLanguage() And yea, this should solve the problem.
Avoid initialize application level module, put your controllers in myApp.controllers and test this module instead.
"We recommend that you break your application to multiple modules. (...) The reason for this breakup is that in your tests, it is often necessary to ignore the initialization code, which tends to be difficult to test."
http://docs.angularjs.org/guide/module
I think you have a wrong order: The setup for angular-translate tries to loading the language right after calling uses(lang) (after the block, indeed).
We had similar problems when developing the adapters in anguluar-translate. Try to look into https://github.com/PascalPrecht/angular-translate-loader-url/blob/16e559030bce819e8ca1b82fed7163286b57bafe/test/unit/translateUrlLoaderSpec.js which are the tests for the url loader plugin.
Should the controller not be injected a step later?

Resources