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!
Related
I'm trying to modularize my first angular project. Need to make my module views relative to this module. In example below i have sample main module, that require controllers module. I can inject VIEWS_PATH to controller, but no to config(). As i know constant can be injected into config(). What is wrong with that ?
mainModule.js
angular.module('mainModule', ['app.main.controllers'])
.constant('VIEWS_PATH', 'js/modules/main/views');
controllersMain.js
angular.module('app.main.controllers', [])
.config(function($routeProvider, VIEWS_PATH) { // error
$routeProvider.when('/hello', {
templateUrl: VIEWS_PATH+'/hello.html',
controller: 'HelloController'
})
})
.controller('HelloController', function($scope, VIEWS_PATH) {
$scope.hello = 'Hello World!';
console.log('VIEWS_PATH: '+VIEWS_PATH); // ok
});
It's because you defined your VIEWS_PATH constant in one module -mainModule and you're trying to use it in a different module - app.main.controllers.
You can define that constant in app.main.controllers module if you wish to use it in the configuration of that module.
angular.module('app.main.controllers', [])
.constant('VIEWS_PATH', 'js/modules/main/views')
.config(function($routeProvider, VIEWS_PATH) {
$routeProvider.when('/hello', {
templateUrl: VIEWS_PATH+'/hello.html',
controller: 'HelloController'
});
});
But Constants from app.main.controllers module will work in mainModule as it listed as dependency in mainModule like below.
angular.module('mainModule', ['app.main.controllers']);
For example, let's say we defined two modules - MyApp & SomeModule
var someModule = angular.module('SomeModule',['someOtherModule']);
someModule.constant('SOME_CONSTANT','SomeValue');
var myApp = angular.module('MyApp',['SomeModule']);
myApp.constant('TEST_CONSTANT','Test');
myApp.config(function(SOME_CONSTANT){
console.log("from dependent module "+SOME_CONSTANT);
});
With the above setup, SOME_CONSTANT from SomeModule can be used in MyApp but TEST_CONSTANT from MyApp cannot be used in SomeModule.
Here's a sample Pen in action.
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.
I am using Angular and I need to define routing in ng-route config but also in my services to be used by $http in services methods.
The problem is that the route strings change in my application according to language ... For example for an about us page I might have:
"en/about-us", "pt/quem-somos", "fr/ ..."
The application has a RouteProvider that returns route strings by key.
The idea would be to create a script on the fly for a service that injected in angular components would allow to get those routes strings.
<script type="text/javascript">
// Create $routes service using backend code ...
</script>
This would be used on app.config as follows:
app.config(['$routeProvider', function($routeProvider, $routes) {
$routeProvider.
when($routes.getByKey('about.home', {
templateUrl: 'about/home.html',
controller: 'AboutController'
})
}
But also in a service as follows:
application.service('CountryService', function ($http, $routes) {
return {
GetList: function () {
return $http.get($routes.getByKey('api.countries.list');
}
}
});
My problem is how to write the JS part of such a service and to plug it into angular components. Is it possible to create such a service?
I would not conflate view routes from the API endpoints - these are different creatures.
And you don't need to create a route per language - just use language as a parameter:
$routeProvider
.when("/:lang/about-us", {
template: "<h3>About Us</h3> in lang: {{language}}",
controller: function($scope, $routeParams, $location){
$scope.language = $routeParams.lang;
}
})
.when("/:lang/something-else", {
template: "<h3>Something Else</h3> in lang: {{language}}",
controller: function($scope, $routeParams){
$scope.language = $routeParams.lang;
}
})
plunker
I came across this tutorial.
http://justinvoelkel.me/laravel-angularjs-part-two-login-and-authentication/
The author used dependency injection to inject the login controller in app.js like this.
app.js:
var app = angular.module('blogApp',[
'ngRoute',
//Login
'LoginCtrl'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
The login controller file looks like this.
LoginController.js:
var login = angular.module('LoginCtrl',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
I don't understand why the dependency injection is required here.
Here is my version of app.js and LoginController.js which works perfectly fine.
app.js:
var app = angular.module('ilapp', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/login', {
controller: 'LoginController'
});
}]);
LoginController.js:
angular.module('ilapp').controller('LoginController', [function () {
this.loginSubmit = function () {
console.dir(this.loginData);
};
}]);
Is there any advantage to the author's approach? What am I missing?
First of all, both are correct way and it should work but you can choose any one method depends upon your project.
Approach 1
In your question, the first approach is modular way. Means, you can register a LoginController controller in a new module LoginCtrl. Here module name LoginCtrl is confusing. you can change the name as loginModule. This approach is helpful for you to organize the files structure for your big application. Also, look this post Angular - Best practice to structure modules
var login = angular.module('loginModule',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
var app = angular.module('blogApp',[
'ngRoute',
'loginModule'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
Approach 2
If your application contains minimal pages and no need to split multiple modules, then you can write all your controllers in app.js itself
I'm using meteor + angular. My intention is to add more dependencies after the app bootstrap (This is because the package is the one handling the bootstrapping at the start and I don't have much control of it). Now while doing that, I would also want to enforce a basic code structure wherein for example, I would compile all controllers in one module.
Here's the basic idea:
'use strict';
angular.module('app.controllers', [])
.controller('MainCtrl', function() {
// ...
})
.controller('SubCtrl', function() {
// ...
})
.controller('AnotherCtrl', function() {
// ...
});
Then include that to the main module as dependency:
angular.module('app', [
'app.filters',
'app.services',
'app.directives',
'app.controllers' // Here
]);
After some research, I've discovered that what I'm trying to do (Adding dependencies after bootstrap) is actually a part of a feature request to the angular team. So my option is using, for example, $controllerProvider and register() function:
Meteor.config(function($controllerProvider) {
$controllerProvider.register('MainCtrl', function($scope) {
// ...
});
});
Meteor.config(function($controllerProvider) {
$controllerProvider.register('SubCtrl', function($scope) {
// ...
});
});
Meteor.config(function($controllerProvider) {
$controllerProvider.register('AnotherCtrl', function($scope) {
// ...
});
});
It's works though not that elegant. The questions are:
What's a more elegant way of doing the config and register part?
Is there a way to register a module instead?
Create your module:
angular.module('app.controllers', []);
Add it as a dependency:
angular.module('app').requires.push('app.controllers');
according to this presentation (slide 12) you can assign controllerProvider to app.
Example of replacing module's controller method: http://jsfiddle.net/arzo/HB7LU/6659/
var myApp = angular.module('myApp', []);
//note overriding controller method might be a little controversial :D
myApp.config(function allowRegisteringControllersInRuntime($controllerProvider) {
var backup = myApp.controller;
myApp.controller = $controllerProvider.register;
myApp.controller.legacy = backup;
})
myApp.run(function ($rootScope, $compile) {
myApp.controller('MyCtrl', function($scope) {
$scope.name = 'Superhero';
})
var elem;
var scope=$rootScope;
elem = $compile('<p ng-controller="MyCtrl">{{name}}</br><input ng-model="name" /></p>')($rootScope, function (clonedElement, scope) {
console.log('newly created element', clonedElement[0])
document.body.appendChild(clonedElement[0]);
});
console.log('You can access original register via', myApp.controller.legacy);
})
The only method that I've seen that works is replacing the angular.module function with your own function returning the module you used to bootstrap your app.
var myApp = angular.module('myApp', []);
angular.module = function() {
return myApp;
}
So that all modules that are registered afterwards are actually being registered in your myApp module.
This method combined with the one you describe in the question (using providers like $controllerProvider) will allow you to add "modules" after angular.bootstrap.
Demo
See this jsfiddle for a demo: https://jsfiddle.net/josketres/aw3L38r4/
Drawbacks
The config blocks of the modules that are added after angular.bootstrap will not be called. Maybe there's a way to fix this, but I haven't found it.
Overriding angular.module feels like a "dirty hack".