SCION Microfrontend Platform | Projects Overview | Changelog | Contributing | Sponsoring |
---|
SCION Microfrontend Platform > Getting Started > Navigating via Intent
In the previous chapter, we embedded the ProductList Microfrontend in the Customer Microfrontend by passing the URL of the products page to the router. This way of integration is simple and straightforward, but also has its drawbacks. For example, you need to know the URL of embedded content, the microfrontend URL becomes public API, and embedded content does not know its integrators.
That's why the SCION Microfrontend Platform features the Intention API, which enables controlled collaboration between micro apps. It is inspired by the Android platform where an application can start an activity via an Intent (such as sending an email). To collaborate, an application must express an Intention, making interaction flows explicit, which is especially important for cross-application communication and navigation.
Let us introduce you to the terminology of the Intention API.
-
Capabilty
A capability represents some functionality of a micro app that is available to qualified micro apps through the Intention API. A micro app declares its capabilities in its manifest. Qualified micro apps can browse capabilities similar to a catalog, or interact with capabilities via intent.
A capability is formulated in an abstract way consisting of atype
and optionally aqualifier
. The type categorizes a capability in terms of its functional semantics (e.g.,microfrontend
if providing a microfrontend). Multiple capabilities can be of the same type. In addition to the type, a capability can define a qualifier to differentiate the different capabilities of the same type. -
Intention
An intention refers to one or more capabilities that a micro app wants to interact with. A micro app declares its intentions in its manifest. Manifesting intentions enables us to see dependencies between applications down to the functional level. -
Intent
The intent is the message that a micro app sends to interact with functionality that is available in the form of a capability. If the application has not declared a respective intention in its manifest, the message will be rejected. -
Qualifier
The qualifier is a dictionary of arbitrary key-value pairs to differentiate capabilities of the same type.
To better understand the concept of the qualifier, a bean manager can be used as an analogy. If there is more than one bean of the same type, a qualifier can be used to control which bean to inject.
For more information on the concepts and usage of the Intention API, please refer to our developer guide:
In this chapter, we will refactor our application to navigate via intent instead of the URL.
Embed the *ProductList Microfrontend* via intent
In this section, we will migrate the embedding of the ProductList Microfrontend in the Customer Microfrontend from url-based to intent-based navigation.
Provide the *ProductList Microfrontend* as microfrontend capability
In order to navigate via intent, we need to provide the ProductList Microfrontend as microfrontend
capability.
- Open the manifest
products-app/src/manifest.json
of the Products App. - Register the ProductList Microfrontend as
microfrontend
capability, as follows:{ "name": "Products App", [+] "capabilities": [ [+] { [+] "type": "microfrontend", [+] "qualifier": { [+] "entity": "products" [+] }, [+] "params": [ [+] { [+] "name": "ids", [+] "required": false [+] } [+] ], [+] "private": false, [+] "properties": { [+] "path": "/product-list/product-list.html#?ids=:ids" [+] } [+] } [+] ] }
type
:
Categorizes the capability as a microfrontend.qualifier
:
Qualifies the microfrontend capability, allows navigating to this microfrontend using the qualifier{entity: 'products'}
.params
:
Declares optional and required parameter(s) of this capability. Required parameters must be passed when navigating to this microfrontend. Parameters can be referenced in the path in the form of named parameters using the colon syntax (:
).
By passing this parameter the navigator can control which products to display.private
:
If set tofalse
, makes this a public microfrontend, allowing other micro apps to navigate to this microfrontend. By default, capabilities have application-private scope.properties
:
Section to associate metadata with a capability.path
:
Metadata specific to themicrofrontend
capability to specify the path to the microfrontend.
The path is relative to the application’s base URL. In the path, you can reference qualifier and parameter values in the form of named parameters. Named parameters begin with a colon (:
) followed by the parameter or qualifier name, and are allowed in path segments, query parameters, matrix parameters and the fragment part. The router will substitute named parameters in the URL accordingly.
Declare microfrontend intention in the *Customers App*
In order to navigate to another application's microfrontend, the navigating app must manifest an intention. If not declaring the intention, the navigation will be rejected.
- Open the manifest
customers-app/src/manifest.json
of the Customers App. - Declare the
microfrontend
intention as follows:The intention qualifier allows using wildcards (such as{ "name": "Customers App", [+] "intentions": [ [+] { [+] "type": "microfrontend", [+] "qualifier": { [+] "entity": "products" [+] } [+] } [+] ] }
*
or?
) to match multiple capabilities simultaneously.
Navigate via intent instead of the URL
In the Customer Microfrontend, we can now embed the ProductList Microfrontend without having to know its URL, as follows:
-
Open the file
customers-app/src/customer/customer.ts
-
Replace the url-based navigation with intent-based navigation, as follows:
Before:
// Display the products purchased by the customer Beans.get(OutletRouter).navigate('http://localhost:4201/product-list/product-list.html#?ids=:ids', { params: {ids: customer.productIds}, outlet: 'customer-products', });
After:
// Display the products purchased by the customer Beans.get(OutletRouter).navigate({entity: 'products'}, { params: new Map().set('ids', customer.productIds), outlet: 'customer-products', });
Instead of the URL, pass the router the qualifier of the ProductList Microfrontend and the parameters declared by the capability via options object. If the microfrontend
capability declares required parameters and we do not pass them, navigation is rejected.
Open the *Product Microfrontend* via intent
In this section, we will migrate the navigation to the Product Microfrontend from url-based to intent-based navigation.
Provide the *Product Microfrontend* as microfrontend capability
In order to navigate via intent, we need to provide the Product Microfrontend as microfrontend
capability.
- Open the manifest
products-app/src/manifest.json
of the Products App. - Register the Product Microfrontend as
microfrontend
capability, as follows:Explanation:{ "name": "Products App", "capabilities": [ { "type": "microfrontend", "qualifier": { "entity": "products" }, "params": [ { "name": "ids", "required": false } ], "private": false, "properties": { "path": "/product-list/product-list.html#?ids=:ids" } }, [+] { [+] "type": "microfrontend", [+] "qualifier": { [+] "entity": "product" [+] }, [+] "params": [ [+] { [+] "name": "id", [+] "required": true [+] } [+] ], [+] "private": true, [+] "properties": { [+] "path": "/product/product.html#?id=:id", [+] "outlet": "aside" [+] } [+] } ] }
params
:
This microfrontend requires the navigator to pass the ID of the product to be displayed. For that reason, we declareid
as required parameter.private
:
We make this an application-privatemicrofrontend
capability that can only be navigated to from the Products App. By default, capabilities are private to the providing application.outlet
:
Themicrofrontend
capability allow us to specify the preferred outlet into which to load the microfrontend. Note that this outlet preference is only a hint that will be ignored if the navigator specifies an outlet for navigation.
Navigate via intent instead of the URL
In the ProductList Microfrontend, we can now navigate to the Product Microfrontend without having to know its URL, as follows:
-
Open the file
products-app/src/product-list/product-list.ts
-
In the
render
method, Replace the url-based navigation with intent-based navigation, as follows:Before:
productLink.addEventListener('click', () => { Beans.get(OutletRouter).navigate(`/product/product.html#?id=${product.id}`, {outlet: 'aside'}); });
After:
productLink.addEventListener('click', () => { Beans.get(OutletRouter).navigate({entity: 'product'}, {params: new Map().set('id', product.id)}); });
Note that we do not specify the outlet because it is already specified as a preference in the microfrontend capability. We also do not need to declare an intention in our manifest, since we are opening a microfrontend provided by the navigating application. An application is implicitly qualified to interact with its own capabilities.
Open the *Customer Microfrontend* via intent
In this section, we will migrate the navigation to the Customer Microfrontend from url-based to intent-based navigation.
Provide the *Customer Microfrontend* as microfrontend capability
In order to navigate via intent, we need to provide the Customer Microfrontend as microfrontend
capability.
- Open the manifest
customers-app/src/manifest.json
of the Customers App. - Register the Customer Microfrontend as
microfrontend
capability, as follows:Explanation:{ "name": "Customers App", [+] "capabilities": [ [+] { [+] "type": "microfrontend", [+] "qualifier": { [+] "entity": "customer" [+] }, [+] "params": [ [+] { [+] "name": "id", [+] "required": true [+] } [+] ], [+] "properties": { [+] "path": "/customer/customer.html#?id=:id", [+] "outlet": "aside" [+] } [+] } [+] ], "intentions": [ { "type": "microfrontend", "qualifier": { "entity": "products" } } ] }
params
:
This microfrontend requires the navigator to pass the ID of the customer to be displayed. For that reason, we declareid
as required parameter.private
:
We make this an application-privatemicrofrontend
capability that can only be navigated to from the Customers App. By default, capabilities are private to the providing application.outlet
:
Themicrofrontend
capability allow us to specify the preferred outlet into which to load the microfrontend. Note that this outlet preference is only a hint that will be ignored if the navigator specifies an outlet for navigation.
Navigate via intent instead of the URL
In the CustomerList Microfrontend, we can now navigate to the Customer Microfrontend without having to know its URL, as follows:
-
Open the file
customers-app/src/customer-list/customer-list.ts
-
In the
render
method, Replace the url-based navigation with intent-based navigation, as follows:Before:
customerLink.addEventListener('click', () => { Beans.get(OutletRouter).navigate(`/customer/customer.html#?id=${customer.id}`, {outlet: 'aside'}); });
After:
customerLink.addEventListener('click', () => { Beans.get(OutletRouter).navigate({entity: 'customer'}, {params: new Map().set('id', customer.id)}); });
Note that we do not specify the outlet because it is already specified as a preference in the microfrontend capability. We also do not need to declare an intention in our manifest, since we are opening a microfrontend provided by the navigating application. An application is implicitly qualified to interact with its own capabilities.
Open the app in the browser
We did it! Run npm run start
to serve the applications and see that the microfrontends are displayed as before the refactoring.
What we did in this chapter
In this chapter, we learned about the Intention API to navigate without having to know the URL.
The products-app/src/manifest.json
looks as following:
{
"name": "Products App",
"capabilities": [
{
"type": "microfrontend",
"qualifier": {
"entity": "products"
},
"params": [
{
"name": "ids",
"required": false
}
],
"private": false,
"properties": {
"path": "/product-list/product-list.html#?ids=:ids"
}
},
{
"type": "microfrontend",
"qualifier": {
"entity": "product"
},
"params": [
{
"name": "id",
"required": true
}
],
"properties": {
"path": "/product/product.html#?id=:id",
"outlet": "aside"
}
}
]
}
The products-app/src/product-list/product-list.ts
looks as following:
import {ProductService} from '../product.service';
import {QueryParams} from '../query-params';
import {MicrofrontendPlatformClient, OutletRouter} from '@scion/microfrontend-platform';
import {Beans} from '@scion/toolkit/bean-manager';
class ProductListController {
public async init(): Promise<void> {
await MicrofrontendPlatformClient.connect('products-app');
QueryParams.observe$.subscribe(queryParams => {
const productIds = queryParams.get('ids')?.split(',');
this.render(productIds);
});
}
public render(ids?: string[]): void {
const productsSection = document.querySelector('section#products');
productsSection.innerHTML = null;
ProductService.INSTANCE.getProducts({ids}).forEach(product => {
// Product Name
const productLink = productsSection.appendChild(document.createElement('a'));
productLink.innerText = product.name;
productLink.addEventListener('click', () => {
Beans.get(OutletRouter).navigate({entity: 'product'}, {params: new Map().set('id', product.id)});
});
// Product Price
productsSection.appendChild(document.createTextNode(`$ ${product.price.toFixed(2)}`));
});
}
}
new ProductListController().init();
The customers-app/src/manifest.json
looks as following:
{
"name": "Customers App",
"capabilities": [
{
"type": "microfrontend",
"qualifier": {
"entity": "customer"
},
"params": [
{
"name": "id",
"required": true
}
],
"properties": {
"path": "/customer/customer.html#?id=:id",
"outlet": "aside"
}
}
],
"intentions": [
{
"type": "microfrontend",
"qualifier": {
"entity": "products"
}
}
]
}
The customers-app/src/customer-list/customer-list.ts
looks as following:
import {CustomerService} from '../customer.service';
import {MicrofrontendPlatformClient, OutletRouter} from '@scion/microfrontend-platform';
import {Beans} from '@scion/toolkit/bean-manager';
class CustomerListController {
public async init(): Promise<void> {
await MicrofrontendPlatformClient.connect('customers-app');
this.render();
}
public render(): void {
const customersSection = document.querySelector('section#customers');
CustomerService.INSTANCE.getCustomers().forEach(customer => {
// Customer Link
const customerLink = customersSection.appendChild(document.createElement('a'));
customerLink.innerText = `${customer.firstname} ${customer.lastname}`;
customerLink.addEventListener('click', () => {
Beans.get(OutletRouter).navigate({entity: 'customer'}, {params: new Map().set('id', customer.id)});
});
// City
customersSection.appendChild(document.createTextNode(customer.city));
});
}
}
new CustomerListController().init();
What's next
In the next chapter, we will integrate the SCION DevTools to inspect integrated micro apps, browse capabilities and analyze dependencies between micro apps. Click here to continue.