Indepth. dev/angular-web…
Indepth. dev/author/arme…
Published: February 5, 2020-8 minutes to read
In this article, you’ll learn how to use Angular elements to integrate a single Angular component within a page. We’ll explore in detail how this works and how to debug applications using Web Components.
Last year, I was tasked with adding several Angular components to an existing ASP.Net application. A messy solution had been implemented (a huge script that loaded a classic Angular app and all its components, regardless of their relevance to the current page), which was obviously a bit “slow” in speed and inefficient. Of course, this approach was also a software development hell, where I had to run a Gulp script, rebuild the ASP.Net application, and forcibly reload the page every time I changed a tiny CSS. The team was not satisfied with either the application performance or the development process, so my goal was
- Create a new solution that allows a single Angular component to be added to a page instead of the entire application.
- It works faster and doesn’t load a lot of unnecessary scripts.
- Will allow faster development build times without the need to constantly rerun scripts.
So, naturally, I turned to Web Components.
But what are Web Components?
Web Components are concepts from Components of frameworks like React or Angular, but are built for the Web as a whole, independent of frameworks. What does that mean?
We’re used to using our simple HTML tags in our UI. For example, we know about div, SPAN, and so on. They have predefined behaviors that may be used in different places in our view.
Web Components essentially allow us to create new HTML tags/elements using JavaScript. Let’s take a look at a small example of how to implement this functionality purely in JavaScript.
class SpanWithText extends HTMLSpanElement {
constructor() {
super(a);this.innerText = `Hello, I am a Web Component! `;
}
}
customElements.define('span-with-text', SpanWithText);
Copy the code
Here, we create a class (just like we write a class for components in Angular) that extends from HTMLElement (more specifically, our HTMLSpanElement). Each time we create this element, It will have an innerText with the value Hello, I am a Web Component. So, whenever we use our element in the DOM, it will itself have a padded text.
<span-with-text></span-with-text>
Copy the code
Cool, can I use it with Angular?
Of course, turning our Angular Components into Web Components is a good thing no matter where they are used, no matter what the environment. It turns out that we can do just that with @angular/ Elements.
Working principle:
First, we’ll start a new Angular application. We’ll create it with a special flag so it only creates configuration files, not template code (AppModule/AppComponent etc…). .
Ng new Web-Components --createApplication=falseCopy the code
We end up with an empty Angular application. Now, let’s create our first Web Components under the new project directory.
Ng Generate Application FirstWebComponent --skipInstall=trueCopy the code
Add this flag to skip reinstalling any dependencies.
So, this command creates a projects folder in our root directory, which will have a folder called FirstWebComponent. If we look inside, we’ll see a typical Angular app: a main.ts file, an app.module, an app.componentand more. Now we need to get the @Angular/Elements library, so let’s run it
ng add @angular/elements
Copy the code
This will bring the library to our node_modules folder so that we can use it to turn our Components into Web Components.
The most important thing about Angular Components becoming Web Components is that they are simple Angular Components — nothing about them needs to be different to be used as Web Components. You can do everything you can with normal Angular components in them, and it will work. Any configuration steps you have to do to make your components work are done at the module level; Nothing changes in the component itself. This means you can turn almost any existing Angular component into a Web Component without much hassle.
Next, let’s create an Angular component in our newly created project that will become our Web Components.
ng generate component UIButton
Copy the code
What we need to do now is make Angular understand that we don’t want it to treat this component as a normal Angular component, but as something different. This is done at the module bootstrap layer — all we need to do is implement the ngDoBootstrap method on the AppModule and tell our module to define a custom element. To do this, we’ll use the createCustomElement function in the @Angular/Elements package.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, DoBootstrap, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
@NgModule({
declarations: [
UIButtonComponent,
],
imports: [
BrowserModule,
],
entryComponents: [UIButtonComponent],
})
export class AppModule implements DoBootstrap {
constructor(private injector: Injector) {
const webComponent = createCustomElement(UIButtonComponent, {injector});
customElements.define('ui-button', webComponent);
}
ngDoBootstrap(){}}Copy the code
There are several important things to note.
- We have to do it manually
injector
Pass to our Web Components. This is to ensure that dependency injection works at run time. - We must place our components in
entryComponents
In the array. This is what the Web Components need to be booted. createCustomElement
Is a function that, in effect, turns our own component into a Web Component — the result of which we pass tocustomElements.define
Instead of our Angular components.- The selector for our Angular component isunrelated. How is it called from other HTML templates that we pass to
customElements.define
Function string. In this case, it will be called as<ui-button></ui-button>
. - We pass to
customElements.define
The selector for a function must consist of two or more words separated by dashes. This is not so much an Angular quirk as a requirement of the Custom Elements API (so it can distinguish Custom Elements from native HTML tags).
Our next step is to build the application!
ng build FirstWebComponent
Copy the code
When we look at the files in the Dist folder, we see the following.
Files generated after the build
These are the files we need to include in another application to be able to use our Web Components. To do this, we can copy the file into another project and include it in a different way.
- If we want to use it in a React application, we can install Polyfill and use the simple
import
Statement to include our file. You can go to Vaadin’sThe official blog postRead more about it in. - To use it in a pure HTML application, we must include all of our generated files through the script tag and then use the component.
<html>
<head>
<script src="./built-files/polyfills.js"></script>
<script src="./built-files/vendor.js"></script>
<script src="./built-files/runtime.js"></script>
<script src="./built-files/styles.js"></script>
<script src="./built-files/scripts.js"></script>
</head>
<body>
<ui-button></ui-button>
</body>
</html>
Copy the code
- To use it in another Angular application, we don’t really need to build the component; since it is a simple Angular component, we can import it and use it. But if we can only access the compiled files, we can do it in the target project
angular.json
In the filescripts
Field contains them andschemas: [CUSTOM_ELEMENTS_SCHEMA]
, added to ourapp.module.ts
File.
So, this first part is easy. There are a few things to note.
- If we have input in our Web Components, the naming pattern will change when we use it. We use camelCase in Angular components, but to access the input from other HTML files, we must use kebab-case.
@Component({/* metadata */})
export class UIButtonComponent {
@Input() shouldCountClicks = false;
}
Copy the code
<ui-button should-count-click="true"></ui-button>
Copy the code
- If we have output in our Angular component, we can pass
addEventListener
To listen for emitted composite events. There is no other way, including React– usuallyon<EventName={callback}>
No, because this is a composite event.
But what about browser compatibility?
So far, so good — we’ve built our application and tested it successfully in major modern browsers, such as Chrome. But what about Internet Explorer, the bane of all web developers? If we open our application, where we use a Web Components, we see that our buttons and counters have been rendered successfully. So, we’re good? Not at all.
If we had a component that counted the number of button clicks, we would see that the counter was not updated when we clicked in Internet Explorer. To spare you further investigation – the problem is that change detection in Angular Web Components doesn’t work on IE. So, should we forget About Web Components on IE? No, thankfully, there’s a simple workaround. We must implement a new change detection zone policy or import one. There is a small bag that fits our needs just fine.
npm i elements-zone-strategy
Copy the code
And slightly modify our app.module.ts file.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, DoBootstrap, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
@NgModule({
declarations: [
UIButtonComponent,
],
imports: [
BrowserModule,
],
entryComponents: [UIButtonComponent],
})
export class AppModule implements DoBootstrap {
constructor(private injector: Injector) {
const strategyFactory = new ElementZoneStrategyFactory(UIButtonComponent, injector);
const webComponent = createCustomElement(UIButtonComponent, {injector, strategyFactory});
customElements.define('log-activity', webComponent);
}
ngDoBootstrap() {}
Copy the code
Here, we just call ElementZoneStrategyFactory constructor, got a new strategyFactory, and put it with the injector to createCustomElement function.
Now you can open the same component in IE and, amazingly, it works. Change detection now works on IE as well.
Don’t forget polyfills
You’ll visit the Polyfills. Ts file often. My advice to customize is, don’t load all polyfills at once, only load what you need.
How do I debug?
Debugging (and error messages in general) is a bit of a problem in Angular Web Components. You don’t have hot reloads, and you’re running the component you built in a different environment than the Angular app, so you might be wondering how you can get this. In fact, you can modify the index.html file to add your Web Components selector instead of app-root.
Then run your application as usual.
<! doctypehtml>
<html lang="en">
<head>
<meta charset="utf-8">
<title>UIButton</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<ui-button></ui-button>
</body>
</html>
Copy the code
ng serve UIButton
Copy the code
This will produce an Angular app that contains only your Web Components. One advantage of this is that you will have thermal overloading (which is a big problem). But there is a downside: error messages are often chewed up: For example, if you forget to provide one of your services, in a common Angular application you get an angry StaticInjectorError detailing which service is not available. In our case, we would just see a blank screen without any errors — a mystery to investigate and get frustrated with.
conclusion
This basic setup gives us everything we need to build and deploy applications that use Angular Components and Web Components. But this setup is far from ideal — we’d like to have some script to implement it
- Automatically generate new Web Components
- Scripts that automatically run our build process
- Hot reshipment
In my next article, we’ll start digging into this script to make our development process more interesting.
Translation via www.DeepL.com/Translator (free version)