Giter VIP home page Giter VIP logo

ngx-mfe's Introduction

Angular micro-frontend library - ngx-mfe

A library for working with MFE in Angular in a plugin-based approach and with Angular routing.

If you have production build issues check this issue. This issue has been fixed in version 2.0.0.

Have problems with updates? Check out the migration guides.

Contents

Version Compliance

ngx-mfe v1.0.0 v1.0.5 v2.0.0 v3.0.0
Angular v12.0.0 v13.0.0 v13.0.0 v14.0.0
@angular-architects/module-federation v12.0.0 v14.0.0 v14.0.0 v14.3.0

Since v15.0.0 version of ngx-mfe library is compatible with Angular version

Motivation

When Webpack 5 came along and the Module Federation plugin, it became possible to separately compile and deploy code for front-end applications, thereby breaking up a monolithic front-end application into separate and independent MicroFrontEnd (MFE) applications.

The ngx-mfe is an extension of the functionality of the @angular-architects/module-federation. Using @angular-architects/module-federation you could only upload one micro-frontend per page (in the Routing), this limitation was the main reason for the creation of this library.

The key feature of the ngx-mfe library is ability to work with micro-frontends directly in the HTML template using a plugin-based approach. You can load more than one micro-frontend per page.

You can use both ngx-mfe and @angular-architects/module-federation libs together in the same project.

Features

πŸ”₯ Load multiple micro-frontend directly from an HTML template with the ability to display a loader component during loading and a fallback component when an error occurs during loading and/or rendering of the mfe component.

πŸ”₯ Easy to use, just declare structural directive *mfeOutlet in your template.

πŸ”₯ Supports Angular Standalone Components.

πŸ”₯ More convenient way to load MFE via Angular Routing.

πŸ”₯ It's easy to set up different remoteEntryUrl MFEs for different builds (dev/prod/etc).

Examples

Conventions

  1. To display a standalone MFE component, you only need to the component file itself.

    A standalone component is a component that does not have any dependencies provided or imported in the module where that component is declared.

    Since Angular v14 standalone component it is component that marked with standalone: true in @Component({...}) decorator.

    When you display a standalone MFE component through [mfeOutlet] directive you must omit [mfeOutletModule] input.

    // Standalone Component - standalone.component.ts
    import { Component } from '@angular/core';
    import { CommonModule } from '@angular/common';
    
    @Component({
      selector: 'app-standalone',
      standalone: true,
      imports: [CommonModule],
      template: ` <p>Standalone component works!</p> `,
      styles: [],
    })
    export class StandaloneComponent {}
    // dashboard-mfe webpack.config
    {
      new ModuleFederationPlugin({
        name: 'dashboard-mfe',
        filename: 'remoteEntry.js',
        exposes: {
          StandaloneComponent: 'apps/dashboard-mfe/src/app/standalone.component.ts',
        },
        [...]
      });
    }
    <!-- shell-app -->
    <ng-template
      mfeOutlet="dashboard-mfe"
      mfeOutletComponent="StandaloneComponent"
    >
    </ng-template>
  2. To display an MFE component with dependencies in the module where the component was declared, you must expose both the component file and the module file from ModuleFederationPlugin.

    This approach is widely used and recommended.

    When you display this type of MFE component with the [mfeOutlet] directive, you must declare an input [mfeOutletModule] with the value of the exposed module name.

  3. The file key of an exposed Module or Component (declared in the ModuleFederationPlugin in the 'expose' property) must match the class name of that file.

    For the plugin-based approach, when loads MFE using [mfeOutlet] directive you must declare Component in the exposed Module and the Component name must match the file key of an exposed Component class.

    // webpack.config
    {
      new ModuleFederationPlugin({
        name: 'dashboard-mfe',
        filename: 'remoteEntry.js',
        exposes: {
          // EntryModule is the key of the entry.module.ts file and corresponds to the exported EntryModule class from this file.
          EntryModule: 'apps/dashboard-mfe/src/app/remote-entry/entry.module.ts',
          // the EntryComponent is key of file entry.module.ts, and match to exported EntryComponent class from that file.
          EntryComponent: 'apps/dashboard-mfe/src/app/remote-entry/entry.component.ts',
        },
        [...]
      });
    }

    If the name of Module doesn't match, you can specify a custom name for this Module in the @Input() property mfeOutletOptions = { componentName: 'CustomName' } of [mfeOutlet] directive, and pass { moduleName: 'CustomName' } options to the loadMfe() function;

    If the name of Component doesn't match, you can specify a custom name for this Component in the @Input() property mfeOutletOptions = { componentName: 'CustomName' } of [mfeOutlet] directive, and pass { moduleName: 'CustomName' } options to the loadMfe() function;

  4. You must follow the rule that only one Component must be declared for an exposed Module. This is known as SCAM (Single Component Angular Module) pattern.

