State Management With Services

Most Apps Don't Need Redux — Use a Service

State Management With Services

A service with tracked state is Ember's idiomatic global store. Combine with getters and tasks for everything most apps need.

4 min read Level 2/5 #ember#services#state
What you'll learn
  • Build a cart service with tracked items
  • Expose getters for derived state
  • Update via action methods

In React-land, global state often means Redux or Zustand. In Ember, the idiomatic answer is simpler: a service with @tracked fields. You already have DI, reactivity, and singletons — that’s all a state library is.

A Cart Service

// app/services/cart.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class CartService extends Service {
  @tracked items = [];

  get count() {
    return this.items.length;
  }

  get total() {
    return this.items.reduce((sum, i) => sum + i.price, 0);
  }

  @action
  add(item) {
    this.items = [...this.items, item];
  }

  @action
  remove(id) {
    this.items = this.items.filter((i) => i.id !== id);
  }

  @action
  clear() {
    this.items = [];
  }
}

Use From Components

import Component from '@glimmer/component';
import { service } from '@ember/service';

export default class CartBadge extends Component {
  @service cart;
}
<span>Cart: {{this.cart.count}}</span>
{{#each this.cart.items as |item|}}
  <CartRow @item={{item}} @onRemove={{this.cart.remove}} />
{{/each}}

Async + State

When mutations are async, pair the service with an ember-concurrency task:

import { task } from 'ember-concurrency';

saveTask = task({ drop: true }, async () => {
  await fetch('/api/cart', {
    method: 'POST',
    body: JSON.stringify(this.items),
  });
});

You get loading flags (this.cart.saveTask.isRunning) for free in templates.

When To Reach For More

If you find yourself wanting time-travel debugging, undo, or rich persistence, look at ember-data (for entities) or tracked-toolbox / ember-data-resources (for derived/async patterns). Most apps never need anything heavier.

Owner & getOwner →