rich object models & angular.js
DESCRIPTION
Super excited about Angular? Ready to change the world and build a super-heroic app? With directives, Angular has pretty much nailed it when it comes to interacting with the DOM. And plain-old Javascript objects as models? AWESOME! Wait, hang on. Say that you're building a app that has lots of business logic and interrelated data. For that sort of app, history shows us that a rich object model can often be the best place to put your logic and data relationships - from both a testing and ease-of-development perspective. That's the approach that frameworks like Ember advocate - but Ember forces you to extend on its own object model. In this talk I'll ask whether we can get the best of both worlds - a rich data model, whilst still using plain-old Javascript objects. We'll delve into things like: - Lazy-loading data relationships between models - Decorating loaded data with business logic - Object identity uniqueness (critical for bindings to work as expected) - Computed properties for models Attendees will leave with an understanding of how a rich object model can help them build beautiful, fast and easy-to-maintain apps.TRANSCRIPT
![Page 1: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/1.jpg)
Rich Object Models & AngularBen Teese, Shine Technologies
![Page 2: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/2.jpg)
Overview
Why?
Loading Data
Adding Business Logic
Advanced Stuff
![Page 3: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/3.jpg)
Most of the apps I build are
CRUD
![Page 4: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/4.jpg)
...it was nice
![Page 5: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/5.jpg)
WARNING
The remainder of this presentation contains UX that some viewers may find
disturbing
![Page 6: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/6.jpg)
![Page 7: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/7.jpg)
Non-Recurring
Engineering
Internal Currency
External Currency Customer
Recurring Engineering
Material Cost Items
Internal Cost Items
DepartmentCost
Currency
External Cost Items
Cost
Currency
Shipsets
Details Years
Line Replaceable
Unit
Sales Price Override
SubassembliesInternal Cost Item
External Cost Items
Standard Sales Price
Spare Parts Sales Price
Customer Type Prices
Currency
Proposal
Currency Currency
Customer Type Sales Price
Currency
Purchase Price RangesSupplier
Purchase Price
Currency
DepartmentCost
Currency
Customer Type
OMG
![Page 8: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/8.jpg)
![Page 9: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/9.jpg)
![Page 10: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/10.jpg)
Restangular
![Page 11: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/11.jpg)
// GET /proposalsRestangular.all('proposals').getList().then( function(proposals) { $scope.proposals = proposals; });
Getting Stuff
![Page 12: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/12.jpg)
or...
// GET /proposals$scope.proposals = Restangular.all('proposals').getList(). $object;
![Page 13: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/13.jpg)
// GET /proposals/:id/cost_items $scope.proposal.getList('cost_items').then( function(costItems) { $scope.costItems = costItems; });
Getting Nested Stuff
![Page 14: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/14.jpg)
Rich Models
![Page 15: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/15.jpg)
angular.module('pimpMyPlane.services', ['restangular']). factory('ProposalSvc', function(Restangular) { Restangular.extendModel('proposals', function(obj) { return angular.extend(obj, {! profit: function() { return this.revenue().minus(this.cost()); }, revenue: function() { return this.price(). convertTo(this.internalCurrency); } ... }); });
return Restangular.all('proposals'); })
![Page 16: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/16.jpg)
angular.module('pimpMyPlane.models'). factory('Proposal', function() { return { profit: function() { return this.revenue().minus(this.cost()); }, revenue: function() { return this.price(). convertTo(this.internalCurrency); }, ... }; }
A Model Mixin
![Page 17: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/17.jpg)
angular.module('pimpMyPlane.services', ['restangular', 'pimpMyPlane.models']).factory('ProposalSvc', function(Restangular, Proposal){ Restangular.extendModel('proposals', function(obj) { return angular.extend(obj, Proposal); });
return Restangular.all('proposals'); });
Using the Mixin
![Page 18: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/18.jpg)
angular.module('pimpMyPlane.services', ['restangular']). factory('ProposalSvc', function(Restangular) { Restangular.extendModel('proposals', function(obj) { angular.extend(obj.recurringEngineering, { ... }); angular.extend(obj.nonRecurringEngineering, { ... }); angular.extend(obj.internalCurrency, { ... }); angular.extend(obj.externalCurrency, { ... });
return angular.extend(obj, Proposal); }); ... })
What about nested models?
![Page 19: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/19.jpg)
angular.module('pimpMyPlane.services', ['restangular', 'pimpMyPlane.models']). factory('Proposals', function(Restangular, Proposal) { Restangular.extendModel('proposals', function(obj) { return Proposal.mixInto(obj); }); ... });
Introduce mixInto()
![Page 20: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/20.jpg)
angular.module('pimpMyPlane.models'). factory('Proposal', function( Currency, RecurringEngineering, NonRecurringEngineering ) { return { mixInto: function(obj) { RecurringEngineering.mixInto( obj.recurringEngineering ); NonRecurringEngineering.mixInto( obj.nonRecurringEngineering ); Currency.mixInto(obj.internalCurrency); Currency.mixInto(obj.externalCurrency)) return angular.extend(obj, this); }, profit: function() { return this.revenue().minus(this.cost()); }, ... }; });
![Page 21: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/21.jpg)
Non-Recurring
Engineering
Internal Currency
External Currency Customer
Recurring Engineering
Material Cost Items
Internal Cost Items
DepartmentCost
Currency
External Cost Items
Cost
Currency
Shipsets
Details Years
Line Replaceable
Unit
Sales Price Override
SubassembliesInternal Cost Item
External Cost Items
Standard Sales Price
Spare Parts Sales Price
Customer Type Prices
Currency
Proposal
Currency Currency
Customer Type Sales Price
Currency
Purchase Price RangesSupplier
Purchase Price
Currency
DepartmentCost
Currency
Customer Type
![Page 22: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/22.jpg)
Shazam
![Page 23: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/23.jpg)
Identity Maps
![Page 24: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/24.jpg)
Non-Recurring
Engineering
Internal Currency
External Currency Customer
Recurring Engineering
Material Cost Items
Internal Cost Items
DepartmentCost
Currency
External Cost Items
Cost
Currency
Shipsets
Details Years
Line Replaceable
Unit
Sales Price Override
SubassembliesInternal Cost Item
External Cost Items
Standard Sales Price
Spare Parts Sales Price
Customer Type Prices
Currency
Proposal
Currency Currency
Customer Type Sales Price
Currency
Purchase Price RangesSupplier
Purchase Price
Currency
DepartmentCost
Currency
Customer Type
![Page 25: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/25.jpg)
Identity Map
USD
EUR
IT
“currency”:1
“currency”:2
“department”:1
......
Finance
“department”:2
......
![Page 26: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/26.jpg)
![Page 27: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/27.jpg)
angular.module('pimpMyPlane.models'). factory('Money', function(Currency, identityMap) { return { mixInto: function(obj) { obj.currency = identityMap( 'currency', Currency.mixInto(obj.currency) ); angular.extend(object, this); }, ... });
Mapping Nested Currencies
![Page 28: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/28.jpg)
angular.module('pimpMyPlane.services', ['restangular', 'pimpMyPlane.models']).factory('CurrenciesSvc', function( Restangular, Currency, identityMap ) { Restangular.extendModel('currencies', function(obj){ return identityMap( 'currency', Currency.mixInto(obj) ); });
return Restangular.all('currencies'); });
Mapping RESTful Currencies
![Page 29: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/29.jpg)
Getter Functions(Uniform Access Principle)
![Page 30: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/30.jpg)
angular.module('pimpMyPlane.models'). factory('Proposal', function(extendWithGetters) { return { mixInto: function(obj) { ... return extendWithGetters(obj, this); }, get profit() { return this.revenue.minus(this.cost); }, get revenue() { return this.price.convertTo( this.internalCurrency ); }, ... }; });
![Page 31: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/31.jpg)
Memoization
![Page 32: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/32.jpg)
angular.module('pimpMyPlane.models'). factory('Proposal', function(Model) { return Model.extend({ memoize: ['revenue', 'cost'], ... get profit() { return this.revenue.minus(this.cost); }, get revenue() { return this.price.convertTo( this.internalCurrency ); }, ... }; });
![Page 33: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/33.jpg)
<div ng-controller="ProposalCtrl"> ... <input type="number" ng-model="currency.conversionFactor" ng-change="proposal.unmemoize()"></input> ... <table> <tr> <td>Number of Aircraft</td> <td> <input type="number" min="1" ng-model="proposal.numberOfAircraft" ng-change="proposal.unmemoize()"></input> </td> </tr> </table></div>
Unmemoization
![Page 34: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/34.jpg)
Computed Properties
![Page 35: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/35.jpg)
angular.module('pimpMyPlane.models'). factory('Proposal', function(...) { return Model.extend({ ... computedProperties: { profit: [function() { return this.revenue.minus(this.cost); }, 'revenue', 'cost'],
cost: [function() { return this.recurringEngineering.cost.plus( this.nonRecurringEngineering.cost ); }, 'recurringEngineering.cost', 'nonRecurringEngineering.cost'] }, }); });
![Page 36: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/36.jpg)
...needs more work
![Page 37: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/37.jpg)
Let’s Wrap This Up
Rich Models can work
Identity Maps
Getters, Memoization
Computed properties
Shrink-wrappedBoeing 737
![Page 38: Rich Object Models & Angular.js](https://reader036.vdocuments.us/reader036/viewer/2022062513/554f7301b4c905bb178b5321/html5/thumbnails/38.jpg)
@benteese
Please enjoy the remainder of your flight