Configuring

Add the ngx-mfe library to a shared property in the ModuleFederationPlugin inside webpack.config.js file for each application in your workspace.

module.exports = {
  [...]
  plugins: [
    [...]
    new ModuleFederationPlugin({
      remotes: {},
      shared: share({
        [...]
        "ngx-mfe": {
          singleton: true,
          strictVersion: true,
          requiredVersion: 'auto',
          includeSecondaries: true
        },
        ...sharedMappings.getDescriptors(),
      }),
      library: {
        type: 'module'
      },
    }),
    [...]
  ],
  [...]
};

To configure this library, you must import MfeModule.forRoot(options: NgxMfeOptions) into the root module of the Host app(s) and the root module of the Remote apps in order for Remote to work correctly when running as a standalone application:

For feature modules just import MfeModule without options, where, you may need the functionality of the library, for example, the MfeOutlet directive.

For core / app module:

@NgModule({
  imports: [
    MfeModule.forRoot({
      mfeConfig: {
        "dashboard-mfe": "http://localhost:4201/remoteEntry.js",
        "loaders-mfe": "http://localhost:4202/remoteEntry.js",
        "fallbacks-mfe": "http://localhost:4203/remoteEntry.js"
      },
      preload: ['loaders-mfe', 'fallbacks-mfe'],
      loader: {
        app: 'loaders',
        module: 'SpinnerModule',
        component: 'SpinnerComponent',
      },
      loaderDelay: 500,
      fallback: {
        app: 'fallbacks',
        module: 'MfeFallbackModule',
        component: 'MfeFallbackComponent',
      },
    }),
  ],
})
export class AppModule {}

For feature module:

@NgModule({
  imports: [
    MfeModule,
  ],
})
export class Feature1Module {}

List of all available options:

  • mfeConfig


    Sync variant of providing mfeConfig:


    object where key is micro-frontend app name specified in ModuleFederationPlugin (webpack.config.js) and value is remoteEntryUrl string. All data will be sets to MfeRegistry.

    Key it's the name same specified in webpack.config.js of MFE (Remote) in option name in ModuleFederationPlugin.

    Value set the following pattern: {url}/{remoteEntrypointFilename}.

    • url is the url where the remote application is hosted.

    • remoteEntrypointFilename is the filename supplied in the remote's webpack configuration.

      Example http://localhost:4201/remoteEntry.js

    (Deprecated from v15.1.0) You can get MfeRegistry from DI :

    class AppComponent {
    
    	constructor(public mfeRegistry: MfeRegistry) {}
    }

    You can even get instace of MfeRegistry like this:

     const mfeRegistry: MfeRegistry = MfeRegistry.instace;

    Async variant of providing mfeConfig:


    NOTE: The application will wait for initialization and completes when the promise resolves or the observable completes.

    Because under the hood used APP_INITIALIZER injection token with useFactory that returns Observale or Promise. More about APP_INITIALIZER

    Also you can provide mfeConfig with loading it from external resource as Observale<MfeConfig> or Promise<MfeConfig>, for this you should provide this type of object:

      type NgxMfeAsyncConfig = {
      /**
        * A function to invoke to load a `MfeConfig`. The function is invoked with
        * resolved values of `token`s in the `deps` field.
        */
      useLoader: (...deps: any[]) => Observable<NgxMfeSyncConfig> | Promise<NgxMfeSyncConfig>;
      /**
        * A list of `token`s to be resolved by the injector. The list of values is then
        * used as arguments to the `useLoader` function.
        */
      deps?: any[];
    };

    For example:

    mfeConfig: {
       	useLoader:  (http: HttpClient): Observable<MfeConfig> =>
        http.get<MfeConfig>('/manifest.json'),
      deps: [HttpClient]
    },
  • preload (Optional) - a list of micro-frontend names, their bundles (remoteEntry.js) will be loaded and saved in the cache when the application starts.

