Introducing Components

You could put all of your application HTML into a single file, but in practice, you'll probably want to break it apart into smaller files.

In Ember, those smaller pieces are called components.

Let's start with the sample HTML for a messaging app (that we introduced in the previous chapter, if you're reading the guides in order):

<div class="messages">
  <aside>
    <div class="avatar is-active" title="Tomster's avatar">T</div>
  </aside>
  <section>
    <h4 class="username">
      Tomster
      <span class="local-time">their local time is 4:56pm</span>
    </h4>

    <p>
      Hey Zoey, have you had a chance to look at the EmberConf brainstorming doc
      I sent you?
    </p>
  </section>

  <aside class="current-user">
    <div class="avatar" title="Zoey's avatar">Z</div>
  </aside>
  <section>
    <h4 class="username">Zoey</h4>

    <p>Hey!</p>

    <p>
      I love the ideas! I'm really excited about where this year's EmberConf is
      going, I'm sure it's going to be the best one yet. Some quick notes:
    </p>

    <ul>
      <li>
        Definitely agree that we should double the coffee budget this year (it
        really is impressive how much we go through!)
      </li>
      <li>
        A blimp would definitely make the venue very easy to find, but I think
        it might be a bit out of our budget. Maybe we could rent some spotlights
        instead?
      </li>
      <li>
        We absolutely will need more hamster wheels, last year's line was
        <em>way</em> too long. Will get on that now before rental season hits
        its peak.
      </li>
    </ul>

    <p>Let me know when you've nailed down the dates!</p>
  </section>

  <form>
    <input />
    <button type="submit">
      Send
    </button>
  </form>
</div>

Breaking it into pieces

