Find out how to easily create libraries in Angular and transform them into web-components.

Below I am describing a step-by-step example of how to create libraries for reuse in Angular projects, but also thanks to Angular Elements they can be used outside of Angular. An example is my blog, where the basis is Hexo, and additional functions I write in Angular Elements and attach them to the project as ordinary HTML tags. The following steps show the creation of cookie consent which is visible on the blog page at the first entry and also in my app qrcode generator

Create Angular Project

Create a new angular project in which all the codes and scripts of the project will be included.

ng new cookie-consent

Go inside of project.

cd cookie-consent

Create library

Generate a library that you can use later in Angular projects.

ng generate library @lacosanostra/ngx-cookie-consent

Edit package.json

{
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build-web-component": "./build.sh",
    "build-ngx-cookie-consent": "ng build @lacosanostra/ngx-cookie-consent",
    "test": "ng test",
    "test-headless": "ng test --watch=false --browsers=ChromeHeadless",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }
}
  • build-web-component will generate the project as a web-component, the build.sh script will be given below.
  • build-ngx-cookie-consent will build the Angular version of the component ready for publication in npm. The command is also needed for the build-web-component because it uses this library. While working on the new module, you can run it with the watch option, example: npm run build-ngx-cookie-consent -- --watch
  • test-headless was created for running tests in Travis

Edit projects/lacosanostra/ngx-cookie-consent/package.json

{
  "publishConfig": {
    "access": "public"
  }
}

Adding publishConfig with access: public allows you to share the module publicly in npm as @organization. By default, access is set to private.

Edit projects/lacosanostra/ngx-cookie-consent/src/lib/ngx-cookie-consent.module.ts

import { NgModule } from '@angular/core';
import { NgxCookieConsentComponent } from './ngx-cookie-consent.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [NgxCookieConsentComponent],
  imports: [
    BrowserAnimationsModule,
    MatIconModule,
    MatButtonModule
  ],
  exports: [NgxCookieConsentComponent]
})
export class NgxCookieConsentModule { }

We only add MatIconModule, MatButtonModule here. I use them on the closing button.

Edit projects/lacosanostra/ngx-cookie-consent/src/lib/ngx-cookie-consent.component.ts

import { Component, OnInit, HostBinding } from '@angular/core';

@Component({
  selector: 'lib-ngx-cookie-consent',
  template: `
    <div class="mat-typography cookie-consent-content">
      <ng-content></ng-content>
    </div>
    <button mat-icon-button aria-label="Close cookie consent" (click)="close()">
      <mat-icon>close</mat-icon>
    </button>
  `,
  styleUrls: ['./ngx-cookie-consent.component.scss']
})
export class NgxCookieConsentComponent implements OnInit {
  @HostBinding('class.visible') visible = false;
  private signature = 'cookie-consent-closed';

  public ngOnInit(): void {
    const isClosed = localStorage.getItem(this.signature) === 'true';
    if (!isClosed) {
      this.visible = true;
    }
  }

  public close(): void {
    localStorage.setItem(this.signature, 'true');
    this.visible = false;
  }

}

The main component of the entire project.

  • <ng-content> the body content is loaded here. In this case it will be information about the use of cookie, for example: This site uses cookies to analyze traffic, remember your preferences, and optimize your experience.
  • <button> A simple button to close our information after reading it. Usage can be seen in the methods of ngOnInit() where I check if I have to make a visible component on startup and close() which is triggered when the button is clicked. The event hides the component and updates the logical value in localStorage.

Edit projects/lacosanostra/ngx-cookie-consent/src/lib/ngx-cookie-consent.component.scss

:host {
  display: none;
  align-items: center;

  &.visible {
    display: flex;
  }

  .cookie-consent-content {
    flex-grow: 1;
  }
}

Styles needed for our element to look nice.

The library ready, our lib-ngx-cookie-consent can already be used in angular projects, just import the module. :) Now let’s go to convert it to a web-component.

Create web-component

Add angular elements to project.

ng add @angular/elements

Delete the unnecessary app component and its test.

rm ./src/app/app.component.ts ./src/app/app.component.spec.ts

Edit src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { NgxCookieConsentModule, NgxCookieConsentComponent } from '@lacosanostra/ngx-cookie-consent';
import { createCustomElement } from '@angular/elements';

