Start Date Release Date Release Versions PR link Tracking Link Stage Teams
  • Framework
  • TypeScript

Deprecate named inject export from @ember/service


As of ember-source@4.1 (and RFC#752), inject is an old alias that's no longer needed


import { service } from '@ember/service' makes more sense than import { inject as service } from '@ember/service'

This allows us to slim down our public API surface area to more of what's needed.

Transition Path

Most folks can do a mass find and replace switch from inject as service to just service.

An example codemod could look something like this

export const parser = 'ts'

export default function transformer(file, api) {
  const j = api.jscodeshift;

  const importNames = new Set();

  const root = j(file.source);

  // find things we want to get rid of
    .forEach(path => {
      if ( === 'inject') {

  // now it's time to replace
  root.find(j.ClassProperty).forEach(path => {
    let node = path.node;

    let hasInject = hasDecorators(node, [...importNames.values()]);

    if (!hasInject) return;

    node.decorators = => {
      let { expression } = decorator;

      if (expression.type === 'Identifier') {
        if (importNames.has( {
          decorator.expression = j.identifier('service');

      if (expression.type === 'CallExpression') {
        decorator.expression.callee = j.identifier('service');

      return decorator;

  return root.toSource();

// Copied from:
function firstMatchingDecorator(node, named = []) {
  if (!node.decorators) return;

  return node.decorators.find((decorator) => {
    let { expression } = decorator;

    switch (expression.type) {
      case 'MethodDefinition': {
      case 'CallExpression': {
        let { callee } = expression;

        switch (callee.type) {
          case 'Identifier':
            return named.includes(;
          case 'MemberExpression': {
            let { object } = callee;

            return named.includes(;
      case 'Identifier':
        return named.includes(;

function hasDecorators(node, named = []) {
  return Boolean(firstMatchingDecorator(node, named));

The test scenarios

import { inject } from '@ember/service';
import { inject as service } from '@ember/service';
// import Service from '@ember/service';
import BaseService from '@ember/service';
import { inject as serviceDecorator } from '@ember/service';
import { inject as x } from '@ember/service';
// import { service } from '@ember/service';
import { service as y } from '@ember/service';
// import Service, { inject, service } from '@ember/service';
import Service, { inject as s } from '@ember/service';

export default class Demo extends Service {


export default class Demo2 extends BaseService {
  // simple
  @inject router;
  @service router1;
  @x router2;
  @y router3;
  @serviceDecorator router4;
  @inject('router') router41;

  // TS-only
  @inject declare router5: Type;
  @inject('router') declare router51: Type;
  @service declare router6: Type;
  @x declare router7: Type;
  @y declare router8: Type;
  @serviceDecorator declare router9: Type;

How We Teach This

The docs / guides already use the new import path.


As with any deprecation, we introduce an upgrade cliff for addons that are updated infrequently, and consequently their consuming apps. As a mitigation, we could, for v1 addons, add an additional transform to ember-cli-babel to automatically upgrade inject from @ember/service to service. This does narrow the range a bit, as service was introduced in ember-source@4.1, so libraries could not support from 3.28 to 6 (or whichever major ends up removing the inject) without adding @embroider/macros to conditionally import inject or service based on the consumer's ember-source version.


do nothing, the cost of an export alias is:

  • a few extra bytes
  • mental gymnastics for teaching
  • "another case to cover" for tooling

add a lint against inject

  • all the downsides of the above ("do nothing") may still be present

Unresolved questions