Let's take the large template and break it up into smaller pieces. We can see that there are 3 distinct parts:

  • The received message (Tomster's message).
  • The sent message (Zoey's message).
  • The "new message" input.

We'll break apart the larger HTML file into files containing each of these parts.

The Received Message

First, let's copy Tomster's message into its own component. Components go in the app/components directory.

<aside>
  <div class="avatar is-active" title="Tomster's avatar">T</div>
</aside>
<section>
  <h4 class="username">
    Tomster
    <span class="local-time">their local time is 4:56pm</span>
  </h4>

  <p>
    Hey Zoey, have you had a chance to look at the EmberConf
    brainstorming doc I sent you?
  </p>
</section>

We've just created our first component!

We can include our new component into our application by using HTML tag syntax.

<div class="messages">
  <aside>
    <div class="avatar is-active" title="Tomster's avatar">T</div>
  </aside>
  <section>
    <h4 class="username">
      Tomster
      <span class="local-time">their local time is 4:56pm</span>
    </h4>

    <p>
      Hey Zoey, have you had a chance to look at the EmberConf
      brainstorming doc I sent you?
    </p>
  </section>
  <ReceivedMessage/>

  <aside class="current-user">
    <div class="avatar" title="Zoey's avatar">Z</div>
  </aside>
  <section>
    <h4 class="username">Zoey</h4>

    <p>Hey!</p>

    <p>
      I love the ideas! I'm really excited about where this year's
      EmberConf is going, I'm sure it's going to be the best one yet.
      Some quick notes:
    </p>

    <ul>
      <li>
        Definitely agree that we should double the coffee budget this
        year (it really is impressive how much we go through!)
      </li>
      <li>
        A blimp would definitely make the venue very easy to find, but
        I think it might be a bit out of our budget. Maybe we could
        rent some spotlights instead?
      </li>
      <li>
        We absolutely will need more hamster wheels, last year's line
        was <em>way</em> too long. Will get on that now before rental
        season hits its peak.
      </li>
    </ul>

    <p>Let me know when you've nailed down the dates!</p>
  </section>

  <form>
    <input />
    <button type="submit">
      Send
    </button>
  </form>
</div>

A component is kind of like your own custom HTML tag. You can tell that a tag refers to an Ember component because it starts with a capital letter. Built-in HTML tags start with lowercase letters (<div>, <p>, <table>). Our component is called <ReceivedMessage>, based on its name on the file system.

Zoey says...
A component's name is derived from its file name. We capitalize the first letter and every letter after -, then remove the hyphens. This is known as pascal case.

The Sent Message

Let's do it again. We'll copy the sent message content into a new component, and then include it in our application template.

<aside class="current-user">
  <div class="avatar" title="Zoey's avatar">Z</div>
</aside>
<section>
  <h4 class="username">Zoey</h4>

  <p>Hey!</p>

  <p>
    I love the ideas! I'm really excited about where this year's
    EmberConf is going, I'm sure it's going to be the best one yet.
    Some quick notes:
  </p>

  <ul>
    <li>
      Definitely agree that we should double the coffee budget this
      year (it really is impressive how much we go through!)
    </li>
    <li>
      A blimp would definitely make the venue very easy to find, but
      I think it might be a bit out of our budget. Maybe we could
      rent some spotlights instead?
    </li>
    <li>
      We absolutely will need more hamster wheels, last year's line
      was <em>way</em> too long. Will get on that now before rental
      season hits its peak.
    </li>
  </ul>

  <p>Let me know when you've nailed down the dates!</p>
</section>
<div class="messages">
  <ReceivedMessage />

  <aside>
    <div class="avatar" title="Zoey's avatar">Z</div>
  </aside>
  <section>
    <h4 class="username">Zoey</h4>

    <p>Hey!</p>

    <p>
      I love the ideas! I'm really excited about where this year's
      EmberConf is going, I'm sure it's going to be the best one yet.
      Some quick notes:
    </p>

    <ul>
      <li>
        Definitely agree that we should double the coffee budget this
        year (it really is impressive how much we go through!)
      </li>
      <li>
        A blimp would definitely make the venue very easy to find, but
        I think it might be a bit out of our budget. Maybe we could
        rent some spotlights instead?
      </li>
      <li>
        We absolutely will need more hamster wheels, last year's line
        was <em>way</em> too long. Will get on that now before rental
        season hits its peak.
      </li>
    </ul>

    <p>Let me know when you've nailed down the dates!</p>
  </section>
  <SentMessage />

  <form>
    <input />
    <button type="submit">
      Send
    </button>
  </form>
</div>

The New Message Input

We have one last component to extract. Let's pull out the new message input.

<form>
  <input />
  <button type="submit">
    Send
  </button>
</form>

And include it in our application.hbs file.

<div class="messages">
  <ReceivedMessage />

  <SentMessage />

  <form>
    <input>
    <button type="submit">
      Send
    </button>
  </form>
  <NewMessageInput />
</div>

Breaking Components Down Further

We can use components within other components, so we can continue to break down our template into smaller pieces if we want. For instance, Tomster's avatar could be made into its own component that is then used within the <ReceivedMessage>.

<aside>
  <div class="avatar is-active" title="Tomster's avatar">T</div>
</aside>
<aside>
  <div class="avatar is-active" title="Tomster's avatar">T</div>
</aside>
<ReceivedMessageAvatar />
<section>
  <h4 class="username">
    Tomster
    <span class="local-time">their local time is 4:56pm</span>
  </h4>

  <p>
    Hey Zoey, have you had a chance to look at the EmberConf
    brainstorming doc I sent you?
  </p>
</section>

We could also extract the username from the message:

<h4 class="username">
  Tomster
  <span class="local-time">their local time is 4:56pm</span>
</h4>
<ReceivedMessageAvatar />
<section>
  <h4 class="username">
    Tomster
    <span class="local-time">their local time is 4:56pm</span>
  </h4>
  <ReceivedMessageUsername />

  <p>
    Hey Zoey, have you had a chance to look at the EmberConf
    brainstorming doc I sent you?
  </p>
</section>

We can do the same for the <SentMessage> component:

<aside class="current-user">
  <div class="avatar" title="Zoey's avatar">Z</div>
</aside>
<h4 class="username">Zoey</h4>
<SentMessageAvatar />
<section>
  <h4 class="username">Zoey</h4>
  <SentMessageUsername />

  <p>Hey!</p>

  <p>
    I love the ideas! I'm really excited about where this year's
    EmberConf is going, I'm sure it's going to be the best one yet.
    Some quick notes:
  </p>

  <ul>
    <li>
      Definitely agree that we should double the coffee budget this
      year (it really is impressive how much we go through!)
    </li>
    <li>
      A blimp would definitely make the venue very easy to find, but
      I think it might be a bit out of our budget. Maybe we could
      rent some spotlights instead?
    </li>
    <li>
      We absolutely will need more hamster wheels, last year's line
      was <em>way</em> too long. Will get on that now before rental
      season hits its peak.
    </li>
  </ul>

  <p>Let me know when you've nailed down the dates!</p>
</section>

Components can be broken down to any level, included in each other and reused.

Nesting Components in Folders

The avatar and username components are directly related to the sent and received message components. Right now, they're grouped at the top level. As you get more components, this could make a big mess! Instead, we want to group the related components together in the filesystem. We can do this by moving them into subfolders within app/components.

app/
  components/
    received-message.hbs
    received-message-avatar.hbs
    received-message-username.hbs
    received-message/
      avatar.hbs
      username.hbs
    sent-message.hbs
    sent-message-avatar.hbs
    sent-message-username.hbs
    sent-message/
      avatar.hbs
      username.hbs

We can then use the :: separator in templates to access components within a folder:

<ReceivedMessageAvatar />
<ReceivedMessage::Avatar />
<section>
  <ReceivedMessageUsername />
  <ReceivedMessage::Username />

  <p>
    Hey Zoey, have you had a chance to look at the EmberConf
    brainstorming doc I sent you?
  </p>
</section>
<SentMessageAvatar />
<SentMessage::Avatar />
<section>
  <SentMessageUsername />
  <SentMessage::Username />

  <p>Hey!</p>

  <p>
    I love the ideas! I'm really excited about where this year's
    EmberConf is going, I'm sure it's going to be the best one yet.
    Some quick notes:
  </p>

  <ul>
    <li>
      Definitely agree that we should double the coffee budget this
      year (it really is impressive how much we go through!)
    </li>
    <li>
      A blimp would definitely make the venue very easy to find, but
      I think it might be a bit out of our budget. Maybe we could
      rent some spotlights instead?
    </li>
    <li>
      We absolutely will need more hamster wheels, last year's line
      was <em>way</em> too long. Will get on that now before rental
      season hits its peak.
    </li>
  </ul>

  <p>Let me know when you've nailed down the dates!</p>
</section>

If you have a component named index.hbs, you can refer to it without the ::Index. So we can refactor app/components/received-message.hbs to app/components/received-message/index.hbs and continue to use it as <ReceivedMessage> without changing all the tags that refer to it:

app/
  components/
    received-message.hbs
    received-message/
      index.hbs
      avatar.hbs
      username.hbs

Components can be nested in multiple sub folders this way, allowing you to organize them as you see fit.

Summary

We've taken a big HTML file and broken it up into components to make the content easier to understand and maintain.

A component is a chunk of HTML that can be included in another component using HTML tag syntax.

© 2020 Yehuda Katz, Tom Dale and Ember.js contributors
Licensed under the MIT License.
https://guides.emberjs.com/v3.25.0/components/introducing-components