Ember 2.0 - Components and Routes only solving half the problem

I like many of the changes that are coming in the next major version of ember, and I feel it will remove a lot of the blurry lines that exist today between controllers, views, and components, but it still feels like Ember is missing a solution / pattern for complex interactions when a page doesn’t follow the traditional ember routing model.

To explain what I mean, let me give an example of what we have today on one of our pages. On the page itself, we have four areas. They consist of:

  • A pending items area
  • Created items filtered by type one
  • Created items filtered by type two
  • Created items filtered by type three

The pending items eventually will become one of the created items. All four of the areas load data independently of each other, and allow independent paging. A screenshot of the actual site, to visualize the groups can be seen here: Dropbox - Screenshot 2015-01-06 10.44.34.png - Simplify your life. As you scroll within each of the groups, more items are ajax loaded and that is where the trouble begins, because it pulls us out of the traditional ember routing model. Data needs to be loaded and changed without a specific route assigned to it.

Problems

Separating the page into different nested routes and resources

We can’t do this with 4 distinct areas on the page. It doesn’t fit within a traditional master/detail view and therefore there isn’t a single path to go down. That keeps us all at the same routing level. Even if we tried to somehow put this page into routes, it would make the routes very unclear as to what you are actually viewing and the state of each area on the page.

Retrieve subsequent data through the router

There isn’t a way to easily notify the router to retrieve data and then get that data back to the component. Especially when our “view” isn’t the entire store for a single model. Let me give an example.

  1. The “created items type one” is scrolled and it triggers an action to load more items
  2. The component receives this action, sets a loading flag to true, and sends an action outside the component to load more data
  3. The route receives this action and sends off a store request to load the next page and waits for the promise to fulfill

At this point we have two scenarios that could happen

  1. The promise fulfills, we had more items to load. The component observers the collection was modified, and sets loading to false. The extra items are displayed. When the component is scrolled again in the future, the events above are triggered and repeated.
  2. Or, the promise fulfills, we have no extra items to load. At this point, how would we then notify the component that there is nothing left to load, and to set loading to false? We have a disconnected state, since the data has finished loading, but the component is still showing loading.

How we handle it currently

For this reason, we are currently loading and tracking data through the components. Based on components design, I feel like this is the wrong approach, but can’t think of better solution. Previously to ember 2.0, this could have been avoided by using a controller and using render to display each one of the groups. I’m not advocating to keep controllers, I like the idea of removing them in ember 2.0, but this problem above is just one example of where more complex components are needed that don’t fit into the traditional ember design.

There have also been other times, we have needed to notify a component that something changed at the route or controller level, and have had to find a hack to notify the component through binding an arbitrary property, or through other means.

Dual-component design

Now getting to my point of this post. I feel like ember works for most situations, or we have found a work around to make it work, but the hack didn’t really follow the ember design pattern. I feel like components should be:

  • Isolated to allow them to be re-used
  • Unaware of their surroundings or context

But it feels like there are almost two types of components that are going to need to be made.

  1. A generic component that can be reused anywhere without care of its context or usage
  2. A specific component that will need to be aware of its context, possibly load data, and very specific to the apps design.

I feel like if we had the ability to communicate down to components and their children, we could avoid some of these design decisions, but how things are setup today, we have a very disconnected communication pattern for complex design.

Any suggestions / ideas to make this work within the current Ember design patterns, or is Ember going to need to change to keep components isolated and more control given at the route?

7 Likes

This is something that I think needs to be addressed. I’m curious how others are dealing with similar situations. Loading data within the routing system in Ember works wonderfully but everytime we have the need to load at a component level things start to get real awkward and hacky.

How are others solving this problem today?

1 Like

I think you have provided a good description of the nuance and some of the problems.

I think at a high level it is a problem of managing state. And there are a couple forms of state:

  • long lived and persisted state (model data loaded from server)
  • ephemeral state (flags, computed properties, etc)

So with ember we have a lot of different places where we can store and manage this state (routes, controllers, components). So it becomes a question of how concerns are separated across these state boundaries.

Because the web is inherently stateless we are having to constantly “bootstrap” or initialize this state. That is what makes the routes and router logic so complicated especially when you have multiple models or bits of information that define what the state should be at a given route/url.

I have experienced similar challenges as you described. It seems that so much complexity goes into setting the router logic. At a high level you probably have to distill down the problem into what pieces of data (model id, or state flags) are needed and when they are needed. All towards the goal of being able to reconstitute your application state from a blank slate to the middle of some state (e.g. scrolled half way through your list for example).

