Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EmberConf 2014: Mr. Router embraces the Controller - Alex Matchneer

machty
March 27, 2014

EmberConf 2014: Mr. Router embraces the Controller - Alex Matchneer

Query params, model dependent state, and the subtleties of the route vs controller paradigm.

machty

March 27, 2014
Tweet

More Decks by machty

Other Decks in Programming

Transcript

  1. The one where Mr. Router
    embraces the Controller
    Alex Matchneer
    EmberConf 2014

    View Slide

  2. “Mr. Router”
    Alex Matchneer
    Ember.js Core
    @machty

    View Slide

  3. Embracing the Controller: Overview
    ● The State of Query Params
    ● What’s the hold up?
    ● The Missing Primitive
    ● Demo
    ● Roadmap
    ● Questions?

    View Slide

  4. /?query=params
    Query Params

    View Slide

  5. A Brief History of Query Params
    ● 2013: Alex Speller implements standalone
    QP lib, later merged into Canary
    ● 2014: Alex Matchneer rewrites query
    params (query-params-new)
    ● 2014: Alex Matchneer rewrites query
    params again (query-params-new-new-final-
    2)

    View Slide

  6. - Everyone
    “When will query params land in beta?”

    View Slide

  7. - Mr. Router
    “Not much longer. It’s complicated.”

    View Slide

  8. - Everyone
    “I’m switching to Angular.”

    View Slide

  9. What’s taking so long?
    Get it together, core team

    View Slide

  10. Where should QPs live? The Router?
    ● The original QP implementation was very
    route-centric:
    ● Specify all QPs in the Router.map DSL
    ● Optionally pass QP values to server queries
    ● Optionally use QP values to set controller
    properties

    View Slide

  11. Problems with Route-centric QPs
    ● Lots of hoops to jump through to get QPs
    into controllers.
    ● Keeping QPs in sync w controller props was
    a manual, cumbersome, error-prone
    process

    View Slide

  12. ...we shifted the focus from routes to controllers...
    What if, instead...

    View Slide

  13. - Mr. Router, 3 months ago
    “What a dumb idea”

    View Slide

  14. - Mr. Router, 2.9 months ago
    “OK I’ll give it a shot”

    View Slide

  15. - Mr. Router, 14 days ago
    “Ah OK I finally get it”

    View Slide

  16. Query Params Today (query-params-new)
    ● Controller-centric API
    App.ArticlesController = Ember.ArrayController.extend({
    queryParams: [‘sortBy’],
    sortBy: ‘publishDate’ // default value
    });

    View Slide

  17. Query Params Today (query-params-new)
    ● Extremely simple API
    ● QPs are bound to controller properties:
    update controller QP prop and URL will
    update, and vice versa
    ● No need for custom observing / coalescing
    of controller QP property changes

    View Slide

  18. QPs: Default values simplify (de)serialization
    App.ArticlesController = Ember.ArrayController.extend({
    queryParams: [‘page’],
    page: 1 // default value
    });
    ● When `page` === 1, URL is “/articles”
    ● When `page` === 2, URL is “/articles?page=2”
    ● If user clicks back button, intelligently deserialize into
    same type as default value; no need for parseInt,
    boolValue === “true”, etc

    View Slide

  19. QPs: and more...
    ● Can opt into full on transition (re-fire model
    hooks) via config hash on Ember.Route
    ● Can also control whether URL query param
    syncs use pushState/replaceState, etc

    View Slide

  20. Query Params
    ● Available at your local Ember Canary

    View Slide

  21. but...
    that’s all well and good

    View Slide

  22. Isn’t it an API smell that you’ve taken
    this thing that’s predominantly been
    the responsibility of the router to
    manage and moved it to controllers?
    - Mr. Router and other h8rz, 17 days ago

    View Slide

  23. No. It is not.
    Let’s find out why...

    View Slide

  24. What is the job of a controller anyway?
    ● Manage application state
    ● Wrap the model with additional information
    to present to the template (also application
    state)

    View Slide

  25. What is the job of the router?
    ● Navigation
    ● Provide a link between URLs and
    hierarchies of controllers / templates

    View Slide

  26. Before query params, Ember app URLs consisted
    only of the path:
    /this/is/what/the/router/manages

    View Slide

  27. But that doesn’t mean that the router should should
    also necessarily be in charge of
    /?all=of&this=stuff

    View Slide

  28. So who should be in charge of
    /?all=of&this=stuff

    View Slide

  29. Why not controllers?

    View Slide

  30. But, but, URLs… they’re tied to the router!
    ● Even URL-less Ember apps benefit greatly
    from the Ember Router
    App.Router.reopen({
    location: 'none'
    });

    View Slide

  31. URL-less Ember Apps have
    ● links between app state hierarchies (with link-to and
    transitionTo)
    ● Deeply nested templates
    ● Loading / error substates
    ● Action bubbling
    ● …and a beautiful API to manage and reason about the
    above very challenging use cases

    View Slide

  32. So the router still has its work cut out for it.

    View Slide

  33. Division of URL Labor
    ● The router serializes hierarchy into the path
    ● Controllers serialize app data into the query

    View Slide

  34. But keep in mind that query params are just one of
    many ways to serialize app state

    View Slide

  35. Serializing App State
    ● It should be easy to swap methods of serialization
    ● e.g. moving ?isActive=true to localStorage instead
    shouldn’t involve gutting your Router.map code

    View Slide

  36. Hopefully we can all agree that
    ● the following is the wrong API:
    this.transitionTo(‘current.route’, { queryParams: { page: 3 } });
    ● really you should just be able to write:
    controller.set(‘page’, 3);

    View Slide

  37. Hopefully we can all agree (cont.)
    ● transitionTo is a beautiful thing…
    ● ...when you’re actually performing a
    hierarchical transition.

    View Slide

  38. - Everyone, one week from now
    “Good for you, you’ve figured it out! So
    why haven’t you shipped?”

    View Slide

  39. Devil is in the details: property stickiness
    ● Enter a route
    ● Set controller query param properties
    ● Leave route
    ● Come back into the route…
    ● Are those QP properties remembered from before?

    View Slide

  40. Controller Property Stickiness
    ● No One Right Answer (only some wrong ones)
    ● Welcome to the Dismal Science of API Design
    ● Goal: find the missing primitive, expose it as
    something that can be configured when necessary,
    but offer a high-level convention so that
    configuration is rare

    View Slide

  41. Can it be done?
    Can we find the missing primitive?

    View Slide

  42. Controller Property Stickiness Today
    ● Route-driven controllers, once instantiated, live forever
    ● Others have shorter lifecycles, e.g. item controllers

    View Slide

  43. Controller Property Stickiness Today
    ● The sticky behavior we have today is coupled to
    controller lifecycle
    ● Very convenient for the ArticlesController at
    /articles
    ● Pretty undesirable / error-prone for the
    ArticlesShowController at /articles/some-
    title

    View Slide

  44. The Primitive?
    ● The difference between the controllers at /articles vs
    /articles/some-title is that one has a uniquely
    identifiable model
    ● Property stickiness for ArticlesController: desirable
    ● Property stickiness for ArticlesShowController:
    only desirable when its model hasn’t changed
    ● e.g. isSelected shouldn’t be preserved when switching
    b/w some-title and other-title articles

    View Slide

  45. The Primitive:
    Model Dependent State

    View Slide

  46. Model-dependent State
    ● State, accessible to controllers, tied to a specific model
    ● Store / restore controller properties scoped to the
    controller’s model
    ● e.g. remember that article with ID ‘some-title’
    isSelected, but ‘other-title’ is not

    View Slide

  47. Who consumes this primitive?
    ● It’s a pattern for caching; any form of
    serialization is a consumer, e.g.:
    ● Query Params
    ● Local Storage
    ● IndexedDB

    View Slide

  48. How is it implemented?
    ● Inject a cache object onto all controllers
    ● Each sticky property is responsible for
    computing a bucket key
    ● Replace sticky properties with bindings into
    the cache object, keyed by bucket key

    View Slide

  49. How is it implemented? (cont.)
    ● Bucket keys are computed properties; when
    they change, a new bucket is allocated (or a
    previous one is looked up)
    ● Bucket keys based on the controller’s model
    ID give us… Model Dependent State

    View Slide

  50. Sounds hard?
    ● Maybe at the low-level, but much potential for
    higher-level API
    ● Thinking about bucket keys should ideally be
    rare

    View Slide

  51. Bucket as thou wilt
    ● Full control over how a bucket is allocated
    ● Could just be a POJO (memory cache,
    forgotten on page reload)
    ● Could be your own LocalStorageProxy that
    writes into local storage as values change
    (remembered upon page reload)

    View Slide

  52. Query Params as a consumer
    ● The question of stickiness is not a query
    params concern; QPs are a layer above

    View Slide

  53. Demo

    View Slide

  54. TL;DR
    ● Decouple preserved app state from
    controller lifecycle

    View Slide

  55. Roadmap
    ● Iterate on query-params-new-new-final-7 to
    use model-dep state
    ● Later, provide higher-level API for property
    stickiness
    ● In the meantime, collect feedback from the
    folk mucking around in the bucket key
    trenches

    View Slide

  56. Roadmap
    Dr. Router’s
    Vaporwarium
    Ember Canary

    View Slide

  57. Roadmap
    ● Ship query params
    ● Explore higher level APIs / conventions for
    model-dependent state

    View Slide

  58. Q/A

    View Slide

  59. Thank you!
    @machty
    Special Thanks:
    Alex Speller
    Ray Tiley

    View Slide