@ama-mfe/ng-utils
TypeScript icon, indicating that this package has built-in type declarations

12.3.7 • Public • Published

Ama MFE Angular Utils

@ama-mfe/ng-utils is an Angular library designed to streamline communication within a micro-frontend architecture that uses iframes. This package is built on the Amadeus Toolkit for Micro Frontends framework and offers a suite of tools - including helpers, wrappers, and services - to facilitate seamless integration and interaction between host and embedded applications.

Key features include:

  • Connect: Connect to the communication protocol and send messages to registered applications.
  • Navigation: Ensures the host application can update its navigation to reflect the embedded application's URL, maintaining consistency even after a page refresh.
  • Theme: Allows the application of unified CSS variables and styles across embedded modules, ensuring a cohesive look and feel.
  • Resize: Dynamically adjusts the iframe dimensions to fit the content of the embedded application, enhancing the user experience.

Installation

To install the package, run:

npm exec ng add @ama-mfe/ng-utils

Consumer and Producer

In the communication protocol, a message is created by a Producer and sent to a Consumer which will read the message and react according to its content.

The @ama-mfe/ng-utils package exposes a set of features based on messages exchanged between a host application and its embedded module. For each feature, the package provides a Producer/Consumer pair. The former service is in charge of delivering the messages based on triggers (resize event, call to a public function etc.) while the latter implements the logic behind the feature (resizing of the iframe, application of a theme, etc.).

How to use

Connection to the communication service

Configure your application connection

Applications first need to provide and configure the ConnectionService to use the communication protocol. The provideConnection method allows an application to register with a unique ID that others will use to connect and target the application.

import {provideConnection} from '@ama-mfe/ng-utils';

export const appConfig: ApplicationConfig = {
  providers: [
    provideConnection({
      id: 'applicationUniqueID'
    })
  ]
};

Initiate the communication with the host application

