Passing Data/State down from component controller to view


#1

How does one go about passing data down from the upper controller down the lowest level, I started using ember today and I can not grasp this concept as most tutorials seems to assume one has some knowledge of this.


#2

The basic data flow mechanics

I’ll try and give a really broad overview and hopefully it will answer your question. lmk if it doesn’t. A very standard vanilla use case would look something like this (let’s say hypothetically you’re working on a view that will display a table of financial transactions for a single user, making this up as i go along so apologies for anything off):

  1. Route loads the data via model hook:
...
  model(params) {
    return hash({
      user: this.store.findRecord('user', params.user_id),
      transactions: this.store.query('transaction', {
        userId: params.user_id,
        fromDate: params.fromDate,
        toDate: params.toDate
      })
    });
  }
...
  1. Ember automatically injects the route’s model into the controller as “model”. That allows these properties to be accessed in the route template as, say, model.user.firstname, etc.

  2. The route template invokes one or more components which can invoke other components, passing data further and further down the chain. The data passing mechanics are very simple, e.g. in the route template imagine you rendered a “transactions-table” component and passed the transaction data to it:

{{transactios-table data=model.transactions}}

This means “I want to render the transactions table component, and give it the transactions array from my model as a property called ‘data’ that it can reference internally”.


But here is where you can go a couple different ways with it:

  1. render a component (in the route template) that takes the data and renders other components and passes the data down to them and so on
  2. Render a contextual component that accepts a block, so you render all child components in the route template

I’ll try and show two examples using the same scenario from above…

Regular components

This method would render a single component in the route template, and that component would render other components, and those components other components and so on in a hierarchy. So obviously there are some more nuances here (like tagName instead of rendering the tags, etc) and this is a very barebones table implementation but I think it works for demonstrating the concepts. Lets say in your route template you render this:

{{transactios-table data=model.transactions}}

Then in templates/components/transactions-table you have this:

<table>
  {{table-header data=data}}
  {{table-body data=data}}
</table>

And in templates/components/table-header you have:

  <thead>
    <tr>
      {{!-- very naive way to do the table header --}}
      {{#each-in data.firstObject as |key value|}}
        <th>{{key}}</th>
      {{/each-in}}
    </tr>
  </thead>

And in templates/components/table-body you have:

  <tbody>
    {{#each data as |transaction|}}
      {{table-row item=transaction}}
    {{/each}}
  </tbody>

And in templates/components/table-row:

<tr>
  {{#each-in item as |key value|}}
    {{!-- could also pull this out into a cell component if you wanted --}}
    <td>value</td>
  {{/each}}
</tr>

Contextual component

In this implementation each components still has its own template but they’re mostly just wrappers that yield blocks and then the route template actually constructs the whole table. So in your route template you’d have something like:

{{#transactions-table as |table|}}
  {{#table.header as |header|}}
    ...
    {{#header.cell}}Amount{{/header.cell}}
    {{#header.cell}}Category{{/header.cell}}
    {{#header.cell}}Account{{/header.cell}}
    ...
  {{/table.header}}
  {{#table.body as |body|}}
    {{#each model.transactions as |transaction|}}
      {{#body.row as |row|}}
        ...
        {{#row.cell}}{{transaction.amount}}{{/row.cell}}
        {{#row.cell}}{{transaction.category}}{{/row.cell}}
        {{#row.cell}}{{transaction.account}}{{/row.cell}}
        ...
      {{/body.row}}
    {{/each}}
  {{/table.body}}
{{/transactions-table}}

And the templates/components/transactions-table would look like:

<table>
  {{yield (hash
    header=(component 'table-header')
    body=(component 'table-body')
  )}}
</table>

And the templates/components/table-header would look like:

<thead>
  <tr>
    {{yield (hash
      cell=(component 'table-header-cell')
    )}}
  </tr>
</thead>

And the templates/components/table-body would look like:

<tbody>
  {{yield (hash
    row=(component 'table-body-row')
  )}}
</tbody>

And the templates/components/table-body-row would look like:

<tr>
  {{yield (hash
    cell=(component 'table-body-cell')
  )}}
</tr>

and so on… left out the cell components but hopefully you get the idea.


#3

So I will be able to pass down the state set in a controller the same way?


#4

In my contoller I am setting the following.

  saving : false,

 cellPromise.then((cell) => {

      this.setHotCell(rowIndex, columnIndex, cell);
      this.set('saving', true);
      if (newCell.value === "" || !newCell.value) {
        cell.destroyRecord().then(() => {
          this.setHotCell(rowIndex, columnIndex, "");
        });
      } else {
        cell.set('value', newCell.value);
        cell.set('cache', "...");
        cell.save().then((cell) => {
          this.setHotCell(rowIndex, columnIndex, cell);
          // this.sendAction('setSaved');
        }).catch(function(){
          this.setHotCell(rowIndex, columnIndex, '');
        });
      }
    });

I want to access the state saving on my lowest level controller/component view so that I can change a dom elements innerHTML with an {{#if saving}} Saving In Progress {{/if}

as an example


#5

Yes, I would say any state that exists in a “context”, be that a controller or a component, can be passed down into another “context” (like a component).

So if you rendered a component you could pass the “saving” value into it:

{{my-component saving=saving}}

and whenever the saving value was updated in the controller the components further down the chain would receive that change.

Is that what you’re asking?


#6

Yes, my last question how can i identify the nested components in the ember console of the browser? Just to ensure I pass it down the correct hierarchy.


#7

The easiest way is probably to install the Ember Inspector browser extension and use both the component and “View Tree” (make sure you check the box that says show components) tabs. From the view tree tab you can spit the component out to the console and use $E.get(...) on it to verify what data was passed.