angular-architects / module-federation-plugin

MIT License
683 stars 184 forks source link

Shared service not provided if different library versions between the shell and the MFE #510

Closed laubuur closed 1 month ago

laubuur commented 2 months ago

I recently migrated my project from Angular 16 to Angular 17. I switched to esbuild and consequently migrated from module federation to native federation. I have an issue that I didn't have before the update. (Same mfe configuration)

I have a library containing services intended to be used by all my micro-frontends. This service (SharedService) is provided in the shell's configuration: (Also tried to use the providedIn option in the @injectable decorator, same result)

import { routes } from './app.routes';
import {
  provideHttpClient,
  withInterceptorsFromDi,
} from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { DialogServiceImpl, SharedService } from 'my-lib';
import { LocationStrategy } from '@angular/common';
import { PathPreserveQueryLocationStrategy } from './services/preserve-queryparams.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAnimations(),
    provideRouter(routes),
    DialogServiceImpl,
    SharedService,
    provideHttpClient(withInterceptorsFromDi()),
    { provide: LocationStrategy, useClass: PathPreserveQueryLocationStrategy },
  ],
};

I use this service in any MFE through dependency injection, and everything works correctly. However, if I update the version of the library (without even touching the code) in the shell but not in the MFE, I get the following error message in the browser console (when I go on a page who use the MFE):

_angular_core-17_2_4-dev.js:4349  ERROR NullInjectorError: R3InjectorError(Standalone[_SearchTabContainerComponent])[_SharedService -> _SharedService -> _SharedService -> _SharedService]: 
  NullInjectorError: No provider for _SharedService!

federation.config.js - shell

const { withNativeFederation, shareAll, sh } = require('@angular-architects/native-federation/config');

module.exports = withNativeFederation({
  shared: {
    ...shareAll({ singleton: true, strictVersion: false }),
  },
  skip: [
    'rxjs/ajax',
    'rxjs/fetch',
    'rxjs/testing',
    'rxjs/webSocket',
  ]
});

federation.config.js - mfe

const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');

module.exports = withNativeFederation({

  name: 'fe-sig',

  exposes: {
    './routes': './src/app/app.routes.ts',
    'MenuEntry': './src/app/signaletique-menu.json',
    'HeaderContextContentComponent': './src/app/components/shared/header-context-content/header-context-content.component.ts'
  },

  shared: {
    ...shareAll({ singleton: true, strictVersion: false }),
  },

  skip: [
    'rxjs/ajax',
    'rxjs/fetch',
    'rxjs/testing',
    'rxjs/webSocket',
    // Add further packages you don't need at runtime
  ],
});

I tried setting strictVersion to true or false, nothing changes.

Shell - dependencies package.json

"dependencies": {
    "@angular-architects/native-federation": "^17.1.6",
    "@angular/animations": "^17.2.4",
    "@angular/cdk": "^17.2.2",
    "@angular/common": "^17.2.4",
    "@angular/compiler": "^17.2.4",
    "@angular/core": "^17.2.4",
    "@angular/forms": "^17.2.4",
    "@angular/platform-browser": "^17.2.4",
    "@angular/platform-browser-dynamic": "^17.2.4",
    "@angular/router": "^17.2.4",
    "my-lib": "0.1.6",
  }

MFE - dependencies package.json

"dependencies": {
    "@angular-architects/native-federation": "^17.1.6",
    "@angular/animations": "^17.2.4",
    "@angular/cdk": "^17.2.2",
    "@angular/common": "^17.2.4",
    "@angular/compiler": "^17.2.4",
    "@angular/core": "^17.2.4",
    "@angular/forms": "^17.2.4",
    "@angular/platform-browser": "^17.2.4",
    "@angular/platform-browser-dynamic": "^17.2.4",
    "@angular/router": "^17.2.4",
    "my-lib": "0.1.5", //Works if 0.1.6 (same as shell)
  }
manfredsteyer commented 1 month ago

Yes, that's right. Currently we need to have exactly the same version to make it shared. Otherweise, each version is loaded. We plan to add all the semantics regarding semver we know from Module Federation eventually.

However, the current behavior is the better one in the Angular world as code compiled with Angular X expects to see the exact same version at runtime.