An application embedded into another one can connect to its host using the [ConnectionService]. To establish the connection, the embedded application requires the host application id (set via the connection service's provider).

// main.ts
import {inject, runInInjectionContext} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {ConnectionService, NavigationConsumerService} from '@ama-mfe/ng-utils';

bootstrapApplication(AppComponent, appConfig)
  .then((m) => {
    runInInjectionContext(m.injector, () => {
      if (window.top !== window.self) {
        // If embedded in an iframe, connect to the host
        inject(ConnectionService).connect('hostUniqueID');
      }
      // Other injections
    })
  });

Initiate the connection to your embedded module

Use the connect directive to initiate the communication between your application and the module in the iframe. The communication pipe will be closed once the iframe is destroyed.

<iframe [src]='myModuleUrl'
        [connect]='myModuleUniqueID'>
</iframe>

In this example, myModuleUniqueID refers to the id provided in the provideConnection method.

Enable a message-based feature

To use a feature based on the message communication protocol, you need first to identify if your application will be a user of the message (Consumer) or the one sending the message (Producer). This may depend on the context and the type of message. For instance, an application can be the consumer of navigation messages but the producer of theme messages.

Consumers

If you are a consumer of the message, call the start and stop methods to respectively enable and disable the feature.

import {Component, inject} from '@angular/core';
import {NavigationConsumerService} from '@ama-mfe/ng-utils';
import {ThemeConsumerService} from "./theme.consumer.service";

@Component({
  selector: 'app-example-module',
  template: './example-module.template.html',
  styleUrl: './example-module.style.scss',
})
export class ExampleModuleComponent {
  private readonly navigationConsumerService = inject(NavigationConsumerService);

  constructor() {
    this.navigationConsumerService.start();
  }

  ngOnDestroy() {
    this.navigationConsumerService.stop()
  }
}

Depending on your use case, you might need to start the service as soon as your application start running. In this case, you may inject it in the main.ts:

// main.ts
import {inject, runInInjectionContext} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {ConnectionService, ThemeConsumerService} from '@ama-mfe/ng-utils';

bootstrapApplication(AppComponent, appConfig)
  .then((m) => {
    runInInjectionContext(m.injector, () => {
      if (window.top !== window.self) {
        // If embedded in an iframe, connect to the host
        inject(ConnectionService).connect('hostUniqueID');
        // Start the service to consume messages
        inject(ThemeConsumerService).start();
      }
      // Other injections
    })
  });

Producers

If your application is a producer, just inject the message producer service and call the trigger when needed. There is no standardization on the name of the methods used to trigger a message. It will be different for each service.

Services provided in @ama-mfe/ng-utils

You will find more information for each service in their respective README.md:

Write your own producer and consumers.

Use the ProducerManagerService and the ConsumerManagerService to support your own custom messages.

A message should be identified by its type and a version to allow different message versions between the host and the embedded applications (and avoid migration issues).

import type {Message} from '@amadeus-it-group/microfrontends';

export interface CustomMessageV1_0 extends Message {
  type: 'custom',
  version: '1.0',
  // Custom properties
  customPayload: any
}
// Use union type here to add all the future version
// For example CustomMessage = CustomMessageV1_0 | CustomMessagev2_0
export type CustomMessageVersions = CustomMessageV1_0;

Consumer

A consumer should implement the MessageConsumer interface and inject the ConsumeManagerService which handles the registration to the communication protocol. It should list the supported versions and map its callback function in a supportedVersions public object.

import type {CustomMessageV1_0, CustomMessageVersions} from '@ama-mfe/messages';
import type {RoutedMessage} from '@amadeus-it-group/microfrontends';
import {DestroyRef, inject, Injectable} from '@angular/core';
import {ConsumerManagerService, type MessageConsumer} from '@ama-mfe/ng-utils';

@Injectable({
  providedIn: 'root'
})
export class CustomConsumerService implements MessageConsumer<CustomMessageVersions> {
  /**
   * The type of messages this service handles ('custom').
   */
  public readonly type = 'custom';

  /**
   * The supported versions of theme messages and their handlers.
   */
  public readonly supportedVersions = {
    '1.0': (message: RoutedMessage<CustomMessageV1_0>) => console.log('Do some stuff with this message version', message)
  };

  private readonly consumerManagerService = inject(ConsumerManagerService);

  constructor() {
    this.start();
    inject(DestroyRef).onDestroy(() => this.stop());
  }

  /**
   * Starts the theme handler service by registering it into the consumer manager service.
   */
  public start() {
    this.consumerManagerService.register(this);
  }

  /**
   * Stops the theme handler service by unregistering it from the consumer manager service.
   */
  public stop() {
    this.consumerManagerService.unregister(this);
  }
}

Producer

A producer should implement the MessageProducer interface and inject the ProducerManagerService which handles the registration to the communication protocol. Once connected, it is able to send messages via the MessagePeerService.

import type {CustomMessageV1_0, CustomMessageVersions} from '../messages';
import {MessagePeerService} from '@amadeus-it-group/microfrontends-angular';
import {inject, Injectable} from '@angular/core';
import {type MessageProducer, type ErrorContent, registerProducer} from '@ama-mfe/ng-utils';

@Injectable({
  providedIn: 'root'
})
export class CustomService implements MessageProducer<CustomMessageVersions> {
  private readonly messageService = inject(MessagePeerService<CustomMessageVersions>);

  constructor() {
    registerProducer(this);
  }

  public handleError(message: ErrorContent<CustomMessage_V1_0>): void {
    // If available, use your own logger
    console.error('Error in custom service message', message);
  }

  public postMessageAction(payload: any): void {
    const messageV10 = {
        type: 'custom',
        version: '1.0',
        customPayload: 'test'
      } satisfies CustomMessageV1_0;
    this.messageService.send(messageV10);
  }
}

Host information

Host application

A host application can send information to the embedded applications using parameters in the URL.

<iframe [src]="'myModuleUrl' | hostInfo: {hostId: 'host-app-id', moduleId: 'my-module-to-embed'}"></iframe>

This will add the location.origin and the application id of the host to the URL of the embedded application.

Embedded application

The embedded application can access the data sent in the previous section using an injection token:

import {inject} from '@angular/core';
import {getHostInfo, isEmbedded} from '@ama-mfe/ng-utils';

export class SomeClass {
  private readonly hostInfo = getHostInfo();

  doSomething() {
    if (this.hostInfo.applicationId === 'app1') {
      // Do something when embedded in app1
    } else if (isEmbedded()) {
      // Do something when embedded elsewhere
    } else {
      // Do something when standalone
    }
  }
}

The host information is stored in session storage so it won't be lost when navigating inside the iframe.

Package Sidebar

Install

npm i @ama-mfe/ng-utils

Weekly Downloads

1,055

Version

12.3.7

License

BSD-3-Clause

Unpacked Size

207 kB

Total Files

90

Last publish

Collaborators

  • kpanot