@hapiness/ng-elements-loader
v7.2.0
Published
Service to load Angular Custom Elements inside Angular's applications
Downloads
9
Maintainers
Readme
NG-ELEMENTS-LOADER
This module exposes an Angular's
service to load easily custom elements in your Angular
application.
We support Angular
version 7.1.0+
.
Installation
$ yarn add @hapiness/ng-elements-loader
or
$ npm install --save @hapiness/ng-elements-loader
Don't miss to install all peer dependencies if not yet done : @angular/common
, @angular/core
, @angular/compiler
, @angular/elements
, @angular/platform-browser
, @angular/platform-browser-dynamic
, core-js
, document-register-element
, rxjs
and zone.js
.
If your in an Angular-CLI
application, all dependencies are already installed. You just need to install @angular/elements
: ng add @angular/elements
Usage
Before to use ElementsLoaderService
exposed by @hapiness/ng-elements-loader
, you must create your own custom-elements
modules.
To create a new library with Angular-CLI
, follow this guide.
1) made-with-love custom element
- Component
This component will be the final custom-element
interpreted in your browser.
projects/made-with-love/src/lib/made-with-love.component.ts:
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'made-with-love',
templateUrl: './made-with-love.component.html',
encapsulation: ViewEncapsulation.ShadowDom
})
export class MadeWithLoveComponent implements OnInit {
private _name: string;
private _url: string;
private _color: string;
private _size: number;
constructor() {
this.size = 1;
this.color = 'red';
}
ngOnInit() {
if (!this._name || this._name.length === 0) {
console.error(`Name attribute must be provided!`);
}
}
get name(): string {
return this._name;
}
@Input()
set name(n: string) {
this._name = n;
}
get url(): string {
return this._url;
}
@Input()
set url(u: string) {
this._url = u;
}
get color(): string {
return this._color;
}
@Input()
set color(c: string) {
this._color = c;
}
get size(): number {
return this._size;
}
@Input()
set size(s: number) {
this._size = s;
}
}
Note: Your component must have encapsulation equals to ViewEncapsulation.ShadowDom
if you want to have shadowdomv1 support else you can delete this line to have original support.
projects/made-with-love/src/lib/made-with-love.component.html:
<ng-template #noUrl>
{{ name }}
</ng-template>
<span [style.font-size.em]="size">
Made with <span [style.color]="color">♥</span> by
<ng-container *ngIf="url && url.length > 0; else noUrl">
<a [attr.href]="url" target="_blank">{{ name }}</a>
</ng-container>
</span>
- Module
projects/made-with-love/src/lib/made-with-love.module.ts:
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WithCustomElementComponent } from '@hapiness/ng-elements-loader';
import { MadeWithLoveComponent } from './made-with-love.component';
@NgModule({
imports: [
CommonModule
],
declarations: [
MadeWithLoveComponent
],
entryComponents: [
MadeWithLoveComponent
],
exports: [
MadeWithLoveComponent
]
})
export class MadeWithLoveModule {}
Note: Component must be declared inside entryComponents
and declaration
meta-data of NgModule
. You can see we've declared the component inside exports
meta-data too, like this the module can be use directly, if you want, with normal import without custom-elements
.
- Dependencies
The minimum package.json
file for your module is described below:
projects/made-with-love/package.json:
{
"name": "made-with-love",
"version": "1.0.0",
"peerDependencies": {
"@hapiness/ng-elements-loader": "^7.2.0"
}
}
If your module has to have others Angular
dependencies, add them in peerDependencies
. If your module has to have externals dependencies, add them in dependencies
like that they will be automatically installed.
- Publish your module
Your custom-element
module is now ready to be used so you have to publish it before use it in your application.
2) made-with-love custom element in your Angular application
Create an Angular-CLI
application with your module and @hapiness/ng-elements-loader
in dependencies.
Install all dependencies
your module must have if not already installed.
- Component uses made-with-love custom element
We create a new component in our application to integrate custom element inside: ng g c say-with-love
This command will create 3 files: say-with-love.component.ts
, say-with-love.component.html
and say-with-love.component.css
and add SayWithLoveComponent
directly inside the declaration
of the main module of your application AppModule
.
src/app/say-with-love/say-with-love.component.ts:
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { ElementsLoaderService } from '@hapiness/ng-elements-loader';
import { tap } from 'rxjs/operators';
import { MadeWithLoveComponent } from 'made-with-love';
@Component({
selector: 'say-with-love',
templateUrl: './say-with-love.component.html',
styleUrls: ['./say-with-love.component.css']
})
export class SayWithLoveComponent implements OnInit {
constructor(private _el: ElementRef, private _rd: Renderer2, private _elementsLoaderService: ElementsLoaderService) {
const element = this._rd.createElement('wc-made-with-love');
this._rd.setAttribute(element, 'name', 'Hapiness Framework');
this._rd.setAttribute(element, 'url', 'https://github.com/hapinessjs/');
this._rd.setAttribute(element, 'size', '2');
this._rd.appendChild(this._el.nativeElement, element);
}
ngOnInit(): void {
this._elementsLoaderService.registerContainingCustomElements({
selector: 'wc-made-with-love',
component: MadeWithLoveComponent
})
.pipe(
tap(_ => console.log('wc made-with-love loaded'))
)
.subscribe();
}
}
say-with-love.component.html
and say-with-love.component.css
files are empty.
src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MadeWithLoveModule } from 'made-with-love';
import { SayWithLoveComponent } from './say-with-love/say-with-love.component';
@NgModule({
declarations: [
SayWithLoveComponent
],
imports: [
BrowserAnimationsModule,
MadeWithLoveModule
],
providers: [],
bootstrap: [ SayWithLoveComponent ]
})
export class AppModule {
}
- Explanation
The creation of the custom element happens inside the constructor
of the wrapper component.
- To not interact directly with the
DOM
, we useRenderer2
to create our custom element:
const element = this._rd.createElement('wc-made-with-love');
- after, we set all attributes we want to display:
this._rd.setAttribute(element, 'name', 'Hapiness Framework');
this._rd.setAttribute(element, 'url', 'https://github.com/hapinessjs/');
this._rd.setAttribute(element, 'size', '2');
- when all elements are ready, we can insert our custom element inside the
DOM
:
this._rd.appendChild(this._el.nativeElement, element);
Loading of the component happens inside OnInit
process.
this._elementsLoaderService.registerContainingCustomElements({
selector: 'wc-made-with-love',
component: MadeWithLoveComponent
})
.pipe(
tap(_ => console.log('wc made-with-love loaded'))
)
.subscribe();
We call registerContainingCustomElements
method of ElementsLoaderService
from @hapiness/ng-elements-loader
.
This method takes CustomElementComponentSelector
or CustomElementComponentSelector[]
in parameter.
export interface CustomElementComponentSelector {
selector: string;
component: Type<any>;
}
Selector is the custom tag
of your custom element
and component is the Angular
component presents in entryComponents
and declarations
of MadeWithLoveModule
.
MadeWithLoveModule
have to be imported inside the main module of your application AppModule
to compile MadeWithLoveComponent
declared in entryComponents
.
- Show the result
Launch your application and you will see your custom element
displayed inside your Angular
application:
Made with ♥ by Hapiness Framework
3) Custom element with custom event
In the previous component we have created only @Input
properties but sometimes, you'll want to emit event from your custom element
to the DOM
with @Ouput
properties.
- Custom element
Here an example of a component emits event to its parent:
projects/hello-world/src/lib/hello-world.component.ts:
import { Component, EventEmitter, OnInit, Output, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class HelloWorldComponent implements OnInit {
private _sayHello$: EventEmitter<string>;
constructor() {
this._sayHello$ = new EventEmitter<string>();
}
ngOnInit() {
}
@Output('sayHello')
get sayHello$(): EventEmitter<string> {
return this._sayHello$;
}
sayHello() {
this._sayHello$.emit('Hello World');
}
}
projects/hello-world/src/lib/hello-world.component.html:
<div>
<button type="button" (click)="sayHello()">Say Hello with Event</button>
</div>
- Use it in your application
To use it and receive event, you must do this:
src/app/say-hello-world/say-hello-world.component.ts:
import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { ElementsLoaderService } from '@hapiness/ng-elements-loader';
import { tap } from 'rxjs/operators';
import { HelloWorldComponent } from 'hello-world';
@Component({
selector: 'say-hello',
templateUrl: './say-hello.component.html',
styleUrls: ['./say-hello.component.css']
})
export class SayHelloComponent implements OnInit {
constructor(private _el: ElementRef, private _rd: Renderer2, private _elementsLoaderService: ElementsLoaderService) {
const element = this._rd.createElement('wc-hello-world');
this._rd.listen(element, 'sayHello', (event: any) => this.alertHello(event.detail));
this._rd.appendChild(this._el.nativeElement, element);
}
ngOnInit(): void {
this._elementsLoaderService.registerContainingCustomElements({
selector: 'wc-hello-world',
component: HelloWorldComponent
})
.pipe(
tap(_ => console.log('wc hello-world loaded'))
)
.subscribe();
}
alertHello(event: string) {
alert(event);
}
}
We set a listener with Renderer2
to catch sayHello
event and do what we want:
this._rd.listen(element, 'sayHello', (event: any) => this.alertHello(event.detail));
- Add custom element support in your application
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2015",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2015", // this line must switch target from es5 to es2015
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
If your browser doesn't support natively customElement
like explain here, you must add document-register-element
inside src/polyfills.ts
file
Change History
- v7.2.0 (2018-11-27)
Angular v7.1.0+
- Add
ElementsLoaderService.registerContainingCustomElements()
method to be used for AoT compiler ElementsLoaderService.loadContainingCustomElements()
method must be used only for JiT compiler- Explain how to create an optimized
webcomponent
bundle with this tutorial - Documentation
- v7.1.0 (2018-11-09)
Angular v7.0.3+
document-register-elements v1.13.1
latest version of thepolyfill
only require if your browser doesn't supportcustomElement
@webcomponents/webcomponentsjs v2.1.3
to fix issue withes5
compilation outsideAngular
application like explain here- Allow custom elements registration in browser even if tag isn't yet present in the
DOM
like this, it can be created or loaded asynchronously after registration - Documentation
- v7.0.0 (2018-11-02)
Angular v7.0.2+
- Documentation
- v6.4.2 (2018-10-18)
Angular v6.1.10+
- Documentation
- v6.4.1 (2018-09-26)
- Fix version to
Angular v6.1.7
to avoid the bug reported in this issue - Documentation
- Fix version to
- v6.4.0 (2018-07-26)
Angular v6.1.0+
- Documentation
Maintainers
License
Copyright (c) 2018 Hapiness Licensed under the MIT license.