Facing difficulties understanding the Ember 2.18 component code flow which is provided on the official ember guides

Can anyone help me understand how this code flows and how it will be rendered? It is not functioning well on my side and I am not able to understand the code flow.

reference

app/templates/rentals.hbs

<div class="jumbo">
  <div class="right tomster"></div>
  <h2>Welcome!</h2>
  <p>We hope you find exactly what you're looking for in a place to stay.</p>
  {{#link-to "about" class="button"}}
  About Us
  {{/link-to}}
</div>

{{#list-filter filter=(action 'filterByCity') as |filteredResults|}}
<ul class="results">
  {{#each filteredResults as |rentalUnit|}}
  <li>{{rental-listing rental=rentalUnit}}</li>
  {{/each}}
</ul>
{{/list-filter}}

app/templates/list-filter.hbs

{{input value=value
        key-up=(action 'handleFilterEntry')
        class="light"
        placeholder="Filter By City"}}
{{yield}}

app/components/list-filter.js

import Component from '@ember/component';

export default Component.extend({
    classNames: ['list-filter'],
    value: 'Hello',

    init() {
        this._super(...arguments);
        this.get('filter')('').then((results) => this.set('results', results));
    },

    actions: {
        handleFilterEntry() {
            let filterInputValue = this.get('value');
            let filterAction = this.get('filter');
            filterAction(rentals.model).then((filterResults) => this.set('results', filterResults));
        },
    }
});

app/controllers/rentals.js

import Controller from '@ember/controller';

export default Controller.extend({
    actions: {
        filterByCity(param) {
            if (param !== '') {
                return this.model.filter((i, n) => n.city === param)
            } else {
                return this.model;
            }
        }
    }
});

I am a beginner trying to learn Ember 2.18 for my office work. I am trying to create a website as per the ember guide v2.18

Hi @Muhamed_Suhail I can try to break it down a little bit. First though, I’m curious, are you looking at Ember 2.18 because that’s what your codebase at work is using? That’s unfortunate because it’s over 4 years old and things are going to look a lot different in modern guides/docs, and the newer programming model is simpler to understand as well. The Ember guides have also come a long way in terms of simplicity and beginner-friendliness and this walkthrough is kinda crazy.

That said I think we can still unpack this without too much trouble.

Let’s start with the main template at the top:

app/templates/rentals.hbs

first this bit:

<div class="jumbo">
  <div class="right tomster"></div>
  <h2>Welcome!</h2>
  <p>We hope you find exactly what you're looking for in a place to stay.</p>
  {{#link-to "about" class="button"}}
    About Us
  {{/link-to}}
</div>

This bit isn’t so bad right? It’s just HTML! Well except for the link-to. That’s an Ember component. And it’s using that weird #/ syntax which means it’s a block component. There are two ways to render a component:

  {{!-- "blockless" style --}}
  {{some-component arg1=value1 arg2=value2}}

  {{!-- "block" style --}}
  {{#some-component arg1=value1 arg2=value2}}
    {{!-- this is the block, you can put anything here! --}}
  {{/some-component}}

When you pass a block to a component the component can then decide what to do with that block e.g. where to render it (and when). The component can do this with {{yield}}. So if the template for some-component looked like this:

<div class="some-component">
  {{yield}}
</div>

and you rendered it like this:

{{#some-component}}
  <span>Div me!</span>
{{/some-component}}

you’d end up with:

<div class="some-component">
  <span>Div me!</span>
</div>

Make sense? Hopefully. Now the second half of the component:

{{!-- another block component! --}}
{{#list-filter filter=(action 'filterByCity') as |filteredResults|}}
  {{!-- this whole thing will be rendered where {{yield}} is in the list-filter component template
  <ul class="results">
    {{!-- this just loops over `filteredResults` and renders each one in a rental listing component inside an <li> tag --}}
    {{#each filteredResults as |rentalUnit|}}
      <li>{{rental-listing rental=rentalUnit}}</li>
    {{/each}}
  </ul>
{{/list-filter}}

We need to look at the components involved to understand this in more detail but so far it’s just passing a block to a component and using the each helper to loop over some stuff.

But wait! What’s this business?

{{#list-filter filter=(action 'filterByCity') as |filteredResults|}}
                                              ^ what is "as |something|"?

When you yield you can yield values to the block that you receive :exploding_head: so what does that mean? Let’s use a simpler example real quick:

// app/templates/components/cat-renderer.hbs
<div class="cat-renderer">
  {{yield "lolcat"}}
</div>

and if you rendered it like this:

{{#cat-renderer as |catType|}}
  <span class="cat">{{catType}}</span>
{{/cat-renderer}}

you’d end up with this:

// app/templates/render-me.hbs
<div class="cat-renderer">
  <span class="cat">lolcat</span>
</div>

Another way to think of blocks is like functions:

function catRenderer(block) {
  return `<div class="cat-renderer">${block('lolcat')}</div>`;
}

function renderMe() {
  return catRenderer(catName => `<span class="cat">${catName}</span>`);
}

Anyway that’s part 1. Next is the components and the business logic/actions which is definitely… less than ideal in this example. Again I think it would be a lot easier to understand what’s going on if you were looking at modern Ember but maybe that’s not possible for you.

Will follow up with more shortly!

2 Likes

Hi @dknutsen,

Thanks for a wonderful breakdown of the tutorial. You have given me a clear and concise explanation.

I was not getting the expected results when I tried to run the app. My account was on hold for sometime which delayed the publishing of my post. In the meantime, I figured out what the issue was.

The problem was with this part of the code

import Component from '@ember/component';

export default Component.extend({
    classNames: ['list-filter'],
    value: 'Hello',

    init() {
        this._super(...arguments);
        **this.get('filter')('').then((results) => this.set('results', results));**
    },

    actions: {
        handleFilterEntry() {
            let filterInputValue = this.get('value');
            let filterAction = this.get('filter');
            **filterAction(rentals.model).then((filterResults) => this.set('results', filterResults));**
        },
    }
});

As per my understanding, the Action method when called does not return a promise object. Wherein, the (.then) waits for a promise object to proceed. I solved the issue by changing the code as

import Component from '@ember/component';

export default Component.extend({
    classNames: ['list-filter'],
    value: '',

    init() {
        this._super(...arguments);
        let rentals = this.get('filter')('');
        this.set('results', results);
    },

    actions: {
        handleFilterEntry() {
            let filterInputValue = this.get('value');
            let filterAction = this.get('filter');
            let rentals = filterAction(filterInputValue);
            this.set('results', filterResults);
        },
    }
});

I am not well versed with JS concepts. I am a beginner in front-end technologies. Please let me know if my understanding is wrong.

Yes I think you’re on the right track. To be honest that tutorial/code seems pretty confusing and probably wrong. I think the tutorial has improved a lot over the years. Sorry you were confused by it! But I’m glad you got it figured out.

Thanks man! Hope my organization migrates the code to a new version soon.

Me too! It can be a lot of work to migrate that far but it’s well worth it and Ember has some great upgrade tooling and versioning.

1 Like