UNPKG

@stencil/angular-output-target

Version:

Angular output target for @stencil/core components.

149 lines (113 loc) 13.7 kB
# @stencil/angular-output-target Stencil can generate Angular component wrappers for your web components. This allows your Stencil components to be used within an Angular application. The benefits of using Stencil's component wrappers over the standard web components include: - Angular component wrappers will be detached from change detection, preventing unnecessary repaints of your web component. - Web component events will be converted to RxJS observables to align with Angular's @Output() and will not emit across component boundaries. - Optionally, form control web components can be used as control value accessors with Angular's reactive forms or [ngModel]. For a detailed guide on how to add the angular output target to a project, visit: https://stenciljs.com/docs/angular. ## Installation ```bash npm install @stencil/angular-output-target ``` ## Angular Support | **@stencil/angular-output-target** | **Angular** | |------------------------------------|-----------------| | 0.10.2 | v18.x and lower | | 1.0.0 | v19.x and above | ## Usage In your `stencil.config.ts` add the following configuration to the `outputTargets` section: ```ts import { Config } from '@stencil/core'; import { angularOutputTarget } from '@stencil/angular-output-target'; export const config: Config = { namespace: 'demo', outputTargets: [ angularOutputTarget({ componentCorePackage: 'component-library', directivesProxyFile: '../component-library-angular/src/directives/proxies.ts', directivesArrayFile: '../component-library-angular/src/directives/index.ts', }), { type: 'dist', esmLoaderPath: '../loader', }, ], }; ``` ## Config Options | Property | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. | | `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. | | `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. | | `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. | | `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. | | `outputType` | Specifies the type of output to be generated. It can take one of the following values: <br />1. `component`: Generates all the component wrappers to be declared on an Angular module. This option is required for Stencil projects using the `dist` hydrated output.<br /> 2. `scam`: Generates a separate Angular module for each component.<br /> 3. `standalone`: Generates standalone component wrappers.<br /> Both `scam` and `standalone` options are compatible with the `dist-custom-elements` output. <br />Note: Please choose the appropriate `outputType` based on your project's requirements and the desired output structure. Defaults to `component`. | | `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Defaults to the `components` directory. Only applies for `outputType: "scam"` or `outputType: "standalone"`. | | `inlineProperties` | Experimental. When true, tries to inline the properties of components. This is required to enable Angular Language Service to type-check and show jsdocs when using the components in html-templates. | | `booleanAttributes` | When `true`, boolean properties are declared with an Angular input transform so they can be set by attribute presence, e.g. `<my-component disabled>` instead of `<my-component [disabled]="true">`. Type-checking the attribute form also requires `inlineProperties`. Defaults to `false`. Refer to [Boolean attributes](#boolean-attributes). | ## Boolean attributes Angular resolves a bare attribute to the empty string, so a boolean property has to be bound explicitly by default: ```html <!-- Type 'string' is not assignable to type 'boolean' under strictTemplates --> <my-component disabled></my-component> <my-component [disabled]="true"></my-component> ``` Setting `booleanAttributes: true` declares boolean properties with an Angular input transform, which accepts the attribute form and coerces it: ```html <my-component disabled></my-component> <my-component disabled="false"></my-component> <my-component [disabled]="isDisabled"></my-component> ``` The transform used is `nullableBooleanAttribute` rather than Angular's `booleanAttribute`. It coerces strings identically, but passes `null` and `undefined` through instead of coercing them to `false`. Components frequently treat those as a state distinct from `false`: ```tsx // `undefined` means "decide based on the mode", which is not the same as `false` const showDetail = detail !== undefined ? detail : mode === 'ios'; // a strict comparison also behaves differently for `null` than it does for `false` const showHandle = handle !== false; ``` Both values reach inputs routinely, from the `async` pipe before its first emission and from form control values, so coercing them would change the behavior of bindings that work today. ### Type-checking requires `inlineProperties` This option changes what the Angular compiler accepts, so it only has a visible effect where the compiler type-checks the wrapper's inputs in the first place. That needs `inlineProperties` enabled as well: ```ts angularOutputTarget({ // ... booleanAttributes: true, inlineProperties: true, }); ``` Without `inlineProperties` the generated wrappers declare no typed members for their inputs, so Angular does not check these bindings at all and there is no `TS2322` for the transform to resolve. The transform itself still runs either way. Angular applies it from the component definition's input map, which does not depend on a declared class member, so enabling `booleanAttributes` on its own moves boolean coercion from Stencil to the transform without the compiler checking what you pass. The two agree on strings, booleans, `null` and `undefined`. If you bind a value outside those, prefer enabling `inlineProperties` too so that `ngAcceptInputType_*` rejects it at compile time rather than silently coercing it. Note that `inlineProperties` is itself experimental, so enabling both is opting into that. With `booleanAttributes` on its own you still get the attribute syntax at runtime, just without the compiler checking it. ### Limitations A property is only transformed when Stencil reports its type as exactly `boolean`. Three kinds of property fall outside that, and they behave differently from each other. In all three the property receives the attribute's empty string rather than `true`, so bind them explicitly when you need a boolean. **Virtual properties** are not transformed and get no inlined class member either, so Angular has nothing to check the binding against and skips it. A bare attribute compiles, and the property receives `''`. Nothing warns you. **Properties declared as `any`** are not transformed, but they do get an inlined member typed `any`, so `''` is assignable. A bare attribute compiles, and the property receives `''`. Nothing warns you here either. **Properties whose type unions `boolean` with something else**, such as `boolean | 'auto'`, are reported to the output target as `any`, so they get no transform, but their inlined member keeps the real union type. This is the only case the compiler catches: ``` TS2322: Type '""' is not assignable to type 'boolean | "auto"' ```