Using Angular-Elements in a Chrome Extension

Oleksandr Reznichenko
Angular In Depth
Published in
6 min readJul 2, 2019
Photo by Greyson Joralemon on Unsplash

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

Say Hi! to Angular-Elements

Despite the fact that Angular-Elements is not yet widely used, they are a powerful tool for writing independent and autonomous components that can be leveraged at any website by adding literally a couple lines of the code.

If you aren’t familiar with Angular-Elements I’d strongly recommend to read about it. In short, Angular-Elements is an Angular application that leverages Web Components standard to provide ability for defining Angular application as an html tag. What differentiate Angular-Elements from an Angular application is the way for bootstrapping::

Bootstrapping Angular-Elements

Angular is responsible for creating the shell of the application, which is subsequently used when registering a custom element in the browser. The customElements.define() function registers an Angular application with the specified custom-element. All you need to do is to add a <custom-element> </ custom-element> to the page markup, and Angular application will be rendered inside this tag.

customElements.define() is part of the ECMAScript standard and is supported by all major browsers.

This approach opens up new horizons for using Angular applications. I’d like to share how to use Angular-Elements with Chrome Extensions; often the essence of the such extensions is to extend page view by adding html tags to it. With Angular-Elements adding and processing elements on the page becomes pretty straightforward.

Specificity of Chrome Extension

Although there is nothing special about using Angular-Elements in the Chrome Extension, it is important to understand how the scripts are loaded onto the page. Let’s consider following diagram:

Page and Extension contexts

As it shown an extension code can be executed in two different contexts:

  • page context: there is access to window object and DOM elements, while there is no access to the Chrome API;
  • extension context: has independent window object which has nothing in common with the window object from the running page. It doesn’t have access to the page javascript, but it is allowed to manipulate the DOM and call the Chrome API.

So, how to load scripts in each of the contexts?

For executing script in the page context, the one must be loaded in the following way:

Load script in the page context

By adding a <script /> tag to the page header, we force the browser to load and execute the script in the same context as a loaded page.

To run a script file in the context of an extension, it is not necessary to perform any additional actions other than to specify the necessary file in manifest.json:

manifest.json in Chome Extension

And so, the files specified in content_scripts will be loaded in the extension context.

Angular-Elements must be run in the page context, otherwise the browser simply will not see yours registered Web Component. This is because in the extension context there is dedicated window object, which is not the same as the one from the loaded page. If you load the Angular-Elements script using the content_scripts in the manifest.json, then the Web Component will be registered in the wrong window object. While parsing the html markup, the browser will find <custom-element /> element but won’t be able to find suitable Web Component for it.

Loading Angular-Elements in Chrome Extension

Usually an Angular application is compiled into several bundles, which can be inconvenient when using in the Chrome Extension, since each file ought to be downloaded separately. This can be solved by compiling Angular application into one javascript file. For that purpose ngx-build-plus or concat npm packages can be used.

And so, having one bundle file, it can be loaded to the page. As it was mentioned, a bundle file ought to be loaded by adding <script /> to the page, which consequently requires specifying bundle file location insrc attribute. Since bundle is belonging to Chrome Extension, it will be loaded from the file system. By default such script loading is forbidden and requires additional configuration in manifest.json file:

Define angular-elements bundle file

The web_accessible_resources allows files access from outside the extension, so that, adding bundle file to that array will let us inject it to the page by using <script /> tag.

Such configuration would be enough for using Angular-Elements in the Chrome Extension (<custom-element /> is the Angular-Elements):

Angular-Elements is rendered inside custom-element tag

Is there any problem ?

For adding Angular-Elements to a non-Angular websites, configuration provided above will be enough. If you need to add Angular-Elements to the site written in Angular — new impediments are popping up.

If you run the Chrome Extension on a website written in Angular, you might see that Angular-Elements is not being loaded, and in the html markup there is an empty <custom-element />. Why does it happen ?

Angular CLI uses the WebPack under the hood, which in follows creates a webpackJsonp global variable with compiled application inside:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["main"],{

Since Angular-Elements is a full-fledged Angular application, it is also compiled using Webpack and leverage same webpackJsonp global variable. It turns out that on the loading Angular-Elements bundle, an already existing webpackJsonp variable is used and the one become mixed with two applications. The problem is that the ngDoBootstrap() method is not called for Angular-Elements and therefore it is not initialized.

There is a ticket described a similar problem in the Angular repository on the github. As a solution, it is proposed to rename the webpackJsonp variable for Angular-Elements. This can be achieved by adjusting webpack configuration file or simply by renaming the webpackJsonp variable in the resulted bundle. Both solutions work and make it possible to upload Angular-Elements to the Angular page.

Are you using `window.ng` ?

If you use the window.ng object, and in particular ng.probe() to debug your application, be ready that this powerful tool will stop to work. The cause is that ng belongs to the BrowserModule and is being created when this module is initialized:

A debug ng object is a part of BrowserModule

Since ng is a global object, it is overwritten when Angular-Elements loads onto the page. Thus, ng.probe will work, but not as you would expect: with ng you can debug an Angular-Elements application only.

The bad news is that it is not possible to avoid ELEMENT_PROBE_PROVIDERS creation, even enableProdMode() will not affect it in any way.

Since ng.probe will stop working, then all chrome extensions which use ng will also be broken, Augury among such extensions.

Pros and cons of using Angular-Elements in Chrome Extension

Pros 👍

  • straightforward way for adding custom html code to the page
  • convenience in development since a full-fledged Angular application with all the benefits
  • minimum code on Chrome Extension side

Cons 👎

  • restriction when using on sites written with Angular
  • possibility to break other extensions are being using ng.probe()

Even though Angular-Elements has some limitation by using in Chrome Extensions it can be considered as powerful way for extending web-pages UI by adding custom elements.

Example project can be found here.

--

--