# Add Ember Components to Discourse

**URL:** https://meta.discourse.org/t/add-ember-components-to-discourse/48891
**Category:** Developer Guides
**Tags:** ember, how-to, code
**Created:** [August 19, 2016, 6:16pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891 "2016-08-19T18:16:34Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [August 19, 2016, 6:16pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/1 "2016-08-19T18:16:34Z")

</div>

In the [previous tutorial](https://meta.discourse.org/t/creating-routes-in-discourse-and-showing-data/48827) I showed how to configure both the server and the client side parts of Discourse to respond to a request.

We now recommend you to read the Ember component documentation: [Introducing Components - Components - Ember Guides](https://guides.emberjs.com/release/components/introducing-components/)

Before writing a component, check the [UI kit](https://meta.discourse.org/t/-/411319) of shared components, helpers, and modifiers; most common controls already exist there.

> **Old tutorial**
>
> In this tutorial, I’m going to create a new [Ember Component](https://guides.emberjs.com/v2.7.0/components/defining-a-component/) as a way to wrap third party Javascript. This is going to be similar to a [YouTube](https://www.youtube.com/watch?v=S_l_DL8ysQQ) video I made a while back, which you may find informative, only this time it’s specific to Discourse and how we lay out files in our project.
> 
> ### Why Components?
> 
> [Handlebars](http://handlebarsjs.com/) is quite a simple tempting language. It’s just regular HTML along with some dynamic parts. This is simple to learn and great for productivity, but not so great for code re-use. If you’re developing a large application like Discourse, you’ll find that you want to re-use some of the same things over and over.
> 
> Components are Ember’s solution to this problem. Let’s create a component that will display our snack in a nicer way.
> 
> ### Creating a new Component
> 
> Components need to have a dash in their name. I’m going to choose `fancy-snack` as the name for this one. Let’s create our template:
> 
> **app/assets/javascripts/admin/templates/components/fancy-snack.hbs**
> 
> ```hbs
> <div class="fancy-snack-title">
> <h1>{{snack.name}}</h1>
> </div>
> 
> <div class="fancy-snack-description">
> <p>{{snack.description}}</p>
> </div>
> 
> ```
> 
> Now, to use our component, we will **replace** our existing `admin/snack` template with this:
> 
> **app/assets/javascripts/admin/templates/snack.hbs**
> 
> ```hbs
> {{fancy-snack snack=model}}
> 
> ```
> 
> We can now re-use our `fancy-snack` component in any other template, just passing in the model as required.
> 
> ### Adding Custom Javascript Code
> 
> Besides re-usability, Components in Ember are great for safely adding custom Javascript, jQuery and other external code. It gives you control of when the component is inserted into the page, and when it is removed. To do this, we define an [Ember.Component](http://emberjs.com/api/classes/Ember.Component.html) with some code:
> 
> **app/assets/javascripts/admin/components/fancy-snack.js**
> 
> ```js
> export default Ember.Component.extend({
> didInsertElement() {
> this._super();
> this.$().animate({ backgroundColor: "yellow" }, 2000);
> },
> 
> willDestroyElement() {
> this._super();
> this.$().stop();
> },
> });
> 
> ```
> 
> If you add the above code and refresh the page, you’ll see that our snack has an animation of a slowly fading yellow background.
> 
> Let’s explain what’s going on here:
> 
> 1. When the component is rendered on the page it will call `didInsertElement`
> 
> 2. The first line of `didInsertElement` (and `willDestroyElement`) is `this._super()` which is necessary because we’re [subclassing Ember.Component](https://guides.emberjs.com/v1.10.0/object-model/classes-and-instances/).
> 
> 3. The animation is done using [jQuery’s animate](http://api.jquery.com/animate/) function.
> 
> 4. Finally, the animation is cancelled in the `willDestroyElement` hook, which is called when the component is removed from the page.
> 
> You might wonder why we care about `willDestroyElement` at all; the reason is in a long lived Javascript application like Discourse it’s important to clean up after ourselves, lest we leak memory or leave things running. In this case we stop the animation, which tells any jQuery timers that they needn’t fire any more as the component is no longer visible on the page.

### Where to go from here

The [final tutorial](https://meta.discourse.org/t/write-ember-acceptance-and-component-tests-for-discourse/49167) in this series covers automated testing.

* * *

This document is version controlled - suggest changes [on github](https://github.com/discourse/discourse/blob/main/docs/developer-guides/docs/03-code-internals/01-ember-components.md).

---

<div class="post-metadata">

### Author: ![gor](https://avatars.discourse-cdn.com/v4/letter/g/5f9b8f/32.png) [@gor](https://meta.discourse.org/u/gor)
#### Post date: [November 14, 2016, 4:35pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/2 "2016-11-14T16:35:13Z")

</div>

Hi, how do i extend a discourse component thru a plugin? Can you give me some points. Thanks

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [November 14, 2016, 4:42pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/3 "2016-11-14T16:42:01Z")

</div>

Generally we prefer you don’t extend Discourse plugins, and you stick to plugin outlets or using the widget decoration API to add stuff.

But if you must, you can create an initializer and use Ember’s `extend` code. [Here’s an example](https://github.com/discourse/discourse/blob/master/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6#L41) that extends an Ember object.

---

<div class="post-metadata">

### Author: ![gor](https://avatars.discourse-cdn.com/v4/letter/g/5f9b8f/32.png) [@gor](https://meta.discourse.org/u/gor)
#### Post date: [November 17, 2016, 1:39pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/4 "2016-11-17T13:39:33Z")

</div>

Tried with initializer but didnt worked. What i actually want to do is to add 2 more classNames and some actions:

```plaintext
import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  // extend component from jsapp/components/topic-list.js.es6
  const TopicList = api.container.lookupFactory('component:topic-list');

  TopicList.extend({
    classNames: ['topic-list', 'round', 'table'],
    actions: {
        clickMe: function() {
            console.log('click');
        }
    }
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};

```

And by “didnt worked”, i mean that topic list completly disappeared from page.  
Thank you

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [November 17, 2016, 5:55pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/5 "2016-11-17T17:55:01Z")

</div>

> [@gor](#):
>
> i mean that topic list completly disappeared from page.

Were there any logs in the console?

---

<div class="post-metadata">

### Author: ![gor](https://avatars.discourse-cdn.com/v4/letter/g/5f9b8f/32.png) [@gor](https://meta.discourse.org/u/gor)
#### Post date: [November 17, 2016, 8:57pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/6 "2016-11-17T20:57:44Z")

</div>

Nope, no logs at all. However i managed to fix it this way. I hope it will help someone.

```plaintext
import { default as TopicList } from 'discourse/components/topic-list';
import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  TopicList.reopen({
    classNames: ['topic-list', 'round', 'table'],
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};

```

---

<div class="post-metadata">

### Author: ![LeoMcA](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/leomca/32/87233_2.png) [@LeoMcA](https://meta.discourse.org/u/LeoMcA)
#### Post date: [November 19, 2016, 10:52pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/7 "2016-11-19T22:52:13Z")

</div>

I just ran into the same problem. Add the following as a css/html customisation and observe empty user cards:

```plaintext
<script type="text/discourse-plugin" version="0.5">
    api.container.lookupFactory('component:user-card-contents')
</script>

```

---

<div class="post-metadata">

### Author: ![thoka](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/thoka/32/115652_2.png) [@thoka](https://meta.discourse.org/u/thoka)
#### Post date: [March 17, 2025, 7:54pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/13 "2025-03-17T19:54:23Z")

</div>

It would be nice to see updated documentation here, which could point to [glimmer component](https://rfcs.emberjs.com/id/0416-glimmer-components/) documentation.

---

<div class="post-metadata">

### Author: ![Lilly](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilly/32/575047_2.png) [@Lilly](https://meta.discourse.org/u/Lilly)
#### Post date: [December 1, 2025, 7:28pm UTC](https://meta.discourse.org/t/add-ember-components-to-discourse/48891/14 "2025-12-01T19:28:29Z")

</div>