I think query params can be a powerful tool to define the little bits of information you need to setup state (the ids and flags). You can continually set scroll cursor positions, indexes or flags that you need to drill down further into your application state. Then look at your address bar URL, paste it into a new browser window and ask do I have enough information to programmatically get back to exactly where I am in the previous browser window? If the answer is no, then probably you need more query params and likely more logic in your route.

I suspect with ember controllers going away a lot more of the setup work is going to live in routes.

IMHO, “routable components” seems very fuzzy and I am eager to see what this looks like in practice. I think that Ember 2.0 is probably heading in the right direction. But probably a lot more clarification will be needed. Hoping the documentation improves on this front.

Sorry not much practical advice in my comments. One thing I have learned with ember is not to get too hung up on the “best pattern”, “best practice”, “only one way to do it” mindset. It becomes too crippling. To be effective I think it is important to think outside the box occasionally. So be confident in whatever workarounds you can build. Being nimble with that stuff is a boon. If something seems too clumsy or burdensome, then just look at that as a sign to reasess from a different vantage point to hopefully iterate and refine the solution.

I personally find it convenient to keep some computed properties in my models. That way I know they are available and I can pass whole models around to various components. The tricky part comes when dealing with collections of model instances. Especially when the number of potential items in a collection can be unbounded or very large. Then there is the problem of keeping track of indexes and where in the collection you are at any given time, and how to traverse the collection (either forward or backward, or laterally). I think in this case you need to figure out how to cleanly implement iterator patterns and apply that to the traversal problem.

Anyway, I am also curious to see what patterns emerge to solve the problems you describe.

Good questions!

Seems like query params would work well. You’d just have to keep a cache of current records, and only load additional records for the model which the query param changed for.

For example, you have three models on one page, and you load 20 records for each. So you can set offset1=20 and not change offset2 or offset3 and have the query params configured for refreshModel: true. Now you’d have to do some calculation against which models you need to pull-in in your model hook, and which you just pass back in (what you got from your last fetch).

This feels “Embery” enough.

Thanks for your responses. While I do think query params can help keep track of state, and would be great for knowing things like:

  1. Current Page
  2. If the group is open or closed
  3. Filter settings

It still doesn’t help with the loading issue. Each component requires data, a subset of data that is in the store, and they also have paging. When a page is requested within a component there is no route change to force that load and binding to the store doesn’t help (since we bind to a subset.) You could add a query parameter that is for loading and pass that back and forth from the route to the component, but that feels out of place to have a loading state in the url. You wouldn’t want someone to sent a url or refresh the page and have one of the components show loading with no actual data being loaded.

To better define the problem, let me explain it another way. With Ember 2.0, bindings will now be top down by default. Breaking this pattern will seem out of place if you are building components that all need two way bindings to work. With that said, the only want to get out of a component then is through actions. I like this approach using actions to send things up, and it works as expected.

Another example that I think explains this need better is, we built out a Stripe component that handles processing a credit card and contacting Stripe. It can be used in multiple places as it is context unaware and its just a wrapper for the Stripe.js library to handle everything within a single component.

Encapsulating all the functionality within a single component is great and works as expected, except we only want to process the card when the next button is clicked, and then receive the results back from Stripe (through an action on the component). The next button is outside of the component (since this is used to say next for more than just the Stripe processing). There isn’t a way to send an action down into the component to tell it to process the card. We had to make this work through some hacky data bindings, that when the binding changes, the component then processes the card.

This is where I feel like ember starts to fail. Everything we need for Stripe is encapsulated in a single component. The component doesn’t know about its surroundings and purely handles processing Stripe data. But since data is only going down and actions up, there isn’t an easy way to communicate with this isolated component to say, okay process the card, everything else on the page is ready.

Hopefully that better defines the problem and some of the limitations of ember today. Overall ember is a great solution, but communicating down to child components isn’t a viable option today. To get around this you either have to hack data bindings together, or build components that are no longer isolated from the app and are data and context aware.

Again, any additional feedback or solutions you have found would be great to hear.

2 Likes

Just had a thought, what if your child component, Stripe, sent an action up .on('init') that included a reference to itself this. Then have the parent component/controller catch that action and saved the reference for later. That way when the Next button is clicked, you have a reference to .send() an action back down to the Stripe component. I’ll try to whip up an example tomorrow if needed, bed time for me. Edit: Couldn’t resist… JS Bin - Collaborative JavaScript Debugging

@Panman8201 I have used a similar approach to that in the past. It can work, but it feels again like a hack. You have to go out of your way to create a new action, the init method, and a callback (method or action on the component). You also have to know the internal method or action to call on the component from the controller. Another alternative would be to send a method back instead of the component instance, but then you have to make sure you call that method from within the run queue.

Those solutions create a more context aware component instead of separating out concerns. Your component and controller have to know the internal actions to take on each other to properly function.

