angular-architects / module-federation-plugin

MIT License
712 stars 189 forks source link

container.get is not a function when using WebComponentWrapper to load a Angular 14 app as WebComponent into a Angular 16 app #410

Open mtuduri opened 8 months ago

mtuduri commented 8 months ago

Hi, I'm attempting to integrate an Angular 14 app as a WebComponent within an Angular 16 app. While I've successfully achieved this integration with an Angular 12, replicating the same steps from the documentation in Angular 14 doesn't work. Despite following the specified procedures, encountering an issue persists when attempting to load the Angular 14 app into the Angular 16 shell app. The error message received is:

Screenshot 2023-12-18 at 10 44 49 AM

MY CODE

Angular 16 (16.2.12) shell app code

Dependencies

    "@angular-architects/module-federation": "^16.0.4",
    "@angular-architects/module-federation-tools": "^16.0.4",

Routes

  {
    path: 'angular-14',
    component: WebComponentWrapper,
    data: {
      remoteEntry: 'http://localhost:1201/remoteEntry.js',
      exposedModule: './web-components',
      elementName: 'angular14',
      type: 'module'
    } as WebComponentWrapperOptions
  },

webpack.config.js

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
});

tsconfig

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2020",
    "module": "es2020",
    "useDefineForClassFields": false,
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

bootstrap

import { AppModule } from './app/app.module';
import { bootstrap } from '@angular-architects/module-federation-tools';

bootstrap(AppModule, {
  production: false,
  appType: 'shell'
});

Angular 14 (14.2.0) WebComponent app code

Dependencies

    "@angular-architects/module-federation": "^14.3.14",
    "@angular-architects/module-federation-tools": "^14.3.14",

webpack.config.js

const {
  shareAll,
  withModuleFederationPlugin,
} = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({
  name: 'angular14',
  library: { type: "var", name: "angular14" },

  filename: "remoteEntry.js",
  exposes: {
    './web-components': './src/bootstrap.ts',
  },

  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
});

app.module

import { Injector, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { createCustomElement } from '@angular/elements';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(private injector: Injector) {
  }

  ngDoBootstrap() {
    const webcomponent = createCustomElement(AppComponent, {injector: this.injector});
    customElements.define('angular14', webcomponent);
  }

}

tsconfig

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2020",
    "module": "es2020",
    "useDefineForClassFields": false,
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

bootstrap

import { AppModule } from './app/app.module';
import { bootstrap } from '@angular-architects/module-federation-tools';

bootstrap(AppModule, {
  production: false,
  appType: 'microfrontend'
});

I suspect that the issue might be connected to the loading mechanism of the web component within the shell app. It's possible that it is related to the problems reported in #103 and #96. Despite attempting all the recommended solutions outlined in those issues, none of them have proven effective for this case.

By debugging the library we can compare how an Angular 12 web component is loaded into the 16 shell vs How Angular 14 is loaded it seems that the way the remoteEntry.js is being built is different:

Angular 12 Screenshot 2023-12-18 at 10 40 09 AM

Angular 14

Screenshot 2023-12-18 at 10 42 37 AM

Is loading it as a module instead of a script, the functions get and init aren't present and is not registering it in the window object either.

DavidAlonso97 commented 7 months ago

Is there any update on this? I have exactly the same problem

shubham-singh98 commented 5 months ago

Even i am facing the same issue with Angular 16. (container.get is not a function) and the route goes into recursive reload. Please help if someone already did this on angular15+.

shubham-singh98 commented 5 months ago

Hi @mtuduri Did you find any solution for this.

dromeroDev commented 5 months ago

Hello, any news? I have same problem

jimyhdolores commented 3 months ago

I have same problem, @manfredsteyer

pedrincandido commented 2 months ago

Any solutions? I have the same problem here

snitramOaoj commented 1 month ago

Hey, Not sure if this response comes to late to anyone, but I was testing this implementation with Angular 18 and in order to make this work you need to follow the instructions mentioned in this blog post, https://www.angulararchitects.io/en/blog/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/.

So in this case to avoid getting this error in the Web component App inside App.module file the bootstrap array needs to be empty, as stated in the blog post, "By going with an empty bootstrap array, Angular won't directly bootstrap any component on startup".

Screenshot 2024-07-11 at 19 25 01

Also on the shell application, in the Routes Array the type for your WebComponentWrapper needs to have a type 'script'. If you use script instead of module, you can see by debugging in the browser that alongside with the url for your local remote app you get an object with your remote app name as key, this way the container created by the containerMap will have a get function and this way it will be possible to create the factory that will build your esModule and render your remote application.

Screenshot 2024-07-11 at 19 30 31

Screenshot 2024-07-11 at 19 33 36

Final Results:

Screenshot 2024-07-11 at 19 34 46

Screenshot 2024-07-11 at 19 34 53