npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hapiness/ng-elements-loader

v7.2.0

Published

Service to load Angular Custom Elements inside Angular's applications

Downloads

9

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.

Back to top

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 use Renderer2 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

Back to top

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));

Back to top

- 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 the polyfill only require if your browser doesn't support customElement
    • @webcomponents/webcomponentsjs v2.1.3 to fix issue with es5 compilation outside Angular 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
  • v6.4.0 (2018-07-26)
    • Angular v6.1.0+
    • Documentation

Back to top

Maintainers

Back to top

License

Copyright (c) 2018 Hapiness Licensed under the MIT license.

Back to top