Building scalable applications with AngularJS and modern applications infrastructure Based on real life stories
Andrey Alpert linkedin.com/in/andreyalpert
The way it was… Mixed client server leads to increased complexity Presentation logic will require a lot of interactions with the server Not a real application, just another website
The way it should be ClientServerData MV* JS framework, Business logic, Mobile-ready layout Data storage. Treated like a black box. Thin-Server, REST, Event
The stack of technologies DRY 1 Declarative 2 Designers friendly 5 Dependency injection 4 Data binding 3
The stack of technologies UI Bootstrap 1 UI Router 2 … and others 5 NG Grid 4 UI Utils 3
Seed projects are sprouting like weeds They all suck
Organizing your app FilesLogicCode
BROS DON’T LET BROS ORGANIZE FILES BY TYPE – the angularjs bro code
Organizing by type Organize by services, controllers, views, etc. 1 One module per directory. 2 Directories become bloated. 4 Poor control over your DI configuration. 3
Folders-by-Feature Structure Organize by component / feature. 1 Directory structure mirrors app structure. 2 Can create angular modules that more accurately reflect injection dependencies. 4 Easier to extract / share components. 3
Folders-by-Feature Structure All files related to a feature live together (html, css, tests). Application-level files live directly under app/ Each section / page of the app has its own folder under app/ Things used throughout the app live under common/ Files related to the app live outside of app/ super-dooper-project/ |- src/ | |- app/ | | |- | |- assets/ | | |- | |- common/ | | |- components/ | | |- services/ | |- less/ | | |- main.less |- bower_components/ | |- bootstrap_components/ | |- angular-bootstrap/ | |- bootstrap/ |- karma/ |-.bowerrc |- bower.json |- build.config.js |- Gruntfile.js |- module.prefix |- module.suffix |- package.json 12435
Application layout Modules should mirror URL Submodules live near main module Submodules listed as dependencies to main module src/ |- app/ | |- user/ | | |- create/ | | |- search/ | | |- user.js | | |- user.ctrl.js | | |- user.less | | |- user.spec.js | | |- user.tpl.html by Josh Miller angular.module(‘app.user', [‘app.user.create’, ‘app.user.search’]); angular.module(‘app.user.create’, []);
Organizing your logic
Angular talks about… Services are app-wide injected singletons. Controllers are bridge between template and the rest of your application logic. Directives are encapsulation of some widget or DOM element behavior. Filters are simple output formatting functions.
That's great! Now you know exactly how to break up your code
UNTIL… That's great! Now you know exactly how to break up your code
The problem with controllers Common functionality List pages, properties pages, etc. 1 Very complicated views can end up having multi-thousand line controllers. 2 How can you break up logic that is going to be used in many controllers? 3
Business Models It is the very heart of your JavaScript application 1 It captures the behavior of the application in terms of its problem domain, independent of the user interface It describes a particular entity in the programming world in terms of data and state It encapsulates your application’s logic and data, provides an API to access and manipulate that data
Business Models angular.module('app', ['ngRoute']); module.factory(‘UserModel’, function (BaseModel) { var UserModel = BaseModel.$extend({ $initialize: function (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } }); UserModel.prototype.getName = function () { return this.firstName + ' ' + this.lastName; }; return UserModel; }); module.controller('UserController', function ($scope, UserModel) { $scope.user= new UserModel('John', 'Doe'); }); {{ user.getName() }}
Collections A collection represents a group of models known as items 1 Collections are used to store, retrieve and manipulate data 2 Collections are wrappers above dynamic arrays and represent higher level of abstraction. 3
Creating Collection angular.module('app', ['ngRoute']); module.factory(‘UserCollection’, function (BaseCollection, UserCollection) { var UserCollection = BaseCollection.$extend({ $model: UserModel }); UserCollection.prototype.getNames = function () { var names = []; this.$getItems().forEach(function(item){ names.push(item.getName()); }); return names; }; return UserCollection; }); module.controller('UserController', function ($scope, UserCollection) { $scope.userCollection = new UserCollection; $scope.userCollection.$add('John', ‘Doe'); }); {{ name }}
Base Collection The BaseCollection class provides the basic interface to create a Collection class of your own. 1 Commonly used methods should be stored in BaseCollection $add $reset $getItems … 2 BaseCollection proxies to Underscore.js to provide iteration functions $forEach $last $find … 3
Resources A resource lets you interact with different data sources 1 In specific cases it can be a reusable proxy to HTTP/JSON endpoints. 2 Resources are injected into models and collections to provide the ability to send/retrieve data 3 module.service(‘UserResource’, function ($http) { this.list = function() { return $http.get(‘/some/endpoint/url’); }; this.update = function() { ………… }; this.delete = function() { ………… }; });
Organizing your code
Technical debt Tests will be in the next release Code entropy: “if touch that code everything will break” Docs? My code is state of art! TODO/FIXME statements Let’s just copy/paste for now Can be found in any project Do NOT let it grow QUALITY
Code style Readability Good names Tabs/Spaces convention Clear logic Docs and comments jshint+stylish plato code painter editorconfig jscs eslint These are your friends Tools
Single responsibility File per each component 1 Gives the most control over how the injector is configured Much easier to extract code into shared components When testing, only have to load specific module under test angular.module('app', ['ngRoute']); angular.module(‘app’).controller('SomeController', SomeController); function SomeController() { } angular.module(‘app’).factory('someFactory', SomeFactory); function SomeFactory() { }
IIFE Wrap components in an Immediately Invoked Function Expression 1 An IIFE removes variables from the global scope. Protects us from having collisions of variables and many global variables. (function() { 'use strict'; angular.module(‘app’).controller('SomeController', SomeController); function SomeController() { } })();
Resolve promises for your controllers A controller may require data before it loads. That data may come from a promise via a custom factory or $http. Using a route resolve allows the promise to resolve before the controller logic executes, so it might take action based on that data from the promise. (function() { 'use strict'; angular.module(‘app’).config(myConfig); function myConfig($routeProvider) { $routeProvider.when('/avengers', { templateUrl: 'avengers.html', controller: 'AvengersCtrl', controllerAs: 'av', resolve: { resolvedMovies: function(MoviesSCollection) { return MoviesCollection.$loadItems(); } }); } })(); (function() { 'use strict'; angular.module(‘app’).controller(‘AvengersCtrl’, Avengers); function Avengers(resolvedMovies) { var ctrl = this; ctrl.moviesList = resolvedMovies; } })(); Really good for testing as you can mock injectable data
RED (Fail) GREEN (Pass) REFACTOR 1. Write a test that fails 2. Make only enough code for it to pass 3. Improve code quality REPEAT PROCESS
Test driven development TDD/BDD Better code understanding Release faster Motivation Reliability Long (hours) Medium (minutes) Fast (seconds) End 2 End API Services Database Headless Smoke tests unit tests Till first failed Remote Local. Stubs+Mocks Safe refactoring
Test driven development TDD/BDD Better code understanding Release faster Motivation Reliability Fast (seconds) it('should have Avengers controller', function() { //TODO }); it('should find 1 Avenger when filtered by name', function() { //TODO }); it('should have 10 Avengers', function() {} //TODO (mock data?) }); it('should return Avengers via XHR', function() {} //TODO ($httpBackend?) }); // and so on
Сode coverage Not tested area of application Dead code detection Acceptance threshold 70-90% Testing quality Reports Tools Istanbul JSCoverage Blanket coverage > 80% is AWESOME coverals.io codeclimate History and stats service
TDD with… + Cool for E2E tests I’m your test runner. The best one. These two guys are GOOD.
Laziness is the mother of INVENTION
What tasks to automate Source Concatenate Uglify SourceMaps Watch LiveReload Rebuild Serve Preprocess LESS SASS Compass Test Karma Mocha Coverage Assets Templates HTML processing Images optimization Custom ChangeLog Notifications console.debug
Task runner: grunt FILE BASED Good for file operations like copy/move/save. Configuration is over the code 1 TONS OF PLUGINS Many of the tasks you need are already available as Grunt Plugins, and new plugins are published every day. 2 grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { options: { banner: '/*! */\n' }, build: { src: 'src/.js', dest: 'build/.min.js' } });
The streaming build system: gulp Fast (seconds) EASY TO USE By preferring code over configuration, gulp keeps simple things simple and makes complex tasks manageable. 1 STREAM BASED Much more faster then Grunt for file-content processing operations 2 var gulp = require('gulp'); gulp.task('default', function() { // place code for your default task here });
SUMMARY
THANKS.QUESTIONS?