AngularJS and Webpack Integration - javascript

I am looking for some help with using webpack for a large AngularJS application. We are using folder structure based on feature (each feature/page has a module and they have controllers, directives). I have successfully configured webpack to get it working with Grunt, which produces one single bundle. I want to create chunks as its going to be a large app, we would like to load modules (page/feature) artifacts asynchronously.
I am going through some of the webpack example to use 'code splitting' using require([deps],fn ) syntax. However I couldn't get the chunks lazy-loaded. First of all, I don't know where exactly, I would need to import these chunks before AngularJS would route the user to next page. I am struggling to find a clear separation of responsibility.
Did someone point me to an example AngularJS application where webpack is used to load controllers/directives/filters asynchronously after each route?
Few of the links I am following:
Should I use Browserify or Webpack for lazy loading of dependancies in angular 1.x
https://github.com/petehunt/webpack-howto#9-async-loading
http://dontkry.com/posts/code/single-page-modules-with-webpack.html

Sagar Ganatra wrote a helpful blog post about code splitting.
Suprisingly code splitting isn't really supported by angular's module system. However, there is a way to achieve code splitting by saving a reference to angular's special providers during the config-phase.
[...] when Angular initializes or bootstraps the application, functions - controller, service etc,. are available on the module instance. Here, we are lazy loading the components and the functions are not available at a later point; therefore we must use the various provider functions and register these components. The providers are available only in the config method and hence we will have to store a reference of these providers in the config function when the application is initialized.
window.app.config([
'$routeProvider',
'$controllerProvider',
'$compileProvider',
'$filterProvider',
'$provide',
function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider.when('/login', {
templateUrl: 'components/login/partials/login.html',
resolve: {
load: ['$q', '$rootScope', function ($q, $rootScope) {
var deferred = $q.defer();
// lazy load controllers, etc.
require ([
'components/login/controllers/loginController',
'components/login/services/loginService'
], function () {
$rootScope.$apply(function () {
deferred.resolve();
});
});
return deferred.promise;
}]
}
});
//store a reference to various provider functions
window.app.components = {
controller: $controllerProvider.register,
service: $provide.service
};
}
]);
Now inside your loginController for instance you write
app.components.controller('loginController');
to define your new controller lazily.
If you want to lazy-load your templates too I recommend to use the ui-router. There you can specify a templateProvider which is basically a function to load templates async

This is a quote from
https://github.com/webpack/webpack/issues/150
webpack is a module bundler not a javascript loader. It package files from local disk and don't load files from the web (except its own chunks).
Use a javascript loader, i. e. script.js
var $script = require("scriptjs");
$script("//ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js", function() {
// ...
});

Related

Should AngularJS modules be in their own file?