Next options are only works in plugin-based approach with MfeOutletDirective:

  • loaderDelay (Optional) - Specifies the minimum loader display time in ms. This is to avoid flickering when the micro-frontend loads very quickly.

    By default is 0.

  • loader (Optional) - Displayed when loading the micro-frontend. Implements the RemoteComponent interface.

    Example:

    // Globally uses the "SpinnerComponent" loader component declared in the "SpinnerModule" of the app "loaders".
    loader: {
      app: 'loaders',
      module: 'SpinnerModule',
      component: 'SpinnerComponent',
    },

    For better UX, add loader micro-frontends to the preload.

  • fallback (Optional) - Displayed when loading or compiling a micro-frontend with an error. Implements the RemoteComponent interface.

    Example:

    // Globally uses the "MfeFallbackComponent" fallback component declared in the "MfeFallbackModule" of the app "fallbacks".
    fallback: {
      app: 'fallbacks',
      module: 'MfeFallbackModule',
      component: 'MfeFallbackComponent',
    },

    For better UX, add fallback micro-frontends to the preload.

You can get all configured options by injecting NGX_MFE_OPTIONS by DI:

class AppComponent {
  constructor(@Inject(NGX_MFE_OPTIONS) public options: NgxMfeOptions) {}
}

Display MFE in HTML template / plugin-based approach

This approach allows us to load micro-frontends directly from HTML.

The advantages of this approach are that we can display several MFEs at once on the same page, even display several of the same MFEs.

More about plugin-based approach here.

Full code of this example can be found at https://github.com/dkhrunov/ngx-mfe-test.

Example app:

image

An example webpack.config.js that exposes the "MfeTestComponent" (brown border in the screenshot above):

// webpack.config.js
return {
	[...]
	resolve: {
		alias: sharedMappings.getAliases(),
	},
	plugins: [
		new ModuleFederationPlugin({
			name: 'test',
			exposes: {
        MfeTestModule: 'apps/test/src/app/mfe-test/mfe-test.module.ts',
        MfeTestComponent: 'apps/test/src/app/mfe-test/mfe-test.component.ts',
			},
			filename: 'remoteEntry',
			shared: share({ ... }),
		}),
		sharedMappings.getPlugin(),
	],
};
  1. Just display the component "MfeTestComponent" inside other MFE component "Form" from "address-form" app:
One variant:
```html
<ng-template
  mfeOutlet="test"
  mfeOutletModule="MfeTestModule"
  mfeOutletComponent="MfeTestComponent"
>
</ng-template>
```

Other variant:
```html
<ng-container
  *mfeOutlet="
    'test';
    module: 'MfeTestModule';
    component: 'MfeTestComponent'
  "
>
</ng-container>
```

