Using Angular-Elements in a Chrome Extension
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::
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:
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 thewindow
object from the running page. It doesn’t have access to the pagejavascript
, 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:
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
:
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:
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):
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:
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.