In this tutorial we will see how we can structure an AngularJS app. There are quite a few setups that work well for smaller apps. Here we will look at an AngularJS setup using Gulp, Browserify and Node that is more scalable and manageable when our app expands and becomes larger.
This article also provides an example project and also a quickstart scalable setup for setting up your next AngularJS project.
Table of Contents
ToggleTools
Node/npm
The tools used below require node. Use npm as package manager instead of bower, as it works better with browserify to require files into the application.
Gulp
Automate your workflow using Gulp. Gulp is a task runner and there is a gulp-plugin for almost any task that can be automated.
Browserify
If you have worked on a node project you would have came across node modules and how node conveniently allows us to use require to import them into our application.
Browsers do not have a built-in require functionality and if our app is large then we will end up loading lots script tags into our application. Browserify lets us use require in our client application. It reads the required dependencies and creates a single browser compatible file which can be included into the application.
BrowserSync
BrowserSync is a tool that lets us do synchronized browser testing. It will create a server and will simultaneously run our application in multiple browsers. It works well with Gulp. We can watch for changes in our code using gulp watch and ask browsersync to reload all browsers.
Directory Structure
There are quite a few ways to structure an angular app. AngularJS encourages modularization. Separating files based on modules or components makes your structure more scalable and manageable. Let’s see it with an example.
node_modules/
src/
app/
components/ (Smaller mini apps/modules)
home/
home.html
home.js (mainApp.home, controller and or services)
signup/
signup.js
signup.html
shared/ (Shared across two or more components/directives)
sidebar/
sidebar.html
sidebarDirective.js
app.js (Main app/configurations and routes.)
assets/
css/
scss/
js/
env/ (Can be single or multiple files depending on the setup)
prod.js
dev.js
index.html
public/
gulpfile.js
package.json
src
Should contain the source code.
app
All the angular app code goes here.
app/app.js
Your main Angular app. Here you will require other mini apps and angular libraries and inject them into your main app. Your angular app configuration and run block can be here. You can add routes into this file although if your files starts to get too big then its better to separate routes into specific component files.
components
For the app to be scalable, it is necessary to keep it modular. components directory will contain the mini independent angular apps or modules, which combine together into a single larger app.
shared
Here the code snippets or parts of the application which would be shared across the app or components should be placed. It could be a shared directive or template or filters.
assets
Assets folder would contain all your app assets that are not related to AngularJS code. For eg. stylesheets, fonts etc.
env
Should contain environment setting files. Gulp can be used to include the appropriate file into the app based on the environment (prod/qa) of execution.
public
Build folder where gulp will output the final files.
Getting Started
Install Node
Download and install node from here.
Initial Setup
Create a project folder. Navigate into it using command line. Then run npm init. This will ask a few question and create a package.json file. This file is used for managing dependencies for node.
Installing Gulp
Install gulp globally
npm install gulp -g
Also remember to save it as a developer dependency.
npm install gulp --save-dev
If you are just getting started with gulp. Check my previous article “Getting Started With Gulp” which shows how to get started with gulp and writing gulp tasks
Working with browserify
Lets see with an example how we can use browserify.
Install Browserify
npm install --save-dev browserify
Install vinyl-source-stream, it’s a package which takes the browserify stream and converts it into something that gulp understands.
npm install vinyl-source-stream --save-dev
Once both are installed, require them in your gulpfile.js file.
Now lets see how we can require script files/ libraries (eg: angular.js) into our client application. Install angularjs using npm. Note to require files using browserify you should use npm as your package manager or else you will have to specify full path to require the files.
npm install angular --save
Now in your app.js require angular require('angular'). You don’t have to include it in your html.
Next lets write a browserify task that will take our app.js file and convert it into some thing that the browser will understand.
var browserify = require('browserify');
var source = require('vinyl-source-stream');
gulp.task('browserify', function() {
// Grabs the app.js file
return browserify('./src/app/app.js')
// bundles it and creates a file called main.js
.bundle()
.pipe(source('main.js'))
.pipe(gulp.dest('./public/'));
});
Above the browserify task takes app.js bundles it into main.js and puts it in the public directory which is the distribution folder. Now we need to include this main.js file into our index.html file.
require('angular');
var app = angular.module('myApp', []);
Working with browser-sync
Install browser-sync
npm install browser-sync -–save-dev
Configure gulp task for browser-sync.
var browserSync = require('browser-sync').create();
gulp.task('browser-sync', function() {
browserSync.init({
server: {
baseDir: "./public",
// The key is the url to match
// The value is which folder to serve (relative to your current working directory)
routes: {
"/bower_components": "bower_components",
"/node_modules": "node_modules"
}
},
browser:"firefox"
});
});
This will create a server and serve files to the browser from the public directory. Check my previous article to know more about BrowserSync.
Environment specific builds
You may want to have diffent builds for different environment (eg: dev, prod). You may choose not to minify and concat files in development enviroment and only do it on production. Use gulp-environments for environment specific builds.
npm install gulp-environments -–save-dev
Lets say we have different configuration files for dev and prod and we want to include them in our application respectively.
var environments = require('gulp-environments');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var development = environments.development;
var production = environments.production;
/** load config file based on enviroment */
var configFile = production() ? "./src/env/prod.js" : "./src/env/dev.js";
gulp.task('scripts', function(){
return gulp.src(['./src/assets/**/*.js',configFile])
.pipe(uglify())
.pipe(concat('vendor.min.js'))
.pipe(gulp.dest('./public/'));
});
Now to run build for specific environment run the below commands.
gulp scripts --env production
The above command builds for production. By default production and development are the two available environments although we can add more. Have a look at the documentation for more options.
Install other required gulp plugins
npm install gulp-jshint -–save-dev
npm install gulp-sass -–save-dev
npm install gulp-uglify -–save-dev
npm install gulp-concat -–save-dev
Demo Application
Let’s have a look at a demo application to get a real view of the above setup.
DEMO
var gulp = require('gulp');
var sass = require('gulp-sass');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var jshint = require('gulp-jshint');
var browserSync = require('browser-sync').create();
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var environments = require('gulp-environments');
var development = environments.development;
var production = environments.production;
/** load config file based on enviroment */
var configFile = production() ? "./src/env/prod.js" : "./src/env/dev.js";
gulp.task('lint', function() {
return gulp.src('./src/app/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
gulp.task('scripts', function(){
return gulp.src(['./src/assets/**/*.js',configFile])
.pipe(uglify())
.pipe(concat('vendor.min.js'))
.pipe(gulp.dest('./public/'));
});
gulp.task('browserify', function() {
// Grabs the app.js file
return browserify('./src/app/app.js')
// bundles it and creates a file called main.js
.bundle()
.pipe(source('main.js'))
.pipe(gulp.dest('./public/'));
})
gulp.task('copy', ['browserify','scss'], function() {
gulp.src(['./src/**/*.html','./src/**/*.css'])
.pipe(gulp.dest('./public'))
.pipe(browserSync.stream())
});
gulp.task('scss', function() {
gulp.src('./src/assets/scss/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('./src/assets/stylesheets/'));
});
gulp.task('build',['lint', 'scss', 'copy', 'scripts']);
gulp.task('browser-sync', ['build'], function() {
browserSync.init({
server: {
baseDir: "./public",
// The key is the url to match
// The value is which folder to serve (relative to your current working directory)
routes: {
"/bower_components": "bower_components",
"/node_modules": "node_modules"
}
},
browser:"firefox"
});
});
gulp.task('default', ['browser-sync'], function(){
gulp.watch("./src/**/*.*", ["build"]);
gulp.watch("./public/**/*.*").on('change', browserSync.reload);
})
require('angular');
require('angular-ui-router');
require('angular-aria');
require('angular-animate');
require('angular-material');
require('./components/home/home.js');
require('./components/about/about.js');
var app = angular.module('myApp', ['ui.router','ngMaterial','myApp.home','myApp.about'])
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('home', {
url: "/",
views : {
"" : {
templateUrl:"app/components/home/home.html"
},
"header@home":{
templateUrl:"app/shared/header/header.html"
}
}
})
.state('about', {
url: "/about",
views : {
"" : {
templateUrl:"app/components/about/about.html"
},
"header@about":{
templateUrl:"app/shared/header/header.html"
}
}
});
});
{
"name": "angularapp",
"version": "1.0.0",
"description": "A scalable structure for angular apps",
"main": "\u001b ",
"scripts": {
"test": " "
},
"repository": {
"type": "git",
"url": " "
},
"author": "Rahil Shaikh",
"license": "MIT",
"devDependencies": {
"browser-sync": "^2.9.11",
"browserify": "^12.0.0",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-environments": "^0.1.1",
"gulp-jshint": "^1.11.2",
"gulp-minify": "0.0.5",
"gulp-sass": "^2.1.0",
"gulp-uglify": "^1.4.2",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
},
"dependencies": {
"angular": "^1.4.7",
"angular-animate": "^1.4.7",
"angular-aria": "^1.4.7",
"angular-material": "^0.11.4",
"angular-ui-router": "^0.2.15"
}
}
Other then these there are component files and stylesheet which you can find with the downloaded code.
Quick Start
If you want to quick start your next AngularJS project with the above structure you can download the code from the link.
Alternatively you can clone the repository by running
git clone https://github.com/rahil471/scalable-angularjs-project-setup-gulp-browserify.git
Next naviagte to the project directory from your command-line and run
npm install
This will install all the necessary node modules. Next we also need to install gulp globally.
npm install -g gulp
Now run
gulp
Your application will run in firefox browser (as configured in browser-sync), you can change the browser or add another browser by editing the browser-sync task in gulpfile.js
Bonus
In this section I’ve listed some of the most popular and useful gulp plugins other than the one’s we used above.
gulp-util
Provides utility functions. Useful for logging and coloring outputs etc. Know more.
gulp-autoprefixer
Parses css and adds vendor(browser) prefixes to your css. Know more.
gulp-load-plugins
Loads all your gulp plugins at once, hence you won’t have to require each plugin one at a time. Know more.
Conclusion
Angular encourages modularaization, to make your app scalabale and managable you should divide it into smaller independent components and bring these smaller components to complete a larger app. With tools like gulp and browserify, this becomes much easier as you won’t have to include every single component file into your index.html, instead just one js file. Also tools like BrowserSync helps us do browser testing with ease there by saving a lot of our time. I hope this tutorial will make your life with AngularJS much more easier.