Angular
Using a Stencil built web component collection within an Angular CLI project is a two-step process. We need to:
- Include the
CUSTOM_ELEMENTS_SCHEMA
in the modules that use the components. - Call
defineCustomElements()
frommain.ts
(or some other appropriate place).
Including the Custom Elements Schema
Including the CUSTOM_ELEMENTS_SCHEMA
in the module allows the use of the web components in the HTML markup without the compiler producing errors. This code should be added into the
AppModule
and in every other modules that use your custom elements.
Here is an example of adding it to
AppModule
:
import { BrowserModule } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
The CUSTOM_ELEMENTS_SCHEMA
needs to be included in any module that uses custom elements.
Calling defineCustomElements
A component collection built with Stencil includes a main function that is used to load the components in the collection. That function is called
defineCustomElements()
and it needs to be called once during the bootstrapping of your application. One convenient place to do this is in
main.ts
as such:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
// Note: loader import location set using "esmLoaderPath" within the output target config
import { defineCustomElements } from 'test-components/loader';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
defineCustomElements();
Edge and IE11 polyfills
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()
})
Accessing components using ViewChild and 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 'test-components';
@Component({
selector: 'app-home',
template: `<test-components #test></test-components>`,
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
@ViewChild('test') myTestComponent: ElementRef<HTMLTestComponentElement>;
async onAction() {
await this.myTestComponent.nativeElement.testComponentMethod();
}
}
Bindings
Angular has a pretty good story for integration with web components but there are a few issues with the developer experience. If you want to know what the story is without the bindings go here: https://stenciljs.com/docs/angular.
With bindings the web components get wrapped in an Angular component and then immediately become available as Angular Components. Some of the advantages of doing this are that you get types for your components and you also get the ability to use ngmodel on inputs. Your developers then consuming your web components from Angular applications import an actual Angular Library and to them it feels as though they are interacting with Angular components.
Install
npm install @stencil/angular-output-target --save-dev
Stencil Config setup
To make use of the AngularOutputPlugin first import it into your stencil.config.ts file. Then add it as an OutputTarget.
import { Config } from '@stencil/core';
import { angularOutputTarget, ValueAccessorConfig } from '@stencil/angular-output-target';
export const config: Config = {
namespace: 'demo',
outputTargets: [
angularOutputTarget({
componentCorePackage: 'component-library',
directivesProxyFile: '../component-library-angular/src/directives/proxies.ts',
valueAccessorConfigs: angularValueAccessorBindings,
}),
{
type: 'dist',
},
],
};
componentCorePackage
This is the NPM package name of your core stencil package. In the case of Ionic we chose ‘@ionic/core’. This is the package that gets published that contains just your web components. This package is then used by the Angular package as a dependency
proxiesFile
This is the output file that gets generated by the outputTarget. This file should be referencing a different package location. In the example case we are choosing a sibling directory’s src directory. We will then create an Angular package that exports all components defined in this file.
valueAccessorConfigs
In order for ngmodel to work on input components we need to define certain pieces of information about the input components. Unfortunately the Stencil compiler cannot infer the intent of components because this is a very conceptual idea.
Setup of Angular Component Library
There is an example component library package available on Github so that you can get started. This repo will likely live as a sibling to your Stencil component library. https://github.com/ionic-team/stencil-ds-angular-template
Usage
import { ComponentLibraryModule } from 'component-library-angular';
@NgModule({
...
imports: [
ComponentLibraryModule
],
...
})
export class AppModule { }