Quantcast
Channel: Ember.JS - Latest topics
Viewing all articles
Browse latest Browse all 4827

Two tracked tales – why does one of them work?

$
0
0

@balint wrote:

Howdy,

I’m striving to understand the intricacies of tracked properties in Ember and came through the following interesting case (while rebuilding the Rock & Roll book for Octane). I’m going to show you two scenarios: both of them work but I have no idea why the second one does.

What I wanted to achieve is to have a new Band object create and have the list of all bands instantly update with the new band. Ember Data is not used at this point.

Common parts

We have a bands route and a bands.new route nested inside it.

In the bands.new controller, we create the band and add it to the catalog – and then transition back to the bands route:

export default class BandsNewController extends Controller {
  @service catalog;

  @action
  saveBand() {
    let band = new Band({ name: this.name, slug: dasherize(this.name) })
    this.catalog.add('band', band);
    this.transitionToRoute('bands');
  }
}

Now, how we render the list of bands and what we track in the catalog slightly differs, let’s see how.

Scenario 1

The catalog service is very similar to the Ember Data store. It looks like this:

import Service from '@ember/service';
import { tracked } from 'tracked-built-ins';

export default class CatalogService extends Service {
  storage = {};

  constructor() {
    super(...arguments);
    this.storage.bands = tracked([]);
    this.storage.songs = tracked([]);
  }

  add(type, record) {
    if (type === 'band') {
      this.storage.bands.push(record);
    }
    if (type === 'song') {
      this.storage.songs.push(record);
    }
  }
}

As you see, I use the excellent tracked-built-ins add-on to mark the collections (the arrays) as tracked so any operation that mutates the arrays (like the push in the add method) will cause a re-computation (re-render).

We can then iterate through the collection of bands in the catalog to display the bands in bands.hbs:

{{#each this.catalog.storage.bands as |band|}}
  <li class="mb-2">
    <LinkTo @route="bands.band.songs" @model={{band.slug}}>
      {{band.name}}
    </LinkTo>
  </li>
{{/each}}

In this case, the model hook doesn’t even need to return anything as we don’t use @model in the template to render the bands.

When a new band is created, it gets added to the bands array in the catalog via this.storage.bands.push(record). Since the array is tracked, this updates this.catalog.storage.bands in the template, and the new band appears.

Scenario 2

We can follow a more “classic” approach and render @model in the template:

{{#each @model as |band|}}
  <li class="mb-2">
    <LinkTo @route="bands.band.songs" @model={{band.slug}}>
      {{band.name}}
    </LinkTo>
  </li>
{{/each}}

We need to then return the list of bands from the corresponding route, bands:

export default class BandsRoute extends Route {
  @service catalog;

  model() {
    return this.catalog.storage.bands;
  }
}

We also make the catalog service simpler, not tracking the type-specific arrays, only the storage object itself:

import { tracked } from '@glimmer/tracking';

export default class CatalogService extends Service {
  @tracked storage = {};

  constructor() {
    super(...arguments);
    this.storage.bands = [];
    this.storage.songs = [];
  }

  add(type, record) {
    if (type === 'band') {
      this.storage.bands.push(record);
    }
    if (type === 'song') {
      this.storage.songs.push(record);
    }
  }
}

As you see, I also changed back to using the built-in @glimmer/tracking. The add method didn’t change one bit.

What’s surprising to me is that this also works. I’ve verified that the model hook in the bands route doesn’t get re-run. However, the @each loop in bands.hbs does get re-run: that’s why we see the new band appear in the list.

I don’t understand, why, though. It’s only storage itself that’s tracked, not the bands array inside it. storage is not mutated, only the bands array (when pushing a new object to it). I’m not reassigning storage or even its bands property anywhere. And yet it works.

I’m very keen to learn why that works. Thank you.

Posts: 1

Participants: 1

Read full topic


Viewing all articles
Browse latest Browse all 4827

Trending Articles