@NgModule({
  declarations: [],
  imports: [
    BrowserModule,
    NgxCookieConsentModule
  ],
  providers: [],
  entryComponents: [NgxCookieConsentComponent],
  bootstrap: []
})
export class AppModule {
  constructor(private injector: Injector) { }

  ngDoBootstrap() {
    const ngElement = createCustomElement(NgxCookieConsentComponent, {
      injector: this.injector
    });
    customElements.define('cookie-consent', ngElement);
  }
}

Now the only thing that the AppModule does is load the NgxCookieConsentModule module and register its component as web-component.

Edit src/polyfills.ts

...
/***************************************************************************************************
 * APPLICATION IMPORTS
 */
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';

This extra import adds us an adapter for custom elements in es5. Without it, we get errors when we running our web-component in the browser.

Edit src/style.scss

/* You can add global styles to this file, and also import other style files */
@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Additional import of a sample theme for Angular Material components.

Edit src/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>CookieConsent</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <style>
    cookie-consent {
      display: none;
    }
  </style>
</head>
<body>
  <cookie-consent>
    This site uses cookies to analyse traffic, remember your preferences, and optimise your experience.
    <a href="/policy">Learn more</a>
  </cookie-consent>
</body>
</html>

Now we can see how our web-component presents itself in the code on an ordinary website. Additionally, for the close button it is required to import icons from Material Design.

Run project

To start the project, we will need to build the library:

npm run build-ngx-cookie-consent

Then our web-component project has all the dependencies needed to start.

npm start

Build release web-component

Create build.sh
Give execute permission. chmod +x build.sh

#!/bin/bash
set -x
APP=cookie-consent
ng build $APP --prod --output-hashing=none && cat dist/$APP/runtime-es5.js dist/$APP/polyfills-es5.js dist/$APP/scripts.js dist/$APP/main-es5.js > dist/$APP/$APP.js

The above script builds the project and combines all the files into one, so that later it would be easier to use our web-component. The combined file is in path dist/cookie-consent/cookie-consent.js

The final file should be added at the bottom of the body of the page where you want to use <cookie-consent>.

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/studioLaCosaNostra/cookie-consent@latest/dist/cookie-consent/cookie-consent.js"></script>

Add material icons to html. For the closing button to display correctly.

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Basically, that’s all. If you have any questions, write in a comment under the post. I will try to answer them and update the content of the article based on it.

The entire source code of the project is available on Github in the link below.

Cookie consent web-component repository

Build on Travis

For those who would like to not worry about upgrading their module in npm registry and updating dist later, I put a ready solution in Travis below.

Create .travis.yml

language: node_js

script:
  - set -e
  - npm run lint
  - npm run test-headless
  - npm run build-ngx-cookie-consent
  - npm run build-web-component
  - cd dist/lacosanostra/ngx-cookie-consent

deploy:
  - provider: npm
    skip_cleanup: true
    email: $NPM_EMAIL
    api_key: $NPM_TOKEN
    on:
      branch: master
  - provider: script
    script: ../../../.travis-commit-changes.sh
    skip_cleanup: true
    on:
      branch: master

Create .travis-commit-changes.sh
Give execute permission. chmod +x .travis-commit-changes.sh

#!/bin/bash

set -x
# doc: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html#The-Set-Builtin


function setup_git() {
  git config --global user.email "travis@travis-ci.org"
  git config --global user.name "Travis CI"
}

function change_to_master_branch() {
  git stash
  git checkout master
  git pull
  git stash pop
}

function commit_changes() {
  git add -A
  # Create a new commit with a custom message
  # with "[skip ci]" to avoid a build loop
  # and Travis build number for reference
  git commit -m "Travis update ($TRAVIS_BUILD_NUMBER)" -m "[skip ci]"
  return $?
}

function push_changes() {
  # Add new "origin" with access token in the git URL for authentication
  git push https://${GH_TOKEN}@github.com/$TRAVIS_REPO_SLUG master
  return $?
}

setup_git

change_to_master_branch

commit_changes

# Attempt to commit to git only if "git commit" succeeded
if [ $? -ne 0 ]; then
  echo "Cannot commit new version"
  exit $?
fi

echo "Push to GitHub"
push_changes

Now, after each update on the master, Travis will update npm if we raise the version number and also update the dist directory