7

Ember 2.0 has gone great lengths towards making everything a component. With routable components coming soon, controllers will probably phased out as well.

Context

However, there is a recurring problem I face when building User interface, which I don't have a satisfying pattern for so far: user interface state.

What am I taking about?

  • Selection state
  • Current focus
  • Folded/unfolded state in some tree display

Basically, any state that is not part of the actual data, yet has to be tracked on an object-by-object basis. In the past, this used to be done with Controllers, acting as proxies to models. This approach is now obsolete. The new approach is Components everywhere, for the better. Component does the bookkeeping, tracks transient state and you get actions back.

A typical pattern I have, though, is with shared state, such as a list with selectable items.

The issue

Building a list component, with the following requirements:

  • Each item can be selected.
  • Selection state changes the DOM (some class bindings).
  • User can draw a rectangle in the list component to select several items at once.
  • Ideally, the whole behavior can be abstracted out as a mixin.

Question: where does the selection flag live?

Attempts

1) Everything is a component.

I make each item a sub-component, say {{my-list-item}}. The component tracks the selection state. Problem: how can the list component update the selection state?

2) Move state out of sub-components.

Put it on the list component. Within a separate state array alongside the item list. Pros: the list has all the state it needs. Cons: it's a nightmare to keep it synced when items are added or removed from the list. And it means the sub-component have no access to the state. Or maybe I could pass it to them as a bound value?

3) Reintroducing proxies.

Coming to think of it, there is a way to share some state: put it on the models. Well, not on the actual models so as not to pollute them with local state, but by setting up an ArrayProxy that will return some ObjectProxy for every item, to hold the state.

Pros: this is the only solution I managed to implement completely. Cons: encapsulation and decapsulation of items is a hassle. Also, after a few layers of being passed around, get and set have to go through 4 ou 5 proxies, which I fear will be a problem with performance.

Also, it does not work well for mixins. Should I want to abstract out some HasSelection mixin, and a HasFoldableItems mixin, and a Sortable mixin, they all need some state.

Back to the drawing board

Is there some better pattern I have not found?

I found the following relevant questions, but that led me nowhere:

Community
  • 1
  • 1
spectras
  • 13,105
  • 2
  • 31
  • 53
  • 1
    Toran is right on track, when state gets too complex for individual components to manage, pull the state out of the ui and stick it on a service. I wrote a comment about this here: http://www.samselikoff.com/blog/comment-on-data-flow-in-ember-apps/ – Sam Selikoff Sep 20 '15 at 02:55

1 Answers1

8

Great question - I actually went to one of the core ember team members to find out and the answer currently is services. Because the component is best left stateless (for the most part) you can leverage a service to persist this state that doesn't fit into a server persisted model.

Here is a great example that Stef Penner put together showing how you might save a "email draft" in your ember app (that isn't persisted backend)

https://github.com/stefanpenner/ember-state-services

Example component for reference (from the github project above)

export default Ember.Component.extend({
  tagName: 'form',
  editEmailService: Ember.inject.service('email-edit'),
  state: Ember.computed('email', function() {
    return this.editEmailService.stateFor(this.get('email'));
  }).readOnly(),

  actions: {
    save() {
      this.get('state').applyChanges();
      this.sendAction('on-save', this.get('email'));
    },

    cancel() {
      this.get('state').discardChanges();
      this.sendAction('on-cancel', this.get('email'));
    }
  }
});
Toran Billups
  • 27,111
  • 40
  • 155
  • 268
  • Reading the state services thing right now. This approach looks very good, basically components will share state when they use the same `stateName`. However, it seems the stateName must be predefined, which means that all instances of the same component will share the same state. But I see how it would be possible to add a level of indirection (have just one service, that can create new statenames on the fly). This approach is better than all 3 I had in my question. Still, I would like to leave the question open for awhile to see if others arise. – spectras Sep 17 '15 at 16:12
  • Alright. The pointed module has a very good concept. This should really go somewhere on ember.js tutorial. It did not completely fit the use case as it is targeted at long-term UI state that needs no lifecycle management, but I could build on the idea: I made the state store an object, that the parent component injects (through an attr) into the subcomponents. **Cons**: no longer a singleton so maybe it breaks the idea a bit. **Pros**: I can couple the lifetime of the component and the state, which makes sense here. – spectras Sep 20 '15 at 02:00
  • @spectras Any chance you could provide a separate answer with your solution? – Jon Oct 02 '15 at 11:16