angular: Using multiple Angular Elements, ngDoBootstrap runs twice, breaking with CustomElementRegistry
I’m submitting a bug report.
Edit: screenshots of this issue are posted below, here
Using multiple Web Components built from Angular Elements breaks the consuming application.
I have the following three repositories:
- example-angular-elments-app - the consuming application
- example-angular-elments-component-box - outputs the box WebComponent
- example-angular-elments-component-button - outputs the button WebComponent
Both components (2 and 3) build the files found in the consuming app’s src/assets/elements folder.
You should receive the same error in your console (or something similar, depending on which component you load dynamically first) after following the reproduction steps further below.
example-angular-elements-component-box.js:4 ngDoBootstrap example-angular-elements-component-box
example-angular-elements-component-box.js:4 ngDoBootstrap example-angular-elements-component-box
example-angular-elements-component-box.js:4 ERROR DOMException: Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry
at ze.define (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:1:12582)
at ze.value (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:3:33689)
at ze.define (http://localhost:4200/assets/elements/example-angular-elements-component-button.js:1:12582)
at ze.value (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:3:33689)
at e.ngDoBootstrap (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:4:121355)
at e._moduleDoBootstrap (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:4:37688)
at http://localhost:4200/assets/elements/example-angular-elements-component-box.js:4:36842
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:4200/polyfills.js:2704:26)
at Object.onInvoke (http://localhost:4200/assets/elements/example-angular-elements-component-box.js:4:30158)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:4200/polyfills.js:2703:32)
After including the second Angular Element WebComponent, you’ll see a line like example-angular-elements-component-box.js:4 ngDoBootstrap example-angular-elements-component-box
logged twice, illustrating that component bootstrapping twice, likely causing the CustomElementRegistry
error.
Expected behavior
The consuming application does not break when consuming multiple WebComponents built from Angular Elements.
The ngDoBoostrap
method is not called twice, or at least checks for component registration.
Minimal reproduction of the problem with instructions
- Clone the example-angular-elments-app Angular 6 project.
- Run
npm i
- Run
npm start
- Open
http://localhost:4200
and open the developer console. - Click on one of the buttons to load a component.
- Click on the other button to load the other component.
- View the console and see how the component that was loaded first runs its’ ngDoBootstrap method again, before breaking the page.
Environment
[xxx example-angular-elments-app]$ npm run ng -- -v
> example-angular-elments-app@0.0.0 ng /home/xxx/WebstormProjects/example-angular-elments-app
> ng "-v"
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 6.0.0
Node: 8.11.1
OS: linux x64
Angular: 6.0.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, http, language-service, material, platform-browser
... platform-browser-dynamic, router
Package Version
-----------------------------------------------------------
@angular-devkit/architect 0.6.0
@angular-devkit/build-angular 0.6.0
@angular-devkit/build-optimizer 0.6.0
@angular-devkit/core 0.6.0
@angular-devkit/schematics 0.6.0
@ngtools/webpack 6.0.0
@schematics/angular 0.6.0
@schematics/update 0.6.0
rxjs 6.1.0
typescript 2.7.2
webpack 4.6.0
Browser:
- [x] Chromium Web Browser (desktop) Version 65.0.3325.181 (Developer Build) Fedora Project (64-bit)
For Tooling issues:
- Node version: 5.6.0
- NPM version: 5.6.0
- Platform: Linux
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 30
- Comments: 65 (20 by maintainers)
Commits related to this issue
- fixed multiple angular custom element not working as https://github.com/angular/angular/issues/23732 — committed to salilvnair/flopkart-mfe-elements by salilvnair 5 years ago
Had a chance to look closer at this today. As suspected, it’s a bundling issue. The root cause is that webpack (which drives the CLI) uses a runtime:
webpackJsonp
global, and you’re overwriting that each time you load another bundle (which also defineswebpackJsonp
) - See https://github.com/webpack/webpack/issues/3791#issuecomment-270821692. The CLI doesn’t expose this option (things like this are why we are not supporting this use case yet). You could (though I don’t recommend it) manually rename that globalwebpackJsonp
in each bundle to something unique.You’re also duplicating all the polyfills, which is likely to cause all kinds of unexpected results, as they’re shimming native APIs and overwriting them at various times. Further, bundling a copy of all the angular packages into each bundle seems suboptimal.
For the moment, if you want to do this sort of use case, your likely best option is going to be to use something like rollup to build UMD bundles, that rely on angular’s UMD bundles and exclude the angular source from each element’s package. It’s going to take some manual work.
Alternately, don’t use the CLI to build the individual elements into binaries, treat them as libraries and bring them into the build properly, so you only have one webpack runtime.
Again, we’re working to enable this use case for v7, so I’m going to close this (as its not actually an issue with Angular core)
I created a detailed step by step descriptions to setup multiple projects and elements.
Unfortunately, this will just help to reproduce the bug, not it’s not a fix, but easily adaptable if we find a solution,
Here is the all in one repo: ng-elements-poc
@gkalpak as you can see in the attached description the polyfills are not included:
Angular Elements Build Setup
The dev-kit has a package available for building web components with angular. You can use the
@angular/elements
package for this. Here you can follow a step by step setup for angular elements running standalone or in another angular app.Setup a new project
Create a new project. Run
ng new ng-elements-poc
in the console.Switch into you new directory:
cd ng-elements-poc
You should now be able to test it by running
ng serve --open
in the console.Setup WebComponents
ng add @angular/elements
in the console. The cli will install some packages to yourpackage.json
:And add a script to your projects scripts config in
angular.json
:ng serve
Setup application for standalone web component
Generate a new project in which we can test an elements setup. Run
ng generate application my-first-element
in the console.Copy the script in your
angular.json
(mentioned in stepSetup WebComponents:1.
) from projectng-elements-poc
tomy-first-element
scripts:Test it:
ng serve --project my-first-element
Setup a script in your
package.json
to start themy-first-element
application:Test it by running
npm run first-element:start
.Now lets create a build task that we can use later on to generate the bundled web component file. Setup a script in your
package.json
to build the application. Note that we set the flagoutput-hashing
tonone
to have the bundles always with the same file names.Run the command and check the file names in the
dist
folder.You can also test the bundles directly. Therefore lets another package:
Install the
http-server
globally:npm install http-server -g
Now we can run
http-server .\dist\my-first-element\
in our root folder. As stated in the console we can now access the serve file under127.0.0.1:8080
.Create component and bootstrapping
We have setup a new project to test standalone web components. Now lets create one.
Create a component called
first-element
and setviewEncapsulation
toNative
:ng generate component first-element --project my-first-element --spec=false --viewEncapsulation=Native
Remove AppComponent from you project.
app.component.ts
,app.component.html
,app.component.css
,app.component.spec.ts
app.module.ts
app.module.ts
remove the empty settings and addFirstElementComponent
to theentryComponents
index.html
replace<app-root></app-root>
with<app-first-element></app-first-element>
:Test your web component. Run
npm run first-element:start
You can also setup a new script in
package.json
to bundle the files to use your web component in another place. Let’s introduce thebundle-standalone
script.npm run first-element:bundle-standalone
in the console to test it.Test web component in another angular app
package.json
to bundle the files for another angular projectRun
npm run first-element:bundle-ng
in the console to test it.Copy
dist/my-first-element/my-first-element-ng.js
intosrc/assets/ng-elements
to serve this file as an asset of your root project.In your root application
ng-elements-poc
openapp.module.ts
Add the following to your ngModule decorator:
And insert following code to
AppModule
constructorapp.component.html
Using multiple element bundles in one app
Test test if we can use multiple elements we can test a multiple elements in the same bundle and b multiple elements in different bundles.
Let’s start with b multiple elements in a different bundle.
Create a new project name
my-other-element
. Do this by following the steps from Setup application for standalone web component and Create component and bootstrappingCreate npm scripts for copying the files over into
src/assets/elements
In your root application
ng-elements-poc
openapp.module.ts
Refactor the creation of the script into a separate function:
Add the html tag into your
app.component.html
This weekend, I’ve created a builder that makes the CLI to do what Rob suggested above: https://www.npmjs.com/package/ngx-build-plus
Now we are at Angular 7. Something new here?
simple enough
if (!customElements.get('element')) customElements.define('element', cardElement);
Not sure how to reconcile these two statements. We’re long past v7, so why do we have to resort to using a separate plugin?
@robwormald why this issue is closed? I didn’t find any solutions how to solve this problem.
@robwormald any ideas?
ngx-builds-plus is the right way to do this for the moment. Once ivy goes out we’ll bring out some new stuff for Elements 😃
On Thu, Apr 18, 2019 at 6:06 AM TCastil notifications@github.com wrote:
@sri1980 i had same error, because module bootstraped twice and
customElements.define
called twice too. You can not define same element twice. I don’t remember exactly, but one of them was the solution, if not all 😃selector
and for defined webcomponent namecustomElements.get('comp')
then do nothing, elsedefine('comp')
.This is very important to fix, I believe, especially considering the fact that once Angular Element WebComponents are built and published for everyone else to use (Wordpress, Polymer, etc.) they all look to be breaking one another in what appears to be friendly fire.
is this fixed in Angular 8?
@gkalpak and @BioPhoton following both of your suggestions, I commented out the
polyfills.ts
from the script that concats the built files, like so:I did this with both of the example components.
I am still getting the same error. The first component’s module is logging to console when the second component is introduced and then breaks.
I will copy and paste my browser’s log and include screen shots below.
I can add multiple box WebComponents without any issue. See how the box’s module logged to the console once, upon introduction into the application.
I then try and load the button WebComponent, breaking the application. You can now see that the box’s module logged to the console, again.
I then try and add another box WebComponent, it is not added and I also get a new error logged in my console (the total log you see in this screenshot was pasted above.)
Thanks! Hopefully Ivy will be released soon. We are building micro front end demo by using Angular elements. Being able to support multiple Angular elements from independent projects on the same page is a key thing to us.
I am able to solve this problem with approach as below.
https://medium.com/@sri1980/multiple-angular-elements-apps-loading-in-one-window-7bcc95887ff4
@BioPhoton - standalone, built individually and independently from one another.
So, for example, if I work with multiple teams then I could download the WebComponent that they built with their own Angular Elements (they’re own dependencies, versions, build pipeline, etc.) I am interested in consuming their built artifact.
By appending
script
s to mydocument
’shead
, I should also be able to lazily load these WebComponents. I’m loading two in my example application, the first one works but once the second one is introduced the app breaks - while they are separate files and separate builds, they’re stepping on each other once introduced and consumed.I have similar problems. zone runs twice…
It’s possible. However, if you compile them separately, it’s not officially supported and you may end up with big bundles. The time after Ivy provides a remedy …
it works!cool~
I have only one angular-element that i want to use inside a regular Angular 7.0.0 app. I’m getting the same error. “ERROR DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’: this name has already been used with this registry”
If I load the webcomponent js with defer (or load it in body below the angular app js files) its working at first, but after refresh or navigating to a non-angular page and then returning to the angular page, the attributes of the webcomponent dont work, only the tag without its properties.
I’m also using the same webcomponent in an Angular 6 app and AngularJS app and there its working without this bug.
It’s not zone.js related, I had separate zone.js file and now i’m using git://github.com/JiaLiPassion/zone.js#duplicate-patch-dist and getting the same error. and i’m using elements-zone-strategy in the webcomponent.
removing node_modules didnt change anything.
@HashanMWanniarachchi I got multiple angular elements on same page working with ngx-build-plus and without ‘noop’ on ngZone. Here is the sample repo multiple-angular-elements Thanks to @manfredsteyer
@robwormald Could you give more details about the bundling pls?
Thanks for your time!
Taking a closer look at the demo, it seems to be trying to use the elements as standalone Angular “mini-apps” (for example each includes
BrowserModule
and does its own bootstrapping). This is likely what is causing the issue. AFAIK, this is not supported just yet (tagging @robwormald, @andrewseguin to confirm).What is supported is including the elements from within an Angular app, which loads the custom element module factories and bootstraps them (sharing the main app’s injector among the custom element modules). This is what we currently do in angular.io btw (and here is the loader we use for reference).
Ill try more tonight. I also will publish my setup on npm and have a cli “story” (docs for setup) nearly done.