Angular Integration
Support: Angular 12+ • TypeScript 4.0+ • Stencil v2.9.0+
Stencil provides a wrapper for your custom elements to be used as first-class Angular components. The goal of a wrapper is to smooth over how Stencil’s code works within a framework. Wrappers provide a function that you can use within Stencil’s Output Targets to automatically create components for the targeted framework that wrap the web components you author in a Stencil project.
One benefit of the wrapper pattern includes improved maintainability since you are writing code once, and reusing it across frameworks. Another benefit of this pattern is that you can have first-class integration with your framework of choice. For example, with the Angular wrapper, you can bind input events directly to a value accessor for seamless integration in Angular’s bi-directional data flow.
Setup
Project Structure
To organize the generated component libraries for different frameworks, we recommend using a monorepo structure. This monorepo will contain your Stencil component library as well as the component libraries for whatever frameworks you choose. The overall structure of a monorepo with Stencil and Angular component libraries might look something like this:
top-most-directory/
├── stencil-library/
│ ├── stencil.config.js
│ └── src/components
└── angular-workspace/
└── projects/
└── component-library/
└── src/
├── lib/
└── public-api.ts
Create an Angular Component Library
First, we’ll create an Angular component library next to your Stencil component library. If you already have an Angular component library prepared, you can skip this step.
In your CLI tool of choice, install Angular’s CLI tools:
npm install -g @angular/cli
Next, you will create your Angular workspace and project. Ideally, this should be placed next to your Stencil project, so a monorepo structure is encouraged.
ng new angular-workspace --no-create-application
cd angular-workspace
ng generate library component-library
Create a Stencil Component Library
Your Stencil library will be the location that you write your web components.
npm init stencil components stencil-library
Install the Angular Output Target in your Stencil Component Library
Now that the project structure is set up, we can install the Angular Output Target package. This package contains the Angular wrapper function that we will use to generate our Angular wrapped components. To install the Angular Output Target package, run the following command in your Stencil project directory
npm i @stencil/angular-output-target
or
yarn add @stencil/angular-output-target
Add the Angular Wrapper Function to your Stencil Component Library
With the Angular Output Target package installed, we can now configure our Stencil Component Library to build our Angular wrapped components. In the
stencil.config.ts
file of your Stencil component library, add the Angular wrapper function.
Within your Stencil’s config file, you can import the Angular wrapper function for use within the outputTargets array. If you copy and paste this, ensure you update the “my-workspace” and “my-lib” to coincide with what you named your component library, as well as to update the
componentCorePackage
to the name of your Stencil component library.
import { angularOutputTarget as angular } from '@stencil/angular-output-target';
export const config: Config = {
namespace: 'demo',
outputTargets: [
angular({
componentCorePackage: `your-stencil-library-name`,
directivesProxyFile: `../your-angular-workspace-name/projects/your-angular-library-name/src/lib/stencil-generated/components.ts`
}),
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements',
},
],
};
Once you have added this and have
directivesProxyFile
pointing to the correct directory of your Angular component library, you can build Stencil.
npm run build
Or
yarn build
Once the build is complete, you will see new files in your Angular component library's directory!
top-most-directory/
├── stencil-library/
│ ├── stencil.config.js
│ └── src/components
└── angular-workspace/
└── projects/
└── component-library/
└── src/
├── lib/
│ └── stencil-generated/components.ts
└── public-api.ts
Add the components to your Angular project’s entry file (public-api.ts)
In order to make the generated files available to your Angular component library and it’s consumers, you’ll need to export everything from within your entry file - commonly the
public-api.ts
file. To do this, you’ll write:
export * from './lib/stencil-generated/components.ts';
Link your packages (optional)
If you’re using a monorepo tool like Lerna or Nx, you can skip this step. Before you can successfully build a local version of your Angular component library, you will need to link the Stencil package to the Angular package.
First, in your Stencil directory, run the following command:
npm link
Or
yarn link
Next, in your Angular component library, run the following command:
npm link {the name of your Stencil package}
Or
yarn link {the name of your Stencil package}
To determine your Stencil’s package name, you can visit your Stencil Component Library's
package.json
file.
And with that, your component libraries are linked together. Now, you can make changes in your Stencil component library and run npm run build to propagate them through to the Angular component library.
NOTE: As an alternative to
npm link
, you can also runnpm install
with a relative path to your Stencil component library. This strategy, however, will modify yourpackage.json
so it is important to make sure you do not commit those changes.
All done!
At this point, once you build your Angular project and import this library into your app, you will have access to all of the wrapped Angular components. You can visit the Angular package and run the following command to see the result.
npm run build
Or
yarn build
Usage
You're now able to import your components into an Angular app and use them directly. Here's how the consumers of your Angular Component Library will use this code.
Adding the components to a module
Your component library consumers will be able to import your Angular components into your app's module by writing:
import { MyComponent } from 'angular-component-library';
import { SomeComponent } from './some.component';
@NgModule({
imports: [MyComponent],
declarations: [SomeViewComponent],
exports: [SomeComponent]
})
export class SomeViewModule {}
Which will automatically define and run your components!
Usage in your templates
<my-component first="Stencil" last="Compiler"></my-component>
Considerations
Please note, you can create your own NgModule that can export and define all of your components, and encourage your customers to use that module within the imports property of their NgModules. There is a Github issue to expand on this feature to align to single component angular modules (or SCAM).
FAQs
What is the best format to write event names?
Event names shouldn’t include special characters when initially written in Stencil, try to lean on using camelCased event names for interoperability between frameworks.
How do I bind input events directly to a value accessor?
You can configure how your input events can map directly to a value accessor, allowing two-way data-binding to be a built in feature of any of your components. Take a look at valueAccessorConfig's option below.
How do I add IE11 or Edge support?
If you want your custom elements to be able to work on older browsers, you should add the
applyPolyfills()
that surround the
defineCustomElements()
function.
import { applyPolyfills, defineCustomElements } from 'test-components/loader';
...
applyPolyfills().then(() => {
defineCustomElements()
})
How do I access components with ViewChild or ViewChildren?
Once included, components could be referenced in your code using
ViewChild
and
ViewChildren
as in the following example:
import {Component, ElementRef, ViewChild} from '@angular/core';
import { TestComponent } from 'test-components';
@Component({
selector: 'app-home',
template: `<test-components #test></test-components>`,
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
@ViewChild(TestComponent) myTestComponent: ElementRef<TestComponent>;
async onAction() {
await this.myTestComponent.nativeElement.testComponentMethod();
}
}
Why aren't my custom interfaces exported from within the index.d.ts file?
Usually when beginning this process, you may bump into a situation where you find that some of the interfaces you've used in your Stencil component library aren't working in your Angular component library. You can resolve this issue by adding an
interfaces.d.ts
file located within the root of your Stencil component library's project folder, then manually exporting types from that file e.g.
export * from './components';
When adding this file, it's also recommended to update your package.json's types property to be the distributed file, something like:
"types": "dist/types/interfaces.d.ts"
API
The angularOutputTarget method accepts 5 parameters:
componentCorePackage
The title of the Stencil package where components are available for consumers. This is used during compilation to write the correct imports for components e.g.
import { IonApp } from '@ionic/core/components/ion-app.js'
directivesProxyFile
This parameter allows you to name the file that contains all the component wrapper definitions produced during the compilation process. This is the first file you should import in your Angular project.
includeImportCustomElements
If
true
, Angular components will import and define elements from the
dist-custom-elements
build, rather than
dist
.
directivesArrayFile
Used to provide a list of type Proxies to the Angular Component Library. See Ionic Framework for a sample.
directivesUtilsFile
This is the file where helper functions for the component wrappers are defined.
valueAccessorConfigs
This lets you define which components should be integrated with ngModel (I.e. form components). It lets you set what the target prop is (I.e.
value
), which event will cause the target prop to change, and more.
const angularValueAccessorBindings: ValueAccessorConfig[] = [
{
elementSelectors: ['my-input[type=text]'],
event: 'myChange',
targetAttr: 'value',
type: 'text',
},
];
export const config: Config = {
namespace: 'component-library',
outputTargets: [
angularOutputTarget({
componentCorePackage: 'component-library',
directivesProxyFile: '{path to your proxy file}',
valueAccessorConfigs: angularValueAccessorBindings,
}),
{
type: 'dist',
esmLoaderPath: '../loader',
},
],
};
excludeComponents
This lets you exclude wrapping certain Web Components. This is useful if you need to write framework-specific versions of components. In Ionic Framework, this is used for routing components - like tabs - so that Ionic Framework can integrate better with Angular's Router.
Contributors
Contents
Setup Project Structure Create an Angular Component Library Create a Stencil Component Library Install the Angular Output Target in your Stencil Component Library Add the Angular Wrapper Function to your Stencil Component Library Add the components to your Angular project’s entry file (public-api.ts) Link your packages (optional) All done! Usage Adding the components to a module Usage in your templates Considerations FAQs What is the best format to write event names? How do I bind input events directly to a value accessor? How do I add IE11 or Edge support? How do I access components with ViewChild or ViewChildren? Why aren't my custom interfaces exported from within the index.d.ts file? API componentCorePackage directivesProxyFile includeImportCustomElements directivesArrayFile directivesUtilsFile valueAccessorConfigs excludeComponents
Thanks for your interest!
We just need some basic information so we can send the guide your way.