facebook
George Anderson
Frontend and backend development guru, lover of all things computer... and cats! Hopes to make your coder life a breeze by sharing some tips and tricks of the trade.
Posted on May 31st 2018
Angular has been one of the best front-end JavaScript frameworks for some time now, and one of the many reasons is their continued effort to bring to developers new and improved ways to get the job done. One such effort is Angular Elements, which were introduced in Angular v6. In a nutshell, they convert components to HTML elements (+ JavaScript), allowing you to use your components in other apps, different frameworks (like React), or even in a simple HTML + JavaScript setup! This is implemented using the new Custom Elements API supported by most modern browsers, and the process of converting a component to a custom element ensures all required Angular infrastructure (data binding, change detection, etc.) is available to the browser. During the course of the article, we will be creating a panel that can display the title and content of a message, and a button to show or hide the message content. We’ll then go ahead and use this panel in a simple HTML page.
 

Requirements

You are expected to have a basic knowledge of Angular and component development. You are also required to have the following installed on your computer:
We are going to take advantage of CodeMix to show you how it will easily configure the working environment for Angular development.
If you haven’t tried our full-stack MyEclipse IDE, now there are more reasons to do so! MyEclipse Stable 2.0 is out, bringing to you Angular 5 and the very latest CLIs support, as well as improvements in TypeScript, DevStyle, Servers, and more.

Creating an Angular Project in CodeMix 

We’ll be using the latest version of all the tech libraries and stacks, as of the time of this writing. To create a new Angular Project, navigate to File New > Project > CodeMix > Angular Project. Then click Next and enter your project name on the following screen. Finally, click Finish to continue with Angular project creation.

Creating a new Angular project in CodeMix

We will now open a terminal for a project using the command palette by pressing  Ctrl/Cmd + Shift + P; then type terminal: create a new integrated terminal and select your project from the drop-down. The terminal will automatically be opened in the project folder.
codemix terminal

Opening a terminal in CodeMix

  • Once you are inside the terminal, execute: npm install
  • Finally, run the Angular app by executing  ng serve
  • To see the Angular app running live, open your browser to http://localhost:4200
Now we need to pull in the Angular element modules and the poly-fills for browser compatibility since they are not fully supported in some browsers, especially (you guessed it!) Microsoft Edge and Internet Explorer. First the Angular element module: `npm install @angular/elements` Then the poly-fill we will be using in the article: `npm install @webcomponents/custom-elements`

Developing the Component

First, we create our component, which we will name `hideaway-message`it is recommended to have yours include domain name and a hyphen in your custom element so that it won’t clash with preexisting elements. For example, if your project name is `flyOne`, the element you create could be named `fly-one-button`. We will create ours using the command below: `ng g component hideaway-message`
 
Codemix terminal

Creating components in CodeMix terminal

 
We can now begin to develop our regular Angular component. First, we define our template with the `./src/app/hideaway-message/hideaway-message.component.html` file (although this could be done in the component main file).
<div class="panel panel-default">
        <div class="panel-heading">
                {{title}}
                <button (click)="hideContent($event)" style="float: right"> 
                  {{ btnText }} 
                </button>
        </div>
        <div class="panel-body" *ngIf="!hidden">
                {{content}}
        </div>
</div>
We also add our styling in our  `./hideaway-message.component.css` within the same folder as the template file:
.panel {
    margin: 10px;
    background-color: #fff;
    border: 1px solid transparent;
    border-radius: 4px;
    -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
    box-shadow: 0 1px 1px rgba(0,0,0,.05);
}
.panel-default {
    border-color: #ddd;
}
.panel-default>.panel-heading {
    color: #333;
    background-color: #f5f5f5;
    border-color: #ddd;
}
.panel-heading {
    padding: 10px 15px;
    border-bottom: 1px solid transparent;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
}
.panel-body {
    padding: 15px;
}
We can now define our component in the `hideaway-message.component.ts` file like this:
import { Component, Input, ViewEncapsulation } from '@angular/core';
@Component({
  selector: 'hideaway-message',
  templateUrl: './hideaway-message.component.html',
  styleUrls: ['./hideaway-message.component.css',],
  encapsulation: ViewEncapsulation.Native
})
export class HideawayMessageComponent {
  constructor() { }
  hidden = false;
  btnText = 'Hide'
  hideContent(e) {
          e.preventDefault();
          this.hidden = !this.hidden;
          this.btnText = this.btnText == 'Hide' ? 'Show' : 'Hide';
  }
  @Input() content;
  @Input() title;
}
ViewEncapsulation helps compile the CSS down to JavaScript (i.e., using Shadow DOM), so the component can be used without having to worry about the CSS referencing properly. We can now check to see if our component is working by putting it within the app-root tag in the `index.html` file:
<body>
  <app-root>
    <hideaway-message title="Lorem ipsum dolor sit amet" 
          content="Lorem ipsum dolor sit amet, iusto appellantur vix te, nam affert feugait menandri eu. Magna simul ad est. Nostrum neglegentur ius at, at pertinax repudiare vel. Vim an adolescens quaerendum.">
    </hideaway-message>
  </app-root>
