# Create and use Custom Elements with Angular

Source: https://tpiros.dev/blog/create-and-use-custom-elements-with-angular

We'll look at the custom elements feature in Angular6: how to create one and how to reuse it in a simple application.

# Web Components

Before we get into the details, let's spend a moment on web components. Web components let us create custom elements that are encapsulated, carry their own functionality, and can be reused in any web app.

Web components have three parts:

- Custom elements: a JavaScript API that lets us define custom elements and their behaviour
- Shadow DOM: Enables encapsulation. The styles and scripts added to our element stay "private" and won't interfere with other parts of our document/application
- HTML templates: Using templates we can create reusable code. Markup placed between `template` elements won't be visible but can be referenced by JavaScript and appended to the DOM.

> One interesting fact about the Shadow DOM: you've probably used it before without realising. The HTML `<video>` element uses the Shadow DOM. It places controls (if the attribute is specified) which adds a whole bunch of items to the shadow-root.

# Angular Elements

With Angular6, we can create custom elements via a package called Angular Elements. It's installed separately: `npm i @angular/elements` (or via `ng add @angular/elements`).

This package gives us access to a `createCustomElement()` function that transforms any Angular component into a custom element. Sounds too easy? It genuinely is. We can now turn any component into a custom element with minimal effort.

Think of these custom elements as Angular components stripped of Angular-specific knowledge. When we include them in an application, we don't need to bring any Angular-specific items along. They work as standalone pieces.

# Creating the application

Let's walk through creating a custom element. As with any Angular project, we'll start by generating a new project via the CLI: `ng new custom-element`.

Once dependencies are installed, `cd` into the folder and add the `@angular/elements` dependency: `ng add @angular/elements`.

Next, create a new component: `ng g c greeter`.

This component is going to be simple (apologies for the lack of creativity, but I want to show the custom element creation without spending ages explaining the component itself). It takes a name attribute and produces a greeting.

Here's the component template:

```html
<span>Hi, it's nice to meet you {{ name }}! 👋</span>
```

And the corresponding TypeScript:

```typescript

@Component({
  selector: 'app-greeter',
  templateUrl: './greeter.component.html',
  styleUrls: ['./greeter.component.css'],
})

  @Input()
  public name: string;

  constructor() {}

  ngOnInit() {}
}
```

## Test the application

Go ahead and test by adding the following to app.component.html:

```html
<app-greeter name="John"></app-greeter>
```

Run `ng serve` and open the browser to see it working. The result should look like the screenshot below:

![](https://res.cloudinary.com/tamas/image/upload/c_scale,f_auto,q_auto,w_600/v1534500256/fullstack-developer-academy/Screen_Shot_2018-08-17_at_12.03.34.png)

# Creating the custom element

Time to transform our component into a custom element. A few things need doing.

First, we make some changes to `app.module.ts`:

```javascript

@NgModule({
  declarations: [
    AppComponent,
    GreeterComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  entryComponents: [ GreeterComponent ]
})

  constructor(private injector: Injector) { }

  ngDoBootstrap() {
    const myCustomElement = createCustomElement(GreeterComponent, { injector: this.injector });
    customElements.define('app-greeter', myCustomElement);
  }
}
```

We've imported `createCustomElement` from `@angular/elements` and `Injector` from `@angular/core`. Notice we've removed the `bootstrap` property entirely from the module definition and added `entryComponents` instead.

We've also added code to the `AppModule` class. The key piece is `ngDoBootstrap()`, which creates the custom element when the Angular application bootstraps.

`createCustomElement()` takes two parameters: the Angular component we want to turn into an element, and a configuration object (in our case, the current Injector instance).

The second part of `ngDoBootstrap()` defines the custom element. This isn't Angular-specific. `customElements` is a property of the `Window` interface, referencing the `CustomElementRegistry` object. Its `define()` method lets us register a new custom element.

Now we build the Angular project to create the actual element: `ng build --prod --output-hashing none`.

> Wondering about `--output-hashing none`? It strips hashes from the generated JavaScript filenames. We're doing this because we'll concatenate all these files later, and we don't need hash values in the names.

The build produces a dist folder with JavaScript files and an `index.html`. The custom element is ready, but at the time of writing, Angular doesn't offer a build option for creating pure custom elements on their own. We can set up a separate build process for that.

Let's add gulp: `npm i gulp gulp-concat` and create a `gulpfile.js`:

```javascript
const gulp = require('gulp');
const concat = require('gulp-concat');

gulp.task('concat', function () {
  return gulp
    .src([
      './dist/custom-element/runtime.js',
      './dist/custom-element/polyfills.js',
      './dist/custom-element/scripts.js',
      './dist/custom-element/main.js',
    ])
    .pipe(concat('app-greeter.js', { newLine: ';' }))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('default', ['concat']);
```

This concatenates all the JavaScript files into one. Run gulp via `node_modules/gulp/bin/gulp.js`. This generates an `app-greeter.js` file in the `dist` folder.

Now we can try the custom element out.

Move `app-greeter.js` somewhere else and create an `index.html` with (at minimum) the following:

```html
<script src="app-greeter.js"></script>
<app-greeter name="Tamas"></app-greeter>
```

Serve this HTML file (via http-server, Python's Simple HTTP server, anything really). The result should look the same as running the Angular app directly.

> The resulting JavaScript file (`app-greeter.js`) weighs 244k, which is considerable. Future Angular releases are expected to include CLI support for building custom elements, and the new rendering engine (Ivy) should help shrink the size.

# Conclusion

Building custom elements with Angular is fairly easy, even though some manual steps are still required. At the moment, custom elements are mainly intended for use inside Angular6 applications. Going forward (perhaps Angular7?), support will grow, both within Angular apps and outside them.
