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


You don't have to use any of these!

  • 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:
    1. PersonState - /person/123
    2. 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,175