</body>
We now start our application so it can be viewed in a browser (on the default Angular port http://localhost:4200/) using the command below in the command line:
ng serve
Now that our component is fully functional, the next step is to convert it into an Angular element. We do this by editing the `app.module.ts` file to look like this:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { HideawayMessageComponent } from './hideaway-message/hideaway-message.component';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    HideawayMessageComponent,
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  entryComponents: [
    HideawayMessageComponent
  ]
})
export class AppModule {
        constructor(private injector: Injector){
        }
        ngDoBootstrap(){
                const element = createCustomElement(
                        HideawayMessageComponent,
                        { injector: this.injector }
                        );
                customElements.define('hideaway-message', element)
        }
}
In the code above, we have stopped Angular from automatically bootstrapping the application. We accomplished that by removing the bootstrap property in the decorator parameter object and overwriting the `ngDoBootstrap` method in the `AppModule` class. We also add the component, `HidawayMassageComponent` to the entryComponents array, to instruct Angular to create the component, even though it is not part of a template.
Then within `ngDoBootstrap` method, the `HidawayMassageComponent` is parsed using the function `createCustomElement` from the Angular element module to prepare it to be added to `CustomElementRegistry` (this process includes making the component extend the HTMLElement abstract class). After the component has been transformed into an element, we then add it to the list of elements available to the DOM using `customElement` object which is available globally in JavaScript. Now we can use the element in an HTML file within our project. For example, the `index.html` file could be rewritten as below:
<body>
    <div style="width: 90%; margin: auto;">
          <hideaway-message 
            title="Lorem ipsum dolor sit amet" 
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">
          </hideaway-message>
          <hideaway-message 
            title="Lorem ipsum dolor sit amet" 
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">
          </hideaway-message>
          <hideaway-message 
            title="Lorem ipsum dolor sit amet" 
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">         
          </hideaway-message>
          <hideaway-message 
            title="Lorem ipsum dolor sit amet" 
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">
          </hideaway-message>
    </div>
</body>

Building the Custom Element

Now that we can use the element in an HTML document within our project, the next step is to try to make sure that we can use the element by referencing a file with the element declaration and dependencies. For this, we need to pull in a module called `concat`, using npm:
npm install concat --save-dev
Next, we create a build script in the root of the project folder and name it accordingly. In the `build-script.js` file, we add the path to the production build files and a path to the output file after concatenation, and pass it to the concat function like below:
const concat = require('concat');
const files = [
        './dist/AngularElements/runtime.js',
        './dist/AngularElements/polyfills.js',
        './dist/AngularElements/main.js'
    ];
const outputFile = './dist/final-bundle.js';
concat(files, outputFile);
This list of files is the product of the Angular build command, and these files are usually found in the dist directory in the project root directory – please replace `AngularElements` in the paths above with the name of your project. Please note that these paths are specific to Angular apps built with version 6.0 of the CLI or higher.
We need to add a script in the `package.json` file to firstly build our application in prod mode, without hashing the output file, and run our build-script. We add the code below to the scripts object:
 
    "build-element": "ng build --prod --output-hashing=none && node build-script.js"
CodeMix code formatting

Code Formatting using CodeMix

Now we can run the command below to get our concatenated file:
npm run build-element
 

Using the Custom Element

The `final-bundle.js` file created by the above script can now be used anywhere. Copy this file to another location, and create a simple HTML file in the same folder with the following code:
<body>
        <div style="width: 90%; margin: auto;">
          <hideaway-message
            title="Lorem ipsum dolor sit amet"
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">
          </hideaway-message>
          <hideaway-message 
            title="Lorem ipsum dolor sit amet" 
            content="Lorem ipsum dolor sit amet, iusto appellantur vix te.">
          </hideaway-message>
        </div>
        <script type="text/javascript" src="final-bundle.js"></script>
</body>
It should look like this in the browser:
Angular element

Angular Element in an HTML file

Awesome, it works – we just used the Angular component we built in a simple HTML file!

Conclusion

Angular elements are here to help make the development of applications smoother and less restrictive. It will be very important in server-side rendering (SSR), and it also allows us to use our component, developed in Angular, in other non-Angular applications, such as those developed in ReactJS, VueJS, etc. Although it has its benefits, it comes with some significant drawbacks, which includes the fact that the size of the script added to the HTML (about 233KB in this case) is quite big for just one element. Also, it is not fully supported by most browsers, as stated before, and there are compatibility issues with some of the prior Angular releases. Despite these drawbacks, Angular elements are still a handy tool – hopefully Angular 7 will address these issues.