An Angular/UI5 hybrid launchpad
Motivated by a customer project requirement and inspired by the SAP Fiori Launchpad we investigated the possibilities of integrating an existing (open)UI5 into an Angular web application and vice versa using a "launchpad" for the hosted application.
Special focus was set on the emerging requirements to integrated application and possible communication schemes for the interaction between the host and the integrated application. After presenting the two PoC applications with their approaches this post gives a short conclusion with an outlook of the topics for future work. Today's web application landscapes usually consist of several heterogenous software systems based on different frameworks and technologies. Although using well-defined and standardized interfaces eases the integration of different software solutions, one still has to cope with several obstacles such as the communication between these solutions or styling and look & feel issues. The usual approach to address these issues is to build an additional "portal" which then requires the integrated or "hosted" applications to follow a special structure or implement a special interface. Rebuilding the whole landscape in a single technology tends to be more time-consuming and error-prone and therefore not feasible to address these issues. Additionally, each framework has its advantages and disadvantages. Therefore, combining different technologies eases exploiting the different advantages in a single solution.
Setup
Both applications (Angular & UI5) were initially created using the corresponding cli tooling. No additional third party libraries are referenced by the projects. We used the angular cli to create an initial project which then is host or hosted in the UI5 web application. Most of the implementation is in typescript - no other libraries were included. The hosting "launchpad" is deployed/started on localhost via the corresponding cli command (i.e. "ng serve" resp. "ui5 serve"). The hosted applications are stand-alone web applications of the "opposite" framework, which are simply copied into an assets folder of the hosting application. Therefore, they could also be started without the hosting "launchpad" proving their independence of the host.
Angular webapp as host - UI5 nested
The Angular PoC consists of three components:
- the main app component
- the listener component for messages from the hosted UI5 webapp
- the "launchpad" component as the host for the UI5 webapp
The three components are connected by a service implementing an event bus. This service implementation is based on the Angular "Subject" class which basically represents a queue. In this case, these queues are used for different channels which can be used for publishing or listening for events. The main app & listener component are therefore mere consumers of this event bus and are completely disconnected from any hosted UI5 webapp. Yet, they have the possibility to communicate with the hosted application. UI5 webapps are hosted by the "launchpad" component. (open)UI5 is a component based framework which provides means to load other components at runtime. This capability is implemented by the so-called ComponentContainer widget included in each (open)UI5 runtime environment. So by integrating this widget in the Angular webapp we can leverage the whole component handling mechanisms of the UI5. This is done by the "launchpad" component which integrates and "imports" the UI5 runtime environment.
First, all necessary scripts have to be attached and loaded to the body section of the document.
const ui5Script = this.renderer.createElement('script'); this.renderer.setProperty(ui5Script, 'id', 'sap-ui-bootstrap'); this.renderer.setProperty(ui5Script, 'src', 'https://openui5.hana.ondemand.com/resources/sap-ui-core.js'); this.renderer.setAttribute(ui5Script, 'data-sap-ui-libs', 'sap.m'); this.renderer.setAttribute(ui5Script, 'data-sap-ui-async', 'true'); this.renderer.setAttribute(ui5Script, 'data-sap-ui-theme', 'sap_fiori_3'); ui5Script.onload = this.initUi5ComponentContainer.bind(this); this.renderer.appendChild(document.body, ui5Script);
By this integration and after the source of this script tag has been loaded, UI5 runtime objects such as widgets are available in the Angular application. Initialization of the aforementioned ComponentContainer can then be done by:
const oCore = sap.ui.getCore(); const eventBus = oCore.getEventBus(); oCore.attachInit(() => { const componentContainer = new sap.ui.core.ComponentContainer({ width: '100%', height: '100%' }); componentContainer.placeAt(this.ui5Content.nativeElement); eventBus.subscribe('UI5Launchpad', 'ui5ToAngular', (channel, eventId, data) => { this.eventBus.publish('comm', data); }); this.eventBus.subscribe('loadComponent', (data: any) => { loadComponent(componentContainer, data.name, data.url); }); });
This bootstrapping code initializes a ComponentContainer and establishes a connection with the event bus service. The loading of a UI5 webapp/component can then simply be accomplished by the method:
const loadComponent = (container: any, name: string, url: string) => { try { const component = sap.ui.getCore().createComponent(name, url); container.setComponent(component); } catch (error) { alert(error); } };
The Angular-UI5-launchpad PoC in action:
UI5 webapp as host - Angular nested
The UI5 PoC consists of two main components:
- the canonical root App component
- the "launchpad" component as the host for the Angular webapp
Hosting an Angular application in a UI5 webapp proved to be rather challenging compared to the inverse approach. The main reasons for this can be identified as:
- missing "hosting" widget such as the ComponentContainer in UI5
- missing "global" Angular-wide event or message bus for applications
- programming language barrier - UI5 in plain JavaScript vs. Angular in TypeScript
The missing "hosting" widget can be bypassed by dynamically introducing a special tag in the DOM analogous to the basic setup of an Angular webapp in its index.html file. Therefore, the UI5 view of the launchpad contains a special HTML element which then will serve as the root for the hosted Angular webapp:
<core:HTML id="angularDiv" />
which then can be extended with the necessary scripts:
... sources: [{ name: "runtime-es2015.js", defer: false, module: true }, { name: "runtime-es5.js", defer: false, module: false },{ name: "polyfills-es5.js", defer: false, module: false }, { name: "polyfills-es2015.js", defer: false, module: true }, { name: "main-es2015.js", defer: false, module: true }, { name: "main-es5.js", defer: false, module: false }], ... attachAngularBootstrapScripts: function () { let that = this; ... let scriptLoadingPromises = this.sources.map(function (source) { return new Promise(function (resolve) { let scriptElement = document.createElement("script"); scriptElement.src = that.dir + "/" + source.name; if (source.defer) { scriptElement.setAttribute("defer", ""); } if (source.module) { scriptElement.type = "module"; scriptElement.onload = resolve; } else { scriptElement.setAttribute("nomodule", ""); resolve(); } document.head.appendChild(scriptElement); }); }); return Promise.all(scriptLoadingPromises); }, ...
In opposite to consumable UI5 components which follow a fix structure and hence are identified by a URL and namespace, the hosted Angular webapp has to be fully bundled and the hosting UI5 webapp needs to have knowledge about all necessary source files. Additionally, as there is currently no official typescript support for (open)UI5, widgets, services and other elements of the Angular runtime environment cannot be simply referenced and parameterized by the surrounding UI5 webapp. Therefore, the only way of hosting an Angular webapp identified during this examination is to integrate the fully bundled webapp as described.
Due to the fact, that Angular does not provide any standard application-wide communication channels such as the event bus in UI5, the hosted Angular webapp unfortunately has to be aware of the surrounding host and the handover of any communication channel is currently solved by passing it over a global window variable. Surely, this can be hidden behind some kind of service implementation, but it still lacks the smooth and elegant handover which was described in the previous chapter.
The UI5-Angular-launchpad PoC in action:
Future work
Besides the mere proof of concept there are several topics which can be part of further investigations - especially the side effects of two interacting frameworks. Special focus can can be set on the IDs of elements in the DOM of the web page. As most frameworks work with DOM manipulations while identifying elements by their ID, there is a possibility for clashing IDs or side effects during DOM manipulations.
Another topic of interest is the overlay of different css style sheets of the two frameworks. Both, the potential ID and the style sheet issues could be addressed by separating namespaces.
Conclusion
Both PoCs prove, that Angular and UI5 webapp can be combined with minor or no adaptions. This can reduce the integration effort in heterogeneous environments tremendously.
The bidirectional communication possibilities open up new interaction schemes between existing web applications easing the reuse of functionalities without additional components or libraries. Besides, it helps to leverage and combine the advantages of different frameworks in a single solution.
References
The content of this article was presented in the ui5con 2020 conference. The leading conference for ui5. (https://openui5.org/ui5con/onair2020/#agenda). The sourcecode for the PoCs is available at Github:
Image sources
The cover image used in this post was created by wikimedia under the following license. All other images on this page were created by eXXcellent solutions under the terms of the Creative Commons Attribution 4.0 International License