This project shows a very basic example of how to connect a web component (generated with Stencil) with a basic angular project.
The following steps explain what I've done to get this working.
The idea is to have a web component that can be data-bound via properties and return its changes by custom events.
For this example I've picked nothing special but a simple slider component. The following code shows the @Component decorator. This decorator adds meta-information to you component like its selector tag. Our slider should use the custom element selector tag fwt-slider (so we can use <fwt-slider> later in the DOM).
import { Component } from '@stencil/core';
@Component({
tag: 'fwt-slider'
})
export class SliderComponent {
...
}Stencil components are very similar to react components. They have the same lifecycle methods and of course they also need a render function. So let's add one:
import { Component } from '@stencil/core';
@Component({
tag: 'fwt-slider'
})
export class SliderComponent {
render() {
return (
<div class="slider-container">
...
</div>
)
}
}Our slider will have a min and a max property and an initial value property. Adding a property to a stencil component is very easy. All you have to do is to use the @Prop decorator:
import { Component, Prop } from '@stencil/core';
@Component({
tag: 'fwt-slider'
})
export class SliderComponent {
@Prop() min: number;
@Prop() max: number;
@Prop() value: number;
render() {
return (
<div class="slider-container">
<input type="range" min={this.min} max={this.max} value={this.value} class="slider">
</input>
</div>
)
}
}Ok so far so good.
How about emitting an event when the value has been changed? That's actually a good idea. For this purpose we only have to use another decorator called @Event. This decorator is used in combination with the EventEmitter interface. This will create a custom event that is called like the event property. You can change the name and other event properties by passing EventOptions to the @Event decorator but for our example we keep things simple.
In this example I'm listening to changes by the range input and pass the current value via the EventEmitter using its emit function.
I've also added some style to our slider by setting the styleUrl (Stencil supports scss and css) in the @Component decorator.
import { Component, Prop, Event, EventEmitter } from '@stencil/core';
@Component({
tag: 'fwt-slider',
styleUrl: 'slider.scss'
})
export class SliderComponent {
@Prop() min: number;
@Prop() max: number;
@Prop() value: number;
@Event() valueChanged: EventEmitter;
valueChangedHandler(event: any) {
this.valueChanged.emit(event.target.value);
}
render() {
return (
<div class="slider-container">
<input type="range" min={this.min} max={this.max} value={this.value} class="slider"
onChange={(event) => this.valueChangedHandler(event)}>
</input>
</div>
);
}
}fwt-slider {
.slider-container {
width: 100%;
.slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 15px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
transition: opacity .2s;
&:hover {
opacity: 1;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
&::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
}
}
}I'm going to add the build output of stencil to www/assets/build because later I will copy this output into the assets folder of a angular-cli project.
Normally the build output will be put to www/build but this can be changed in the stencil.config.js.
I also register my fwt-slider component there.
exports.config = {
bundles: [
{ components: ['fwt-slider'] }
],
buildDir: 'assets/build',
collections: [
]
};
exports.devServer = {
root: 'www',
watchGlob: '**/**'
}Ok let's start the build with npm run build inside the stencil dir.
After the build is done you must copy the whole build dir from /stencil/www/assets/ to /angular/src/assets/.
Our angular project only has one basic app module containing only one simple app component.
To enable the use of web components we must add the CUSTOM_ELEMENTS_SCHEMA from the @angular/core module to our app module.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
bootstrap: [AppComponent]
})
export class AppModule { }Next we need to add the generated javascript of our stencil project to our angular project. To do this we add a script tag somewhere in the head of the index.html of our angular project:
<script src="assets/build/app.js"></script>An even better way would be to add your additional web component scripts to the .angular-cli.json config file as explained here.
We said that our slider component has three properties (min, max, value). So let's establish angular data-binding to these properties in our app component.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
min = 0;
max = 100;
value = 50;
}And in the app.component.html we are adding our slider web component like this:
<fwt-slider [min]="min" [max]="max" [value]="value"></fwt-slider>One last thing I want to show is how we can listen to the custom events and use them to update the data-bound properties. So let's add another slider with its own value and two event handlers that set the value property of the other slider component ('yeah I know, very clever example').
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
min = 0;
max = 100;
value1 = 50;
value2 = 50;
onValue1Changed(event: CustomEvent) {
console.log(event.detail);
this.value2 = event.detail;
}
onValue2Changed(event: CustomEvent) {
console.log(event.detail);
this.value1 = event.detail;
}
}<fwt-slider [min]="min" [max]="max" [value]="value1"
(valueChanged)="onValue1Changed($event)">
</fwt-slider>
<fwt-slider [min]="min" [max]="max" [value]="value2"
(valueChanged)="onValue2Changed($event)">
</fwt-slider>See what we have done by running npm start inside the angular directory.