> These two examples are equal and display the MFE "MfeTestComponent".
  1. You can pass/bind @Input and @Output props to MFE component:

    <!-- form.component.html file -->
    <ng-container
      *mfeOutlet="
        'test';
        module: 'MfeTestModule';
        component: 'MfeTestComponent';
        inputs: { text: text$ | async };
        outputs: { click: onClick };
      "
    ></ng-container>
    // form.component.ts file
    @Component({
      selector: 'app-form',
      templateUrl: './form.component.html',
      styleUrls: ['./form.component.scss'],
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class FormComponent {
      [...]
      // timer emits after 1 second, then every 2 seconds
      public readonly text$: Observable<number> = timer(1000, 2000);
    
      // on click log to console event
      public onClick(event: MouseEvent): void {
        console.log('clicked', event);
      }
      [...]
    }

    If you try to bind a @Output() property that is not in the component, then an error will fall into the console: "Output someOutput is not output of SomeComponent."

    If you try to pass a non-function, then an error will fall into the console: "Output someOutput must be a function."

  2. To override the default loader delay, configured in MfeModule.forRoot({ ... }), provide custom number in ms to property loaderDelay:

    <ng-container
      *mfeOutlet="
        'test';
        module: 'MfeTestModule';
        component: 'MfeTestComponent';
        loaderDelay: 1000
      "
    ></ng-container>
  3. To override the default loader and fallback MFE components, configured in MfeModule.forRoot({ ... }), specify content with TemplateRef, pass it to the appropriate properties loader and fallback:

    <ng-container
      *mfeOutlet="
        'test';
        module: 'MfeTestModule';
        component: 'MfeTestComponent';
        loader: loaderTpl;
        fallback: fallbackTpl
      "
    ></ng-container>
    
    <ng-template #loaderTpl>
      <div>loading...</div>
    </ng-template>
    
    <ng-template #fallbackTpl>
      <div>Ooops! Something went wrong</div>
    </ng-template>
    <!-- TemplateRef that render loader as MFE component -->
    <ng-template
      mfeOutlet="test"
      mfeOutletModule="MfeTestModule"
      mfeOutletComponent="MfeTestComponent"
      [mfeOutletLoader]="loaderMfeTpl"
    ></ng-template>
    
    <ng-template #loaderMfeTpl>
      <ng-template
        mfeOutlet="loaders-mfe"
        mfeOutletModule="SpinnerModule"
        mfeOutletComponent="SpinnerComponent"
        [mfeOutletLoader]="undefined"
        [mfeOutletLoaderDelay]="0"
      >
      </ng-template>
    </ng-template>
  4. You can also provide a custom injector for a component like this:

    <ng-template
      mfeOutlet="test"
      mfeOutletModule="MfeTestModule"
      mfeOutletComponent="MfeTestComponent"
      [mfeOutletInjector]="customInjector"
    ></ng-template>

Display Angular v14 Standalone components

Example app:

image

An example webpack.config.js that exposes the "StandaloneComponent" (green border in the screenshot above):

// webpack.config.js
return {
	[...]
	resolve: {
		alias: sharedMappings.getAliases(),
	},
	plugins: [
		new ModuleFederationPlugin({
			name: 'test',
			exposes: {
        [...]
        StandaloneComponent: 'apps/test/src/app/standalone/standalone.component.ts',
			},
			filename: 'remoteEntry',
			shared: share({ ... }),
		}),
		sharedMappings.getPlugin(),
	],
};
// standalone.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
	selector: 'app-standalone',
	standalone: true,
	imports: [CommonModule],
	template: ` <p>Standalone component works!</p> `,
	styles: [],
})
export class StandaloneComponent {}
<!-- form.component.html of the address-form app -->
[...]
<h3>Angular v14 Standalone component loaded as MFE:</h3>
<ng-template
  mfeOutlet="test"
  mfeOutletComponent="StandaloneComponent"
></ng-template>

Passing Data to the MFE Component via mfeOutlet directive

