Provides element modifiers that hook into specific portions of the rendering lifecycle:
didInsert— runs a callback when an element is inserted into the DOMdidUpdate— runs a callback when any of its arguments change (skips initial render)willDestroy— runs a callback just before an element is removed from the DOM
Caution
The modifiers provided in this package are ideal for quickly migrating away from classic Ember components to Glimmer components, because they largely allow you to use the same lifecycle hook methods you've already written. We strongly encourage you to avoid these modifiers in new code. Classic lifecycle hooks can be rewritten as custom modifiers.
The modifiers provided in this package are ideal for quickly migrating away from
classic Ember components to Glimmer components, because they largely allow you to
use the same lifecycle hook methods you've already written while attaching them to
these modifiers. For example, a didInsertElement hook could be called by
{{didInsert this.didInsertElement}} to ease your migration process.
However, we strongly encourage you to take this opportunity to rethink your
functionality rather than use these modifiers as a crutch. In many cases, classic
lifecycle hooks like didInsertElement can be rewritten as custom modifiers that
internalize functionality manipulating or generating state from a DOM element.
Other times, you may find that a modifier is not the right fit for that logic at all,
in which case it's worth revisiting the design to find a better pattern.
Either way, we recommend using these modifiers with caution. They are very useful for quickly bridging the gap between classic components and Glimmer components, but they are still generally an anti-pattern. We recommend considering a custom modifier in most use-cases where you might want to reach for this package.
For more on why these modifiers exist and what the modern alternatives look like, watch this talk from EmberFest 2022.
- Ember.js v4.12 or above
- Embroider or ember-auto-import v2
- Node.js v18 or above
pnpm add @ember/render-modifiersOr with npm/yarn:
npm install @ember/render-modifiers
# or
yarn add @ember/render-modifiersIn most cases, a custom modifier (or even just the component constructor) is a better fit than reaching for these lifecycle modifiers. Below are common patterns and their modern equivalents.
When you need element access, write a custom modifier with ember-modifier:
import { modifier } from 'ember-modifier';
const fadeIn = modifier((element) => {
element.classList.add('fade-in');
});
<template>
<div {{fadeIn}} class="alert">
Hello!
</div>
</template>With didInsert
import { didInsert } from '@ember/render-modifiers';
function fadeIn(element) {
element.classList.add('fade-in');
}
<template>
<div {{didInsert fadeIn}} class="alert">
Hello!
</div>
</template>A custom modifier automatically re-runs when its arguments change,
replacing both {{didInsert}} and {{didUpdate}}:
import { modifier } from 'ember-modifier';
const scrollTo = modifier((element, [position]) => {
element.scrollTop = position;
});
<template>
<div {{scrollTo @scrollPosition}} class="scroll-container">
{{yield}}
</div>
</template>With didInsert + didUpdate
import { didInsert, didUpdate } from '@ember/render-modifiers';
function setScrollPosition(element, [scrollPosition]) {
element.scrollTop = scrollPosition;
}
<template>
<div
{{didInsert setScrollPosition @scrollPosition}}
{{didUpdate setScrollPosition @scrollPosition}}
class="scroll-container"
>
{{yield}}
</div>
</template>Return a cleanup function from your custom modifier — it runs automatically when the element is removed:
import { modifier } from 'ember-modifier';
const tooltip = modifier((element) => {
const instance = createTooltip(element);
return () => {
instance.destroy();
};
});
<template>
<div {{tooltip}}>
Hover me
</div>
</template>With willDestroy
import { willDestroy } from '@ember/render-modifiers';
function teardown(element) {
// cleanup logic here
}
<template>
<div {{willDestroy teardown}}>
{{yield}}
</div>
</template>If you still need these modifiers (e.g., during migration), here is the full API.
Import the modifiers directly and use them with template tag syntax:
import { didInsert, didUpdate, willDestroy } from '@ember/render-modifiers';By default, the executed function will be unbound. If you need to access
component state (this) in your callback, use the @action decorator to bind
the method:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { didInsert, didUpdate } from '@ember/render-modifiers';
export default class ScrollContainer extends Component {
@tracked scrollPosition = 0;
@action
setScrollPosition(element, [scrollPosition]) {
this.scrollPosition = scrollPosition;
element.scrollTop = scrollPosition;
}
<template>
<div
{{didInsert this.setScrollPosition @scrollPosition}}
{{didUpdate this.setScrollPosition @scrollPosition}}
class="scroll-container"
>
{{yield}}
</div>
</template>
}In classic loose-mode templates, the modifiers are available as {{did-insert}},
{{did-update}}, and {{will-destroy}} without any imports:
Modifiers are fully typed out of the box. For loose-mode Glint support, register the modifiers in your app's type declarations:
// types/glint.d.ts
import type RenderModifiersRegistry from '@ember/render-modifiers/template-registry';
declare module '@glint/environment-ember-loose/registry' {
export default interface Registry extends RenderModifiersRegistry {
// ...
}
}See the Contributing guide for details.
This project is licensed under the MIT License.