Building scalable applications with AngularJS and modern applications infrastructure Based on real life stories Andrey Alpert andrey.alpert@dataart.com 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 MV* JS framework, Business logic, Mobile-ready layout Client Thin-Server, REST, Event Server Data storage. Treated like a black box. Data The stack of technologies 1 DRY 2 Declarative 3 Data binding 4 Dependency injection 5 Designers friendly The stack of technologies 1 UI Bootstrap 2 UI Router 3 UI Utils 4 NG Grid 5 … and others Seed projects are sprouting like weeds They all suck Organizing your app Files Logic Code BROS DON’T LET BROS ORGANIZE FILES BY TYPE – the angularjs bro code Organizing by type 1 Organize by services, controllers, views, etc. 2 One module per directory. 3 Poor control over your DI configuration. 4 Directories become bloated. Folders-by-Feature Structure 1 Organize by component / feature. 2 Directory structure mirrors app structure. 3 Easier to extract / share components. 4 Can create angular modules that more accurately reflect injection dependencies. Folders-by-Feature Structure 1 All files related to a feature live together (html, css, tests). 2 Application-level files live directly under app/ 3 Things used throughout the app live under common/ 4 Each section / page of the app has its own folder under app/ 5 Files related to the app live outside of app/ super-dooper-project/ |- src/ | |- app/ | | |- <app logic> | |- assets/ | | |- <static files> | |- 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 Application layout 1 Modules should mirror URL 2 Submodules live near main module 3 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 angular.module(‘app.user', [‘app.user.create’, ‘app.user.search’]); angular.module(‘app.user.create’, []); https://github.com/ngbp/ngbp by Josh Miller Organizing your logic Angular talks about… Services Controllers Directives Filters are app-wide injected singletons. are bridge between template and the rest of your application logic. are encapsulation of some widget or DOM element behavior. are simple output formatting functions. That's great! Now you know exactly how to break up your code That's great! Now you know exactly how to break up your code UNTIL… The problem with controllers 1 Common functionality 2 Very complicated views can end up having multi-thousand line controllers. 3 How can you break up logic that is going to be used in many controllers? List pages, properties pages, etc. Business Models 1 It is the very heart of your JavaScript application • • • 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 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; }; angular.module('app', ['ngRoute']); return UserModel; }); module.controller('UserController', function ($scope, UserModel) { $scope.user= new UserModel('John', 'Doe'); }); <span>{{ user.getName() }}</span> Collections 1 A collection represents a group of models known as items 2 Collections are used to store, retrieve and manipulate data 3 Collections are wrappers above dynamic arrays and represent higher level of abstraction. Creating Collection 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; }; angular.module('app', ['ngRoute']); return UserCollection; }); module.controller('UserController', function ($scope, UserCollection) { $scope.userCollection = new UserCollection; $scope.userCollection.$add('John', ‘Doe'); }); <span ng-repeat=“userCollection.getNames() as name”>{{ name }}</span> Base Collection 1 The BaseCollection class provides the basic interface to create a Collection class of your own. Commonly used methods should be stored in BaseCollection 2 3 • $add • $reset • $getItems … BaseCollection proxies to Underscore.js to provide iteration functions • $forEach • $last • $find … Resources 1 A resource lets you interact with different data sources 2 In specific cases it can be a reusable proxy to HTTP/JSON endpoints. 3 Resources are injected into models and collections to provide the ability to send/retrieve data 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 Can be found in any project Do NOT let it grow 1 Tests will be in the next release 2 Code entropy: “if touch that code everything will break” 3 TODO/FIXME statements 4 Docs? My code is state of art! 5 Let’s just copy/paste for now QUALITY http://en.wikipedia.org/wiki/Technical_debt Code style 1 Readability 2 Good names 3 Clear logic 4 5 Tabs/Spaces convention Docs and comments Tools These are your friends • jshint+stylish • plato • code painter • editorconfig • jscs • eslint Single responsibility 1 File per each component • • • 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 1 Wrap components in an Immediately Invoked Function Expression • • 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. Really good for testing as you can mock injectable data (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; } })(); RED (Fail) 1. Write a test that fails REPEAT PROCESS 3. Improve code quality REFACTOR GREEN (Pass) 2. Make only enough code for it to pass Test driven development 1 TDD/BDD Long (hours) End 2 End 2 3 Better code understanding Medium (minutes) API Services Database Headless Motivation Fast (seconds) Smoke tests unit tests Remote 4 5 Release faster Local. Stubs+Mocks Reliability Safe refactoring Till first failed Test driven development 1 TDD/BDD 2 Better code understanding 3 Motivation 4 Release faster 5 Reliability it('should have Avengers controller', function() { //TODO }); Fast (seconds) 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 1 Not tested area of application 2 Dead code detection 3 Testing quality 4 Acceptance threshold 70-90% 5 Reports coverals.io codeclimate History and stats service Tools Istanbul JSCoverage Blanket coverage > 80% is AWESOME TDD with… + I’m your test runner. The best one. These two guys are GOOD. Cool for E2E tests 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 1 2 http://gruntjs.com http://gruntjs.com/plugins FILE BASED Good for file operations like copy/move/save. Configuration is over the code TONS OF PLUGINS Many of the tasks you need are already available as Grunt Plugins, and new plugins are published every day. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { options: { banner: '/*! <%= pkg.name %>*/\n' }, build: { src: 'src/<%= pkg.name %>.js', dest: 'build/<%= pkg.name %>.min.js' } } }); The streaming build system: gulp 1 2 EASY TO USE By preferring code over configuration, gulp keeps simple things simple and makes complex tasks manageable. STREAM BASED Fast (seconds) Much more faster then Grunt for file-content processing operations var gulp = require('gulp'); http://gulpjs.com http://gulpjs.com/plugins gulp.task('default', function() { // place code for your default task here }); SUMMARY THANKS. QUESTIONS?