After using this library for some time, as the author of this library, I came to the conclusion that using @Inputs and @Outputs of an MFE component through the [mfeOutletInputs] [mfeOutletOutputs] properties is not the best practice. Try to make your MFE components as independent as possible from the external environment. But if you still have to pass some values ​​to the component, you can do it in two ways:

  1. As I wrote above through the properties [mfeOutletInputs] [mfeOutletOutputs]

    component.html:

    <ng-template
      mfeOutlet="test"
      mfeOutletModule="MfeTestModule"
      mfeOutletComponent="MfeTestComponent"
      [mfeOutletInputs]="{ text: text$ | async }"
      [mfeOutletOutputs]="{ click: onClick }"
    >
    </ng-template>

    component.ts

    @Component({ ... })
    export class Component {
      public text$ = new BehaviorSubject<string>('Test string');
    
      constructor() { }
    
      public onClick(bool: MouseEvent): void {
        console.log('login', bool);
      }
    }
  2. The second way is to create a new injector and add the necessary data for the MFE component to it. The [mfeOutlet] directive has the [mfeOutletInjector] property through which you can pass the desired injector, when the component is created, the previously passed injector in the [mfeOutletInjector] property will be used instead of the current injector.

    component.html:

    <ng-template
      mfeOutlet="test"
      mfeOutletModule="MfeTestModule"
      mfeOutletComponent="MfeTestComponent"
      [mfeOutletInjector]="testComponentInjector"
    >
    </ng-template>

    component.ts

    @Component({ ... })
    export class Component {
      public readonly testComponentInjector: Injector;
    
      constructor(private readonly _injector: Injector) {
        this.testComponentInjector = Injector.create({
          parent: this._injector,
          providers: [
            {
              provide: TEST_DATA,
              useValue: data,
            },
          ],
        });
      }
    }

Load MFE by Route

To use micro-frontends in Routing, you must import and apply the helper function called loadMfe, like in the example below:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadMfe } from '@dkhrunov/ng-mfe';

const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => loadMfe('dashboard-mfe', 'EntryModule'),
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabledBlocking' })],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Changelog

Changes in v2.1.0

Fixed:

  • Fix error, if the fallback is also unavailable, then simply clear the view;

Refactored:

  • Renamed MfeService to RemoteComponentLoader;
  • Renamed MfeComponentsCache to RemoteComponentsCache;
  • Renamed ModularRemoteComponent type to RemoteComponentWithModule;
  • Wrapped to ngZone.runOutside the loadMfe function calls inside the RemoteComponentLoader;
  • Added new type ComponentWithNgModuleRef<TComponent, TModule>, that holds component class Type<T> and NgModuleRef;
  • Changed cached value for RemoteComponentWithModule from ComponentFactory to ComponentWithNgModuleRef;
  • In RemoteComponentLoader (old name MfeService) renamed function loadModularComponent to loadComponentWithModule
  • Changed return type of method loadComponentWithModule inside class RemoteComponentLoader from Promise<ComponentFactory<TComponent>> to Promise<ComponentWithNgModuleRef<TComponent, TModule>>;

Changes in v2.0.0 (Breaking changes)

Why has the API changed? - The problem is that when you use the [mfeOutlet] directive issue, it tries to find the component inside the compiled module by name (as a string), but in runtime the class name will be optimized and replaced with a short character. For example, you have a class TestComponent, it can be changed to the class name a and this causes this error.

