rich object models & angular.js

Post on 10-May-2015

7.578 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

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

Rich Object Models & AngularBen Teese, Shine Technologies

Overview

Why?

Loading Data

Adding Business Logic

Advanced Stuff

Most of the apps I build are

CRUD

...it was nice

WARNING

The remainder of this presentation contains UX that some viewers may find

disturbing

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

Restangular

// GET /proposalsRestangular.all('proposals').getList().then( function(proposals) { $scope.proposals = proposals; });

Getting Stuff

or...

// GET /proposals$scope.proposals = Restangular.all('proposals').getList(). $object;

// GET /proposals/:id/cost_items $scope.proposal.getList('cost_items').then( function(costItems) { $scope.costItems = costItems; });

Getting Nested Stuff

Rich Models

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'); })

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

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

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?

angular.module('pimpMyPlane.services', ['restangular', 'pimpMyPlane.models']). factory('Proposals', function(Restangular, Proposal) { Restangular.extendModel('proposals', function(obj) { return Proposal.mixInto(obj); }); ... });

Introduce mixInto()

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()); }, ... }; });

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

Shazam

Identity Maps

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

Identity Map

USD

EUR

IT

“currency”:1

“currency”:2

“department”:1

......

Finance

“department”:2

......

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

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

Getter Functions(Uniform Access Principle)

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 ); }, ... }; });

Memoization

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 ); }, ... }; });

<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

Computed Properties

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'] }, }); });

...needs more work

Let’s Wrap This Up

Rich Models can work

Identity Maps

Getters, Memoization

Computed properties

Shrink-wrappedBoeing 737

@benteese

Please enjoy the remainder of your flight

top related