Building scalable applications with AngularJS

advertisement
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?
Download