General:

  • To properly use the plugin-based approach in a micro-frontend architecture, or simply if you are use [mfeOutlet] directive, you must now expose both the component file and module file in which the component is declared to the ModuleFederationPlugin.

    Rarerly : or, if your micro-frontend component is standalone (a standalone component is a component that does not have any dependencies declared or imported in the module where that component is declared), then it is sufficient to provide just that component file to the ModuleFederationPlugin;

  • Now ngx-mfe does not use Micro-frontend string (or anouther name MFE string)β€Šis aβ€Škebab-case style string and matches the pattern "mfe-app-name/exposed-file-name" (it was used until version 2.0.0);

  • MFE string has been replaced by a new type RemoteComponent;

  • The validateMfe function has been removed (it was used until version 2.0.0);

  • The loader and fallback properties in the NgxMfeOptions has been changed from MFE string to RemoteComponent type:

    Before v2.0.0:

    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        MfeModule.forRoot({
          mfeConfig: {
            "dashboard-mfe": "http://localhost:4201/remoteEntry.js",
            "loaders-mfe": "http://localhost:4202/remoteEntry.js",
            "fallbacks-mfe": "http://localhost:4203/remoteEntry.js"
          },
          loader: 'loaders/spinner',
          fallback: 'fallbacks/mfe-fallback',
        }),
      ],
      bootstrap: [AppComponent],
    })
    export class AppModule {}

    Since v2.0.0:

    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        MfeModule.forRoot({
          mfeConfig: {
            "dashboard-mfe": "http://localhost:4201/remoteEntry.js",
            "loaders-mfe": "http://localhost:4202/remoteEntry.js",
            "fallbacks-mfe": "http://localhost:4203/remoteEntry.js"
          },
          loader: {
            app: 'loaders',
            module: 'SpinnerModule',
            component: 'SpinnerComponent',
          },
          fallback: {
            app: 'fallbacks',
            module: 'MfeFallbackModule',
            component: 'MfeFallbackComponent',
          },
        }),
      ],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
  • Removed moduleName property from LoadMfeOptions type;

  • Now, wherever you need to specify the name of the exposed file through the config in the webpack.config in the ModuleFederationPlugin, you must specify exactly the same name as in the config itself, the kebab-style name was used earlier.

    // webpack.config.js
    exposes: {
      // LoginModule name of the exposed file login.module.ts
      	LoginModule: 'apps/auth-mfe/src/app/login/login.module.ts',
      },  

    Before v2.0.0:

    loadMfe('auth-mfe/login-module')

    Since v2.0.0:

    loadMfe('auth-mfe' 'LoginModule')

LoadMfe function:

  • Arguments changed in LoadMfe function:

    Before v2.0.0:

    async function loadMfe<T = unknown>(mfeString: string, options?: LoadMfeOptions): Promise<Type<T>> {}

    Since v2.0.0:

    async function loadMfe<T = unknown>(remoteApp: string, exposedFile: string, options?: LoadMfeOptions): Promise<Type<T>> {}
    • remoteApp - is the name of the remote app as specified in the webpack.config.js file in the ModuleFederationPlugin in the name property;
    • exposedFile - is the key (or name) of the exposed file specified in the webpack.config.js file in the ModuleFederationPlugin in the exposes property;

MfeOutlet directive:

  • Since the Mfe string has been removed from the library, the API of [mfeOutlet] directive has changed:

    1. mfeOutletLoader and mfeOutletFallback now accept only TemplateRef, more details below.
    2. To load a standalone component, you must specify the following details: mfeOutlet with the name of the application, mfeOutletComponent with the name of the component's open file from the ModuleFederationPlugin in webpack.config. But to load a non-standalone component, you must additionally specify mfeOutletModule with the name of the open module file in which the component is declared for the ModuleFederationPlugin in webpack.config.
  • @Input('mfeOutletOptions')' options changed type from MfeComponentFactoryResolverOptions to LoadMfeOptions;

  • @Input('mfeOutletLoader')' loader and @Input('mfeOutletFallback') fallback now accept only TemplateRef, not TemplateRef or Mfe string. But you can still use micro-frontend component for loader and fallback in the [mfeOutlet], like in the example below:

    <!-- With Mfe loader -->
    <ng-template
      mfeOutlet="dashboard-mfe"
      mfeOutletModule="EntryModule"
      mfeOutletComponent="EntryComponent"
      [mfeOutletLoader]="loaderMfe"
    >
    </ng-template>
    
    <!-- Mfe component for loader -->
    <ng-template #loaderMfe>
        <!-- For loader Mfe you should set mfeOutletLoader to undefined, and mfeOutletLoaderDelay to 0. For better UX. -->
        <ng-template
          mfeOutlet="loaders-mfe"
          mfeOutletModule="SpinnerModule"
          mfeOutletComponent="SpinnerComponent"
          [mfeOutletLoader]="undefined"
          [mfeOutletLoaderDelay]="0"
        >
        </ng-template>
    </ng-template>
    
    <!-- With simple HTML content as loader -->
    <ng-template
      mfeOutlet="dashboard-mfe"
      mfeOutletModule="EntryModule"
      mfeOutletComponent="EntryComponent"
      [mfeOutletLoader]="loaderMfe"
    >
    </ng-template>
    
    <!-- Simple HTML content. -->
    <ng-template #loader>
        <div>loading...</div>
    </ng-template>

