Route Actions

Methods on the Route Decorated With `@action`

Route Actions

Action methods on a route handle events bubbled from templates, or central concerns like global error handling and navigation.

4 min read Level 2/5 #ember#routes#actions
What you'll learn
  • Define `@action` methods on a route class
  • Trigger them from templates via the `{{on}}` modifier
  • Use built-in action hooks like `error` and `loading`

A route action is a method on the route class decorated with @action. It handles events bubbled from child templates, or hooks like error and loading. Route actions are the right home for cross-cutting concerns that span an entire URL branch.

A Route With Actions

// app/routes/posts.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { action } from '@ember/object';

export default class PostsRoute extends Route {
  @service store;
  @service notify;

  async model() {
    return this.store.findAll('post');
  }

  @action
  async saveAll() {
    const dirty = this.modelFor('posts').filter((p) => p.hasDirtyAttributes);
    await Promise.all(dirty.map((p) => p.save()));
    this.notify.success(`Saved ${dirty.length} post(s)`);
  }

  @action
  refreshList() {
    this.refresh();
  }
}

Invoke From a Template

{{! app/templates/posts.hbs }}
<button type="button" {{on "click" this.saveAll}}>Save all</button>
<button type="button" {{on "click" this.refreshList}}>Refresh</button>

{{outlet}}

Inside a route template, this is the controller — but actions defined on the route are also reachable. Most teams put actions on a controller or component instead unless they truly need to live at the route level.

Built-in Route Action Hooks

Some action names are recognized by Ember itself:

  • error(reason, transition) — handle a rejected model()
  • loading(transition) — handle a slow transition
  • willTransition(transition) — fired before leaving
  • didTransition() — fired after arriving
@action
willTransition(transition) {
  if (this.controller.hasUnsavedChanges) {
    if (!confirm('Discard unsaved changes?')) {
      transition.abort();
    }
  }
}

This pattern (a “dirty-record guard”) prevents accidental navigation away from a form with pending edits.

Component Class →