Created by Thomas `tomatao` Hudspith-Tatham / tomht@thinkful.com
Oh and also…
Working with a sample App
var i = 1;
// some code
i = i + ""; // oops!
// some more code
i + 1; // evaluates to the String '11'
i - 1; // evaluates to the Number 0
0.1 - 0.3; // evaluates to -0.19999999999999998
NaN == NaN; // evaluates to false
// etc... with sprinkles of media devices and browsers
Recently released: Angular v2 TodoMVC it's in alpha
(Not to mention ES6)
Yes... they do
“ Programming is the art of doing one thing at a time. ”
- Michael C. Feathers, Working Effectively with Legacy Code
“ Simplicity is prerequisite for reliability. ”
- Edsger W. Dijkstra, some book?
hold on a minute... JavaScript was a problem!?
Same old ideas…
Is this all a bit too general?…
Why?
// magic, do not touch.
Scope | Goal |
---|---|
Individual Files | Simple and Small |
Whole Code Base | Manageable |
The Application | Resilient and Fun |
Integration | Configurable |
Features | Modular |
By products of these:
Make migration easier
It doesn't matter ¯\_(ツ)_/¯
“ Without requirements or design, programming is the art of adding bugs to an empty text file. ”
- Louis Srygley - Application Architect UPS
No layering strategy
Works for a while - then you realise it's too big to change and doesn't work
Prevention is better than solution!
What happens on boot?
Difficult to test
Debugging just to understand logic!
Sharing state between controllers?
Need to duplicate state!
“ Walking on water and developing software from a specification are easy if both are frozen. ”
- Edward Berard, The Object Agency, Inc
Focus on the requirements
Actions, aka Interactions, Controls, Service Layer or Use Cases
“ Routing for JavaScript Applications is like a party at the bottom. ”Eh?
- Thomas `tomatao` Hudspith-Tatham, Just Now
Cool benefit from being simple and modular
Show me an example!
angular.module('routes')
.constant('addCourseView', {
controller: 'addCourseCtrl',
controllerAs: 'aC',
})
.controller('addCourseCtrl', function(Course, addCourse, closeModal) {
var vm = this;
vm.course = Course.get();
vm.addModalId = 'create-course-modal';
vm.modalAction = function modalAction() {
return addCourse()
.then(function() {
closeModal(vm.addModalId);
});
};
});
Folder names that reflect the route
Decouple routes from URIs
angular.module('routes').config(function(){
$stateProvider
.state('manageLayout', {
abstract: true,
templateUrl: ROUTESURL + 'manage/manage.tmpl.html',
controllerAs: 'manage',
controller: 'manageCtrl',
resolve: {
weekResolve: 'appResolve'
}
})
.state('courses', {
url: URLMAP.courses,
parent: 'manageLayout',
views: {
addCourse: angular.extend(addCourseView, {
templateUrl: ROUTESURL + 'manage/add-course.tmpl.html',
}),
anotherView: ...
}
})
});
Just one URL per file with multiple smaller controllers
One route per file! (long lived app with moving parts!)
Resolve blocks (explicit dependencies with perks) for named routes!
Small files!
Seriously one file per route
Self contained features
Nice to have semantic business logic that delegates
So use layers inside modules too!
“ The goal for our software architecture is to provide the key mechanisms that are required to implement a wide variety of cross-layer adaptations described by our taxonomy. ”
- Soon Hyeok Choi (2008), A Software Architecture for Cross-layer Wireless Networks
But what about global states and actions? User store, notifications services and Session storage!?
…special module!
Same as others - but shared!
Other modules can depend on it
If the others are using it, does that mean it is it's own layer?...Yes!
“ You could probably write an entire book on why global state is bad. ”
- Stack overflow - Top answer to some question
.factory('Model', function(){
function Model(){
this.data = {};
}
Model.prototype.set = function(modelData, value){
if (typeof modelData == "string")
this.data[modelData] = value;
else
angular.extend(this.data, modelData);
};
Model.prototype.get = function(prop) {
if (prop == null) return this.data;
return this.data[prop];
};
Model.prototype.reset = function() {
// keep reference but clear all properties
for (var member in this.data) delete this.data[member];
};
return Model;
});
describe('Model', function () {
var modelData = { id: 123, name: 'TEST001' };
beforeEach( module("models") );
it('should start empty', inject(function(Model) {
expect(Model.get()).toEqual({});
}));
it('should get the updated object', inject(function(Model) {
var m = Model.get();
m.id = 'newIdValue';
expect(Model.get()).toBe(m);
}));
it('should provide a set method', inject(function (Model) {
Model.set(modelData);
expect(Model.get()).toEqual(modelData);
}));
});
I find AngularStrap to be really good
Components don't touch state!
Make the most of isolated scope and attributes
.factory('notify', function(Notifications, uuid) {
// types: success, info, warning, danger
return function notify(type, message){
// add to stack, stack pops itself after time
// notifications component will listen to stack and display
Notifications.push({
id: uuid(),
type: type || 'info',
message: message,
});
};
})
`Notifications` extends the Base Collection
Using a uuid helper service
.factory('extendPrototype', function(){
return function(protoProps, staticProps) {
var parent = this,
child;
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
_.extend(child, parent, staticProps);
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
if (protoProps) _.extend(child.prototype, protoProps);
child.__super__ = parent.prototype;
return child;
};
});
High level abstractions for a single entry point
Good for transactions or persistence layers
I like to make multiple abstractions around these for store specific gateways
IDB gateway for a `Courses` persistence layer
angular.module('indexeddbGateways')
.factory('indexeddbCoursesGateway', function( $q, idbGateway ){
var coursesPromise = idbGateway('Courses');
return {
save: function(course){
var d = $q.defer();
course = angular.copy(course);
coursesPromise.then(function(store){
store.put( course, function success(id){
d.resolve(angular.extend(course, { id: id }));
});
});
return d.promise;
},
getAll: function(){
var d = $q.defer();
coursesPromise.then(function(store){
store.getAll(function success(courseList){
d.resolve(courseList);
});
});
return d.promise;
}
};
});
The idbGateway connects Tables (Stores), and returns a promise that resolves the said store - then we can `put`, `getAll`, etc…
They have the same layers as core.
You might want to nest modules too.
Name modules by their feature
REMEMBER NOT TO DEPEND ON OTHER MODULES!
Not truly hierarchical - you can accidentally inject from another's dependencies :/
Mismatch with AMD and CJS11 - they're upside-down
They don't load any files
$scope
)$scope
$scope
is just for controllers$scope
can reference states, not own them$scope
$watch
, $apply
and $observe
Directives
)
angular.module('courseManager')
.factory('CourseList', function(BaseCollection) {
var CourseList = BaseCollection.extend({
addStudent: function(courseId) {
var course = this.get(courseId);
course.studentCount = (course.studentCount != null)
? course.studentCount + 1 : 1;
}
});
return new CourseList();
});
Using the said Core Module's extend prototype service
Adding specific methods for this store
Uses new
to work with singleton in an Angular style
angular.module('course-manager')
.factory('addCourse', function(
$interpolate,
coursesGateway,
CourseList,
notify
){
var msgExp = $interpolate(
'Success! Course {{ name }} saved!');
return function addCourse(course){
return coursesGateway.save(Course.get())
.then(function(savedCourse){
notify('success', msgExp({ name: savedCourse.name }));
CourseList.add(savedCourse);
});
};
});
The coursesGateway also returns a promise
Angular interpolate for template strings
Using the notification service from core
Finally mutating the CourseList state and resetting the Course state
They live inside routes where the party happens, not inside modules
Depend on modules and map their states onto views
Use module actions when users do stuff
“ Angular JS Controllers should be treated more like little kittens than donkeys ”
- Thomas `tomatao` Hudspith-Tatham, Just now
Poor donkeys :(
Depends on the configuration
Doesn't really do much, just loads the other layers!
Top of the pile!
Anything can depend on it
DEV:true
Track by
is pretty whiz!$apply
is so rare!
$compileProvider.debugInfoEnabled(ENV.DEV);
$httpProvider.useApplyAsync(!ENV.DEV);
One more thing...
One more thing...
_.clone
to tame objectsOne more thing...
One more thing...
Let's dive into the workshop project you'll be working with!
One more thing...