Angular module dependencies and config file separation - angularjs

I have messed a bit in the understanding of the following things:
How to define required dependencies only in that modules I want and not define all of them in main app.js module?
Make it sense to store each config in separate file(e.g. config for routes, config for an angular plugin like 'toastr' and so on)
As for modules` dependencies, I cannot understand why I cannot move some dependencies from main app.js(like toastr, ngAnimate in my case) to a certain modules where these dependencies are required definetely. Look at my app structure and all dependencies:
app.js
'use strict';
angular
.module('app', [
'app.routes',
'app.constants',
// Below dependencies are not necessary here for all my modules.
// It is enough to have it only in logger.js module
'ngAnimate',
'toastr'
])
// TODO: Move 'toastr' configuration to separate file
// Angular Toastr taken from https://github.com/Foxandxss/angular-toastr/
.config(function (toastrConfig) {
angular.extend(toastrConfig, {
allowHtml: false,
closeButton: false,
closeHtml: '<button>×</button>',
containerId: 'toast-container',
extendedTimeOut: 1000,
iconClasses: {
error: 'toast-error',
info: 'toast-info',
success: 'toast-success',
warning: 'toast-warning'
},
maxOpened: 0,
messageClass: 'toast-message',
newestOnTop: true,
onHidden: null,
onShown: null,
positionClass: 'toast-bottom-full-width',
tapToDismiss: true,
target: 'body',
timeOut: 5000,
titleClass: 'toast-title',
toastClass: 'toast'
});
});;
app.constants.js
'use strict';
angular
.module('app.constants',[])
.constant('API_URI', 'http://localhost:8080/api/');
app.routes.js
'use strict';
angular
// Only this dependency work well here as I expect and I don't need to define it in main app.js module
.module('app.routes', ['ngRoute'])
.config(config);
function config($routeProvider, $locationProvider) {
// Use the HTML5 History API
$locationProvider.html5Mode(true);
$routeProvider.
when('/', {
templateUrl: 'todo/todo.html',
controller: 'TodoController'
})
.otherwise({
redirectTo: '/'
});
}
logger.js
'use strict'
angular
// Why I cannot define 'toastr', 'ngAnimate' dependencies here(not in app.js file) where it is definetely needed?
.module('app')
.factory('logger', logger);
logger.$inject = ['$log', 'toastr'];
function logger($log, toastr) {
var service = {
error: error,
info: info,
success: success,
warning: warning,
log: $log.log
}
return service;
function error(title, message, data) {
toastr.error(message, title);
$log.error('Error: ' + message, data);
}
function info(title, message, data) {
toastr.info(message, title);
$log.info('Info: ' + message, data);
}
function success(title, message, data) {
toastr.success(message, title);
$log.success('Success: ' + message, data);
}
function warning(title, message, data) {
toastr.warning(message, title);
$log.warning('Warning: ' + message, data);
}
}
If I trying to move 'toastr' and 'ngAnimate' dependencies from app.js to logger.js file, I receive the following error message:
Error: error:modulerr
Module Error
What I do wrong using dependencies in relative modules in comparison with main app.js module?
And the last short question, make it sense to store each configuration of something in a separate file? I think this way lead to better code readability, usage, robustness but what do you think about it?

You currently don't have a separate module for logger. The module it uses is the 'app' module.
Create a new 'app.logger' module in the same way you've created 'app.constants' and 'app.routes'. Add 'toastr' and 'ngAnimate' as dependencies of 'app.logger'
Once that's done, add 'app.logger' as a dependency of 'app'.
It makes no difference to the code, but it's good to keep files short and organize your files in a way that makes it easy to find what you need.

Related

Unit Testing AngularJS 'config' Written in TypeScript using Jasmine

I'm currently trying to Unit Test the config of a new AngularJS component. We are using ui-router to handle the routing in our application. We have been able to successfully test it for all our previous components, but the code for all of them was written in plain Javascript. Now that we switched to TypeScript we are having some issues.
This is the TypeScript code where we make the configuration of the module:
'use strict';
// #ngInject
class StatetiworkpaperConfig {
constructor(private $stateProvider: ng.ui.IStateProvider) {
this.config();
}
private config() {
this.$stateProvider
.state('oit.stateticolumnar.stateticolumnarworkpaper', {
url: '/stateticolumnarworkpaper',
params: { tabToLoad: null, groupTabId: null, jurisdiction: null, showOnlyItemsWithValues: false, showOnlyEditableItems: false},
template: '<stateticolumnarworkpaper-component active-tab-code="$ctrl.activeTabCode"></stateticolumnarworkpaper-component>',
component: 'stateticolumnarworkpaperComponent',
resolve: {
onLoad: this.resolves
}
});
}
//#ngInject
private resolves($q, $stateParams, ColumnarWorkpaperModel, ChooseTasksModel, localStorageService) {
// Some not important code
}
}
angular
.module('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper')
.config(["$stateProvider", ($stateProvider) => {
return new StatetiworkpaperConfig($stateProvider);
}]);
This is the Spec file, which is written in Javascript:
describe('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper', function () {
beforeEach(module('oit.components.batch.batchprocess.stateticolumnar.stateticolumnarworkpaper'));
beforeEach(module('oit'));
var state = 'oit.stateticolumnar.stateticolumnarworkpaper';
it('has a route', inject(function ($state) {
var route = $state.get(state);
expect(route.url).toBe('/stateticolumnarworkpaper');
}));
});
My issue is when executing the line var route = $state.get(state), as the route variable is always null. I could verify that the config() method is being executed, but I'm simply out of ideas as to why route is always null on my test.
Just for reference, this is the configuration of another component, but using Javascript
'use strict';
angular
.module('oit.components.binders.binder.dom_tas.taxaccountingsystem.stateworkpapers.stateworkpapersreview')
.config(stateworkpapersreviewConfig);
function stateworkpapersreviewConfig($stateProvider) {
$stateProvider
.state('oit.binder.taxaccountingsystem.stateworkpapersreview', {
url: '/stateworkpapersreview?reviewType&binderId&year&jurisdiction&chartId&withBalance',
templateUrl: 'components/binders/binder/dom_tas/taxaccountingsystem/stateworkpapers/stateworkpapersreview/stateworkpapersreview.tpl.html',
controller: 'StateworkpapersreviewController',
controllerAs: 'stateworkpapersreviewCtrl',
resolve: {
onLoad: resolves
}
});
function resolves($q, $stateParams, StateTiBinderJurisdictionsModel, WorkpaperModel, localStorageService, StateTiFiltersModel) {
// Some not important code
}
}
As you can see the code is basically the same, but still, I can successfully test this component's config in the way I described, but when I try with the one written in TypeScript I get the error I mentioned.
PD: I'm aware of several similar posts (like this one), but none of them deal with TypeScript, which is my issue.
There is huge difference between the TS snippet and the JS one.
I’m not sure why you are using a class to elite a function? .config suppose to get a function.
You can write the same code as in JS just with .ts suffix, it is a valid TS code.
Then you just can import that config function, pass it all the injectables and test it.

RequireJs Angular execution order of dependencies

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.

refactor large AngularJS module config into separate files

Problem: Large config()
The config of my AngularJS app is growing quite large. How would you refactor the following into separate files?
// app.js
angular.module('myApp')
.config(function($urlRouterProvider, $stateProvider, $httpProvider) {
// Configure routing (uiRouter)
$urlRouterProvider.when('/site', '/site/welcome');
$stateProvider.state('main', ...
...
// Configure http interceptors
$httpProvider.interceptors.push(function () {
...
});
});
Option 1. Multiple config()s
I know that I can have multiple config()s and place them in separate files like this:
// app.js
angular.module('myApp');
// routerConfiguration.js
angular.module('myApp')
.config(function($urlRouterProvider, $stateProvider) {
// Configure routing (uiRouter)
$urlRouterProvider.when('/site', '/site/welcome');
$stateProvider.state('main', ...
...
// httpInterceptorConfig.js
angular.module('myApp')
.config(function($httpProvider) {
// Configure http interceptors
$httpProvider.interceptors.push(function () {
...
});
});
What I do not like about this, is that in the original app.js, there is no way of getting an overview of what is run at startup.
Option 2. Inject something
I would prefer to do something like this, because it would be easier to see what is configured, directly in the app.js. However I know that this is not possible, since we cannot inject services into config().
Can I use providers to solve this? Is there a better way?
// app.js
angular.module('myApp')
.config(function(routerConfig, httpInterceptorConfig) {
routerConfig.setup();
httpInterceptorConfig.setup();
});
// routerConfig.js
angular.module('myApp')
.factory('routerConfig', function($urlRouterProvider, $stateProvider) {
return {
setup: function() {
// Configure routing (uiRouter)
$urlRouterProvider.when('/site', '/site/welcome');
$stateProvider.state('main', ...
...
}
};
});
});
// httpInterceptorConfig.js
angular.module('myApp')
.factory('httpInterceptorConfig', function($httpProvider) {
return {
setup: function() {
// Configure http interceptors
$httpProvider.interceptors.push(function () {
...
}
};
});
});
You can use .constant as a dependency in a .config, so you could use the config declaration as a manual composition root. For example:
angular.module('myApp')
.config(function($urlRouterProvider, $stateProvider, $httpProvider,
routerConfig, httpInterceptorConfig) {
routerConfig($urlRouterProvider, $stateProvider);
httpInterceptorConfig($httpProvider);
});
// routerConfig.js
angular.module('myApp')
.constant('routerConfig', function($urlRouterProvider, $stateProvider) {
...
});
// httpInterceptorConfig.js
angular.module('myApp')
.constant('httpInterceptorConfig', function($httpProvider) {
...
});
Drawbacks to this approach are that you have to inject all of your config dependencies into the root config function, then manually inject them into your constant config functions. If you have a lot of different config functions, this could get messy. It can also look kind of like your config functions are having dependencies injected by Angular, but in fact you are doing it manually. You would need to make sure you remember to do the manual wiring every time you add a new config function.
My preferred approach is just to have a folder called "config" containing exclusively config calls.
I would look at browserify
It allows you to use markup to require modules in your javascript.
This would allow you to break up your config into multiple files while keeping them together (see example).
browserify compiles your javascript into a single bundle.js by recursively tracing the requires from your main javascript file.
You then only need to include <script src="bundle.js"></script> on your index.html
Short Example
Javascript
'use strict';
require('angular/angular');
require('angular-ui/ui-router');
var app = angular.module('vw', ['ui.router', 'myApp.feature1', 'myApp.feature2'])
.config(require('./config/route'))
.config(require('./config/httpProvider'));
angular.bootstrap(document, ['myApp']);
File Structure
myApp
config
route.js
httpProvider.js
myApp.js
index.html
I just did this recently with a provider.
angular.module('EventView')
.provider('routing', ['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
this.init = function() {
// For any unmatched url, redirect to /home
$urlRouterProvider.otherwise('/home');
// Now set up the states
$stateProvider
.state('login', {
url: '/account/login?{redirectUrl}',
controller: 'loginCtrl',
templateUrl: 'partials/account/login/login.tpl.html'
})
}
// has to be here
this.$get = function() {
return {};
};
}
]);
In the config you can just call the function.
angular.module('EventView').config(['routingProvider', function (routingProvider) {
routingProvider.init();
}
]);
There are two gotachas, first that the name of the provider adds provider to it when you reference from the config and the second is that you have to implement the $get and return a function.
Good luck!

AngularJS and RequireJS: No module: myApp

I'm trying for the first time to use AngularJS in conjunction with RequireJS using this guide as a basis. As far I can tell after a lot of debugging I'm loading all my modules in the correct order, but when the application runs Angular throws an Error / Exception with the following message:
Argument 'fn' is not a function, got string from myApp
I've seen this message before due to syntax errors, so even though I've looked trough the code multiple times I won't rule out the possibility of a simple syntax error. Not making a Fiddle just yet in case it is something as simple as a syntax error, but I'll of course do so if requested.
Update: I just noticed when setting ng-app="myApp" in the <html> tag I also get an additional error,
No module: myApp
Update II: Okay, it turns out it indeed was an syntax error in the only file not included below. I am though still left with the problem from update I.
RequireJS bootstrap
'use strict';
define([
'require',
'angular',
'app/myApp/app',
'app/myApp/routes'
], function(require, ng) {
require(['domReady'], function(domReady) {
ng.bootstrap(domReady, ['myApp']);
});
});
app.js
'use strict';
define([
'angular',
'./controllers/index'
], function(ng) {
return ng.module('myApp', [
'myApp.controllers'
]);
}
);
controllers/index
'use strict';
define([
'./front-page-ctrl'
], function() {
});
controllers/module
'use strict';
define(['angular'], function (ng) {
return ng.module('myApp.controllers', []);
});
controllers/front-page-ctrl
'use strict';
define(['./module'], function(controllers) {
controllers.
controller('FrontPageCtrl', ['$scope',
function($scope) {
console.log('I\'m alive!');
}
]);
});
Delete ng-app="myApp" from your html.
Because it has bootstrapped manually
ng.bootstrap(domReady, ['myApp']);
RequireJS docs on Dom ready state:
Since DOM ready is a common application need, ideally the nested
functions in the API above could be avoided. The domReady module also
implements the Loader Plugin API, so you can use the loader plugin
syntax (notice the ! in the domReady dependency) to force the
require() callback function to wait for the DOM to be ready before
executing. domReady will return the current document when used as a
loader plugin:
So, when you require 'domReady' the result is a function:
function domReady(callback) {
if (isPageLoaded) {
callback(doc);
} else {
readyCalls.push(callback);
}
return domReady;
}
But when you append the domReady string with ! sign the result will be the actual document element:
'use strict';
define([
'require',
'angular',
'app/myApp/app',
'app/myApp/routes'
], function(require, ng) {
require(['domReady!'], function(domReady) {
// domReady is now a document element
ng.bootstrap(domReady, ['myApp']);
});
});

Angular ui-router fails to resolve named dependencies

I recently migrated from ui-router 0.0.1 to 0.2.0. Since the migration, ui-router fails to resolve named dependencies that needs to be injected into a view's controller. Here's the sample code which works fine with ver 0.0.1 but fails in ver 0.2.0
angular.module( 'sample.test', [
'ui.router',
'i18nService'
])
.config(function config($stateProvider) {
$stateProvider.state( 'mystate', {
url: '/mystate',
resolve: {i18n: 'i18nService'},
views: {
'main': {
controller: 'MyCtrl',
templateUrl: 'templates/my.tpl.html'
}
}
});
})
.controller('MyCtrl', ['i18n', function(i18n) {
// fails to resolve i18n
}]);
i18nService is a simple service that return a promise
angular.module('i18nService', [])
.factory('i18nService', ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
$http.get('..').then(..);
return deferred.promise;
}]);
I get the error "Unknown provider: i18nProvider <- i18n" when using v0.2.0
If i change the resolve config to:
resolve: {
i18n: function(i18nService) {
return i18nService
}
},
everything works fine. Is this an expected behaviour, or am I missing some configuration?
Here's the plunker: http://plnkr.co/edit/johqGn1CgefDVKGzIt6q?p=preview
This is a bug that was fixed last month:
https://github.com/angular-ui/ui-router/commit/4cdadcf46875698aee6c3684cc32f2a0ce553c45
I don't believe it's in any currently released version, but you could either get the latest from github or make the change yourself in your js file. It's simply changing key to value in that one line (you can see it in the github commit).
A workarround is to just not change the name for now.... do
resolve :{
i18nService: 'i18nService'
}
Then inject i18nService to your controller instead of i18n. It's a bit of a hack, but it does work (it injects the resolved service not the promise).

Resources