Generating a web component is quite similar to generating a regular journey.
As with a regular journey, there are two scenarios:
webcomponent-config generatorTo generate your journey as a web component inside your Nx workspace we strongly recommend you to start by running the webcomponent-config generator provided by TALY. This generator will update your project configuration, making it ready to develop, generate, serve, build and bundle your web component.
You can run the webcomponent-config generator in any stage of your development process. This will ensure that your Nx project configuration contains the required settings to produce a web component ready to be integrated.
When running the generator like this:
npx nx generate webcomponent-config --project="my-project"it will update the default targets and configurations in your project.json file. That command is all you need if you just did minimal changes to your project.json after you generated your project with the journey generator. It is exactly the same as if you would have run:
npx nx generate webcomponent-config \
--project="my-project" \
--developTarget="develop:webcomponent" \
--generateTarget="generate-only:webcomponent" \
--serveTarget="serve-generated-app:webcomponent" \
--buildTarget="build-generated-app:webcomponent" \
--bundleTarget="webcomponent-bundle"Note that the bundle target is already exclusive for the web component case, and therefore no configuration needs to be provided.
We know that you own the project.json file and you can specify your custom targets and configurations any time. Therefore, when running the webcomponent-config generator you can optionally specify for each target separately the target name and its configuration. TALY will then know which parts of your Nx project configuration need to be updated.
For example, if your "develop" and "build" target names and/or their web component configs deviate from the TALY defaults, you would run the command as follows:
npx nx generate webcomponent-config \
--project="my-project" \
--developTarget="my-develop-target:my-wc-config" \
--buildTarget="build-generated-app:a-different-webcomponent-config"# Example for a "develop" target with a "webcomponent" configuration
npx nx develop <the-name-of-your-project> --configuration=webcomponentThe generated app will run in your browser in standalone mode, i.e., the source code is ready to be integrated as a web component, but with the develop command it runs as a regular app, which shall be good enough for testing purposes. In order to integrate it as a web component in a parent application, please follow the next steps.
# Example for a "generate-only" target with a "webcomponent" configuration
npx nx generate-only <the-name-of-your-project> --configuration=webcomponentThe generated app will include the necessary changes to turn it into a web component.
By default, TALY will infer the deploy URL at runtime, allowing the web component's assets like images to load properly. For this to work, images need to use the talyNormalizeUrl pipe.
Note that this feature of automatically detecting the deploy url does not work for the url() function in CSS. If a used Building Block relies on url() in its styles, you need to explicitly pass the --deploy-url of the web component (make sure it ends with a slash) as an option to the configuration (in the example above it would be the webcomponent configuration).
To now turn this generated app into a web component you need to first build it with the proper configuration:
# Example for a "build-generated-app" target with a "webcomponent" configuration
npx nx build-generated-app <the-name-of-your-project> --configuration=webcomponentThanks to the changes applied by the webcomponent-config generator, you already had a valid web component configuration for the relevant build target. Here are some details about its content:
deploy-url needs to match the value passed through the generator parameter --deploy-url, in case this option is passed to the generator at all. This configuration is required by the Angular build to properly hardcode the paths to the lazily loaded bundles.vendorChunk needs to be turned off. This is automatically disabled and recommended being turned off for production build by Angular.deploy-url is given then inlineCritical optimization needs to be turned off to make sure that the application contains all the necessary styles. Angular 12 activated the inlineCritical optimization by default. The effect of this optimization is, that critical CSS gets directly inlined into the index.html file. As the content of this file is not used, the CSS from there would be lost.localize to true or to an array of a subset of the previously defined locale identifiers to build a separate distributable copy of the application for each locale.assets:AllianzNeo and Allianz-Icons fonts are added to the build assets. This is necessary for the web component to automatically load the fonts and icons it needs in case the parent application does not provide them, or when the web component is consumed as a standalone app.normalize.css of ng-aquila is added to the assets.After building your app with the web component configuration, you can now bundle the built application into a single web component loader file:
# Example for a "webcomponent-bundle" target
npx nx webcomponent-bundle <the-name-of-your-project>For each locale produced by the build-generated-app command, this creates two loader files: webcomponent-bundle.js and webcomponent-bundle.esm.js. Use the appropriate file based on your integration:
webcomponent-bundle.js for classic <script> tags.webcomponent-bundle.esm.js for <script type="module"> tags.To serve the web component(s), host the locale-specific directory in your dist folder (e.g. /dist/my-journey/en-US) with the server of your choice.
Note that the above command does not remove the generated index.html file nor its dependent files. This lets you either:
index.html file, orwebcomponent-bundle*.js files, depending on your loader.To generate a journey that exists outside of an Nx workspace as a web component, you can use functions and CLI commands provided by the package @allianz/taly-sdk. Install it like so:
yarn add @allianz/taly-sdk
# or
npm install @allianz/taly-sdkAfterwards you can use the SDK and the CLI like described in Generate a Journey Outside of an Nx workspace Using the TALY SDK.
To generate a journey as a web component with the TALY CLI, pass the --target=webcomponent flag, like so:
npx taly generate-journey --name="My customer journey" --target=webcomponentTo generate a web component with the generateJourney SDK function, you can pass the option target: 'webcomponent', like so:
import { generateJourney } from '@allianz/taly-sdk/node';
for await (const progress of generateJourney({
name: 'My customer journey',
target: 'webcomponent'
})) {
// do something with the progress
}The app will be generated with the dependencies and the changes required for the web component.
By default, TALY will infer the deploy URL at runtime, allowing the web component's assets like images to load properly. You can override this behavior by passing the future deployUrl/--deploy-url of the web component (make sure it ends with a slash). In both cases, for local development or tests this will most likely be some localhost URL.
webcomponent ConfigurationThe generated workspace will include an Nx project with your new journey. If you want to run your journey in the browser, you can serve it locally with this command (note the --configuration webcomponent):
cd <path/to/your/generated/workspace>
npx nx serve-generated-app <name-of-your-journey> --configuration webcomponentwebcomponent ConfigurationAs already mentioned, the generated workspace will include an Nx project with your new journey. In addition, the journey source code is already generated and ready to be built.
You can build your journey by running this command (note the --configuration webcomponent):
cd <path/to/your/generated/workspace>
npx nx build-generated-app <name-of-your-journey> --configuration webcomponentYou now need to bundle all of the produced files in the dist folder into a single file to serve it as a web component.
To bundle all relevant files in the dist folder of your journey in the generated workspace, you can run this command:
cd <path/to/your/generated/workspace>
npx nx webcomponent-bundle <name-of-your-journey>For each locale built by the build-generated-app command, this creates two loader files: webcomponent-bundle.js and webcomponent-bundle.esm.js. Use the appropriate file based on your integration:
webcomponent-bundle.js for classic <script> tags.webcomponent-bundle.esm.js for <script type="module"> tags.To serve the web component(s), host the locale-specific directory in your dist folder (e.g. ./generated/apps/my-journey/dist/en-US) with a server of your choice.
Note that the above command does not remove the generated index.html file nor its dependent files. This lets you either:
index.html file, orwebcomponent-bundle*.js files, depending on your loader.The idea of web components is to have elements that you can easily use anywhere on any page and they are expected to "just work". While this is mostly true, web components and the underlying technologies are still a fairly young trend and browsers still lack some functionality to make web components as pleasant as they sound.
💡 If your application is hosted in oneMarketing you don't need to worry about these requirements. oneMarketing provides the necessary environment to run TALY-generated web components.
If you want to integrate a TALY web component into a parent application, the host should import @allianz/ngx-brand-kit/css/allianz-base.css so that the TALY web component and other web components can access the font definitions. If the parent application does not provide the required fonts, TALY will load them automatically. For this purpose, make sure that the build options contain the required assets.
💡 A TALY web component only loads fonts that the host doesn't provide and will never load a font that is already provided by the host.
TALY does not provide any utility to integrate the generated web component into the parent app. You can use already existing third party libraries like Angular Extensions Elements or your own script to load the web component in the host.
🧩 A full example setup can be found in the webcomponent-integration recipe
Integrating the generated web component in a Non-Angular web application is very straightforward.
webcomponent-bundle.js or webcomponent-bundle.esm.js file in your scripts depending on your loader.webcomponent-bundle.js for classic <script> tags.webcomponent-bundle.esm.js for <script type="module"> tags.selector in the HTML💡 The actual selector is the name of the project. If you generated a project called
my-custom-journey, then use<my-custom-journey></my-custom-journey>. Note, that if the name of the project is not a valid name for a web component, TALY tries to make it valid. To verify the selector, check your generated app'sAppModulefor the value of theWEB_COMPONENT_IDinjection token:{ provide: WEB_COMPONENT_ID, useValue: 'my-custom-journey' }
<!-- Classic script -->
<script src="https://your-server.com/webcomponent-bundle.js"></script>
<!-- OR: ESM module -->
<script type="module" src="https://your-server.com/webcomponent-bundle.esm.js"></script>
<!-- Your component -->
<my-custom-journey></my-custom-journey>It is possible to enable the Shadow DOM encapsulation for your web component to ensure that the styling used inside the component does not affect the outside world. We recommend activating this feature. For instructions on how to enable it, please refer to the Experimental Features section.
However, when using Shadow DOM, be aware that XPath queries in E2E tests won't work directly. XPath queries cannot traverse through Shadow DOM boundaries because Shadow DOM creates a separate DOM tree that's encapsulated from the main document. You can resolve the issue using one of the solutions below:
<div data-testid="unique-id">Content</div>document.querySelector('[data-testid="unique-id"]');A "Location Strategy" in Angular determines the way that the routing in an application affects the URL.
We provide a location strategy for Angular called WebComponentLocationStrategy that uses a special syntax to allow web component routing that is reflected in the URL without interfering with the hosting app's routing - assuming that it is not using hash based routing. This location strategy is the default for TALY-generated web components.
Given a WEB_COMPONENT_ID of "my-web-component" and a the route /internalPath in that web component, the WebComponentLocationStrategy will produce paths like this:
.../some/host/path/#(my-web-component:/internalPath)Given there are multiple web components on the same page that use this location strategy they will neatly live alongside each other and produce paths like so:
.../path/#(my-web-component:/internalPath,other-web-component:/other/path)⚠️ This location strategy changes the hash of the URL.
This location strategy works fine if there are multiple web components on the same page that use this location strategy.
Scenarios exist where it might not be a good idea to take control over or change the URL's hash. This is a (probably incomplete) list of scenarios where you would want to avoid using this location strategy:
Fragment scrolling
The WebComponentLocationStrategy is not meant to be used on pages that use fragments as part of their URLs. If a page some/path#overview contains a web component that uses this location strategy it will lead to paths like some/path#overview(some-web-component:/some-path). This new URL will break the browser-built-in fragment-scrolling.
Hash based routing in host application
If the host application of your web component is using a hash based location strategy (like Angular's built-in HashLocationStrategy) then the WebComponentLocationStrategy cannot be used since it would interfere with the host application's routing. In this case it is advised to use the NoopLocationStrategy (see below) for your web component that doesn't change the URL at all. Another solution would be to ask the host application to change to a path based location strategy.
Someone else owns the hash
If the web component that uses the WebComponentLocationStrategy lives on the same page with another element that wants to take control over the hash (e.g. another web component that uses a different strategy or some other element/script) then they might get in each other's way. In this case it's better to use the NoopLocationStrategy in your web component (see below).
Apart from the WebComponentLocationStrategy, we also provide another location strategy called NoopLocationStrategy that does not change the URL at all. This is useful if you find that the WebComponentLocationStrategy leads to conflicts with the host application of your web component.
If you want to use the NoopLocationStrategy you can pass the flag --use-noop-location-strategy to your generator call. Alternatively, you can also set the useNoopLocationStrategy flag in the executor options of your project.json:
{
"...": "...",
"options": {
"useNoopLocationStrategy": true
}
}Building Blocks that include assets (e.g. images) need to use the talyNormalizeUrl pipe (documentation) in order to work properly inside a web component. Otherwise, the browser will try to load the assets from the hosting application instead of the web component server.
If you notice missing assets in your web component, make sure that the used Building Blocks are using the talyNormalizeUrl pipe to normalize their asset URLs.
💡 This feature is only supported for Retail journeys.
TALY provides the ability to link back to another page from two different places throughout the journey. It is possible to display a back link in the upper part of the TALY Frame stage and/or to navigate to the same URL when clicking on the "Next" button on the last page of the journey. This feature is mostly intended to navigate back to an external link from which the journey was started, but it could be used for any other purpose.
To enable this feature, the host application needs to provide the value(s) to use for the back links. See the below subsection for an AEM integration example. If you are using a TALY generated web component without AEM and need to configure back links, please reach out to us.
For the back link in the TALY Frame stage, the link can be disabled on the page level using the hideBackLink flag inside the page configuration for the journey:
// Page configuration
{
"id": "my-page-c",
"blocks": [{ "...": "..." }],
"title": {
"headline": "Page C is good",
"showAsStage": true,
"subline": "Really good",
"hideBackLink": true
}
}Additionally, and likewise only applicably to the stage back link, a PFE Service Activator can be executed when clicking on the back link, before redirecting. TALY will prevent the redirect if the Service Activator call fails. Here is an example of how to configure this in your journey configuration:
{
"...": "...",
"frame": {
"stage": {
"backLink": {
"onClickServiceActivator": "users"
}
},
"...": "..."
},
"...": "..."
}For this feature to work with AEM pages, the back link configuration needs to be added to the customconfiguration object via the AEM user interface. More precisely, a key called backLinkConfig needs to be set to a value that implements the BackLinkConfiguration interface defined in TALY. This is an example of this value (note the use of double quotes preceded by a backslash, this is required for the data to be extracted from customconfiguration at runtime).
{\"backLinks\": [ {\"path\": \"dashboard\", \"stageLinklabel\": \"Go Back to Dashboard\", \"nextButtonLabel\": \"Go to Dashboard\"}, {\"path\": \"localhost:4000\", \"stageLinkLabel\": \"Back to example app\"} ], \"default\": {\"path\": \"https://allianz.com\", \"stageLinkLabel\": \"Default\" }}TALY reads the data from the root element and processes it. The functionality is based on the value of document.referrer. In a nutshell, it does the following:
backLinks via customconfiguration, that item is used for the back link.backLinks, if provided it takes the default entry from customconfiguration.stageLinkLabel field, the back link will not be displayed in the TALY Frame stage. The same applies to the "Next" button on the last page of the journey. If the field nextButtonLabel is not provided, the back link feature is disabled for that button, and the button will be hidden unless it is explicitly configured to be shown in the PFE configuration.The host application needs to pass the configuration data to the web component in a way that allows the corresponding adapter to read it. An example is the AEM integration, explained in the previous section.
TALY-based customer journey web components provide the possibility to get and restore their state from outside.
The current PFE state can be retrieved via the getState() method provided in the web component element.
For example:
const webComponent = // get the element reference from the DOM, for example by the tag name
const currentState = webComponent.getState();The host application can provide an initial state for the customer journey. This state is then restored when the customer journey starts up and the journey continues where it left off.
The journey can be initialized with a state like this:
<my-taly-app initial-state='{"state": "value"}'> </my-taly-app>For Angular hosts, the initial state can alternatively be passed to the journey via dynamic binding:
// Component class
// Imports and component decorator...
export class AppComponent {
webComponentInitialState = '{ "state": "value" }';
}<!-- Component template -->
<my-taly-app [initialState]="webComponentInitialState"> </my-taly-app>In this case, the bound property can be a JSON string or a promise that eventually resolves to a JSON string.
💡 Keep in mind that this feature is incompatible with other PFE state initialization strategies, like web component inputs added to the journey configuration or PFE state updates performed by TALY plugin modules during their initialization. This is the intended behavior, since the initial state feature is meant to fully restore the PFE state from a previous session, and no other PFE state initializations should happen in parallel to that.
Instead of providing the entire state as shown above, TALY-generated web components can be configured to receive individual values from their host application. This has to be configured ahead of generating the web component in the pages.json file, like so:
// pages.jsonc
{
// other configuration
"webcomponent": {
"inputs": [
{
// name of the input that will be available on the web component
"name": "customer-name",
// the path where the received value will be stored in the web component's PFE store
"storeExpression": "$.fromHost.customerName"
}
]
}
}The hosting application can then provide values for these inputs via regular HTML attributes, like so:
<my-web-component customer-name="John Doe"></my-web-component>The default Angular Elements lifecycle leads to only one app instance being created, which is then shared between multiple component instances. A consequence of this is that if a component is removed and re-added to the DOM, the dependency injection tree stays the same. This leads, for example, to service instances being the same old ones.
TALY ships with a modified Web Component lifecycle which ensures that every time a TALY journey is removed from the DOM and re-added, a new instance is being created.
The TALY Web Component lifecycle also enables the possibility to add multiple instances of the same TALY journey to the same page.
There is one caveat with multiple instances that cannot be solved automatically: Everything that is shared on a JavaScript/TypeScript class/module level is also shared between all app instances.
A few examples:
export const thisWillBeSharedBetweenAllAppInstances = {};class AClass {
static thisWillAlsoBeShared = {};
}{
providers: [
{
provide: SOMETHING,
// This object instance will be shared between different instances of the same app:
useValue: {}
// Alternative: Use a factory:
// useFactory: () => {
// return {};
// },
}
];
}
export class AppModule {}In most cases this is not a problem as it will be const values or stateless elements/functions. But in some cases this might lead to unexpected behavior.