AngularJS ORM*
*And scalable application structure
WHOIS Dean
-
Dean Sofer .com
- Github.com/ ProLoser
- Founder of AngularUI
- Works @ Paxata* in CA
*we're hiring (you know; like everyone else)
Topics*
*that I want to cover but probably can't
- $scope clutter
- Relational models
- Organization / Packaging
- States + Nesting
- Module / File breakdown
- Complex 'live' applications (sockets)
- Event management
- Ponies (like Directive Layering)
Technologies
- Interesting syntax
- Last line always returned
- Promises++
- CLASSES IN JAVASCRIPT!
- "Should I use Coffeescript?"
- Nesting Views
- Multiple / Named Views
- State Transitions
- State Management
- Token Regex
- More...
- Live stream of events
- 2 way data communication
- Stream and event library
- Includes Features Like:
- Subscribing
- Filtering
- Mapping
- Others I don't care that much about...
- Makes dealing with large streams easier
- Powerful alternative to $broadcast() / $emit()
How to read my Coffeescript
Javascript:
function( x, y, z ){
...
}
this.property( x )
var x = {
prop: true
}
Coffeescript:
( x, y, z ) ->
...
@property x
x =
prop: true
1
Lets Build an App
// First Steps...
<a ng-click=" open = !open ">Toggle</a>
<div ng-show=" open ">Content!</div>
Eventually...
// Later Steps...
<a ng-click=" userOpen = !userOpen ">Toggle</a>
<a ng-click=" carOpen = !carOpen ">Toggle</a>
<a ng-click=" animalOpen = !animalOpen ">Toggle</a>
<a ng-click=" insaneOpen = !insaneOpen ">Toggle</a>
- Hundreds of confusing scope variables
- Clobbering and inconsistent names
- No explicit API defined for reference
- Bloated controllers
Objects Should Manage Their Data
(Not controllers)
module.controller 'Person', ($scope, mySocket, $http, Person) ->
$scope.person = Person.get()
$scope.save = (data) ->
...
$http(...)
$scope.$on 'socketMessage', (event) ->
if event.object != 'Pet' return
switch event.type
when 'CREATE', 'GET'
$scope.pet = event.data
when 'UPDATE'
...
when 'DELETE'
delete $scope.pet
Models on Crack
module.factory 'BaseObject', ($q) -> class BaseObject constructor: (initData) -> angular.extend( @, initData ) module.factory 'PersonObject', (BaseObject) -> class PersonObject extends BaseObject constructor: (initData) -> super(initData) ... remove: -> ... save: -> @id && @create() || @update() create: -> ... update: -> ... module.factory 'AppObject', (BaseObject) -> class AppObject extends BaseObject
... # Returns singleton instance instead of class return new AppObject()
Clean Controllers & Scopes!
# Resolved in Router
module.controller 'Person', ($scope, person) ->
$scope.person = person
<form ng-submit="person.save()">...</form>
<a ng-click="person.getPets()">Show Pets</a>
<h2>Pets:</h2>
<div ng-repeat="pet in person.pets">
<h3>{{pet.name}}</h3>
<a ng-click="pet.getTricks()">List Tricks</a>
<ul>
<li ng-repeat="trick in pet.tricks">{{trick.name}}</li>
</ul>
</div>
Sockets + Events
- Lots of CRUD events for every Object
- $broadcast / $emit is atomic and limited
- Tedious listener delegation
- Tedious collection cache management
- "Context" on all queries
Sockets + Bacon.JS
Hooking it up
# Create new Bacon streams
socketStream = new Bacon.Bus()
queryStream = new Bacon.Bus()
# Subscribe to socket events
mySocket.onmessage = ( event ) ->
$rootScope.$apply ->
# Broadcast new stream event
socketStream.push ( event )
# Subscribe to query requests
queryStream.onValue ( event ) ->
mySocket.send( JSON.stringify( event ) )
Feeding it through
class AppObject extends BaseObject
constructor: ( @downStream, @upStream ) ->
query: ( data ) ->
@upStream.push( data )
app = new AppObject( socketStream, queryStream )
Sockets + Bacon.JS
Listening in
class ProjectObject extends BaseObject
constructor: ( @downStream, @upStream, initData ) ->
# All events on this object and deeper filtered to this project object
@downStream = @downStream.filter (event) ->
return ( event.projectId == @id )
# All queries get decorated with context
@upStream.onValue (event) ->
event.projectId = @id
Relationship Streaming
class PersonObject extends BaseObject
getPets: ->
...
getPet: (id) ->
if !@pets
@getPets().then ->
return @pets[id]
else if @pets[id]
$q.resolve(@pets[id])
else
@query({ type: 'READ', object: 'Pet', id: id }).then (petData) ->
# spins up a new instance and passes in streams and data
return @pets[id] = new PetObject(@downStream, @upStream, petData)
class PetObject extends BaseObject
constructor: ( @downStream, @upStream, initData ) ->
super( @downStream, @upStream, initData )
# All events on this object and deeper filtered to this Pet object
@downStream = @downStream.filter (event) ->
return ( event.petId == @id )
Object Maintanence
class PersonObject extends BaseObject
constructor: ( @downStream, @upStream, initData )
@super( @downStream, @upStream, initData )
@downStream.filter( (event) ->
return (event.type == 'UPDATE' && event.object == 'Person')
).onValue (event) ->
angular.extend(@, event.data)
@downStream.filter( (event) ->
return (@pets && event.type == 'CREATE' && event.object == 'Pet')
).onValue (event) ->
@pets[event.data.id] = new PetObject(@downStream, @upStream, event.data)
UI-Router
Nested Views
$stateProvider.state 'top',
url: '/top/{topId}' # /top/123
template: '<div> ... <ui-view></ui-view> ... </div>'
controller: ($stateParams) ->
$stateParams.topId
$stateProvider.state 'top.middle',
url: '/middle/{middleId}' # /top/123/middle/123
template: '<div> ... <ui-view></ui-view> ... </div>'
controller: ($stateParams) ->
$stateParams.topId
$stateParams.middleId
$stateProvider.state 'bottom',
url: '/bottom/{middleId}' # /top/123/middle/123/bottom/123
parent: 'top.middle'
template: '<div> ... <ui-view></ui-view> ... </div>'
controller: ($stateParams) ->
$stateParams.topId
$stateParams.middleId
$stateParams.bottomId
UI-Router
Named Views
<nav ui-view="nav"></nav>
...
<ui-view></ui-view>
$stateProvider.state 'top',
url: '/top/{topId}' # /top/123
template: '<div>...<ui-view></ui-view>...</div>'
controller: -> ...
$stateProvider.state 'top.middle',
url: '/middle/{middleId}' # /top/123/middle/123
template: '<div> ... <ui-view></ui-view> ... </div>'
views:
'': # un-named <ui-view>
template: '<div>Body</div>'
controller: -> ...
'nav@': # nav <ui-view> at the top level
template: '<h1>Middle</h1>'
controller: -> ...
Resolve Through Relationships
$stateProvider.state 'person',
url: 'person/{personId}'
resolve:
person: ( $stateParams, AppObject ) ->
return AppObject.getPerson( $stateParams.personId )
controller: ( person ) ->
...
$stateProvider.state 'person.pet',
resolve:
pet: ( $stateParams, person ) ->
return person.getPet( $stateParams.petId )
$stateProvider.state 'person.pet.tricks',
resolve:
tricks: ( pet ) ->
return pet.getTricks()
controller: ( tricks, pet, person ) ->
...
Router Manages Active State
(NOT SERVICES)
$stateProvider.state 'person',
url: '/person/{personId}'
resolve:
person: ( $stateParams, AppObject ) ->
return AppObject.getPerson( $state.personId )
onEnter: ( person ) ->
person.open()
onExit: ( person ) ->
person.close()
Organized Modules
angular.module('myApp.directives')
angular.module('myApp.models')
How can we do better?
angular.module('myApp.Core')
angular.module('myApp.Person')
angular.module('myApp.Car')
Even better?
angular.module('myApp.Person.Child')
angular.module('myApp.Animal.Dog')
angular.module('myApp.Admin') // 'mixin' module
Dependency Madness!
Dependencies should be distributed:
# Wrapper module module = angular.module( 'myApp.Person', [ 'myApp.Person.Core', 'myApp.Person.Child', 'ui.utils', 'ui.bootstrap', 'ui.router' ] ) # Bootstrapping Module core = angular.module( 'myApp.Person.Core', [ 'myApp.Core', # BaseObject 'myApp.Animal.Pet', # PetObject 'ui.router' ] )
core.config ($stateProvider) -> ... core.factory 'PersonObject', (PetObject) -> module.directive( 'dash-person', ... ) module.controller( 'People', ... )
WAt?
States must be declared in order at bootstrap:
-
PersonState - /person/123
- Person.ChildState - /person/123/child/123
But Object dependencies are reversed:
# ChildObject must be defined
module.factory 'PersonObject', ['BaseObject', 'ChildObject']
Dependency Trees
Just call wrapper modules
- myApp
- myApp.Animal
- myApp.Animal.Dog
- myApp.Core
- myApp.Core
- myApp.Person
- myApp.Person.Child
- myApp.Core
- myApp.Core
#mindblown
Feen.
AngularJS ORM
By Dean Sofer
AngularJS ORM
Checkout my talk that goes along with the slides: http://www.youtube.com/watch?v=Iw-3qgG_ipU
- 13,223