I would love to hear from the ember team if they have run into any of these kinds of problems and their solutions.

I’ve been out of Ember land for awhile, but reading over this thread I have to ask why on earth you can’t emit events downward. It would solve just about every problem raised here. I understand the restricting of certain development anti patterns but in the case of sending events “down” the chain it seems most unreasonable to prevent it.

Right @tristanpendergr, this is the point I’ve been trying to make. I feel like there are one of three possible answers to the questions posed above.

  1. How we are structuring our app and components are wrong. If we changed our design and workflow, the problems posed above would be solved. (I feel like we’ve tried to follow the ember guides and best practices, and still ran into the issues above.)
  2. There is already a pattern for sending events down or communication with child components. Hopefully this is without trying to hack around the current ember pattern and best practices. Meaning, there is a built in way that doesn’t require accesses internal methods or properties, or sending references to components up through actions. (If this is the case, hopefully the ember team can update us on the correct usage, and how they solved these issues.)
  3. Data Down Actions Up isn’t a solution for some complex situations, and DDAU either needs to change, or more options and control need to be added to allow ember developers to structure their code correctly.

I feel like number three is probably what is going to need to happen. My point of this discussion isn’t to harp on the ember team that they didn’t wrong, but to try and find a solution that will fix these issues we are running into. Ember feels like it’s 95% there for our needs, and finding the right solution for some of these complex design patterns will only make ember better overall.

I also think this is a problem. FYI core team is thinking about this, see e.g. this comment on the block params RFC.

It may end up looking something like this:

<video-player ref="my-video" src={{src}}></video-player>
<button {{action 'play' target=(ref 'my-video')}}>Play</button>
4 Likes

Also worth noting if you return something like this from the parent route here

model: function() {
  return this.store.all('item');
}

then in the route’s controller you could have the three different filtered lists:

itemsForA: Ember.computed.filterBy('items', 'type', 'A'),
itemsForB: Ember.computed.filterBy('items', 'type', 'B'),
itemsForC: Ember.computed.filterBy('items', 'type', 'B')

then in your route’s template you could render the three different components for each area

<div>
  {{area-1 items=itemsForA}}
</div>
<div>
  {{area-2 items=itemsForB}}
</div>
...

You could then have pagination/infinite scrolling controls within these area components, that send an action back up to the route, and the route would load additional items:

<!-- template.hbs -->
<div>
  {{area-2 items=itemsForB action='loadMoreItems'}}
</div>
//routes/items.js
actions: {
  loadMoreItems: function(query) {
    return this.store.find('item', {some: query})
  }
}

You could keep state related to what’s currently loaded either in the route/controller (eventually the routable component) or the individual components.

Then, after the additional records are fetched, the components will automatically display the new records (since they’re simply getting passed everything that’s in the store). You can do more work with tagging new ones to control how the new items are displayed, or use something like liquid-fire.

3 Likes

@samselikoff thanks for sharing that comment. That would definitely solve the problem mentioned above with our stripe component. The chain of events might be a little hard to track, but you could do something like:

  1. On the next button (that’s outside the Stripe components block), send an action to the Stripe component using the target ref
  2. When the Stripe component finishes it’s action, send an action up to the route
  3. The route completes its actions and transitions to the next route

As for your second response, we actually setup our route this way initially, we had the store and then three sub filters, one for each group. The problem again was how to get back down to the component if there wasn’t data loaded, to notify the component that it was done loading.

Using the idea from the ref block comment, there could be a better way to send actions based on another action. A response action might be an interesting approach. I hesitate to say a callback method, because it would probably be nested under the actions object still, but something like a generic response action, might lend itself better to allow communication up and down. 99% of the time, you only need to respond down, from an action that first came up.

A possible idea of how it could look is:

actions: 
  loadMore: ->
    @set('loading', true)
    @sendAction('toTheRoute', params, { response: 'loadComplete' })
    
responses: 
  loadComplete: -> 
   @set('loading', false)
1 Like

Makes sense.

So for now (since there’s no sane way for a parent component to target a child component), you can do what was mentioned above and have each of the {{item-area}} components register themselves with the parent controller.

<!-- the route template -->
{{item-areaA items=itemsForA
  action='loadMoreItems'
  register-as=itemAreaA}}

Then the route could “ping” the item area to let it know it has finished:

//routes/items.js
loadMoreItems: function(query) {
  this.store.find('item', {some: query}).then((model) => {
    if (model.length === 0) {
      this.get('itemAreaA').noMoreRecords();
    }
}

Does feel kind of hacky now but the idea will be the same once there’s an API introduced for it.

I go over an example of this in a blog post: Getting Ember components to respond to actions