From 7b89711402cca6d4b5e4d6a6095f1ca7aa4a323d Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 31 Jul 2018 15:51:01 +0300 Subject: [PATCH] docs(elements): add section about custom element typings in `elements` guide (#25219) PR Close #25219 --- aio/content/guide/elements.md | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/aio/content/guide/elements.md b/aio/content/guide/elements.md index e9de7bf5b3..b7b076d60a 100644 --- a/aio/content/guide/elements.md +++ b/aio/content/guide/elements.md @@ -164,3 +164,53 @@ For comparison, the demo shows both methods. One button adds the popup using the Only offer a `.zip` to download for now. --> You can download the full code for the example here. + + +## Typings for custom elements + +Generic DOM APIs, such as `document.createElement()` or `document.querySelector()`, return an element type that is appropriate for the specified arguments. For example, calling `document.createElement('a')` will return an `HTMLAnchorElement`, which TypeScript knows has an `href` property. Similarly, `document.createElement('div')` will return an `HTMLDivElement`, which TypeScript knows has no `href` property. + +When called with unknown elements, such as a custom element name (`popup-element` in our example), the methods will return a generic type, such as `HTMLELement`, since TypeScript can't infer the correct type of the returned element. + +Custom elements created with Angular extend `NgElement` (which in turn extends `HTMLElement`). Additionally, these custom elements will have a property for each input of the corresponding component. For example, our `popup-element` will have a `message` property of type `string`. + +There are a few options if you want to get correct types for your custom elements. Let's assume you create a `my-dialog` custom element based on the following component: + +```ts +@Component(...) +class MyDialog { + @Input() content: string; +} +``` + +The most straight forward way to get accurate typings is to cast the return value of the relevant DOM methods to the correct type. For that, you can use the `NgElement` and `WithProperties` types (both exported from `@angular/elements`): + +```ts +const aDialog = document.createElement('my-dialog') as NgElement & WithProperties<{content: string}>; +aDialog.content = 'Hello, world!'; +aDialog.content = 123; // <-- ERROR: TypeScript knows this should be a string. +aDialog.body = 'News'; // <-- ERROR: TypeScript knows there is no `body` property on `aDialog`. +``` + +This is a good way to quickly get TypeScript features, such as type checking and autocomplete support, for you custom element. But it can get cumbersome if you need it in several places, because you have to cast the return type on every occurrence. + +An alternative way, that only requires defining each custom element's type once, is augmenting the `HTMLELementTagNameMap`, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as `document.createElement()`, `document.querySelector()`, etc.): + +```ts +declare global { + interface HTMLElementTagNameMap { + 'my-dialog': NgElement & WithProperties<{content: string}>; + 'my-other-element': NgElement & WithProperties<{foo: 'bar'}>; + ... + } +} +``` + +Now, TypeScript can infer the correct type the same way it does for built-in elements: + +```ts +document.createElement('div') //--> HTMLDivElement (built-in element) +document.querySelector('foo') //--> Element (unknown element) +document.createElement('my-dialog') //--> NgElement & WithProperties<{content: string}> (custom element) +document.querySelector('my-other-element') //--> NgElement & WithProperties<{foo: 'bar'}> (custom element) +```