# ember-cli-typescript

{% hint style="success" %}

***

### TypeScript docs have moved! 🎉

This documentation is now hosted on the ember guides website here: [Using TypeScript with Ember](https://guides.emberjs.com/release/typescript/)

***

{% endhint %}

## ember-cli-typescript

This guide is designed to help you get up and running with TypeScript in an Ember app.

{% hint style="warning" %}
**This is&#x20;*****not*****&#x20;an introduction to TypeScript&#x20;*****or*****&#x20;Ember. Throughout this guide, we’ll link back to** [**the TypeScript docs**](https://www.typescriptlang.org/docs/home.html) **and** [**the Ember Guides**](https://guides.emberjs.com/release/) **when there are specific concepts that we will not explain here but which are important for understanding what we’re covering!**
{% endhint %}

To get started, check out the instructions in [Getting Started: Installation](/installation)

* If you're totally new to using TypeScript with Ember, start with [TypeScript and Ember](/ts).
* Once you have a good handle on the basics, you can dive into the guides to working with the APIs specific to [Ember](/ember) and [Ember Data](/ember-data).
* If you're working with legacy (pre-Octane) Ember and TypeScript together, you should read [the Legacy Guide](/legacy).
* Looking for type-checking in Glimmer templates? Check out [Glint](https://typed-ember.gitbook.io/glint/).

### Why TypeScript?

What is TypeScript, and why should you adopt it?

> TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.\
> —[typescriptlang.org](http://www.typescriptlang.org)

TypeScript lets you build *ambitious web applications* with confidence—so it’s a perfect fit for Ember apps!

* Get rid of `undefined is not a function` and `null is not an object` once and for all.
* Enjoy API docs… that are always up-to-date.
* Experience better developer productivity through top-notch editor support, including incredible autocomplete, guided refactorings, automatic imports, and more.


# Installation

You can simply `ember install` the dependency like normal:

```bash
ember install ember-cli-typescript@latest
```

All dependencies will be added to your `package.json`, and you're ready to roll!

**If you're upgrading from a previous release, see (./upgrade-notes.md).**

Installing ember-cli-typescript modifies your project in two ways:

* installing a number of other packages to make TypeScript work in your app or addon
* generating a number of files in your project

## Other packages this addon installs

We install all of the following packages at their current "latest" value, :

* `typescript`
* `ember-cli-typescript-blueprints`
* `@types/ember`
* `@types/ember-data`
* `@types/ember__*` – `@types/ember__object` for `@ember/object` etc.
* `@types/ember-data__*` – `@types/ember-data__model` for `@ember-data/model` etc.
* `@types/rsvp`

## Files this addon generates

We also add the following files to your project:

* [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
* `types/<app name>/index.d.ts` – the location for any global type declarations you need to write for you own application; see [**Using TS Effectively: Global types for your package**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/getting-started/docs/ts/using-ts-effectively/README.md#global-types-for-your-package) for information on its default contents and how to use it effectively
* `app/config/environment.d.ts` – a basic set of types defined for the contents of the `config/environment.js` file in your app; see [Environment and configuration typings](#environment-and-configuration-typings) for details


# Configuration

## `tsconfig.json`

We generate a good default [`tsconfig.json`](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/tsconfig.json), which will usually make everything *Just Work™*. In general, you may customize your TypeScript build process as usual using the `tsconfig.json` file.

However, there are a few things worth noting if you're already familiar with TypeScript and looking to make further or more advanced customizations (but *most* users can just ignore this section!):

1. The generated tsconfig file does not set `"outDir"` and sets `"noEmit"` to `true`. The default configuration we generate allows you to run editors which use the compiler without creating extraneous `.js` files throughout your codebase, leaving the compilation to ember-cli-typescript to manage.

   You *can* still customize those properties in `tsconfig.json` if your use case requires it, however. For example, to see the output of the compilation in a separate folder you are welcome to set `"outDir"` to some path and set `"noEmit"` to `false`. Then tools which use the TypeScript compiler (e.g. the watcher tooling in JetBrains IDEs) will generate files at that location, while the Ember.js/[Broccoli](https://broccoli.build) pipeline will continue to use its own temp folder.
2. Closely related to the previous point: any changes you do make to `outDir` won't have any effect on how *Ember* builds your application—we run the entire build pipeline through Babel's TypeScript support instead of through the TypeScript compiler.
3. Since your application is built by Babel, and only *type-checked* by TypeScript, we set the `target` key in `tsconfig.json` to the current version of the ECMAScript standard so that type-checking uses the latest and greatest from the JavaScript standard library. The Babel configuration in your app's `config/targets.js` and any included polyfills will determine the final build output.
4. If you make changes to the paths included in or excluded from the build via your `tsconfig.json` (using the `"include"`, `"exclude"`, or `"files"` keys), you will need to restart the server to take the changes into account: ember-cli-typescript does not currently watch the `tsconfig.json` file. For more details, see [the TypeScript reference materials for `tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

## Enabling Sourcemaps

To enable TypeScript sourcemaps, you'll need to add the corresponding configuration for Babel to your `ember-cli-build.js` file:

```typescript
const app = new EmberApp(defaults, {
  babel: {
    sourceMaps: 'inline',
  },
});
```

(Note that this *will* noticeably slow down your app rebuilds.)

If you are using [Embroider](https://github.com/embroider-build/embroider), you might need to include [devtool](https://webpack.js.org/configuration/devtool/) in your webpack configuration:

```ts
return require('@embroider/compat').compatBuild(app, Webpack, {
  packagerOptions: {
    webpackConfig: { 
      devtool: 'source-map'
    }
  }
}
```

If you're updating from an older version of the addon, you may also need to update your `tsconfig.json`. (Current versions generate the correct config at installation.) Either run `ember generate ember-cli-typescript` or verify you have the same sourcemap settings in your `tscsonfig.json` that appear in [the blueprint](https://github.com/typed-ember/ember-cli-typescript/blob/master/blueprint-files/ember-cli-typescript/files/tsconfig.json).


# TypeScript and Ember

This guide covers the common details and "gotchas" of using TypeScript with Ember. Note that we do *not* cover the use of TypeScript *or* Ember in general—for those, you should refer to the corresponding documentation:

* [TypeScript docs](https://www.typescriptlang.org/docs/index.html)
* [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
* [Ember docs](https://emberjs.com/learn/)

## Outline

* [Using TypeScript With Ember Effectively](/ts/using-ts-effectively)
* [Decorators](/ts/decorators)
* [Current limitations](/ts/current-limitations)
* [Building Addons in TypeScript](/ts/with-addons)
* [Understanding the `@types` Package Names](/ts/package-names)


# Using TypeScript With Ember Effectively

## Incremental adoption

If you are porting an existing app to TypeScript, you can install this addon and migrate your files incrementally by changing their extensions from `.js` to `.ts`. As TypeScript starts to find errors (and it usually does!), make sure to celebrate your wins—even if they're small!—with your team, especially if some people are not convinced yet. We would also love to hear your stories!

Some specific tips for success on the technical front:

First, use the *strictest* strictness settings that our typings allow (currently all strictness settings except `strictFunctionTypes`). While it may be tempting to start with the *loosest* strictness settings and then to tighten them down as you go, this will actually mean that "getting your app type-checking" will become a repeated process—getting it type-checking with every new strictness setting you enable—rather than something you do just once.

The full recommended *strictness* settings in your `"compilerOptions"` hash (which are also the settings generated by the ember-cli-typescript blueprint):

```json5
{
  "compilerOptions": {
    // Strictness settings -- you should *not* change these: Ember code is not
    // guaranteed to type check with these set to looser values.
    "strict": true,
    "noUncheckedIndexedAccess": true,

    // You should feel free to change these, especially if you are already
    // covering them via linting (e.g. with @typescript-eslint).
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
  }
}
```

A good approach is to start at your "leaf" modules (the ones that don't import anything else from your app, only Ember or third-party types) and then work your way back inward toward the most core modules that are used everywhere. Often the highest-value modules are your Ember Data models and any core services that are used everywhere else in the app – and those are also the ones that tend to have the most cascading effects (having to update *tons* of other places in your app) when you type them later in the process.

Finally, leave `"noEmitOnError": true` (the default) in the `"compilerOptions"` hash in your `tsconfig.json`. This will fail your build if you have type errors, which gives you the fastest feedback as you add types.

![example of a build error during live reload](https://user-images.githubusercontent.com/108688/38774630-7d9224d4-403b-11e8-8dbc-87dad977a4c4.gif)

## What about missing types?

There are two schools of thought on how to handle things you don't have types for as you go:

* Liberally use `any` for them and come back and fill them in later. This will let you do the strictest strictness settings but with an escape hatch that lets you say "We will come back to this when we have more idea how to handle it." This approach lets you move faster, but means you will still have lots of runtime type errors: `any` just turns the type-checker *off* for anything touching those modules. You’ll have to come back later and clean those up, and you’ll likely have more difficult refactorings to do at that time.
* Go more slowly, but write down at least minimally accurate types as you go. (This is easier if you follow the leaves-first strategy recommended above.) This is much slower going, and can feel harder because you can’t just skip over things. Once you complete the work for any given module, though, you can be confident that everything is solid and you won’t have to revisit it in the future.

There is an inherent tradeoff between these two approaches; which works best will depend on your team and your app.

## Install other types!

You'll want to use other type definitions as much as possible. The first thing you should do, for example, is install the types for your testing framework of choice: `@types/ember-mocha` or `@types/ember-qunit`. Beyond that, look for types from other addons: it will mean writing `any` a lot less and getting a lot more help both from your editor and from the compiler.

*Where can I find types?* Some addons will ship them with their packages, and work out of the box. For others, you can search for them on [Definitely Typed](https://github.com/DefinitelyTyped/DefinitelyTyped), or on npm under the `@types` namespace. (In the future we hope to maintain a list of known types; keep your eyes open!)

## The `types` directory

During installation, we create a `types` directory in the root of your application and add a `"paths"` mapping that includes that directory in any type lookups TypeScript tries to do. This is convenient for a few things:

* global types for your package (see the next section)
* writing types for third-party/`vendor` packages which do not have any types
* developing types for an addon which you intend to upstream later

These are all fallbacks, of course, you should use the types supplied directly with a package

### Global types for your package

At the root of your application or addon, we include a `types/<your app>` directory with an `index.d.ts` file in it. Anything which is part of your application but which must be declared globally can go in this file. For example, if you have data attached to the `Window` object when the page is loaded (for bootstrapping or whatever other reason), this is a good place to declare it.

In the case of applications (but not for addons), we also automatically include declarations for Ember's prototype extensions in this `index.d.ts` file, with the `Array` prototype extensions enabled and the `Function` prototype extensions commented out. You should configure them to match your own config (which we cannot check during installation). If you are [disabling Ember's prototype extensions](https://guides.emberjs.com/v2.18.0/configuring-ember/disabling-prototype-extensions/), you can remove these declarations entirely; we include them because they're enabled in most Ember applications today.

### Environment configuration typings

Along with the @types/ files mentioned above, ember-cli-typescript adds a starter interface for `config/environment.js` in `app/config/environment.d.ts`. This interface will likely require some changes to match your app.

We install this file because the actual `config/environment.js` is (a) not actually identical with the types as you inherit them in the content of an application, but rather a superset of what an application has access to, and (b) not in a the same location as the path at which you look it up. The actual `config/environment.js` file executes in Node during the build, and Ember CLI writes its result as `<my-app>/config/environment` into your build for consumption at runtime.

## String-keyed lookups

Ember makes heavy use of string-based APIs to allow for a high degree of dynamicism. With some limitations, you can nonetheless use TypeScript very effectively to get auto-complete/IntelliSense as well as to accurately type-check your applications.

A few of the most common speed-bumps are listed here to help make this easier:

### Nested keys in `get` or `set`

In general, `this.get` and `this.set` will work as you'd expect *if* you're doing lookups only a single layer deep. Things like `this.get('a.b.c')` don't (and can't ever!) type-check; see the blog posts for a more detailed discussion of why.

The workaround is simply to do one of two things:

1. **The type-safe approach.** This *will* typecheck, but is both ugly and only works \*if there are no `null`s or `undefined`s along the way. If `nested` is `null` at runtime, this will crash!

   ```typescript
   import { get } from '@ember/object';

   // -- Type-safe but ugly --//
   get(get(get(someObject, 'deeply'), 'nested'), 'key');
   ```
2. **Using `// @ts-ignore`.** This will *not do any type-checking*, but is useful for the cases where you are intentionally checking a path which may be `null` or `undefined` anywhere long it.

   ```typescript
   // @ts-ignore
   get(someObject, 'deeply.nested.key');
   ```

   It's usually best to include an explanation of *why* you're ignoring a lookup!

### Service and controller injections

Ember does service and controller lookups with the `inject` functions at runtime, using the name of the service or controller being injected up as the default value—a clever bit of metaprogramming that makes for a nice developer experience. TypeScript cannot do this, because the name of the service or controller to inject isn't available at compile time in the same way.

The officially supported method for injections with TypeScript uses *decorators*.

```typescript
// my-app/services/my-session.ts
import Service from '@ember/service';
import RSVP from 'rsvp';

export default class MySession extends Service {
  login(email: string, password: string): RSVP.Promise<string> {
    // login and return the confirmation message
  }
}

declare module '@ember/service' {
  interface Registry {
    'my-session': MySession;
  }
}
```

Then we can use the service as we usually would with a decorator, but adding a type annotation to it so TypeScript knows what it's looking at:

```typescript
// my-app/components/user-profile.ts
import Component from '@ember/component';
import { inject as service } from '@ember/service';

import MySession from 'my-app/services/my-session';

export default class UserProfile extends Component {
  @service declare mySession: MySession;

  login(email: string, password: string) {
    this.mySession.login(email, password);
  }
}
```

Note that we need the `MySession` type annotation this way, but we *don't* need the string lookup (unless we're giving the service a different name than the usual on the class, as in Ember injections in general). Without the type annotation, the type of `session` would just be `any`. This is because decorators are not allowed to modify the types of whatever they decorate. As a result, we wouldn't get any type-checking on that `session.login` call, and we wouldn't get any auto-completion either. Which would be really sad and take away a lot of the reason we're using TypeScript in the first place!

Also notice [the `declare` property modifier](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier). This tells TypeScript that the property will be configured by something outside the class (in this case, the decorator), and guarantees it emits spec-compliant JavaScript.

(This also holds true for all other service injections, computed property macros, and Ember Data model attributes and relationships.)

### Earlier Ember versions

A couple notes for consumers on earlier Ember versions:

On Ember versions **earlier than 3.1**, you'll want to wrap your service type in [`ComputedProperty`](https://www.emberjs.com/api/ember/release/classes/ComputedProperty), because [native ES5 getters](https://github.com/emberjs/rfcs/blob/master/text/0281-es5-getters.md) are not available there, which means that instead of accessing the service via `this.mySession`, you would have to access it as `this.get('mySession')` or `get(this, 'mySession')`.

On Ember versions **earlier than 3.6**, you may encounter problems when providing type definitions like this:

```typescript
import Component from '@ember/component';

export default class UserProfile extends Component {
  username?: string;
}
```

When invoked via a template `{{user-profile username='example123'}}`, you would expect that `username` would have the value of `example123`, however prior to the native class feature released in Ember `3.6`, this will result in `username` being undefined.

For users who remain on Ember versions below `3.6`, please use <https://github.com/pzuraq/ember-native-class-polyfill>

### Ember Data lookups

We use the same basic approach for Ember Data type lookups with string keys as we do for service or controller injections. As a result, once you add the module and interface definitions for each model, serializer, and adapter in your app, you will automatically get type-checking and autocompletion and the correct return types for functions like `findRecord`, `queryRecord`, `adapterFor`, `serializerFor`, etc. No need to try to write out those (admittedly kind of hairy!) types; just write your Ember Data calls like normal and everything *should* just work.

The declarations and changes you need to add to your existing files are:

* Models

  ```typescript
  import Model from '@ember-data/model';

  export default class UserMeta extends Model {}

  declare module 'ember-data/types/registries/model' {
    export default interface ModelRegistry {
      'user-meta': UserMeta;
    }
  }
  ```
* Adapters

  ```typescript
  import Adapter from '@ember-data/adapter';

  export default class UserMeta extends Adapter {}

  declare module 'ember-data/types/registries/adapter' {
    export default interface AdapterRegistry {
      'user-meta': UserMeta;
    }
  }
  ```
* Serializers

  ```typescript
  import Serializer from '@ember-data/serializer';

  export default class UserMeta extends Serializer {}

  declare module 'ember-data/types/registries/serializer' {
    export default interface SerializerRegistry {
      'user-meta': UserMeta;
    }
  }
  ```
* Transforms

  ```typescript
  import Transform from '@ember-data/serializer/transform';

  export default class ColorTransform extends Transform {}

  declare module 'ember-data/types/registries/transform' {
    export default interface TransformRegistry {
      color: ColorTransform;
    }
  }
  ```

#### Opt-in unsafety

Also notice that unlike with service and controller injections, there is no unsafe fallback method by default, because there isn't an argument-less variant of the functions to use as there is for `Service` and `Controller` injection. If for some reason you want to opt *out* of the full type-safe lookup for the strings you pass into methods like `findRecord`, `adapterFor`, and `serializerFor`, you can add these declarations somewhere in your project:

```typescript
import Model from '@ember-data/model';
import Adapter from '@ember-data/adapter';
import Serializer from '@ember-data/serializer';

declare module 'ember-data/types/registries/model' {
  export default interface ModelRegistry {
    [key: string]: Model;
  }
}
declare module 'ember-data/types/registries/adapter' {
  export default interface AdapterRegistry {
    [key: string]: Adapter;
  }
}
declare module 'ember-data/types/registries/serializer' {
  export default interface SerializerRegistry {
    [key: string]: Serializer;
  }
}
```

However, we ***strongly*** recommend that you simply take the time to add the few lines of declarations to each of your `Model`, `Adapter`, and `Serializer` instances instead. It will save you time in even the short run!

#### Fixing the Ember Data `error TS2344` problem

If you're developing an Ember app or addon and *not* using Ember Data (and accordingly not even have the Ember Data types installed), you may see an error like this and be confused:

```
node_modules/@types/ember-data/index.d.ts(920,56): error TS2344: Type 'any' does not satisfy the constraint 'never'.
```

This happens because the types for Ember's *test* tooling includes the types for Ember Data because the `this` value in several of Ember's test types can include a reference to the Ember Data `Store` class.

**The fix:** add a declaration like this in a new file named `ember-data.d.ts` in your `types` directory:

```typescript
declare module 'ember-data/types/registries/model' {
  export default interface ModelRegistry {
    [key: string]: unknown;
  }
}
```

This works because (a) we include things in your types directory automatically and (b) TypeScript will merge this module and interface declaration with the main definitions for Ember Data from DefinitelyTyped behind the scenes.

If you're developing an addon and concerned that this might affect consumers, it won't. Your types directory will never be referenced by consumers at all!

### Class property setup errors

Some common stumbling blocks for people switching to ES6 classes from the traditional EmberObject setup:

* `Assertion Failed: InjectedProperties should be defined with the inject computed property macros.` – You've written `someService = inject()` in an ES6 class body in Ember 3.1+. Replace it with the `.extend` approach or by using decorators(`@service` or `@controller`) as discussed [above](#service-and-controller-injections). Because computed properties of all sorts, including injections, must be set up on a prototype, *not* on an instance, if you try to use class properties to set up injections, computed properties, the action hash, and so on, you will see this error.
* `Assertion Failed: Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.` – You failed to pass `...arguments` when you called `super` in e.g. a component class `constructor`. Always do `super(...arguments)`, not just `super()`, in your `constructor`.

## Type definitions outside `node_modules/@types`

By default, the TypeScript compiler loads all type definitions found in `node_modules/@types`. If the type defs you need are not found there and are not supplied in the root of the package you're referencing, you can register a custom value in `paths` in the `tsconfig.json` file. See the [tsconfig.json docs](http://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options) for details.


# Decorators

Ember makes heavy use of decorators, and TypeScript does not currently support deriving type information from decorators.

As a result, there are three important points that apply to *all* decorator usage in Ember:

1. Whenever using a decorator to declare a class field the framework sets up for you, you should mark it with `declare`. That includes all service and controller injections as well as all Ember Data attributes and relationships.

   Normally, TypeScript determines whether a property is definitely not `null` or `undefined` by checking what you do in the constructor. In the case of service injections, controller injections, or Ember Data model decorations, though, TypeScript does not have visibility into how instances of the class are *initialized*. The `declare` annotation informs TypeScript that a declaration is defined somewhere else, outside its scope.
2. For Ember Data Models, you will need to use the optional `?` operator on field declarations if the field is optional (`?`). See the Ember Data section of the guide for more details!
3. You are responsible to write the type correctly. TypeScript does not currently use decorator information at all in its type information. If you write `@service foo` or even `@service('foo') foo`, *Ember* knows that this resolves at runtime to the service `Foo`, but TypeScript does not and—for now—*cannot*.

   This means that you are responsible to provide this type information, and that you are responsible to make sure that the information remains correct and up to date

For examples, see the detailed discussions of the two main places decorators are used in the framework:

* [Services](/ember/services)
* [Ember Data Models](/ember-data/models)


# Current limitations

While TS already works nicely for many things in Ember, there are a number of corners where it *won't* help you out. Some of them are just a matter of further work on updating the [existing typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/ember); others are a matter of further support landing in TypeScript itself, or changes to Ember's object model.

## Some `import`s don't resolve

You'll frequently see errors for imports which TypeScript doesn't know how to resolve. **These won't stop the build from working;** they just mean TypeScript doesn't know where to find those.

Writing these missing type definitions is a great way to pitch in! Jump in `#topic-typescript` on the [Ember Community Discord server](https://discord.gg/zT3asNS) and we'll be happy to help you.

## Templates

Templates are currently totally non-type-checked. This means that you lose any safety when moving into a template context, even if using a Glimmer `Component` in Ember Octane.

Addons need to import templates from the associated `.hbs` file to bind to the layout of any components they export. The TypeScript compiler will report that it cannot resolve the module, since it does not know how to resolve files ending in `.hbs`. To resolve this, you can provide this set of definitions to `my-addon/types/global.d.ts`, which will allow the import to succeed:

```ts
declare module '\*/template' {
  import { TemplateFactory } from 'ember-cli-htmlbars';
  const template: TemplateFactory; export default template;
}


declare module 'app/templates/\*' {
  import { TemplateFactory } from 'ember-cli-htmlbars';
  const template: TemplateFactory; export default template;
}

declare module 'addon/templates/\*' {
  import { TemplateFactory } from 'ember-cli-htmlbars';
  const template: TemplateFactory; export default template;
}
```

## Invoking actions

TypeScript won't detect a mismatch between this action and the corresponding call in the template:

```ts
import Component from '@ember/component';
import { action } from '@ember/object';

export default class MyGame extends Component {
  @action turnWheel(degrees: number) {
    // ...
  }
}
```

```hbs
<button {{on "click" (fn this.turnWheel "potato")}}>
Click Me
</button>
```

Likewise, it won't notice a problem when you use the `send` method:

```ts
// TypeScript compiler won't detect this type mismatch
this.send\('turnWheel', 'ALSO-NOT-A-NUMBER'\);
```


# Building Addons in TypeScript

Building addons in TypeScript offers many of the same benefits as building apps that way: it puts an extra tool at your disposal to help document your code and ensure its correctness. For addons, though, there's one additional bonus: publishing type information for your addons enables autocomplete and inline documentation for your consumers, even if they're not using TypeScript themselves.

## Key Differences from Apps

To process `.ts` files, `ember-cli-typescript` tells Ember CLI to [register a set of Babel plugins](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/) so that Babel knows how to strip away TypeScript-specific syntax. This means that `ember-cli-typescript` operates according to the same set of rules as other preprocessors when used by other addons.

* Like other addons that preprocess source files, **`ember-cli-typescript` must be in your addon's `dependencies`, not `devDependencies`**.
* Because addons have no control over how files in `app/` are transpiled, **you cannot have `.ts` files in your addon's `app/` folder**.

## Publishing

When you publish an addon written in TypeScript, the `.ts` files will be consumed and transpiled by Babel as part of building the host application the same way `.js` files are, in order to meet the requirements of the application's `config/targets.js`. This means that no special steps are required for your source code to be consumed by users of your addon.

Even though you publish the source `.ts` files, though, by default you consumers who also use TypeScript won't be able to benefit from those types, because the TS compiler isn't aware of how `ember-cli` resolves import paths for addon files. For instance, if you write `import { foo } from 'my-addon/bar';`, the typechecker has no way to know that the actual file on disk for that import path is at `my-addon/addon/bar.ts`.

In order for your addon's users to benefit from type information from your addon, you need to put `.d.ts` *declaration files* at the location on disk where the compiler expects to find them. This addon provides two commands to help with that: `ember ts:precompile` and `ember ts:clean`. The default `ember-cli-typescript` blueprint will configure your `package.json` to run these commands in the `prepack` and `postpack` phases respectively, but you can also run them by hand to verify that the output looks as you expect.

The `ts:precompile` command will populate the overall structure of your package with `.d.ts` files laid out to match their import paths. For example, `addon/index.ts` would produce an `index.d.ts` file in the root of your package.

The `ts:clean` command will remove the generated `.d.ts` files, leaving your working directory back in a pristine state.

The TypeScript compiler has very particular rules when generating declaration files to avoid letting private types leak out unintentionally. You may find it useful to run `ember ts:precompile` yourself as you're getting a feel for these rules to ensure everything will go smoothly when you publish.

## Linking Addons

Often when developing an addon, it can be useful to run that addon in the context of some other host app so you can make sure it will integrate the way you expect, e.g. using [`yarn link`](https://yarnpkg.com/en/docs/cli/link#search) or [`npm link`](https://docs.npmjs.com/cli/link).

When you do this for a TypeScript addon, the source files will be picked up in the host app build and everything will execute at runtime as you'd expect. If the host app is also using TypeScript, though, it won't be able to resolve imports from your addon by default, for the reasons outlined above in the Publishing section.

You could run `ember ts:precompile` in your addon any time you change a file, but for development a simpler option is to temporarily update the `paths` configuration in the host application so that it knows how to resolve types from your linked addon.

Add entries for `<addon-name>` and `<addon-name>/*` in your `tsconfig.json` like so:

```javascript
compilerOptions: {
  // ...other options
  paths: {
    // ...other paths, e.g. for your app/ and tests/ trees
    // resolve: import x from 'my-addon';
    "my-addon": [
      "node_modules/my-addon/addon"
    ],
    // resolve: import y from 'my-addon/utils/y';
    "my-addon/*": [
      "node_modules/my-addon/addon/*"
    ]
  }
}
```

## In-Repo Addons

[In-repo addons](https://ember-cli.com/extending/#detailed-list-of-blueprints-and-their-use) work in much the same way as linked ones. Their `.ts` files are managed automatically by `ember-cli-typescript` in their `dependencies`, and you can ensure imports resolve correctly from the host by adding entries in `paths` in the base `tsconfig.json` file.

```javascript
compilerOptions: {
  // ...other options
  paths: {
    // ...other paths, e.g. for your tests/ tree
    "my-app": [
      "app/*",
      // add addon app directory that will be merged with the host application
      "lib/my-addon/app/*"
    ],
    // resolve: import x from 'my-addon';
    "my-addon": [
      "lib/my-addon/addon"
    ],
    // resolve: import y from 'my-addon/utils/y';
    "my-addon/*": [
      "lib/my-addon/addon/*"
    ]
  }
}
```

One difference as compared to regular published addons: you know whether or not the host app is using `ember-cli-typescript`, and if it is, you can safely put `.ts` files in an in-repo addon's `app/` folder.


# Understanding the @types Package Names

You may be wondering why the packages added to your `package.json` and described in [**Installation: Other packages this addon installs**](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs) are named things like `@types/ember__object` instead of something like `@types/@ember/object`. This is a conventional name used to allow both the compiler and the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) publishing infrastructure ([types-publisher](https://github.com/Microsoft/types-publisher)) to handle scoped packages, documented under [**What about scoped packages?**](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master#what-about-scoped-packages) in [the DefinitelyTyped README](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master).

See also:

* [Microsoft/types-publisher#155](https://github.com/Microsoft/types-publisher/issues/155)
* [Microsoft/Typescript#14819](https://github.com/Microsoft/TypeScript/issues/14819)


# Working With Ember

In this section, we cover how to use TypeScript effectively with specific Ember APIs (anything you'd find under the `@ember` package namespace).

We do *not* cover general usage of Ember; instead, we assume that as background knowledge. Please see the Ember [Guides](https://guides.emberjs.com/release/) and [API docs](https://api.emberjs.com/ember/release)!

## Outline

* [Components](/ember/components)
* [Services](/ember/services)
* [Routes](/ember/routes)
* [Controllers](/ember/controllers)
* [Helpers](/ember/helpers)
* [Testing](/ember/testing)


# Components

{% hint style="info" %}
New to Ember or the Octane edition specifically? You may want to read [the Ember Guides’ material on `Component`s](https://guides.emberjs.com/release/components/) first!
{% endhint %}

Glimmer Components are defined in one of three ways: with templates only, with a template and a backing class, or with only a backing class (i.e. a `yield`-only component). When using a backing class, you get a first-class experience using TypeScript! For type-checking Glimmer templates as well, see [Glint](https://typed-ember.gitbook.io/glint/).

## A simple component

A *very* simple Glimmer component which lets you change the count of a value might look like this:

```
<button {{on "click" this.minus}}>&minus;</button>
{{this.count}}
<button {{on "click" this.plus}}>+</button>
```

```typescript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class Counter extends Component {
  @tracked count = 0;

  @action plus() {
    this.count += 1;
  }

  @action minus() {
    this.count -= 1;
  }
}
```

Notice that there are no type declarations here – but this *is* actually a well-typed component. The type of `count` is `number`, and if we accidentally wrote something like `this.count = "hello"` the compiler would give us an error.

## Adding arguments and giving them a type

So far so good, but of course most components aren’t quite this simple! Instead, they’re invoked by other templates and they can invoke other components themselves in their own templates.

Glimmer components can receive both *arguments* and *attributes* when they are invoked. When you are working with a component’s backing class, you have access to the arguments but *not* to the attributes. The arguments are passed to the constructor, and then available as `this.args` on the component instance afterward.

Since the implementation of [RFC 748](https://github.com/emberjs/rfcs/pull/748), Glimmer and Ember components accept a `Signature` type parameter as part of their definition. This parameter is expected to be an object type with (up to) three members: `Args`, `Element` and `Blocks`.

`Args` represents the arguments your component accepts. Typically this will be an object type mapping the names of your args to their expected type. For example:

```
export interface MySignature {
  Args: {
    arg1: string;
    arg2: number;
    arg3: boolean;
  }
}
```

If no `Args` key is specified, it will be a type error to pass any arguments to your component. You can read more about `Element` and `Block` in the Glint [Component Signatures documentation](https://typed-ember.gitbook.io/glint/using-glint/ember/component-signatures).

Let’s imagine a component which just logs the names of its arguments when it is first constructed. First, we must define the Signature and pass it into our component, then we can use the `Args` member in our Signature to set the type of `args` in the constructor:

```typescript
import Component from '@glimmer/component';

const log = console.log.bind(console);

export interface ArgsDisplaySignature {
  Args: {
    arg1: string;
    arg2: number;
    arg3: boolean;
  }
}

export default class ArgsDisplay extends Component<ArgsDisplaySignature> {
  constructor(owner: unknown, args: ArgsDisplaySignature['Args']) {
    super(owner, args);

    Object.keys(args).forEach(log);
  }
}
```

{% hint style="info" %}
If you’re used to the classic Ember Object model, there are two important differences in the constructor itself:

* we use `super` instead of `this._super`
* we *must* call `super` before we do anything else with `this`, because in a subclass `this` is set up by running the superclass's constructor first (as implied by [the JavaScript spec](https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation))
  {% endhint %}

Notice that we have to start by calling `super` with `owner` and `args`. This may be a bit different from what you’re used to in Ember or other frameworks, but is normal for sub-classes in TypeScript today. If the compiler just accepted any `...arguments`, a lot of potentially *very* unsafe invocations would go through. So, instead of using `...arguments`, we explicitly pass the *specific* arguments and make sure their types match up with what the super-class expects.

{% hint style="info" %}
This might change in the future! If TypeScript eventually adds [support for “variadic kinds”](https://github.com/Microsoft/TypeScript/issues/5453), using `...arguments` could become safe.
{% endhint %}

The types for `owner` here and `args` line up with what the `constructor` for Glimmer components expect. The `owner` is specified as `unknown` because this is a detail we explicitly *don’t* need to know about. The `args` are the `Args` from the Signature we defined.

The `args` passed to a Glimmer Component [are available on `this`](https://github.com/glimmerjs/glimmer.js/blob/2f840309f013898289af605abffe7aee7acc6ed5/packages/%40glimmer/component/src/component.ts#L12), so we could change our definition to return the names of the arguments from a getter:

```typescript
import Component from '@glimmer/component';

export interface ArgsDisplaySignature {
  Args: {
    arg1: string;
    arg2: number;
    arg3: boolean;
  }
}

export default class ArgsDisplay extends Component<ArgsDisplaySignature> {
  get argNames(): string[] {
    return Object.keys(this.args);
  }
}
```

```
<p>The names of the <code>@args</code> are:</p>
<ul>
  {{#each this.argNames as |argName|}}
    <li>{{argName}}</li>
  {{/each}}
</ul>
```

### Understanding `args`

Now, looking at that bit of code, you might be wondering how it knows what the type of `this.args` is. In the `constructor` version, we explicitly *named* the type of the `args` argument. Here, it seems to just work automatically. This works because the type definition for a Glimmer component looks roughly like this:

```typescript
export default class Component<Args extends {} = {}> {
  readonly args: Args;

  constructor(owner: unknown, args: Args);
}
```

{% hint style="info" %}
Not sure what’s up with `<Args>` *at all*? We highly recommend the [TypeScript Deep Dive](https://basarat.gitbooks.io/typescript/) book’s [chapter on generics ](https://basarat.gitbooks.io/typescript/docs/types/generics.html)to be quite helpful in understanding this part.
{% endhint %}

The type signature for Component, with `Args extends {} = {}`, means that the component *always* has a property named `args` —

* with the type `Args`
* which can be anything that extends the type `{}` – an object
* and *defaults* to being just an empty object – `= {}`

This is analogous to the type of `Array` : since you can have an array of `string` , or an array of `number` or an array of `SomeFancyObject` , the type of array is `Array<T>` , where `T` is the type of thing in the array, which TypeScript normally figures out for you automatically at compile time:

```typescript
let a = [1, 2, 3];  // Array<number>
let b = ["hello", "goodbye"]; // Array<string>
```

In the case of the Component, we have the types the way we do so that you can’t accidentally define `args` as a string, or `undefined` , or whatever: it *has* to be an object. Thus, `Component<Args extends {}>` . But we also want to make it so that you can just write `extends Component` , so that needs to have a default value. Thus, `Component<Args extends {} = {}>`.

### Giving `args` a type

Now let’s put this to use. Imagine we’re constructing a user profile component which displays the user’s name and optionally an avatar and bio. The template might look something like this:

```
<div class='user-profile' ...attributes>
  {{#if this.avatar}}
    <img src={{this.avatar}} class='user-profile__avatar'>
  {{/if}}
  <p class='user-profile__bio'>{{this.userInfo}}</p>
</div>
```

Then we could capture the types for the profile with an interface representing the *arguments*:

```typescript
import Component from '@glimmer/component';
import { generateUrl } from '../lib/generate-avatar';

interface User {
  name: string;
  avatar?: string;
  bio?: string;
}

export default class UserProfile extends Component<User> {
  get userInfo(): string {
    return this.args.bio ? `${this.args.name} ${this.args.bio}` : this.args.name;
  }

  get avatar(): string {
    return this.args.avatar ?? generateUrl();
  }
}
```

Assuming the default `tsconfig.json` settings (with `strictNullChecks: true`), this wouldn't type-check if we didn't *check* whether the `bio` argument were set.

## Generic subclasses

If you'd like to make your *own* component subclass-able, you need to make it generic as well.

{% hint style="warning" %}
Are you sure you want to provide an inheritance-based API? Oftentimes, it's easier to maintain (and involves less TypeScript hoop-jumping) to use a compositional API instead. If you're sure, here's how!
{% endhint %}

```typescript
import Component from '@glimmer/component';

export interface FancyInputArgs {
  // ...
}

export default class FancyInput<Args extends FancyInputArgs = FancyInputArgs> extends Component<Args> {
  // ...
}
```

Requiring that `Args extends FancyInputArgs` means that subclasses can have *more* than these args, but not *fewer*. Specifying that the `Args = FancyInputArgs` means that they *default* to just being `FancyInputArgs`, so users don't need to supply an explicit generic type parameter here unless they're adding more arguments to the class.


# Services

Ember Services are global singleton classes that can be made available to different parts of an Ember application via dependency injection. Due to their global, shared nature, writing services in TypeScript gives you a build-time-enforcable API for some of the most central parts of your application.

{% hint style="info" %}
If you are not familiar with Services in Ember, first make sure you have read and understood the [Ember Guide on Services](https://guides.emberjs.com/release/services/)!
{% endhint %}

## A basic service

Let's take this example from the [Ember Guide](https://guides.emberjs.com/release/services/):

```typescript
import { A } from '@ember/array';
import Service from '@ember/service';

export default class ShoppingCartService extends Service {
  items = A([]);

  add(item) {
    this.items.pushObject(item);
  }

  remove(item) {
    this.items.removeObject(item);
  }

  empty() {
    this.items.clear();
  }
}
```

Just making this a TypeScript file gives us some type safety without having to add any additional type information. We'll see this when we use the service elsewhere in the application.

{% hint style="info" %}
When working in Octane, you're better off using a `TrackedArray` from [tracked-built-ins](https://github.com/pzuraq/tracked-built-ins) instead of the classic EmberArray:

```typescript
import { TrackedArray } from 'tracked-built-ins';
import Service from '@ember/service';

export default class ShoppingCartService extends Service {
  items = new TrackedArray();

  add(item) {
    this.items.push(item);
  }

  remove(item) {
    this.items.splice(1, this.items.findIndex((i) => i === item));
  }

  empty() {
    this.items.clear();
  }
}
```

Notice that here we are using only built-in array operations, not Ember's custom array methods.
{% endhint %}

## Using services

You can use a service in any container-resolved object such as a component or another service. Services are injected into these objects by decorating a property with the `inject` decorator. Because decorators can't affect the type of the property they decorate, we must manually type the property. Also, we must use `declare` modifier to tell the TypeScript compiler to trust that this property will be set up by something outside this component—namely, the decorator.

Here's an example of using the `ShoppingCartService` we defined above in a component:

```typescript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

import ShoppingCartService from 'my-app/services/shopping-cart';

export default class CartContentsComponent extends Component {
  @service declare shoppingCart: ShoppingCartService;

  @action
  remove(item) {
    this.shoppingCart.remove(item);
  }
}
```

Any attempt to access a property or method not defined on the service will fail type-checking:

```typescript
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

import ShoppingCartService from 'my-app/services/shopping-cart';

export default class CartContentsComponent extends Component {
  @service declare shoppingCart: ShoppingCartService;

  @action
  remove(item) {
    // Error: Property 'saveForLater' does not exist on type 'ShoppingCartService'.
    this.shoppingCart.saveForLater(item);
  }
}
```

Services can also be loaded from the dependency injection container manually:

```typescript
import Component from '@glimmer/component';
import { getOwner } from '@ember/owner';
import { action } from '@ember/object';

import ShoppingCartService from 'my-app/services/shopping-cart';

export default class CartContentsComponent extends Component {
  get cart() {
    return getOwner(this)?.lookup('service:shopping-cart') as ShoppingCartService;
  }

  @action
  remove(item) {
    this.cart.remove(item);
  }
}
```

Here we need to cast the lookup result to `ShoppingCartService` in order to get any type-safety because the lookup return type is `any` (see caution below).

{% hint style="danger" %}
This type-cast provides no guarantees that what is returned by the lookup is actually the service you are expecting. Because TypeScript cannot resolve the lookup micro-syntax (`service:<name>`) to the service class, a typo would result in returning something other than the specified type. It only gurantees that *if* the expected service is returned that you are using it correctly.

There is a merged (but not yet implemented) [RFC](https://emberjs.github.io/rfcs/0585-improved-ember-registry-apis.html) which improves this design and makes it straightforward to type-check. Additionally, TypeScript 4.1's introduction of [template types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types) may allow us to supply types that work with the microsyntax.

For now, however, remember that *the cast is unsafe*!
{% endhint %}


# Routes

Working with Routes is in general just working normal TypeScript classes. Ember's types supply the definitions for the various lifecycle events available within route subclasses, which will provide autocomplete and type-checking along the way in general.

However, there is one thing to watch out for: the types of the arguments passed to methods will *not* autocomplete as you may expect. This is because in *general* a subclass may override a superclass method as long as it calls its superclass's method correctly. This is very bad practice, but it is legal JavaScript! This is never a concern for lifecycle hooks in Ember, because they are called by the framework itself. However, TypeScript does not and cannot know that, so we have to provide the types directly.

Accordingly, and because the `Transition` type is not currently exported as a public type, you may find it convenient to define it using TypeScript's `ReturnType` utility type, which does exactly what it sounds like and gives us a local type which is the type returned by some function. The `RouterService.transitionTo` returns a `Transition`, so we can rely on that as stable public API to define `Transition` locally ourselves:

```typescript
import Route from '@ember/routing/route';
import type RouterService from '@ember/routing/router-service';
type Transition = ReturnType<RouterService['transitionTo']>;

export default class MyRoute extends Route {
  beforeModel(transition: Transition) {
    // ...
  }
}
```

This inconsistency will be solved in the future. For now, this workaround gets the job done, and also shows the way to using this information to provide the type of the route's model to other consumers: see [Working with Route Models](/cookbook/working-with-route-models) for details!

```typescript
import Route from '@ember/routing/route';

type Resolved<P> = P extends Promise<infer T> ? T : P;

export type MyRouteModel = Resolved<ReturnType<MyRoute['model']>>;

export default class MyRoute extends Route {
  model() {
    // ...
  }
}
```

The `Resolved<T>` utility type takes in any type, and if the type is a `Promise` it transforms the type into whatever the `Promise` resolves to; otherwise it just returns the same type. (If you’re using TypeScript 4.5 or later, you can use the built-in `Awaited<T>` type, which does the same thing but more robustly: it also handles nested promises.) As we saw above, `ReturnType` gets us the return type of the function. So our final `MyRouteModel` type takes the return type from our `model` hook, and uses the `Resolved` type to get the type the promise will resolve to—that is, exactly the type we will have available as `@model` in the template and as `this.model` on a controller.&#x20;

This in turn allows us to use the route class to define the type of the model on an associated controller.

```typescript
import Controller from '@ember/controller';
import type { MyRouteModel } from '../routes/my-route';

export default class MyController extends Controller {
  declare model?: MyRouteModel;

  // ...
}
```

Notice here that the `model` is declared as optional. That’s intentional: the `model` for a given controller is *not* set when the controller is constructed (that actually happens *either* when the page corresponding to the controller is created *or* the first time a `<LinkTo>` which links to that page is rendered). Instead, the `model` is set on the controller when the corresponding route is successfully entered, via its `setupController` hook.


# Controllers

Like [routes](/ember/routes), controllers are just normal classes with a few special Ember lifecycle hooks and properties available.

The main thing you need to be aware of is special handling around query params. In order to provide type safety for query param configuration, Ember's types specify that when defining a query param's `type` attribute, you must supply one of the allowed types: `'boolean'`, `'number'`, `'array'`, or `'string'` (the default). However, if you supply these types as you would in JS, like this:

```typescript
import Controller from "@ember/controller";

export default class HeyoController extends Controller {
  queryParams = [
    {
      category: { type: "array" },
    },
  ];
}
```

Then you will see a type error like this:

```
Property 'queryParams' in type 'HeyoController' is not assignable to the same property in base type 'Controller'.
  Type '{ category: { type: string; }; }[]' is not assignable to type '(string | Record<string, string | QueryParamConfig | undefined>)[]'.
    Type '{ category: { type: string; }; }' is not assignable to type 'string | Record<string, string | QueryParamConfig | undefined>'.
      Type '{ category: { type: string; }; }' is not assignable to type 'Record<string, string | QueryParamConfig | undefined>'.
        Property 'category' is incompatible with index signature.
          Type '{ type: string; }' is not assignable to type 'string | QueryParamConfig | undefined'.
            Type '{ type: string; }' is not assignable to type 'QueryParamConfig'.
              Types of property 'type' are incompatible.
                Type 'string' is not assignable to type '"string" | "number" | "boolean" | "array" | undefined'.ts(2416)
```

This is because TS currently infers the type of `type: "array"` as `type: string`. You can work around this by supplying `as const` after the declaration:

```diff
import Controller from "@ember/controller";

export default class HeyoController extends Controller {
  queryParams = [
    {
-     category: { type: "array" },
+     category: { type: "array" as const },
    },
  ];
}
```

Now it will type-check.


# Helpers

Helpers in Ember are just functions or classes with a well-defined interface, which means they largely Just Work™ with TypeScript. However, there are a couple things you’ll want to watch out for.

{% hint style="info" %}
As always, you should start by reading and understanding the [Ember Guide on Helpers](https://guides.emberjs.com/release/templates/writing-helpers/)!
{% endhint %}

## Function-based helpers

The basic type of a helper function in Ember is:

```typescript
type FunctionBasedHelper =
  (positional: unknown[], named: Record<string, unknown>) => string | void;
```

This represents a function which *may* have an arbitrarily-long list of positional arguments, which *may* be followed by a single dictionary-style object containing any named arguments.

There are three important points about this definition:

1. `positional` is an array of `unknown`, of unspecified length.
2. `named` is a `Record`.
3. Both arguments are always set, but may be empty.

Let’s walk through each of these.

### Handling `positional` arguments

The type is an array of `unknown` because we don’t (yet!) have any way to make templates aware of the information in this definition—so users could pass in *anything*. We can work around this using [type narrowing](https://microsoft.github.io/TypeScript-New-Handbook/chapters/narrowing/)—TypeScript’s way of using runtime checks to inform the types at runtime.

```typescript
function totalLength(positional: unknown[]) {
  // Account for case where user passes no arguments
  assert(
    'all positional args to `total-length` must be strings',
    positional.every(arg => typeof arg === 'string')
  );

  // safety: we can cast `positional as string[]` because we asserted above
  return (positional as string[]).reduce((sum, s) => sum + s.length, 0);
}
```

### Handling `named` arguments

We specified the type of `named` as a `Record<string, unknown>`. `Record` is a built-in TypeScript type representing a fairly standard type in JavaScript: an object being used as a simple map of keys to values. Here we set the values to `unknown` and the keys to `string`, since that accurately represents what callers may actually pass to a helper.

(As with `positional`, we specify the type here as `unknown` to account for the fact that the template layer isn’t aware of types yet.)

### `positional` and `named` presence

Note that even if the user passes *no* arguments, both `positional` and `named` are always present. They will just be *empty* in that case. For example:

```typescript
import { helper } from '@ember/component/helper';

const describe = (entries: string): string => (entries.length > 0 ? entries : '(none)');

export function showAll(positional: unknown[], named: Record<string, unknown>) {
  // pretty print each item with its index, like `0: { neat: true }` or
  // `1: undefined`.
  const positionalEntries = positional
    .reduce<string[]>((items, arg, index) => items.concat(`${index}: ${JSON.stringify(arg)}`), [])
    .join(', ');

  // pretty print each item with its name, like `cool: beans` or
  // `answer: 42`.
  const namedEntries = Object.keys(named)
    .reduce<string[]>(
      (items, key) => items.concat(`${key}: ${JSON.stringify(named[key], undefined, 2)}`),
      []
    )
    .join(', ');

  return `positional: ${describe(positionalEntries)}\nnamed: ${describe(namedEntries)}`;
}

export default helper(showAll);
```

### Putting it all together

Given those constraints, let’s see what a (very contrived) actual helper might look like in practice. Let’s imagine we want to take a pair of strings and join them with a required separator and optional prefix and postfixes:

```typescript
import { helper } from '@ember/component/helper';
import { assert } from '@ember/debug';
import { is } from '../../type-utils'

export function join(positional: [unknown, unknown], named: Dict<unknown>) {
  assert(
    `'join' requires two 'string' positional parameters`,
    is<[string, string]>(
      positional,
      positional.length === 2 &&
      positional.every(el => typeof el === 'string')
    )
  );
  assert(`'join' requires argument 'separator'`, typeof named.separator === 'string');

  const joined = positional.join(named.separator);
  const prefix = typeof named.prefix === 'string' ? named.prefix : '';

  return `${prefix}${joined}`;
}

export default helper(join);
```

## Class-based helpers

The basic type of a class-based helper function in Ember is:

```typescript
interface ClassBasedHelper {
  compute(positional?: unknown[], named?: Record<string, unknown>): string | void;
}
```

Notice that the signature of `compute` is the same as the signature for the function-based helper! This means that everything we said above applies in exactly the same way here. The only differences are that we can have local state and, by extending from Ember’s `Helper` class, we can hook into the dependency injection system and use services.

```typescript
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import Authentication from 'my-app/services/authentication';

export default class Greet extends Helper {
  @service authentication: Authentication;

  compute() {
    return this.authentication.isAuthenticated
      ? `Welcome back, ${authentication.userName}!`
      : 'Sign in?';
}
```

For more details on using decorators, see our [guide to using decorators](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28../ts/decorators/%29/README.md). For details on using services, see our [guide to services](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/%28./services/%29/README.md).


# Testing

Testing with TypeScript mostly works just the same as you'd expect in a non-TypeScript Ember application—so if you're just starting out with Ember, we recommend you read the official Ember [Testing Guides](https://guides.emberjs.com/release/testing/) first. The rest of this guide assumes you're already comfortable with testing in Ember!

When working with TypeScript in Ember tests, there are a few differences in your experience, and there are also differences in how you should handle testing app code vs. addon code.

## App tests

One major difference when working with TypeScript in *app* code is that once your app is fully converted, there are a bunch of kinds of tests you just don't need to write any more: things like testing bad inputs to functions. We'll use an admittedly silly and contrived example here, an `add` function to add two numbers together, so that we can focus on the differences between JavaScript and TypeScript, rather than getting hung up on the details of this particular function.

First, the function we're testing might look like this.

{% hint style="info" %}
Here we’re using the `assert` from `@ember/debug`. If you’re not familiar with it, you might want to take a look at its [API docs](https://api.emberjs.com/ember/3.14/functions/@ember%2Fdebug/assert)! It’s a development-and-test-only helper that gets stripped from production builds, and is very helpful for this kind of thing!
{% endhint %}

```javascript
// app/utils/math.js

export function add(a, b) {
  assert(
    'arguments must be numbers',
    typeof a === number && typeof b === number
  );

  return a + b;
}
```

Then the test for it might look something like this:

```javascript
// tests/unit/utils/math-test.js

import { module, test } from 'qunit';
import { add } from 'app/utils/math';

module('the `add` function', function(hooks) {
  test('adds numbers correctly', function(assert) {
    assert.equal('2 + 2 is 4', add(2, 2), 4);
    assert.notEqual('2 + 2 is a number', add(2, 2), NaN);
    assert.notEqual('2 + 2 is not infinity', add(2, 2), Infinity);
  });

  test('throws an error with strings', function(assert) {
    assert.throws(
      'when the first is a string and the second is a number',
      () => add('hello', 1)
    );
    assert.throws(
      'when the first is a number and the second is a string',
      () => add(0, 'hello')
    );
    assert.throws(
      'when both are strings',
      () => add('hello', 'goodbye')
    );
  })
});
```

In TypeScript, that wouldn't make any sense at all, because we'd simply add the types to the function declaration:

```typescript
// app/utils/math.ts

export function add(a: number, b: number): number {
  assert(
    'arguments must be numbers',
    typeof a === number && typeof b === number
  );

  return a + b;
}
```

We might still write tests to make sure what we actually got back was what we expected—

```typescript
// tests/unit/utils/math-test.ts

import { module, test } from 'qunit';
import { add } from 'app/utils/math';

module('the `add` function', function(hooks) {
  test('adds numbers correctly', function(assert) {
    assert.equal('2 + 2 is 4', add(2, 2), 4);
    assert.notEqual('2 + 2 is a number', add(2, 2), NaN);
    assert.notEqual('2 + 2 is not infinity', add(2, 2), Infinity);
  });
});
```

—but there are a bunch of things we *don't* need to test. All of those special bits of handling for the case where we pass in a `string` or `undefined` or whatever else? We can drop that. Notice, too, that we can drop the assertion from our function definition, because the *compiler* will check this for us:

```typescript
// app/utils/math.ts

export function add(a: number, b: number): number {
 return a + b;
}
```

## Addon tests

Note, however, that this *only* applies to *app code*. If you're writing an Ember addon (or any other library), you cannot assume that everyone consuming your code is using TypeScript. You still need to account for these kinds of cases. This will require you to do something that probably feels a bit gross: casting a bunch of values `as any` for your tests, so that you can test what happens when people feed bad data to your addon!

Let's return to our silly example with an `add` function. Our setup will look a lot like it did in the JavaScript-only example—but with some extra type coercions along the way so that we can invoke it the way JavaScript-only users might.

First, notice that in this case we’ve added back in our `assert` in the body of the function. The inputs to our function here will get checked for us by any TypeScript users, but this way we are still doing the work of helping out our JavaScript users.

```typescript
function add(a: number, b: number): number {
  assert(
    'arguments must be numbers',
    typeof a === number && typeof b === number
  );

  return a + b;
}
```

Now, back in our test file, we’re similarly back to testing all those extra scenarios, but here TypeScript would actually stop us from even having these tests work *at all* if we didn’t use the `as` operator to throw away what TypeScript knows about our code!

```javascript
// tests/unit/utils/math-test.js

import { module, test } from 'qunit';
import { add } from 'app/utils/math';

module('the `add` function', function(hooks) {
  test('adds numbers correctly', function(assert) {
    assert.equal('2 + 2 is 4', add(2, 2), 4);
    assert.notEqual('2 + 2 is a number', add(2, 2), NaN);
    assert.notEqual('2 + 2 is not infinity', add(2, 2), Infinity);
  });

  test('throws an error with strings', function(assert) {
    assert.throws(
      'when the first is a string and the second is a number',
      () => add('hello' as any, 1)
    );
    assert.throws(
      'when the first is a number and the second is a string',
      () => add(0, 'hello' as any)
    );
    assert.throws(
      'when both are strings',
      () => add('hello' as any, 'goodbye' as any)
    );
  })
});
```

## Gotchas

### The `TestContext`

A common scenario in Ember tests, especially integration tests, is setting some value on the `this` context of the tests, so that it can be used in the context of the test. For example, we might need to set up a `User` type to pass into a `Profile` component.

We’re going to start by defining a basic `User` and `Profile` so that we have a good idea of what we’re testing.

The `User` type is very simple, just an `interface`:

```typescript
// app/types/user.ts

export default interface User {
  displayName: string;
  avatarUrl?: string;
}
```

Then our component might be defined like this:

```
{{! app/components/profile.hbs }}

<div class='user-profile' ...attributes>
  <img
    src={{this.avatar}}
    alt={{this.description}}
    class='avatar'
    data-test-avatar
  />
  <span class='name' data-test-name>{{@displayName}}</span>
</div>
```

```typescript
import Component from '@glimmer/component';
import User from 'app/types/user';
import { randomAvatarURL  } from 'app/utils/avatar';

export default class Profile extends Component<User> {
  get avatar() {
    return this.args.avatar ?? randomAvatarURL();
  }

  get description() {
    return this.args.avatar
      ? `${this.args.displayName}'s custom profile picture`
      : 'a randomly generated placeholder avatar';
  }
}
```

{% hint style="info" %}
Not familiar with how we define a Glimmer `Component` and its arguments? Check out [our guide](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/ember/components/README.md)!
{% endhint %}

Now, with that setup out of the way, let’s get back to talking about the text context! We need to set up a `User` to pass into the test. With TypeScript on our side, we can even make sure that it actually matches up to the type we want to use!

```typescript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

import User from 'app/types/user';

module('Integration | Component | Profile', function(hooks) {
  setupRenderingTest(hooks);

  test('given a user with an avatar', async function(assert) {
    this.user: User = {
      displayName: 'Rey',
      avatar: 'https://example.com/star-wars/rey',
    };

    await render(hbs`<Profile @user={{this.user}}`);

    assert.dom('[data-test-name]').hasText(this.user.displayName);

    assert.dom('[data-test-avatar]')
      .hasAttribute('src', this.user.avatar);
    assert.dom('[data-test-avatar]')
      .hasAttribute('alt', `${this.user.displayName}'s custom profile picture`);
  });

  test('given a user without an avatar', async function(assert) {
    this.user: User = {
      displayName: 'Rey',
    };

    await render(hbs`<Profile @user={{this.user}}`);

    assert.dom('[data-test-name]').hasText(this.user.displayName);

    assert.dom('[data-test-avatar]')
      .hasAttribute('src', /rando-avatars-yo/);
    assert.dom('[data-test-avatar]')
      .hasAttribute('alt', 'a randomly generated placeholder avatar');
  });
});
```

This is a decent test, and TypeScript actually makes the experience of writing certain parts of it pretty nice. Unfortunately, though, it won’t type-check. TypeScript reports that the `user` field doesn't exist on the `TestContext`. Now, TypeScript *does* know that QUnit sets up that helpfully-named `TestContext`—so a lot of the things we can do in tests work out of the box—but we haven’t told TypeScript that `this` now has a `user` property on it.

To inform TypeScript about this, we need to tell it that the type of `this` in each test assertion includes the `user` property, of type `User`. We’ll start by importing the `TestContext` defined by Ember’s test helpers, and extending it:

```typescript
import { TestContext } from '@ember/test-helpers';

import User from 'app/types/user';

interface Context extends TestContext {
  user: User;
}
```

Then, in every `test` callback, we need to [specify the `this` type](https://www.typescriptlang.org/docs/handbook/functions.html#this):

```typescript
test('...', function(this: Context, assert) {

});
```

Putting it all together, this is what our updated test definition would look like:

```typescript
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, TestContext } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

import User from 'app/types/user';

interface Context extends TestContext {
  user: User;
}

module('Integration | Component | Profile', function(hooks) {
  setupRenderingTest(hooks);

  test('given a user with an avatar', async function(this: Context, assert) {
    this.user: User = {
      displayName: 'Rey',
      avatar: 'https://example.com/star-wars/rey',
    };

    await render(hbs`<Profile @user={{this.user}}`);

    assert.dom('[data-test-name]').hasText(this.user.displayName);

    assert.dom('[data-test-avatar]')
      .hasAttribute('src', this.user.avatar);
    assert.dom('[data-test-avatar]')
      .hasAttribute('alt', `${this.user.displayName}'s custom profile picture`);
  });

  test('given a user without an avatar', async function(this: Context, assert) {
    this.user: User = {
      displayName: 'Rey',
    };

    await render(hbs`<Profile @user={{this.user}}`);

    assert.dom('[data-test-name]').hasText(this.user.displayName);

    assert.dom('[data-test-avatar]')
      .hasAttribute('src', /rando-avatars-yo/);
    assert.dom('[data-test-avatar]')
      .hasAttribute('alt', 'a randomly generated placeholder avatar');
  });
});
```

Now everything type-checks again, and we get the nice auto-completion we’re used to when dealing with `this.user` in the test body.

{% hint style="info" %}
If you’ve been around TypeScript a little, and you look up the type of the `TestContext` and realize its an interface, you might be tempted to reach for declaration merging here. Don’t! If you do that, *every single test in your entire application* will now have a `user: User` property on it!
{% endhint %}

There are still a couple things to be careful about here, however. First, we didn’t specify that the `this.user` property was *optional*. That means that TypeScript won’t complain if you do `this.user` *before* assigning to it. Second, every test in our module gets the same `Context`. Depending on what you’re doing, that may be fine, but you may end up needing to define multiple distinct test context extensions. If you *do* end up needing to define a bunch of different test context extension, that may be a sign that this particular set of tests is doing too much. That in turn is probably a sign that this particular *component* is doing too much!

### QUnit Dom for Component tests

When writing [Component Tests](https://guides.emberjs.com/release/testing/testing-components/), you will use lots of `assert.dom()` calls.\
Out of the box, Typescript will complain that `Property 'dom' does not exist on type 'Assert'.`.

This can be fixed by importing `qunit-dom` in your test module:

```typescript
import 'qunit-dom';
```


# Working With Ember Data

In this section, we cover how to use TypeScript effectively with specific Ember Data APIs (anything you'd find under the `@ember-data` package namespace).

We do *not* cover general usage of Ember Data; instead, we assume that as background knowledge. Please see the Ember Data [Guides](https://guides.emberjs.com/release/models) and [API docs](https://api.emberjs.com/ember-data/release)!


# Models

Ember Data models are normal TypeScript classes, but with properties decorated to define how the model represents an API resource and relationships to other resources. The decorators the library supplies "just work" with TypeScript at runtime, but require type annotations to be useful with TypeScript.

For details about decorator usage, see [our overview of how Ember's decorators work with TypeScript](/ts/decorators).

## `@attr`

The type returned by the `@attr` decorator is whatever [Transform](https://api.emberjs.com/ember-data/release/classes/Transform) is applied via the invocation. See [our overview of Transforms for more information](/ember-data/transforms).

* If you supply no argument to `@attr`, the value is passed through without transformation.
* If you supply one of the built-in transforms, you will get back a corresponding type:
  * `@attr('string')` → `string`
  * `@attr('number')` → `number`
  * `@attr('boolean')` → `boolean`
  * `@attr('date')` → `Date`
* If you supply a custom transform, you will get back the type returned by your transform.

So, for example, you might write a class like this:

```typescript
import Model, { attr } from '@ember-data/model';
import CustomType from '../transforms/custom-transform';

export default class User extends Model {
  @attr()
  declare name?:  string;

  @attr('number')
  declare age: number;

  @attr('boolean')
  declare isAdmin: boolean;

  @attr('custom-transform')
  declare myCustomThing: CustomType;
}
```

**Very important:** Even more than with decorators in general, you should be careful when deciding whether to mark a property as optional `?` or definitely present (no annotation): Ember Data will default to leaving a property empty if it is not supplied by the API or by a developer when creating it. That is: the *default* for Ember corresponds to an optional field on the model.

The *safest* type you can write for an Ember Data model, therefore, leaves every property optional: this is how models *actually* behave. If you choose to mark properties as definitely present by leaving off the `?`, you should take care to guarantee that this is a guarantee your API upholds, and that ever time you create a record from within the app, *you* uphold those guarantees.

One way to make this safer is to supply a default value using the `defaultValue` on the options hash for the attribute:

```typescript
import Model, { attr } from '@ember-data/model';

export default class User extends Model {
  @attr()
  declare name?:  string;

  @attr('number', { defaultValue: 13 })
  declare age: number;

  @attr('boolean', { defaultValue: false })
  declare isAdmin: boolean;
}
```

## Relationships

Relationships between models in Ember Data rely on importing the related models, like `import User from './user';`. This, naturally, can cause a recursive loop, as `/app/models/post.ts` imports `User` from `/app/models/user.ts`, and `/app/models/user.ts` imports `Post` from `/app/models/post.ts`. Recursive importing triggers an [`import/no-cycle`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md) error from eslint.

To avoid these errors, use [type-only imports](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html), available since TypeScript 3.8:

```ts
import type User from './user';
```

### `@belongsTo`

The type returned by the `@belongsTo` decorator depends on whether the relationship is `{ async: true }` (which it is by default).

* If the value is `true`, the type you should use is `AsyncBelongsTo<Model>`, where `Model` is the type of the model you are creating a relationship to.
* If the value is `false`, the type is `Model`, where `Model` is the type of the model you are creating a relationship to.

So, for example, you might define a class like this:

```typescript
import Model, { belongsTo, type AsyncBelongsTo } from '@ember-data/model';
import type User from './user';
import type Site from './site';

export default class Post extends Model {
  @belongsTo('user')
  declare user: AsyncBelongsTo<User>;

  @belongsTo('site', { async: false })
  declare site: Site;
}
```

These are *type*-safe to define as always present, that is to leave off the `?` optional marker:

* accessing an async relationship will always return an `AsyncBelongsTo<Model>` object, which itself may or may not ultimately resolve to a value—depending on the API response—but will always be present itself.
* accessing a non-async relationship which is known to be associated but has not been loaded will trigger an error, so all access to the property will be safe *if* it resolves at all.

Note, however, that this type-safety is not a guarantee of there being no runtime error: you still need to uphold the contract for non-async relationships (that is: loading the data first, or side-loading it with the request) to avoid throwing an error!

### `@hasMany`

The type returned by the `@hasMany` decorator depends on whether the relationship is `{ async: true }` (which it is by default).

* If the value is `true`, the type you should use is `AsyncHasMany<Model>`, where `Model` is the type of the model you are creating a relationship to.
* If the value is `false`, the type is `SyncHasMany<Model>`, where `Model` is the type of the model you are creating a relationship to.

So, for example, you might define a class like this:

```typescript
import Model, { hasMany, type AsyncHasMany, type SyncHasMany } from '@ember-data/model';
import type Comment from './comment';
import type User from './user';

export default class Thread extends Model {
  @hasMany('comment')
  declare comments: AsyncHasMany<Comment>;

  @hasMany('user', { async: false })
  declare participants: SyncHasMany<User>;
}
```

The same basic rules about the safety of these lookups as with `@belongsTo` apply to these types. The difference is just that in `@hasMany` the resulting types are *arrays* rather than single objects.


# Transforms

In Ember Data, `attr` defines an attribute on a [Model](https://guides.emberjs.com/release/models/defining-models/). By default, attributes are passed through as-is, however you can specify an optional type to have the value automatically transformed. Ember Data ships with four basic transform types: `string`, `number`, `boolean` and `date`.

You can define your own transforms by subclassing [Transform](https://guides.emberjs.com/release/models/defining-models/#toc_custom-transforms). Ember Data transforms are normal TypeScript classes. The return type of `deserialize` method becomes type of the model class property.

You may define your own transforms in TypeScript like so:

```typescript
# app/transforms/coordinate-point.ts
import Transform from '@ember-data/serializer/transform';

declare module 'ember-data/types/registries/transform' {
  export default interface TransformRegistry {
    'coordinate-point': CoordinatePointTransform;
  }
}

export type CoordinatePoint = {
  x: number;
  y: number;
};

export default class CoordinatePointTransform extends Transform {
  deserialize(serialized): CoordinatePoint {
    return { x: value[0], y: value[1] };
  }

  serialize(value): number {
    return [value.x, value.y];
  }
}

# app/models/cursor.ts
import Model, { attr } from '@ember-data/model';
import { CoordinatePoint } from 'agwa-data/transforms/coordinate-point';

declare module 'ember-data/types/registries/model' {
  export default interface ModelRegistry {
    cursor: Cursor;
  }
}

export default class Cursor extends Model {
  @attr('coordinate-point') declare position: CoordinatePoint;
}
```

Note that you should declare your own transform under `TransformRegistry` to make `attr` to work with your transform.


# Cookbook

This “cookbook” section contains recipes for various scenarios you may encounter while working on your app or addon.

{% hint style="info" %}
Have an idea for an item that should fit here? [Open an issue for it!](https://github.com/typed-ember/ember-cli-typescript/issues/new/choose) We'd love to help you help us make this experience more awesome for everyone.
{% endhint %}

## Contents

* [Working with route models](/cookbook/working-with-route-models)


# Working with route models

We often use routes’ models throughout our application, since they’re a core ingredient of our application’s data. As such, we want to make sure that we have good types for them!

We can start by defining some type utilities to let us get the resolved value returned by a route’s model hook:

```typescript
import Route from '@ember/routing/route';

/**
  Get the resolved type of an item.

  - If the item is a promise, the result will be the resolved value type
  - If the item is not a promise, the result will just be the type of the item
 */
export type Resolved<P> = P extends Promise<infer T> ? T : P;

/** Get the resolved model value from a route. */
export type ModelFrom<R extends Route> = Resolved<ReturnType<R['model']>>;
```

How that works:

* `Resolved<P>` says "if this is a promise, the type here is whatever the promise resolves to; otherwise, it's just the value"
* `ReturnType<T>` gets the return value of a given function
* `R['model']` (where `R` has to be `Route` itself or a subclass) uses TS's mapped types to say "the property named `model` on `R`

Putting those all together, `ModelFrom<Route>` ends up giving you the resolved value returned from the `model` hook for a given route:

```typescript
type MyRouteModel = ModelFrom<MyRoute>;
```

## `model` on the controller

We can use this functionality to guarantee that the `model` on a `Controller` is always exactly the type returned by `Route::model` by writing something like this:

```typescript
import Controller from '@ember/controller';
import MyRoute from '../routes/my-route';
import { ModelFrom } from '../lib/type-utils';

export default class ControllerWithModel extends Controller {
  declare model: ModelFrom<MyRoute>;
}
```

Now, our controller’s `model` property will *always* stay in sync with the corresponding route’s model hook.

**Note:** this *only* works if you do not mutate the `model` in either the `afterModel` or `setupController` hooks on the route! That's generally considered to be a bad practice anyway. If you do change the type there, you'll need to define the type in some other way and make sure your route's model is defined another way.


# Working With Ember Classic

We emphasize the happy path of working with Ember in the [Octane Edition](https://emberjs.com/editions/octane/). However, there are times you’ll need to understand these details:

1. Most existing applications make heavy use of the pre-Octane (“legacy”) Ember programming model, and we support that model—with caveats.
2. Several parts of Ember Octane (specifically: routes, controllers, services, and class-based helpers) continue to use these concepts under the hood, and our types support that—so understanding them may be important at times.

The rest of this guide is dedicated to helping you understand how `ember-cli-typescript` and the classic Ember system interact.


# EmberComponent


# Mixins

Mixins are fundamentally hostile to robust typing with TypeScript. While you can supply types for them, you will regularly run into problems with self-referentiality in defining properties within the mixins.

As a stopgap, you can refer to the type of a mixin using the `typeof` operator.

In general, however, prefer to use one of the following four strategies for migrating *away* from mixins before attempting to convert code which relies on them to TypeScript:

1. For functionality which encapsulates DOM modification, rewrite as a custom modifier using [ember-modifier](https://github.com/emeber-modifier/ember-modifier).
2. If the mixin is a way of supplying shared behavior (not data), extract it to utility functions, usually just living in module scope and imported and exported as needed.
3. If the mixin is a way of supplying non-shared state which follows the lifecycle of a given object, replace it with a utility class instantiated in the owning class's `constructor` (or `init` for legacy classes).
4. If the mixin is a way of supplying long-lived, shared state, replace it with a service and inject it where it was used before. This pattern is uncommon, but sometimes appears when mixing functionality into multiple controllers or services.

You can also use inheritance and class decorators to accomplish some of the same semantics as mixins classically supplied. However, these patterns are more fragile and therefore not recommended.


# Computed Properties

There are two variants of Ember’s computed properties you may encounter:

* the decorator form used with native (ES6) classes
* the callback form used with classic classes (based on EmberObject)

## Decorator form

```typescript
import Component from '@ember/component';
import { computed } from '@ember/object/computed';

export default class UserProfile extends Compoennt {
  name = 'Chris';
  age = 33;

  @computed('name', 'age')
  get bio() {
    return `${this.name} is `${this.age}` years old!`;
  }
}
```

Note that it is impossible for `@computed` to know whether the keys you pass to it are allowed or not. Migrating to Octane eliminates this issue, since you mark reactive root state with `@tracked` and leave getters undecorated, rather than vice versa.

## Callback form

Computed properties in the classic object model take a callback instead:

```typescript
import Component from '@ember/component';
import { computed } from '@ember/object/computed';

const UserProfile = Component.extend({
  name: 'Chris',
  age: 32,

  bio: computed('name', 'age', function() {
    return `${this.get('name')} is `${this.get('age')}` years old!`;
  }),
})

export default UserProfile;
```

This definition will not type-check, however. You will need to explicitly write out a `this` type for computed property callbacks for `get` and `set` to type-check correctly:

```typescript
import Component from '@ember/component';
import { computed } from '@ember/object/computed';

const UserProfile = Component.extend({
  name: 'Chris',
  age: 32,

  bio: computed('name', 'age', function(this: UserProfile) {
    //                                  ^---------------^
    // `this` tells TS to use `UserProfile` for `get` and `set` lookups;
    // otherwise `this.get` below would not know the types of `'name'` or
    // `'age'` or even be able to suggest them for autocompletion.
    return `${this.get('name')} is `${this.get('age')}` years old!`;
  }),
})

export default UserProfile;
```

Note that this *does not always work*: you may get warnings from TypeScript about the item being defined in terms of itself.

**Accordingly, we strongly recommend migrating classic classes to ES native classes** ***before*** **adding TypeScript!**


# EmberObject

When working with the legacy Ember object model, `EmberObject`, there are a number of caveats and limitations you need to be aware of. For today, these caveats and limitations apply to any classes which extend directly from `EmberObject`, or which extend classes which *themselves* extend `EmberObject`:

* `Component` – meaning *classic* Ember components, which imported from `@ember/component`, *not* Glimmer components which are imported from `@glimmer/component` and do *not* extend the `EmberObject` base class.
* `Controller`
* `Helper` – note that this applies only to the *class* form. Function-based helpers do not involve the `EmberObject` base class.
* `Route`
* `Router`
* `Service`
* Ember Data’s `Model` class

Additionally, Ember’s mixin system is deeply linked to the semantics and implementation details of `EmberObject`, *and* it has the most caveats and limitations.

{% hint style="info" %}
In the future, some of these may be able to drop their `EmberObject` base class dependency, but that will not happen till at least the next major version of Ember, and these guides will be updated when that happens.
{% endhint %}

## Mixins and classic class syntax

The Ember mixin system is the legacy Ember construct TypeScript supports *least* well, as described in [Mixins](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/legacy/mixins/README.md). While this may not be intuitively obvious, the classic class syntax simply *is* the mixin system. Every classic class creation is a case of mixing together multiple objects to create a new base class with a shared prototype. The result is that any time you see the classic `.extend({ ... })` syntax, regardless of whether there is a named mixin involved, you are dealing with Ember's legacy mixin system. This in turn means that you are dealing with the parts of Ember which TypeScript is *least* able to handle well.

While we describe here how to use types with classic (mixin-based) classes insofar as they *do* work, there are many failure modes. As a result, we strongly recommend moving away from both classic classes and mixins, and as quickly as possible. This is the direction the Ember ecosystem as a whole is moving, but it is *especially* important for TypeScript users.

{% hint style="info" %}
The [Ember Atlas](https://emberatlas.com) includes guides for migrating [from classic classes to native classes](https://www.notion.so/Native-Classes-55bd67b580ca49f999660caf98aa1897), along with [a variety of patterns](https://www.notion.so/Converting-Classes-with-Mixins-5dc68c0ac3044e51a218fa7aec71c2db) for dealing with specific kinds of mixins in your codebase.
{% endhint %}

### Failure modes

You often need to define `this` in actions hashes, computed properties, etc. That in turn often leads to problems with self-referential `this`: TypeScript simply cannot figure out how to stop recursing through the definitions of the type.

Additionally, even when you get past the endlessly-recursive type definition problems, when enough mixins are resolved TypeScript will occasionally just give up because it cannot resolve the property or method you're interested in across the many shared base classes.

Finally, when you have "zebra-striping" of your classes between classic classes and native classes, your types will often stop resolving.

## Native classes

### `EmberObject`

In general, we recommend (following the Ember Octane guides) that any class which extends directly from the `EmberObject` base class eliminate any use of `EmberObject`-specific API and convert to standalone class, with no base class at all. You can follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow to eliminate the base class—switching from `init` to `constructor`, getting rid of uses of methods like `this.set` and `this.get` in favor of using standalone `set` and `get`, and so on.

### `EmberObject`-descended classes

The framework base classes which depend on `EmberObject` cannot follow the exact same path. However, as long as you are using native class syntax, all of these (`Component`, `Controller`, `Helper`, etc.) work nicely and safely with TypeScript. In each of these cases, the same caveats apply as with `EmberObject` itself, and you should follow the [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator) workflow with them as well if you are converting an existing app or addon. However, because these base classes themselves descend from `EmberObject`, you will not be able to remove the base classes as you can with your *own* classes which descend *directly* from `EmberObject`. Instead, you will continue to extend from the Ember base classes:

```typescript
import Component from '@ember/component';
export default class Profile extends Component {}
```

```typescript
import Controller from '@ember/controller';
export default class IndexController extends Controller {}
```

```typescript
import Helper from '@ember/component/helper';
export default class Localize extends Helper {}
```

```typescript
import Route from '@ember/routing/route';
export default class ApplicationRoute extends Route {}
```

```typescript
import EmberRouter from '@ember/routing/router'
export default class AppRouter extends EmberRouter {}
```

```typescript
import Service from '@ember/service';
export default class Session extends Service {}
```

```typescript
import Model from '@ember-data/model';
export default class User extends Model {}
```


# Upgrading from 1.x

There are a number of important changes between ember-cli-typescript v1 and v2, which mean the upgrade process is *straightforward* but *specific*:

1. Update ember-cli-babel. Fix any problems introduced during the upgrade.
2. Update ember-decorators. Fix any problems introduced during the upgrade.
3. Update ember-cli-typescript. Follow the detailed upgrade guide below to fix discrepancies between Babel and TypeScript's compiled output.

If you deviate from this order, you are likely to have a *much* more difficult time upgrading!

## Update ember-cli-babel

ember-cli-typescript **requires** ember-cli-babel at version 7.1.0 or above, which requires ember-cli 2.13 or above. It also **requires** @babel/core 7.2.0 or higher.

The recommended approach here is to deduplicate existing installations of the dependency, remove and reinstall ember-cli-babel to make sure that all its transitive dependencies are updated to the latest possible, and then to deduplicate *again*.

If using yarn:

```bash
npx yarn-deduplicate
yarn remove ember-cli-babel
yarn add --dev ember-cli-babel
npx yarn-deduplicate
```

If using npm:

```bash
npm dedupe
npm uninstall ember-cli-babel
npm install --save-dev ember-cli-babel
npm dedupe
```

Note: If you are also using ember-decorators—and specifically the babel-transform that gets added with it—you will need update @ember-decorators/babel-transforms as well (anything over 3.1.0 should work):

```bash
ember install ember-decorators@^3.1.0 @ember-decorators/babel-transforms@^3.1.0
```

## Update ember-decorators

If you're on a version of Ember before 3.10, follow the same process of deduplication, reinstallation, and re-deduplication as described for ember-cli-babel above for ember-decorators. This will get you the latest version of ember-decorators and, importantly, its @ember-decorators/babel-transforms dependency.

## Update ember-cli-typescript

Now you can simply `ember install` the dependency like normal:

```bash
ember install ember-cli-typescript@latest
```

***Note:*** **To work properly, starting from v2, ember-cli-typescript must be declared as a `dependency`, not a `devDependency` for addons. With `ember install` this migration would be automatically handled for you.**

If you choose to make the upgrade manually with yarn or npm, here are the steps you need to follow:

1. Remove ember-cli-typescript from your `devDependencies`.

   With yarn:

   ```bash
    yarn remove ember-cli-typescript
   ```

   With npm:

   ```bash
    npm uninstall ember-cli-typescript
   ```
2. Install the latest of ember-cli-typescript as a `dependency`:

   With yarn:

   ```bash
    yarn add ember-cli-typescript@latest
   ```

   With npm:

   ```bash
    npm install --save ember-cli-typescript@latest
   ```
3. Run `ember generate`:

   ```bash
    ember generate ember-cli-typescript
   ```

### Account for addon build pipeline changes

Since we now integrate in a more traditional way into Ember CLI's build pipeline, there are two changes required for addons using TypeScript.

* Addons can no longer use `.ts` in `app`, because an addon's `app` directory gets merged with and uses the *host's* (i.e. the other addon or app's) preprocessors, and we cannot guarantee the host has TS support. Note that `.ts` will continue to work for in-repo addons because the app build works with the host's (i.e. the app's, not the addon's) preprocessors.
* Similarly, apps must use `.js` to override addon defaults in `app`, since the different file extension means apps no longer consistently "win" over addon versions (a limitation of how Babel + app merging interact).

### Account for TS → Babel issues

ember-cli-typescript v2 uses Babel to compile your code, and the TypeScript compiler only to *check* your code. This makes for much faster builds, and eliminates the differences between Babel and TypeScript in the build output that could cause problems in v1. However, because of those differences, you’ll need to make a few changes in the process of upgrading.

Any place where a type annotation overrides a *getter*

* Fields like `element`, `disabled`, etc. as annotated defined on a subclass of `Component` and (correctly) not initialized to anything, e.g.:

  ```typescript
    import Component from '@ember/component';

    export default class Person extends Component {
      element!: HTMLImageElement;
    }
  ```

  This breaks because `element` is a getter on `Component`. This declaration then shadows the getter declaration on the base class and stomps it to `undefined` (effectively `Object.defineProperty(this, 'element', void 0)`. (It would be nice to use `declare` here, but that doesn't work: you cannot use `declare` with a getter in a concrete subclass.)

  Two solutions:

  1. Annotate locally (slightly more annoying, but less likely to troll you):

     ```typescript
      class Image extends Component {
        useElement() {
          let element = this.element as HTMLImageElement;
          console.log(element.src);
        }
      }
     ```
  2. Use a local getter:

     ```typescript
      class Image extends Component {
        // We do this because...
        get _element(): HTMLImageElement {
          return this.element as HTMLImageElement;
        }

        useElement() {
          console.log(this._element.src);
        }
      }
     ```

     Notably, this is not a problem for Glimmer components, so migrating to Octane will also help!
* `const enum` is not supported at all. You will need to replace all uses of `const enum` with simply `enum` or constants.
* Using ES5 getters or setters with `this` type annotations is not supported through at least Babel 7.3. However, they should also be unnecessary with ES6 classes, so you can simply *remove* the `this` type annotation.
* Trailing commas after rest function parameters (`function foo(...bar[],) {}`) are disallowed by the ECMAScript spec, so Babel also disallows them.
* Re-exports of types have to be disambiguated to be *types*, rather than values. Neither of these will work:

  ```typescript
  export { FooType } from 'foo';
  ```

  ```typescript
  import { FooType } from 'foo';
  export { FooType };
  ```

  In both cases, Babel attempts to emit a *value* export, not just a *type* export, and fails because there is no actual value to emit. You can do this instead as a workaround:

  ```typescript
  import * as Foo from 'foo';
  export type FooType = Foo.FooType;
  ```


# Troubleshooting

Stuck with something? Hopefully one of the documents below can help. If not, file an issue on GitHub and we'll try to help you get it sorted (and it may end up in here).

## Outline

* [Conflicting Type Dependencies](/troubleshooting/conflicting-types)


# Conflicting Type Dependencies

You will sometimes see **Duplicate identifier** errors when type-checking your application.

An example duplicate identifier error:

```bash
yarn tsc --noEmit yarn run v1.15.2
$ /Users/chris/dev/teaching/emberconf-2019/node_modules/.bin/tsc --noEmit
node_modules/@types/ember__object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'.
23 export default class EmberObject extends CoreObject.extend(Observable) {}
~~~
node_modules/@types/ember__component/node_modules/@types/ember__object/index.d.ts:23:22
23 export default class EmberObject extends CoreObject.extend(Observable) {}
~~~ 'EmberObject' was also declared here.
node_modules/@types/ember__component/node_modules/@types/ember__object/index.d.ts:23:22 - error TS2300: Duplicate identifier 'EmberObject'.
8 export default class EmberObject extends CoreObject.extend(Observable) {}
~~~ 
node_modules/@types/ember__object/index.d.ts:23:22
23 export default class EmberObject extends CoreObject.extend(Observable) {}
~~~ 'EmberObject' was also declared here. Found 2 errors. error Command failed with exit code 1.
```

This occurs whenever your `yarn.lock` or `package-lock.json` files include more than a single copy of a given set of type definitions—here, types for `@ember/object`, named `@types/ember__object`. See below for details on the package manager behavior, and **Understanding the Package Names** for details on the package names.

## Workarounds

There are currently three recommended workarounds for this:

* If using `npm`, you can use `npm upgrade --depth=1 @types/ember__object` to upgrade just that specific dependency and anywhere it is used as a transitive dependency of your top-level dependencies. You can also use its `npm dedupe` command, which may resolve the issue.
* If using `yarn`, you can specify a specific version of the package to use in the `"resolutions"` key in `package.json`. For example, if you saw that you had `@types/ember__object@3.0.8` from the default package installs but `@types/ember__object@3.0.5` from `some-cool-ts-addon`, you could force yarn to use `3.0.8` like so:

  ```javascript
    {
      "resolutions": {
        "@types/ember__object": "3.0.8"
      }
    }
  ```
* You can identify the dependencies which installed the type dependencies transitively, and uninstall and reinstall them. For example, if running `yarn why` reported you had one version of `@types/ember__object` from [the normally-installed set of packages](https://github.com/typed-ember/ember-cli-typescript/tree/3a434def8b8c8214853cea0762940ccedb2256e8/docs/README.md#other-packages-this-addon-installs), and one from `some-cool-ts-addon`, you could run this:

  ```bash
    yarn remove @types/ember some-cool-ts-addon
    yarn add -D @types/ember some-cool-ts-addon
  ```

You may *also* be able to use [`yarn-deduplicate`](https://github.com/atlassian/yarn-deduplicate), but this does not work 100% of the time, so if you try it and are still seeing the issues, try one of the solutions above.

## Understanding the Problem

When you are using TypeScript in your Ember application, you consume Ember's types through [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped), the tool the TypeScript team built to power the `@types/*` definitions. That tooling examines the dependencies implied by the package imports and generates a `package.json` with those types specified with a `*` dependency version. On initial installation of your dependencies, yarn installs the highest version of the package available, and correctly deduplicates that across both your own package and all the `@types` packages which reference each other.

However, later installs may introduce conflicting versions of the types, simply by way of yarn's normal update rules. TypeScript requires that there be one and only one type definition a given item can resolve to. Yarn actively avoids changing a previously-installed version of a transitive dependency when a newly installed package depends on the same dependency transitively. Thus, if one of your dependencies *also* depends on the same package from `@types/*` that you do, and you upgrade your dependence on that type by editing your `package.json` file and running `yarn` or `npm install` again, TypeScript will suddenly start offering the error described in detail above:

> Duplicate identifier 'EmberObject'.ts(2300)

Let's imagine three packages, `A`, `B`, and `C`, where `A` is *your* app or library, and `B` and `C` have the following versions and dependencies:

* `C` is currently at version `1.2.3`.
* `B` is at version `4.5.6`. It depends on `C` with a `*` dependency. So the `dependencies` key in its `package.json` looks like this:

  ```javascript
    {
      "dependencies": {
        "C": "*"
      }
    }
  ```

Now, you install *only* `B` (this is the equivalent of installing just the basic type definitions in your package):

```javascript
{
  "dependencies": {
    "B": "~4.5.6"
  }
}
```

The first time you install these, you will get a *single* version of `C` – `1.2.3`.

Now, let's say that `C` publishes a new version, `1.2.4`, and `A` (your app or library) adds a dependency on both `C` like so:

```javascript
{
  "dependencies": {
    "B": "~4.5.6",
    "C": "~1.2.0"
  }
}
```

When your package manager runs (especially in the case of `yarn`), it goes out of its way to leave the *existing* installation of `C` in place, while adding a *new* version for you as a top-level consumer. So now you have two versions of `C` installed in your `node_modules` directory: `1.2.3` (for `B`) and `1.2.4` (for `A`, your app or library).

What's important to understand here is that this is *exactly* the behavior you want as the default in the Node ecosystem. Automatically updating a transitive dependency—even when the change is simply a bug fix release—*can* cause your entire app or library to stop working. If one of your dependencies accidentally depended on that buggy behavior, and adding a direct dependency on the fixed version caused the buggy version to be upgraded, you're just out of luck. Yarn accounts for this by resolving packages to the same version during initial installation, but leaving existing package resolutions as they are when adding new dependencies later.

Unfortunately, this is also the *opposite* of what you want for TypeScript, which needs a single source of truth for the types in your app or library. When you install the type definitions, and then *later* install a package which transitively depends on those type definitions, you end up with multiple sources of truth for the types.

## Understanding the Workarounds

The solutions listed above both make sure npm apd Yarn only install a single version of the package.

* Explicitly upgrading the dependencies or using `dedupe` resolves to a single version in npm.
* Specifying a version in the `"resolutions"` field in your `package.json` simply forces Yarn to resolve *every* reference to that package to a single version. This actually works extremely well for types, but it means that every time you either update the types package(s) yourself *or* update a package which transitively depends on them, you have to edit this value manually as well.
* Uninstalling and reinstalling both the impacted packages and *all* the packages which transitively depend on them gives you the same behavior as an initial install… because that's exactly what you're doing. The downside, of course, is that you have to identify and uninstall and reinstall all top-level packages which transitively depend on the files, and this introduces risk by way of *other* transitive dependencies being updated.


