DEV Community

Eduard Krivanek for Bitovi

Posted on

Developer Preview of Standalone Support in Angular Elements

Have you seen it already? Angular version 14.2 is here with new features and improvements! One of the merged PRs includes creating custom elements without NgModule. In this post, we will take a look at how to create Angular elements in Angular v14.2.

The source code for the examples in this post is available on GitHub here.

What Are Angular Elements?

Angular Elements allow Angular Components to be consumed outside of the Angular ecosystem. Angular Elements are shipped in a single JavaScript file and behave just as regular HTML elements. They instantiate automatically when they are added to the DOM and destroy themselves when they are removed. The developer doesn’t need any prior knowledge of the Angular framework to use them.

Creating Angular Elements (The Old Way)

Let’s say you have created a custom component VotingOldComponent which you want to include in your non-Angular application. For the purpose of this blog post, it is not important what the component itself does, however, the following code is an example of how you would have created Angular elements before Angular v14.2.

// app.module.ts

import { DoBootstrap, Injector, NgModule } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { BrowserModule } from '@angular/platform-browser';
import { VotingOldComponent } from './voting-old/voting-old.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { VotingOldModule } from './voting-old/voting-old.module';

@NgModule({
    declarations: [],
    imports: [BrowserModule, BrowserAnimationsModule, VotingOldModule],
    providers: [],
})
export class AppModule implements DoBootstrap {
    constructor(private injector: Injector) {}

    ngDoBootstrap(): void {
        const votingOld = createCustomElement(
            VotingOldComponent, 
            { injector: this.injector }
        );
        customElements.define('voting-old', votingOld);
    }
}
Enter fullscreen mode Exit fullscreen mode
// main.ts

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

After running the commands ng buildand cat ./dist/angular-elements/runtime*.js ./dist/angular-elements/polyfills*.js ./dist/angular-elements/main*.js > ./custom-voting-element.js you would end up with a custom-voting-element.js file, which if you include in the script tag to one of your other web applications, we can use a custom HTML tag voting-oldand it will render a component.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>AngularElements</title>
        <base href="/" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script defer src="./custom-voting-element.js"></script>
    </head>
    <body>
        <voting-old></voting-old>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Creating Angular Elements in Version 14.2

However, since the introduction of standalone components in version 14.0, Angular developers are able to slowly move away from modules. Let’s say you have created a standalone component VotingNewComponent.

// voting-new.component.ts

@Component({
    selector: 'app-voting-new',
    templateUrl: './voting-new.component.html',
    styleUrls: ['./voting-new.component.scss'],
    standalone: true,
    imports: [CommonModule, ...]
    encapsulation: ViewEncapsulation.ShadowDom,
})
export class VotingNewComponent implements OnInit {
    ...
}
Enter fullscreen mode Exit fullscreen mode

All you need to do to create an Angular element from the VotingNewComponent standalone component is to modify the main.ts file to include an application instance by createApplication() function, create one or multiple Angular elements by createCustomElement(), and define autonomous custom elements by customElements() function with unique keys.

// main.ts

// get a hand on the `ApplicationRef` to access its injector
createApplication({ providers: [] }).then((appRef) => {
    // create a constructor of a custom element
    const votingNew = createCustomElement(
        // component for Angular element
        VotingNewComponent, 
        // used to inject the component to the DOM
        { injector: appRef.injector } 
    );

    // register in a browser
    customElements.define('voting-new', votingNew);
});
Enter fullscreen mode Exit fullscreen mode

Important Note: createApplication is on Developer Preview. Features on Developer Preview might change APIs without notice. If you decide to use createApplication on production, be aware when updating your application.

Without bootstrapping any components, the createApplication method produces an instance of the application. Its strength resides in its ability to delay or decouple the rendering of components with various injector trees. Changing the main.ts file, generating a custom-voting-element.js, and including this script to whatever web application, you can use your custom voting-new element in the following way:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>AngularElements</title>
        <base href="/" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script defer src="./custom-voting-element.js"></script>
    </head>
    <body>
        <voting-new></voting-new>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

How to Create an Angular Element from a Standalone Component in Angular v14.2

In summary, here’s how to create an Angular element from a Standalone Component in Angular v14.2:

  1. Include all standalone components in the application’s main.ts entry point to instantiate them after createApplication() is resolved
  2. Build all Angular elements by running ng build
  3. Concat generated files into one by running the script cat ./dist/angular-elements/runtime*.js ./dist/angular-elements/polyfills*.js ./dist/angular-elements/main*.js > ./custom-voting-element.js
  4. Include the script custom-voting-element.js in any other web application to use Angular standalone elements as standard HTML tags

Mistakes to Watch Out For

When creating standalone Angular elements, you might think to follow the old approach (before version 14.2) to implement DoBootstrap interface in app.component.ts and include AppComponent in bootstrapApplication in main.ts as the official documentation dictates. If you did that in this example, you could end up with the following:

// app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  standalone: true,
  imports: [CommonModule, VotingNewComponent],
})
export class AppComponent implements DoBootstrap {
  // PR: https://github.com/angular/angular/pull/46475
  async ngDoBootstrap(): Promise<void> {
    // get a hand on the `ApplicationRef` to access its injector
    const applicationRef = await createApplication();

    // create a constructor of a custom element
    const votingNew = createCustomElement(
      VotingNewComponent, 
      { injector: applicationRef.injector }
    );

    // register in a browser
    customElements.define('voting-new', votingNew);
  }
}
Enter fullscreen mode Exit fullscreen mode
// main.ts
bootstrapApplication(AppComponent, {
    providers: [],
}).catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

However, this approach would lead to an error

The selector "app-root" did not match any element. 
Enter fullscreen mode Exit fullscreen mode

By using bootstrapApplication(AppComponent) Angular tries to initialize and load our compiled application in whatever framework we are including our built script. This will result in an error because, in this context where VotingNewComponent is an Angular element, AppComponent doesn't exist.

Conclusion

Angular 14.2 has introduced some amazing new features and improvements, including the ability to create Angular elements from standalone components. Check out the other posts in this series for more information and Angular v14.2 tutorials.

Need help upgrading your Angular project to the latest version to take advantage of awesome new features? Contact us for Angular Consulting or check out our Bitovi Academy.

Top comments (1)

Collapse
 
mthompsoncode profile image
Mark Thompson

I recently tried out Angular elements for a side project where I couldn't use Angular directly. Glad to know the Angular team is bringing improvements to Angular elements