Skip to content

Commit 5c233d1

Browse files
authored
Merge branch 'master' into de-simplify-watcher
2 parents db43778 + 62e3f94 commit 5c233d1

File tree

1 file changed

+52
-54
lines changed

1 file changed

+52
-54
lines changed

README.md

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ Use TypeScript in your Ember 2.x and 3.x apps!
66

77
* [Setup and Configuration](#setup-and-configuration)
88
* [Ember Support](#ember-support)
9-
* [:construction: Ember 3.1](#construction-ember-31)
109
* [`tsconfig.json`](#tsconfigjson)
1110
* [Sourcemaps](#sourcemaps)
1211
* [Using TypeScript with Ember effectively](#using-typescript-with-ember-effectively)
@@ -19,9 +18,12 @@ Use TypeScript in your Ember 2.x and 3.x apps!
1918
* [`this` type workaround](#this-type-workaround)
2019
* [Nested keys in `get` or `set`](#nested-keys-in-get-or-set)
2120
* [Service and controller injections](#service-and-controller-injections)
21+
* [Using `.extend`](#using-extend)
22+
* [Using decorators](#using-decorators)
2223
* [Ember Data lookups](#ember-data-lookups)
2324
* [Opt-in unsafety](#opt-in-unsafety)
2425
* [Fixing the Ember Data `error TS2344` problem](#fixing-the-ember-data-error-ts2344-problem)
26+
* [Class property setup errors](#class-property-setup-errors)
2527
* [Type definitions outside `node_modules/@types`](#type-definitions-outside-node_modulestypes)
2628
* [ember-browserify](#ember-browserify)
2729
* [ember-cli-mirage](#ember-cli-mirage)
@@ -68,12 +70,6 @@ In addition to ember-cli-typescript, we make the following changes to your proje
6870

6971
ember-cli-typescript runs its test suite against the 2.12 LTS, the 2.16 LTS, the 2.18 LTS, the current release, the beta branch, and the canary branch. It's also in active use in several large applications. Any breakage for upcoming releases _should_ be detected and fixed ahead of those releases, but you can help us guarantee that by running your own Ember.js+TypeScript app with beta and canary turned on and let us know if you run into issues with upcoming Ember.js releases.
7072

71-
#### :construction: Ember 3.1
72-
73-
Support for (mostly in the form of documentation!) for the changes in Ember 3.1 is inbound shortly. You *can* use this addon with Ember 3.1, but the docs are not yet updated, and some of the patterns recommended below will actually cause your app to break. :grimacing: Come ping us in #topic-typescript in the [Ember Community Slack][Slack] and we'll help you. (And updated docs for 3.1 are inbound *soon*!)
74-
75-
[Slack]: https://ember-community-slackin.herokuapp.com
76-
7773
### `tsconfig.json`
7874

7975
We generate a good default [`tsconfig.json`][blueprint], which will usually make everything _Just Work™_. In general, you may customize your TypeScript build process as usual using the `tsconfig.json` file.
@@ -240,94 +236,88 @@ The workaround is simply to do one of two things:
240236

241237
#### Service and controller injections
242238

243-
Ember does service and controller lookups with the `inject` helpers 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. This means that if you do things the normal Ember way, you will have to specify the type of your service or controller explicitly everywhere you use it.
239+
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.
244240

245-
```ts
246-
// my-app/services/session.ts
247-
import Service from '@ember/service';
248-
import RSVP from 'rsvp';
241+
This means that if you do things the normal Ember way, you will have to specify the type of your service or controller explicitly everywhere you use it. But… where should we put that? If we try to set it up as a [class property], we'll get an error as of Ember 3.1 (and it only accidentally works before that): computed properties and injections must be installed on the prototype.
249242

250-
export default class Session extends Service {
251-
login(email: string, password: string): RSVP.Promise<string> {
252-
// login and return the confirmation message
253-
}
254-
}
255-
```
243+
[class property]: https://basarat.gitbooks.io/typescript/docs/classes.html#property-initializer
256244

257-
```ts
258-
// my-app/components/user-profile.ts
259-
import Component from '@ember/component';
260-
import Computed from '@ember/object/computed';
261-
import { inject as service } from '@ember/service';
262-
263-
import Session from 'my-app/services/session';
245+
There are two basic approaches we can take. The first uses the `.extend` method in conjunction with class definitions to make sure the injections are set up correctly; the second leans on the still-experimental [Ember Decorators][decorators] project to let us do everything in the class body while still getting the niceties of ES6 classes. The decorators approach is much nicer, and likely to eventually become the standard across Ember in general assuming the decorators spec stabilizes. For today, however, it's an opt-in rather than the default because it remains an experimental extension to the JavaScript standard.
264246

265-
export default class UserProfile extends Component {
266-
session: Computed<Session> = service();
267-
268-
actions = {
269-
login(this: UserProfile, email: string, password: string) {
270-
this.get('session').login(email, password);
271-
},
272-
};
273-
}
274-
```
247+
[decorators]: https://github.com/ember-decorators/ember-decorators
275248

276-
The type of `session` would just be `Computed<Service>` if we didn't explicitly type it out. As a result, we wouldn't get any type-checking on that `.login` call (and in fact we'd get an incorrect type _error_!), 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!
249+
##### Using `.extend`
277250

278-
The handy workaround we supply is simply to write some boilerplate (though take heart! We have generators for any _new_ services or controllers you create) and then explicitly name the service or controller with its "dasherized" name, just like you would if you were mapping it to a different local name:
251+
The officially supported method for injections uses a combination of class body and traditional `EmberObject.extend` functionality. We generate a service like normal by running `ember generate service my-session`. The resulting definition will look like this:
279252

280253
```ts
281-
// my-app/services/session.ts
254+
// my-app/services/my-session.ts
282255
import Service from '@ember/service';
283256
import RSVP from 'rsvp';
284257

285-
export default class Session extends Service {
258+
export default class MySession extends Service {
286259
login(email: string, password: string): RSVP.Promise<string> {
287260
// login and return the confirmation message
288261
}
289262
}
290263

291264
declare module '@ember/service' {
292265
interface Registry {
293-
session: Session;
266+
'my-session': MySession;
294267
}
295268
}
296269
```
297270

271+
(If you're converting an existing service, remember to add the module declaration at the end. This is what we'll use to tell TypeScript what the type of the service is in the injection.)
272+
273+
Then we can use the service as usual:
274+
298275
```ts
299276
// my-app/components/user-profile.ts
300277
import Component from '@ember/component';
301278
import { inject as service } from '@ember/service';
302279

303-
export default class UserProfile extends Component {
304-
session = service('session');
305-
306-
actions = {
307-
login(this: UserProfile, email: string, password: string) {
308-
this.get('session').login(email, string);
309-
}
280+
export default class UserProfile extends Component.extend({
281+
mySession: service('my-session'),
282+
}) {
283+
login(email: string, password: string) {
284+
this.session.login(email, password);
310285
}
311286
}
312287
```
313288

314-
The corresponding declaration for controllers is:
289+
Notice that the type of `mySession` will be the `MySession` type here: TypeScript is using the "registry" set up in the last lines of the `my-session` module to look up the type by its name. If we had written just `service()` instead, Ember would have resolved the correct type at runtime as usual, but TypeScript would not be able to tell *which* service we had, only that it was a `Service`. In that case, the `this.session` would not have a `login` property from TS's perspective, and this would fail to type-check. That extra string gives TS the information it needs to resolve the type and give us auto-completion, type-checking, etc.
290+
291+
(In Ember 3.0 or earlier, we would have `this.get('session').login(email, password);` instead.)
292+
293+
Although this may look a little strange, everything works correctly. We can use other ES6 class functionality and behaviors (including class properties) as normal; it is just the special Ember pieces which have to be set up on the prototype like this: injections, computed properties, and the `actions` hash.
294+
295+
##### Using decorators
296+
297+
The alternative here is to use [Ember Decorators][decorators]. In that case, we'd have precisely the same definition for our `MySession` service, but a much cleaner implementation in the component class:
315298

316299
```ts
317-
declare module '@ember/controller' {
318-
interface Registry {
319-
// add your key here, like `'dasherized-name': ClassifiedName;`
300+
// my-app/components/user-profile.ts
301+
import Component from '@ember/component';
302+
import { service } from '@ember-decorators/service';
303+
import MySession from 'my-app/services/my-session';
304+
305+
export default class UserProfile extends Component {
306+
@service mySession: MySession;
307+
308+
login(this: UserProfile, email: string, password: string) {
309+
this.session.login(email, password);
320310
}
321311
}
322312
```
323313

324-
You'll need to add that module and interface declaration to all your existing service and controller declarations for this to work (again, see the [blog post][pt4] for further details), but once you do that, you'll have this much nicer experience throughout! It's not quite vanilla Ember.js, but it's close—and this way, you still get all those type-checking and auto-completion benefits, but with a lot less noise! Moreover, you actually get a significant benefit over "vanilla" Ember: we type-check that you typed the key correctly in the `service` invocation.
314+
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 (as of TS 2.8 – 3.0) 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!
325315

326-
If you have a reason to fall back to just getting the `Service` or `Controller` types, you can always do so by just using the string-less variant: `service('session')` will check that the string is a valid name of a service; `session()` will not.
316+
You'll need to add that module and interface declaration to all your existing service and controller declarations for this to work (again, see the [blog post][pt4] for further details), but once you do that, you'll have this much nicer experience throughout! It's not quite vanilla Ember.js, but it's close—and this way, you still get all those type-checking and auto-completion benefits, but with a lot less noise! Moreover, you actually get a significant benefit over "vanilla" Ember: we type-check that you typed the key correctly in the `service` invocation.
327317

328318
#### Ember Data lookups
329319

330-
The same basic approach is in play for Ember Data lookups. 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.
320+
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.
331321

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

@@ -431,6 +421,14 @@ This works because (a) we include things in your types directory automatically a
431421

432422
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!
433423

424+
#### Class property setup errors
425+
426+
Some common stumbling blocks for people switching to ES6 classes from the traditional EmberObject setup:
427+
428+
`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 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.
429+
430+
- `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`.
431+
434432
### Type definitions outside `node_modules/@types`
435433

436434
By default the typescript compiler loads up any type definitions found in `node_modules/@types`. If the type defs you need are not found here you can register a custom value in `paths` in the `tsconfig.json` file. For example, if you are using [True Myth], you'll need to follow that project's installation instructions (since it ships types in a special location to support both CommonJS and ES Modules):

0 commit comments

Comments
 (0)