MfeComponentFactoryResolver:

  • The MfeComponentFactoryResolver has been replaced with MfeService and the API has been changed;
  • The MfeComponentFactoryResolverOptions type has been removed;

MfeComponentCache

  • Now the MfeComponentCache not only saves ComponentFactory<T> but also Type<T>;
  • In version 2.1.0 ComponentFactory<T> was replaced to ComponentWithNgModuleRef<TComponent, TModule>;

DynamicComponentBinding

  • The bindInputs() and bindOutputs() methods now require ComponentRef<any> in the first argument, MfeOutletInputs/MfeOutletOutputs are method dependent in the second, and the third argument has been removed;
  • The DynamicComponentInputs and DynamicComponentOutputs types have been removed because these types are replaced in bindInputs() and bindOutputs() respectively by the ComponentRef<any> type;
  • The validateInputs() method has been removed;
  • The validateOutputs() method is now private;

Changes in v1.1.0:

  • Deleted the loadMfeComponent helper function;

  • Deleted the parseMfeString helper function;

  • Renamed the loadMfeModule helper function to loadMfe and added optional parameter options: LoadMfeOptions. LoadMfeOptions has property a moduleName, that sets a custom name for the Module class within the opened file, and has type that specify type of Module Federation;

  • Renamed the MfeService to MfeComponentFactoryResolver;

  • MfeComponentFactoryResolver has the same method as MfeService, but now it can accepts an optional options: MfeComponentFactoryResolver parameter. This parameter extends LoadMfeOptions type, added a componentName parameter, that sets a custom name for the Component class.

  • Added new Input prop to the MfeOutletDirective - options: MfeComponentFactoryResolver, this parameter provided to resolveComponentFactory method of the MfeComponentFactoryResolver when resolving the component factory of MFE.

  • Since v1.1.0 you don't need to expose from ModuleFederationPlugin for plugin-based approach both Module and Component, just specify the Module file.

    The exposed Module key must match the name of the exposed module without the 'Module' suffix. Also, if the name doesn't match, you can specify a custom Module name in the options { moduleName: 'CustomName' } in the property mfeOutletOptions inside MfeOutletDirective and in the options parameter of the loadMfe helper function.

    For the plugin-based approach, when loads MFE using MfeOutletDirective you must declare Component in the exposed Module and the Component name must match the exposed Module key without suffix 'Component'. Also, if the name doesn't match, you can specify a custom Component name in the Input property mfeOutletOptions = { componentName: 'CustomName' };


Changes in v1.0.8:

  • IMfeModuleRootOptions interface renamed to NgxMfeOptions;
  • Property delay in the NgxMfeOptions renamed to loaderDelay;
  • OPTIONS injection token renamed to NGX_MFE_OPTIONS;

ngx-mfe's People

Contributors

dkhrunov avatar tfaster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ngx-mfe's Issues

Multiple mfeOutlet in the same page.

Hi dkhrunov,

