This guide is a cheat sheet for using Ember.js. The examples are for Ember 7.3 and use the <template> tag. It doesn't cover everything, but it should get you started! PRs welcome at the GitHub repository.
For in-depth information, see The Ember Guides.
API
§Arguments
§
Arguments are the values that the caller passes to a component. Read an argument in the template with @name, and in the class with this.args.name. A component cannot assign to its arguments, because the caller owns them. When the caller passes a new value, everything that reads the argument updates.
import Component from '@glimmer/component';
export default class Greeting extends Component {
get name() {
return this.args.name ?? 'stranger';
}
<template>
Hello, {{this.name}}! You are {{@age}} years old.
</template>
}
In a .gts file, a signature describes the arguments, the blocks, and the element that receives ...attributes.
import Component from '@glimmer/component';
interface Signature {
Element: HTMLParagraphElement;
Args: {
age: number;
name?: string;
};
}
export default class Greeting extends Component<Signature> {
<template>
<p ...attributes>{{@name}} is {{@age}} years old.</p>
</template>
}
Lifecycle
§
A Glimmer component has two lifecycle hooks. The constructor runs one time, when Ember creates the component. willDestroy runs one time, when Ember removes the component.
import Component from '@glimmer/component';
export default class Clock extends Component {
constructor(owner, args) {
super(owner, args);
// The component exists. Its arguments are ready. It has no DOM yet.
}
willDestroy() {
super.willDestroy();
// The component is no longer in use.
}
}
There are no hooks for render or update. To compute a value from other values, use a getter. To work with a DOM element, use a modifier.
How To
§Accessing Arguments
§
Use @name in the template. Use this.args.name in the class. A getter that reads an argument computes again when the argument changes.
import Component from '@glimmer/component';
export default class LineItem extends Component {
get total() {
return this.args.price * this.args.quantity;
}
<template>
{{@quantity}} × {{@price}} = {{this.total}}
</template>
}
Updating args
§A component cannot change its arguments. To change a value, the owner of the data also passes a function, and the component calls that function. The data goes down, and the action goes up.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
const Counter = <template>
<button type="button" {{on "click" @onIncrement}}>
{{@count}}
</button>
</template>;
export default class Scoreboard extends Component {
@tracked count = 0;
increment = () => {
this.count++;
};
<template>
<Counter @count={{this.count}} @onIncrement={{this.increment}} />
</template>
}
Do something on render
§
Use a modifier to run code when an element enters the DOM. The modifier receives the element. The modifier function comes from the ember-modifier package.
import { modifier } from 'ember-modifier';
const autofocus = modifier((element) => {
element.focus();
});
<template>
<input {{autofocus}} />
</template>
If the code does not need an element, a render hook is usually not necessary. Compute the value in a getter, and the template reads it during the render.
Do something during teardown
§A modifier can return a function. That function runs after Ember removes the element from the DOM, and before the modifier runs again. Use it to undo what the modifier did.
import { modifier } from 'ember-modifier';
const onResize = modifier((element, [callback]) => {
const observer = new ResizeObserver(callback);
observer.observe(element);
return () => observer.disconnect();
});
<template>
<div {{onResize @onResize}}>
{{yield}}
</div>
</template>
For work that does not belong to an element, use registerDestructor. The destructor runs when Ember destroys the component, and it keeps the cleanup next to the setup. The willDestroy hook does the same job.
import Component from '@glimmer/component';
import { registerDestructor } from '@ember/destroyable';
export default class Poller extends Component {
constructor(owner, args) {
super(owner, args);
const interval = setInterval(() => this.args.onTick(), 1000);
registerDestructor(this, () => clearInterval(interval));
}
}
Do something when a property changes
§
Do not watch for the change. Derive the value. A getter that reads tracked state or arguments gives a new result after one of them changes. Add @cached when the computation is expensive, so that the getter runs one time for each change.
import Component from '@glimmer/component';
import { cached, tracked } from '@glimmer/tracking';
export default class Search extends Component {
@tracked query = '';
@cached
get results() {
return this.args.items.filter((item) => item.name.includes(this.query));
}
updateQuery = (event) => {
this.query = event.target.value;
};
<template>
<input value={{this.query}} {{on "input" this.updateQuery}} />
{{#each this.results as |item|}}
{{item.name}}
{{/each}}
</template>
}
To update the DOM when a value changes, pass the value to a modifier. The modifier runs again when one of its arguments changes.
import { modifier } from 'ember-modifier';
const scrollTo = modifier((element, [position]) => {
element.scrollTop = position;
});
<template>
<div {{scrollTo @position}}>
{{yield}}
</div>
</template>
Get a reference to an element
§A modifier receives the element as its first parameter. Store the element if a method needs it later.
import Component from '@glimmer/component';
import { modifier } from 'ember-modifier';
export default class Player extends Component {
video;
setVideo = modifier((element) => {
this.video = element;
});
play = () => {
this.video.play();
};
<template>
<video src={{@src}} {{this.setVideo}}></video>
<button type="button" {{on "click" this.play}}>Play</button>
</template>
}
Redirect to a different URL
§
Inject the router service and call transitionTo with a route name or a URL. replaceWith does the same, but it replaces the current entry in the browser history.
import Component from '@glimmer/component';
import { service } from '@ember/service';
export default class LogoutButton extends Component {
@service router;
logout = () => {
this.router.transitionTo('login');
};
<template>
<button type="button" {{on "click" this.logout}}>Log out</button>
</template>
}
When the user starts the navigation, render a link with <LinkTo>.
import { LinkTo } from '@ember/routing';
<template>
<LinkTo @route="posts.show" @model={{@post}}>
{{@post.title}}
</LinkTo>
</template>
References
Handling Events
§
The on modifier adds an event listener to an element, and the handler receives the event. Use fn to pass arguments before the event. Define the handler as an arrow function in a class field, so that this is always the component.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class ProductList extends Component {
@tracked selected;
select = (product, event) => {
event.preventDefault();
this.selected = product;
};
<template>
{{#each @products as |product|}}
<a href="/products/{{product.id}}" {{on "click" (fn this.select product)}}>
{{product.name}}
</a>
{{/each}}
</template>
}
Render a component that is a value
§
Every value that is a component can be invoked with angle brackets, for example <this.Kind />, <@Kind />, or a block parameter. Attributes and modifiers work as they do on every other component: they go to the element that has ...attributes.
import Component from '@glimmer/component';
import ErrorMessage from './error-message';
import InfoMessage from './info-message';
const kinds = {
error: ErrorMessage,
info: InfoMessage,
};
export default class Message extends Component {
get Kind() {
return kinds[this.args.kind];
}
<template>
<this.Kind class="message" {{on "click" @onDismiss}} />
</template>
}
An invocation with curly braces cannot take attributes or modifiers. Use the component helper as a value to set arguments in advance, and invoke the result with angle brackets.
import Message from './message';
<template>
{{#let (component Message kind="error") as |ErrorMessage|}}
<ErrorMessage class="message" {{on "click" @onDismiss}} />
{{/let}}
</template>
Patterns
§Provider Components
§A provider component owns state and behavior, and renders no markup of its own. It yields the state and the functions, and the caller decides what to render.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class Toggle extends Component {
@tracked isOn = false;
toggle = () => {
this.isOn = !this.isOn;
};
<template>
{{yield this.isOn this.toggle}}
</template>
}
The caller receives the yielded values as block parameters.
import Toggle from './toggle';
<template>
<Toggle as |isOn toggle|>
<button type="button" {{on "click" toggle}}>
{{if isOn "On" "Off"}}
</button>
</Toggle>
</template>
Template-Only Components
§
A component without a class. It has no this and no state, so its output depends only on its arguments. A .gjs file that has only a <template> is a template-only component.
<template>
<span class="badge" ...attributes>
{{@label}}
</span>
</template>
Assign a <template> to a variable to define small components in the same file.
const Badge = <template>
<span class="badge">{{@label}}</span>
</template>;
<template>
<Badge @label="New" />
<Badge @label="Sale" />
</template>
Named Blocks
§
A component with more than one block lets the caller fill more than one place. Render each block with yield and to="name". Use has-block to find out if the caller passed a block.
<template>
<section class="card">
{{#if (has-block "header")}}
<header>{{yield to="header"}}</header>
{{/if}}
<div class="body">
{{yield to="body"}}
</div>
</section>
</template>
The caller fills each block by name.
import Card from './card';
<template>
<Card>
<:header>Ember</:header>
<:body>A framework for ambitious web developers.</:body>
</Card>
</template>
References
Contextual Components
§
A component that yields other components with some arguments already set. The component helper sets the arguments, and hash gives each component a name. The caller gets parts that already know their parent.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
const Tab = <template>
<button
type="button"
class={{if (eq @name @active) "active"}}
{{on "click" (fn @onSelect @name)}}
>
{{yield}}
</button>
</template>;
export default class Tabs extends Component {
@tracked active = this.args.initial;
select = (name) => {
this.active = name;
};
<template>
{{yield (hash Tab=(component Tab active=this.active onSelect=this.select))}}
</template>
}
The caller passes only the arguments that the parent did not set.
import Tabs from './tabs';
<template>
<Tabs @initial="one" as |tabs|>
<tabs.Tab @name="one">One</tabs.Tab>
<tabs.Tab @name="two">Two</tabs.Tab>
</Tabs>
</template>