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: Handles navigation messages between the host and embedded applications. Embedded applications can use
RoutingServiceto sync their internal routing with the host application. The host application can useNavigationConsumerServiceto get notifications about navigation events in the embedded applications. - 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.
- User Activity: Tracks user interactions across embedded micro-frontends for session timeout functionality, analytics, or any feature that needs to detect user activity.
- Iframe Embed: A ready-to-use Angular component that renders an iframe with all MFE integration directives pre-wired (connect, scalable, host info, theme).
Installation
To install the package, run:
npm exec ng add @ama-mfe/ng-utilsConsumer 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(App, 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
The recommended way to embed a module is to use the IframeEmbedComponent, which
pre-wires connect, scalable, hostInfo and applyTheme on the iframe:
<mfe-iframe-embed
src="https://my-embedded-app.example.com"
moduleId="myModuleUniqueID"
hostApplicationId="hostUniqueID" />Alternatively, if you need full control over the iframe, you can use the connect directive directly.
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-component',
template: './example-module-component.html',
styleUrl: './example-module-component.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(App, 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;Message version compatibility
The ConsumerManagerService dispatches incoming messages with a semver-compatible fallback, applied uniformly to every
message type in this package (navigation, theme, resize, user-activity, history, and custom messages).
Within a major version, a consumer's highest declared minor ≤ the incoming minor, is used. This lets producers add
optional fields in a new minor version (e.g. v1.1) without breaking consumers that only implement v1.0 — the older
handler runs and simply ignores the unknown fields.
Majors are isolated — no cross-major fallback in either direction:
- An incoming v2.x will never fall back to a v1.x handler (breaking changes are expected across majors).
- A consumer declaring only v2.x will not match an incoming v1.x (the consumer is ahead of the producer).
Both cross-major cases produce a version_mismatch error. Introducing a new major version therefore requires coordinated
updates on both the producer and consumer sides.
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);
}
}Iframe Embed Component
The IframeEmbedComponent is a ready-to-use component that renders an iframe with all MFE integration directives
pre-configured. It handles URL sanitization, cross-iframe communication (connect), auto-resizing (scalable),
host info injection (hostInfo) and theming (applyTheme).
import {IframeEmbedComponent} from '@ama-mfe/ng-utils';
@Component({
selector: 'app-host',
imports: [IframeEmbedComponent],
template: `
<mfe-iframe-embed
src="https://my-embedded-app.example.com"
moduleId="my-module-id"
hostApplicationId="host-app-id"
sandbox="allow-scripts allow-same-origin" />
`
})
export class HostComponent {}| Input | Type | Description |
|---|---|---|
src |
string | SafeResourceUrl |
The URL for the iframe. Plain strings are automatically sanitized. |
moduleId |
string |
Unique identifier for the embedded module (used by the connect directive). |
hostApplicationId |
string |
The host application identifier sent to the embedded module via hostInfo. |
sandbox |
string |
The iframe sandbox permissions. Defaults to 'allow-scripts allow-same-origin'. |
The sandbox attribute is set once at initialization for security reasons (runtime changes are ignored).
Host information
Host application
A host application can send information to the embedded applications using parameters in the URL.
The IframeEmbedComponent handles this automatically via its moduleId and
hostApplicationId inputs — no extra setup is needed.
If you manage the iframe yourself, you can apply the hostInfo pipe directly:
<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.
Browser history handling
When using iframes to embed applications, the browser history might be shared by the main page and the embedded iframe. For example <iframe src="https://example.com" sandbox="allow-same-origin"> will share the same history as the main page. This can lead to unexpected behavior when using browser 'back' and 'forward' buttons.
To avoid this, the @ama-mfe/ng-utils will forbid the application running in the iframe to alter the browser history. It will happen when connection is configured using provideConection() function. This will prevent the iframe to be able to use the history.pushState and history.replaceState methods.
User Activity Tracking
The User Activity Tracking feature allows a host application (shell) to monitor user interactions across embedded micro-frontends. This is useful for implementing session timeout functionality, analytics, or any feature that needs to know when users are actively interacting with the application.
For detailed documentation, configuration options, and examples, see the User Activity README.