Overview

This guide will cover converting your Services, Routes, Controllers and class-based Helpers to native class syntax. Each of these types of framework classes can be converted directly to native classes without many complications, so they should be some of the first classes you convert.

Table Of Contents

Services

Services have no particular gotchas: they can simply be converted directly to native classes. Using the Native Class Codemod plus [@classic Decorator Workflow](https://www.notion.so/classic-Decorator-Workflow-f3f30898679c4f44b33179929aa127f1), you can safely convert any existing service progressively to a fully Octane-ready definition.

<aside> 💡 For a guide to writing new Octane-ready Services, see the Ember Guides!

</aside>

Classic service

We'll start with a an example of an intentionally simple session service, which is responsible for maintaining information about the user. This has a couple features on it which our process will highlight how to

import Service from "@ember/service";
import { bool, not } from "@ember/object/computed";

const BASKET_URL = "<http://example.com/basket>";

export default Service.extend({
  basket: null,

  hasItems: bool("basket.items.length"),
  isEmpty: not("hasItems"),

  add(itemId) {
    let basket = this.get("basket");
    let body = basket
      ? JSON.stringify({
          id: basket.id,
          item: itemId
        })
      : JSON.stringify({ item: itemId });

    fetch(BASKET_URL, { method: "POST", body })
      .then(response => response.json())
      .then(basket => {
        this.set("basket", basket);
      })
      .catch(() => {
        alert("Whoops, something went wrong! Try refreshing your browser!");
      });
  },

  remove(itemId) {
    let url = `${BASKET_URL}?removeItem=${itemId}`;
    let body = JSON.stringify({ id: this.basket.id });
    fetch(url, { method: "PATCH", body })
      .then(basket => {
        this.set("basket", basket);
      })
      .catch(() => {
        alert("Whoops, something went wrong! Try refreshing your browser!");
      });
  }
});

Converted service

First, we'll add the @classic decorator to our app or add-on, by running ember install ember-classic-decorator. (You can also do this directly with npm or yarn, of course!)

Then we can run the codemod on the service. In one terminal session, start your app:

$ ember serve 

In another terminal session, run the codemod on your app, targeting services:

$ npx ember-native-class-codemod <http://localhost:4200> --type=services app/**/*.js

The output should look like this:

import Service from "@ember/service";
import { bool, not } from "@ember/object/computed";
import classic from "ember-classic-decorator";

const BASKET_URL = "<http://example.com/basket>";

@classic
export default class Basket extends Service {
  basket = null;

  @bool("basket.items.length") hasItems;
  @not("hasItems") isEmpty;

  init() {
    super.init(...arguments);
    this.set("basket", {
      id: null,
      items: []
    });
  }

  add(itemId) {
    let basketId = this.get("basket.id");
    let body = basketId
      ? JSON.stringify({
          id: basketId,
          item: itemId
        })
      : JSON.stringify({ item: itemId });

    fetch(BASKET_URL, { method: "POST", body })
      .then(response => response.json())
      .then(basket => {
        this.set("basket.id", basket.id);
        this.set("basket.items", basket.items);
      })
      .catch(() => {
        alert("Whoops, something went wrong! Try refreshing your browser!");
      });
  }

  remove(itemId) {
    let url = `${BASKET_URL}?removeItem=${itemId}`;
    let body = JSON.stringify({ id: this.basket.id });
    fetch(url, { method: "PATCH", body })
      .then(basketItems => {
        this.set("basket.items", basketItems);
      })
      .catch(() => {
        alert("Whoops, something went wrong! Try refreshing your browser!");
      });
  }
}

Now you have a native class for your service!

Cleaned up service