first of all, Thank you very much for putting this directive together. I am using it and works very nicely. The only issue I have is when I try to add more than one mfeOutlet on the page. For instance, the page has a header, footer, and body. each of them needs to have mfeOutlet.

 <ng-container *mfeOutlet="'XXXX/XXXXXX'"></ng-container>
 <ng-container *mfeOutlet="'YYYYYY/YYYYY'"></ng-container>

Error:

ngx-mfe.mjs:317 TypeError: (0 , rxjs__WEBPACK_IMPORTED_MODULE_2__.lastValueFrom) is not a function
    at ngx-mfe.mjs:207:37
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at asyncToGenerator.js:32:1
    at new ZoneAwarePromise (zone.js:1387:1)
    at asyncToGenerator.js:21:1
    at MfeComponentFactoryResolver.resolveComponentFactory (ngx-mfe.mjs:203:122)
    at ngx-mfe.mjs:339:55
    at Generator.next (<anonymous>)

Thank you very much!
hmendezm

directive does not work when Build for production

HI dkhrunov,

Sorry for bothering you. Now I am working on deploying the app in production mode. In this case, I have all remotes working standard alone. The problem is when I use the directive I got the error below. My guess is that when the directive tries to upload the component, this is not found. Note: The app works if I run serve (development mode)

do you have any idea what is the problem?
I have the following in place:

HTML

 <ng-container *mfeOutlet="'sharedcomponents/Loader';inputs: { isLoading: isLoading, messages:loadingMsg};">
  </ng-container>

Configuration

	MfeModule.forRoot({
			mfeConfig:{
 'sharedcomponents': 'http://localhost:5005/remoteEntry.js',
},
      preload: ['sharedcomponents'],
			loaderDelay: 0,
		}),

Sharedcompoenent MFE

webpack.config

  exposes: {
        'Loader':'apps/sharedcomponents/src/app/loader/loader.module.ts',
         },

Loader.module

import { LoaderComponent } from './loader.component';
import {  NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [CommonModule],
  declarations: [LoaderComponent],
  exports: [LoaderComponent]
})
export class LoaderModule {
  constructor(){
    console.log("%cLoaderModule constructor: LIBS/COMPONENTS/LOADER","color: blue; font-size: 20px");
  }
}

ERROR

329.cf6448c1ab7708fd.js:1 Error: LoaderComponent don't registered in module i
    at 329.cf6448c1ab7708fd.js:1:65453
    at Generator.next (<anonymous>)

Provide a way to configure module in an async way

First - Great job your lib simplifies so much.

Can you offer a way to configure the modul asynchronously.

You could inject the NgxMfeOptions in forRoot or offer another method
forRootAsync(options$: Obeservable) | forRootAsync(options$: Promise)

So that the individual mfe configurations can also be obtained via api or fetch.

public static forRoot(options: NgxMfeOptions): ModuleWithProviders<MfeModule> {

OnChanges Not WORk

When changing the inputs, the ngOnChanges method is not being called, and we must use a setTimeOut to delay fetching the data. Under no circumstances is the ngOnChanges method being invoked

This is not recognized on outputs

I followed the steps in your example everything works except when I try to use this. This always is undefined.

HTML

  <ng-container *mfeOutlet="'targetarea/Settings';outputs: { okEvent: next};">
      </ng-container>

CODE BEHIND

  public next(e) {

    if (e.sucess) {
      that.nameStep = true;
    }
  }

Targetarea/Settings CODE

export class SettingsComponent implements OnInit, AfterViewInit {
  @Output() public okEvent = new EventEmitter();
....


  public continue() {
    const projectTypes = PROJECTTYPES;
    this.okEvent.emit({ sucess: true });
  }

}

nx required?

Should I use Nx and monorepo in order to use these library?
I'am using Angular v13 with @angular-architects/module-federation 12.5.3, so following the readme I should use ngx-mfe 1.0.2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.