hamobi
hamobi

Reputation: 8130

Angular 6 - Is it possible to use two angular element components on the same page?

I'm trying to host independent angular elements that I can drop into a non-angular webpage. It's working great when I have just one.. but when I load two or more on the same page I get this error:

Uncaught Error: Zone already loaded.

I'm using a build script to concatenate my dist files into one js file.. and then dropping that into my project

const fs = require('fs-extra')
const concat = require('concat')


const files = [
    './dist/elementsApp/runtime.js',
    './dist/elementsApp/polyfills.js',
    './dist/elementsApp/scripts.js',
    './dist/elementsApp/main.js',
]


fs.ensureDir('elements')
.then(() => {
  console.log('success!')
  concat(files, 'elements/include-me-somewhere-else.js')
})
.catch(err => {
  console.error(err)
})

I can only have one of these js files on one page. I understand angular 7 will make this whole process easier but I'm wondering how I can accomplish this right now.

I've also tried wrapping my exported js file in a self-executing function to try and limit scope.. Doesn't fix anything unfortunately.

Any ideas?

People seem confused about what angular elements are. For clarification you can watch this video https://www.youtube.com/watch?v=4u9_kdkvTsc. the last few mins in particular show an angular element being exported and included on a non angular page. I'm trying to include multiple elements on the same page.

Upvotes: 29

Views: 5511

Answers (4)

androbin
androbin

Reputation: 1759

I think you should not use 2 angular on one site.

Solutions can be:

  • Build the whole site in Angular. Other parts of the code can be added to the app.
  • Use submodules and merge the two app with a parent router.
  • Use iframe for each Angular component. Then ngzone will not collide.

EDIT:

Since my original was posted there has been a technology around. Using Microfrontends (MFE) you can add more apps to the same page. In Angular it is going to be a new router element mainly. Any place where ESM import() can be used for lazy loading, and Angular only supports it in the Router by default.

Upvotes: 5

MichaelSolati
MichaelSolati

Reputation: 2872

So here's my thoughts based on our earlier discussion. Because each bundle you create with an element is loading in zone.js you're getting error messages:

Uncaught Error: Zone already loaded.

What you can and should consider is NOT concatinating the './dist/elementsApp/polyfills.js' into your bundle (for each element you bundle you produce). Instead use the polyfill.js file as a separate import into your HTML, and import it before any and all element bundles you're importing.

Upvotes: 5

m.akbari
m.akbari

Reputation: 592

This is a solution that works perfectly on angular 5+. for angular 6 i have not done this. I have done this for a huge CMS project and I inserted components in several pages and even 2 same angular module in one page. You can use one angular project.

in your site, write component tags.

You need to create another module (for example mainHelper) to export each selected angular tags.

import {NgModule, ApplicationRef, ComponentFactoryResolver} from '@angular/core';
import {FirstModule} from 'YOUR MODULE PATH';
import {FirstComponent} from 'YOU COMPONENT PATH';
import {SecondModule} from 'YOUR MODULE PATH';
import {SecondComponent} from 'YOU COMPONENT PATH';


@NgModule({
  imports: [
    FirstModule,
    SecondModule
  ],
})

export class MainHelper {
  constructor(private resolver: ComponentFactoryResolver) {
  }
  components = [
FirstComponent,
SecondComponent
  ];


  ngDoBootstrap(ref: ApplicationRef) {
    this.components.forEach((component) => {
      const factory = this.resolver.resolveComponentFactory(component);
      const elements = document.getElementsByTagName(factory.selector); //get angular tags on your main website
      if (elements.length > 0) {
        if (elements.length > 1) {
          const selector = factory.selector;
          for (let i = 0; i < elements.length; i++) {
            elements[i].id = selector + '-' + i;
            (<any>factory).factory.selector = '#' + elements[i].id;
            ref.bootstrap((<any>factory).factory);
            // elements[i].id = '';
          }
          (<any>factory).factory.selector = selector;
        } else {
          ref.bootstrap(component);
        }
      }
    });
  }
}

in your main.ts, you can import MainHelper module and bootstrap the MainHelper.

and your FirstModule would be something like this:

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {BrowserModule} from '@angular/platform-browser'; 
import {FirstComponent} from './first.component';
import {AnotherComponent} from './another/another.component';
@NgModule({
  imports: [
    CommonModule,
    BrowserModule,
  ],
  providers: [
    SampleService // you need provide your services for all the modules
  ],
  declarations: [
    FirstComponent,
    AnotherComponent,
  ],
  bootstrap: [FirstComponent] // important
})
export class ECommerceModule {
}

I hope this helps you.

Upvotes: 1

Edward Newsome
Edward Newsome

Reputation: 778

Google's Polymer may be what you're looking for instead of angular:

https://www.polymer-project.org/

The Polymer library provides a set of features for creating custom elements. These features are designed to make it easier and faster to make custom elements that work like standard DOM elements

Upvotes: 2

Related Questions