Fairly simple question, I can't seem to find a definitive answer. At the minute I've my module declared in one file :
var module = angular.module("app", ["agGrid", "ngAnimate", "ngSanitize", "ui.bootstrap"]);
and my controller in another :
angular.module("app").controller("mainCtrl", ["$scope", "$timeout", "dateFilter", "$http", "shareDataService", "getDataService", function ($scope, $timeout, dateFilter, $http, shareDataService, getDataService) {
Is this good structure or a waste of time and space?
Single Responsibility
Define 1 component per file.
The following example defines the app module and its dependencies, defines a controller, and defines a factory all in the same file.
Avoid this
angular
.module('app', ['ngRoute'])
.controller('SomeController', SomeController)
.factory('someFactory', someFactory);
function SomeController() { }
function someFactory() { }
The same components are now separated into their own files.
Do this
// app.module.js
angular
.module('app', ['ngRoute']);
// some.controller.js
angular
.module('app')
.controller('SomeController', SomeController);
function SomeController() { }
// someFactory.js
angular
.module('app')
.factory('someFactory', someFactory);
function someFactory() { }
https://github.com/johnpapa/angular-styleguide#style-y001
It's a good practice to keep them separate. This way, you don't end up mixing the module(s) definition and controllers / services / directives.
here you get some of the best practices in angular js -
Instead of slicing your app across horizontals that can't be broken
up, group your code into related bundles. This way if you remove a
module, your app still works.
https://github.com/angular/angular.js/wiki/Best-Practices
Having separate files for each type of component is ideal. I follow the structure in development:
app.js //where the module resides
routes.js //consists of routes
controllers/
services/
factories/
filters/
directives/
Define your module:
var app = angular.module();
Then use 'app' to declare other nested js in separate files, example:
app.directive()
However, in production, it is preferable to use task runner (eg gulp) to combine all the files and minify the final file.

AngularJS: how to use $http in config phase

I am building a single page application using angular. We use Continuous Integration with several Dev and QA environments. This means that every time I deploy the app in a different environment I need to update some config parameters such as SPA baseUrl, rest api url or url for my oauth server.
To achieve this, our CI engine generates a config.json file with the correct parameters for each environment when we are about to deploy there. To get this I have created a really simple service:
(function () {
'use strict';
angular
.module('app.core')
.factory('config', config);
config.$inject = ['$http'];
function config($http) {
return {
load: load
};
//////////
function load() {
return $http.get('assets/config/config.json');
}
}
}());
Most of my services are configured through providers during the config phase. And here is where the problem comes up. As you know, services cannot be injected during this phase but I would like to use config service to set up my providers with the correct URLs and parameters.
Is there a proper way to do this?
The only thing I can consider so far is having these config parameters embed in my module creation file so they are there ready to use. It seems quite ugly since I donĀ“t want any external process to my app modifying my source code.
If these are asynchronous services that rely on config.json, it is perfectly ok to change the way they are configured and fit your config into async workflow, e.g.
app.factory('config', function ($http) {
return $http.get('config.json', { cache: true })
.then(function (response) {
return response.data;
});
});
app.factory('asyncService', function ($http, config) {
return config.then(function (config) {
return $http...
}).then(...);
});
Otherwise the best option is to build json config to a service with Gulp/Grunt/whatever.
Think of
angular.module('config', []).constant('config', <%= configJson %>);
as of a compiled template rather than 'external process to my app modifying my source code'.
What I am doing at the moment (but I am open for a better solution) is to have a separate config file with the json object that contains all the config info.
Example of the file content:
var appConfigInfo = {
DEV: true,
API_PATH: 'your_custom_url'
// etc.
};
Then I add it as a constant in my app. Example:
angular
.module('app', [])
.constant('APP_CONFIG', appConfigInfo);
Now you can access all your configuration values via the constant. Don't forget to inject it in your controllers, etc.
I hope it helps.

lazy loading of components in angular with browserify

I am in process of outlining a architecture of a fairly complex application based on angular. So I started with the angular-seed project and it seems to be a good starting point. What bothers me is that angular apps by nature involves loading everything upfront. With script loaders, there doesn't seems to be a clean way around.
I came from a backbone.js background and there it was quiet straight to use the require.js for lazy loading based on the router callbacks. In angular routes are somewhat defined like the below:
// Declare app level module which depends on views, and components
angular.module('myApp', [
'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when({templateURL:'../tmpl.html',controller:'view1Ctrl'})
.when({templateURL:'../tmpl.html',controller:'view1Ctrl'})
.otherwise({redirectTo: '/view1'});
}]);
now here, $routeProvider.when({templateURL:'../tmpl.html',controller:'view1Ctrl'}) I would like to lazily load the controller and the template. I was tempted to use something like:
$routeProvider.when({templateURL:'../tmpl.html',controller:require('view1Ctrl'})
using browserify but then it doesn't seems to be clean and not even with require. I know this question has been asked several times on SO some way or the other but i haven't found a emphatic answer to this.
My preference here is to use the browserify as it supports the much loved cjs modules in browser.
I'm not sure how to do this with Browserify, as I've never tried it myself but I would strongly recommend you look into ocLazyLoad.
As a standalone service, it works wonders with loading files (json, css, js, templates - you name it) and injecting it into your already running angular application.
With that said, it works even better(imo) coupled with a router (the default angular one, or ui-router).
There are some 'seed projects' that showcase how one could do it with ocLazyLoad coupled with SystemJS.
https://github.com/Swimlane/angular-systemjs-seed
https://github.com/lookfirst/systemjs-seed
https://github.com/lookfirst/ocLazyLoad-SystemJS-Router
https://github.com/kasperlewau/ng-jspm-seed
But you don't even need that.
If you go with ui-router, ui-router-extras and ocLazyLoad you can put something like this together to lazy load states:
main.js
/**
* Inject the needed dependencies into our main module.
*/
var main = angular.module('main', [ 'ui.router', 'ct.ui.router.extras.future', 'oc.lazyLoad' ]);
/**
* Define the lazy loaded states.
*/
main.constant('lazyLoadedStates', [
{
name: 'about',
url: '/about',
type: 'lazy',
src: [
'/path/to/about.module.js',
'/path/to/AboutController.js'
]
}
]);
/**
* Setup the behaviour for when we hit a futureState with the 'lazy'
* type.
*
* 1. Setup a deferred object.
* 2. Resolve the promise when every file defined in the futureState.src has been loaded.
* 3. Return the promise.
*/
main.config(function ($futureStateProvider, lazyLoadedStates) {
$futureStateProvider.stateFactory('lazy', function ($q, $ocLazyLoad, futureState) {
var deferred = $q.defer();
$ocLazyLoad.load(futureState.src).then(function () {
deferred.resolve();
});
return deferred.promise;
});
lazyLoadedStates.forEach($futureStateProvider.futureState);
});
That's the 'framework' out of the way - now you just need to keep adding more modules, with more code, and match a real state definition with the dummy one in the lazyLoadedStates constant.
about.module.js
/**
* Setup the _real_ '/about' state in this lazy loaded file.
*/
angular.module('about', []).config(function ($stateProvider) {
$stateProvider.state('about', {
url: '/about',
controller: 'AboutController',
template: 'some_template.html'
});
});
AboutController.js
/**
* Register the AboutController in a lazy loaded file. This could be done in about.module.js aswell,
* but we'll do it here to separate stuff and showcase loading of multiple files.
*/
angular.module('about').controller('AboutController', function ($state) {
console.log('Im on a lazy loaded state!', $state.current);
});
I hope that gives you the rough idea of how to setup lazy loaded states in Angular. I have yet to find an easier way to do this, but I'm sure there are articles out there on how to couple ui-router (or the default angular router) with some other 'lazy-loader'.
Doc links:
ocLazyLoad
ui-router-extras#future

Angular $routeProvider override-able fallback routes

I'm creating my first sizable angular project, so I've opted to break out my angular application code into directories like so:
app/
calendar/
calendar.js
contacts/
contacts.js
companies/
companies.js
...
app.js
Each of these directories will include a handful of files for views (.html), directives, services, etc.
In my app.js file, I want to setup default routes that will be used maybe 80% of the time, like this:
$routeProvider
.when('/:page', {
templateUrl: function($routeParams) {
return 'app/' + $routeParams.page + '/index.html';
},
controller: 'PageController'
})
However, I want the ability to "override" that route from within my modules so I can do things like swap out the controller, do dependency injection stuff, and so on. I would like that sort of logic contained within the module itself to keep things...well...modular, so I would rather not define each module's routing logic within app.js. So for example, in my app/calendar/calendar.js file I want to say:
$routeProvider
.when('/calendar', {
templateUrl: 'app/calendar/index.html',
controller: 'CalendarIndexController',
resolve: { ... }
})
The problem is that the definition in app.js is matched against the location first, so this calendar route is never used.
To achieve this, right now I'm just including an extra file after all of my module javascript files that sets up my fallback routes last, but this seems a little clunky. Is there anyway to define the routes within app.js so that they are override-able?
You might be able to adapt this technique for dynamically loading controllers described by Dan Wahlin here http://weblogs.asp.net/dwahlin/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs combined with your default routes where the parameter controls which view to load. You could pass the same page parameter to the resolve function to control which combination of view and controller to serve up?

How do I get an Angularjs module and invoke .value on it?

I'm trying to use a vertxbus module. It's configuration is set using .value.
How do I update these configuration values from my 'appModule'?
To my understanding
angular.module('knalli.angular-vertxbus')
should return a reference to the module and .value should change the injected value.
I've created a jsFiddle and here is the js used:
'use strict';
var testmodule = angular.module('TestModule', []).value('key', 'module').value('key', 'module2');
testmodule.factory('MyService', ['key', function (key) {
return key;
}]);
var module = angular.module('myApp', ['TestModule'])
.run(function () {
testmodule.value('key', 'run1');
angular.module('TestModule').value('key', 'run2');
}).controller('MyCtrl', ['$scope', 'MyService', 'key', function ($scope, MyService, key) {
$scope.value = MyService;
$scope.key = key;
}]);
I would expect a result of run2 or at least run1 but module2 is returned. Why?
The actual idea behind .value, .constant or more generally .config is the possibility to change how the modules' components should constructed themselves.
In that case, the angular-translate's vertxBus component will invoke implicitly a SockJS instance which connects implicitly to a server. That means the configuration settings must be known before the application will be running. Because of this, there are two phases (see answer by Ilan Frumer): config and run.
However, because .value and .config are too static and could collide with other components, the angular module angular-vertxbus has an own provider for all the settings (since version 0.5: https://github.com/knalli/angular-vertxbus/releases/tag/v0.5.0).
Starting with 0.5, this could be look like this:
var module = angular.module('app', ['knalli.vertx-eventbus']);
module.config(function(vertxEventBus) {
vertxEventBus.useDebug(true).useUrlPath('/eventbus');
});
Just like the others, the provider is only available in the config-phase.
Disclaimer: I'm the author of angular-vertxbus
$provide shorthands
Under the hood, constant & value & service & factory & provider are all shorthands for the $provide service which is available only during the configuration phase.
From angular.js documentation:
A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.
your case:
You cannot register providers (in your case value) inside run blocks because they are only available during the configuration phase.
Read more about modules and $provide

Categories

Resources