Computed Properties – A Love Story



Computed Properties – A Love Story

0 0


emberatx-2013-02-28

Slides for my EmberATX presentation

On Github cowboyd / emberatx-2013-02-28

Computed Properties

A Love Story

Created by Charles Lowell / @cowboyd

MVC

Are all letters created equal?

M -> vc

No! Get your model right, and everything else comes easily.

Sadly, this point is never made in the documentation of popular frameworks

(Ember is guilty of this)

Models Done Right

  • define entities and their relationships
  • change views in reaction to changes in these entities
  • transition in response to external stimuli (events)

But this is nothing new!

MVC is Kung Fu. There are many different styles

Ember's is strong

FRP Style

Functional Reactive Programming

enabled with computed properties

Computed Properties

  • A pure no-arg function that yields a single value
  • may depend on any number of other properties

(computed or otherwise)

Circles!

Circle = Ember.Object.extend
  area: (->
    r = get 'r'
    Math.PI * r * r
  ).property 'r'
c = Circle.create(r: 1)
c.get 'area' # => 3.141592653589793
c.set 'r', 2
c.get 'area' # => 12.566370614359172

Just a normal property

  • read via get()
  • changes are observable

React!

c = Circle.create(r: 1)
c.addObserver 'area', -> console.log "area changed to #{c.get 'area'}"
c.set 'r', 2

area changed to 12.566370614359172

A non trivial Example

Interaction: What a Drag!

What is a drag?

Code it!

Drag = Ember.Object.extend
  path: []
  start: (->
    @get('path.firstObject') || Point.create()
  ).property 'path.firstObject'
  end: (->
    @get('path.lastObject') || Point.create()
  ).property 'path.lastObject'
  delta: (->
    x: @get('end.x') - @get('start.x')
    y: @get('end.y') - @get('start.y')
  ).property 'start', 'end'
Drag.Interaction = Ember.Object.extend
  init: ->
    #...
    $(document.body).on 'mousemove', (e)=>
      @get('drag.path').pushObject Point.create x: e.pageX, y: e.pageY

Data Flow

Applying a Drag to an element

Code it!

Drag = Ember.Object.extend
  #...
  offset:
    left: @get('offset0.left') + @get('delta.x')
    top: @get('offset0.top') + @get('delta.y')
  ).property 'delta.x', 'delta.y', 'offset0.left', 'offset0.top'

Drag.Interaction = Ember.Object.extend
  #...
  updateOffsets: (->
    $(@get 'element').css
      left: @get 'drag.offset.left'
      top: @get 'drag.offset.top'
  ).observes 'drag.offset.left', 'drag.offset.top'

Data Flow

Key Takeaways

Observers are for chumps!

Make your model work for you

  • Invest heavily "deriving" your model iteratively
  • Think hard about which facts are fundamental
  • Don't waste time copying values around inside observers
  • Let the ember runtime do all the heavy lifting

In our apps

Models based on computed properties outnumber all other classes by at least 10:1

In our apps

Only use observers where the surface of your model makes contact with the DOM

FIN

https://github.com/cowboyd/emberatx-2013-02-28