Skip to Content
Technical Articles
Author's profile photo Jiawei Cao

UI5er Buzz #46 – Routing with Nested Components

In this blog post, I would like to give you an overview of some new routing features and demonstrate how you can leverage them to enhance the user experience of your applications.

The UI5 Routing provides applications with a way to interact with the browser history to create a new entry or to navigate forward/backward with the ability to persist primitive data into the browser hash. This is useful for returning to the same view and context after a browser refresh. Each sap.ui.core.UIComponent can define its routing configuration in its manifest, and a UI5 router instance is created out of the box after the component is initialized.

Since UI5 version 1.60, a UI5 router is capable of loading a component directly, which in turn may have a routing configuration of its own. This can lead to situations in which more than one router is active at the same time. These routers react and make changes to the browser hash simultaneously, which may lead to unwanted effects.

With UI5 version 1.63, UI5 therefore introduced built-in hierarchy management between the routers in parent and child components, in which the parent router synchronizes both the loading of the child component and the match of the patterns in the child component. Once a child component was initialized, its router received an empty string as an initial hash, and it was not possible to navigate to a specific state of the child component directly.

With UI5 version 1.72, UI5 enhanced this behavior by extending the “navTo” method in sap.ui.core.routing.Router. You can now pass an additional parameter to this method in order to provide extra routing information to the nested components. The basic feature set is now complete, and UI5 created a sample application to demonstrate the feature “Routing with nested components”.

As a sample data set three entities are selected from the Northwind Sample OData Service: Product, Supplier, and Category. The relationship among them is shown below:

Each product has the properties “SupplierID” and “CategoryID”, pointing to the supplier and category, respectively. A supplier or category has a navigation property called “Products”, which contains the elements of the Product entity that belong to this supplier or category.

Create a Component for Each Entity

First, three distinct components are created which represent the three entities separately. All three components have a similar routing configuration. They all have two routes defined, namely a route for showing a list of elements and another route for showing the detailed information for a certain element.

Integrate the Component

As I mentioned in the preceding section, each supplier or category contains a list of products that belong to it. I would like to show a list of products in the detail view of a supplier or category. Because there’s already a component for the Product entity which is capable of showing a list of products, I want to reuse this component in the detail view of a supplier or category.

Let’s extend the Suppliers component as an example. The Categories component can be done in a similar way.

But first, some work needs to be done on the Products component as preparation.

Extend the List Page of the Products Component

Currently, the list page of the Products component shows a list of all products which are available in the OData service. In order to show only a list of products that belong to a certain entity (either Supplier or Category), the route “list” needs to be extended to have a “basepath” which indicates the origin of the product list. If the “basepath” isn’t given, it provides a list of all available products in the service.

{
  ...,
  routes: [
    {
      "name": "list",
      "pattern": ":basepath:",
      "target": "list"
    },
    ...
  ],
  targets: {
    ...
  }
}

After adapting the Products Component, the Suppliers component can also be adapted now.

Integrate the Products Component into the Suppliers Component

In the manifest.json file of the Suppliers component, there’s a route “detail” which displays the target with name “detail” to show the detail information of a supplier. In the detail view, the list page of a Products component can be used to show all products which belong to a particular supplier. To load an additional (nested) component through another component’s routing you can define it in a target and connect this target to a route. This target is now responsible for loading the nested component.

In the sample, the nested Products component isn’t directly added to the root view of the Suppliers component but to the existing detail view of a supplier. The existing “detail” target is configured as the parent of a new component target. The “detail” target will be loaded and placed before the new component target. The new component target can use a container within the “detail” target of the outer component to place itself.

One important thing to note is that the new component target is used directly in the “detail” route without having the “detail” target configured. This is because the parent-child relationship between the “detail” target and the new component target guarantees that the “detail” target is loaded and placed before the new component target.

Another important point is the usage of a prefix. When using a component target in a route, a prefix must be given together with the name of the target. This prefix is used by the router of the child component to identify its own hash part in the browser’s hash because the browser hash is shared among multiple routers. With this prefix, a clash between nested patterns of the same structure can be avoided.

Note:
Please refer to Enabling Routing in Nested Components in the UI5 development guide regarding the syntax of defining a target with type “Component” and setting this target to a Route.

The manifest.json file of the Suppliers component now looks like the following:

{
  "sap.ui5": {
    ...,
    "componentUsages": {
      "productsComponent": {
        "name": "sap.ui.core.sample.RoutingNestedComponent.reuse.products",
        "settings": {},
        "componentData": {},
        "lazy": true
      }
    },
    ...,
    "routing"; {
      config: {
        ...,
        "viewType": "XML",
        "path": "sap.ui.core.sample.RoutingNestedComponent.reuse.suppliers.view",
        "controlId": "app",
        "controlAggregation": "pages",
        "async": "true",
        ...
      },
      routes: [
        {
          "name": "detail",
          "pattern": "detail/{id}",
          "target": {
            "name": "products",
            "prefix": "p"
          }
        },
        ...
      ],
      targets: {
        "products": {
          "type": "Component",
          "usage": "productsComponent",
          "parent": "detail",
          "controlId": "box",
          "controlAggregation": "items",
          "id": "productInSupplier"
        },
        "detail": {
          "type": "View",
          "id": "list",
          "name": "List"
        }
      }
    }
  }
}

The same step can also be done to the Categories component in order to show a list of products that belong to a certain category within the detail view in the Categories component.

The following diagram illustrates the current state of the 3 components:

Create a Root Component to Integrate All 3 Entity Components

Now it’s time to create another component that integrates all 3 entity components. It has a container control that enables the navigation into each of the 3 entity components. It defines a route and displays a target with type “Component” for each of the 3 entity components. Its routing configuration looks like this:

{
  "sap.ui5": {
    ...,
    "componentUsages": {
      "suppliersComponent": {
        "name": "sap.ui.core.sample.RoutingNestedComponent.reuse.suppliers",
        "settings": {},
        "componentData": {},
        "lazy": true
      },
      "categoriesComponent": {
        "name": "sap.ui.core.sample.RoutingNestedComponent.reuse.categories",
        "settings": {},
        "componentData": {},
        "lazy": true
      },
      "productsComponent": {
        "name": "sap.ui.core.sample.RoutingNestedComponent.reuse.products",
        "settings": {},
        "componentData": {},
        "lazy": true
      }
    },
    ...,
    "routing": {
      "config": {
        "routerClass": "sap.m.routing.Router",
        "controlId: "app",
        "controlAggregation": "pages",
        "async": true,
        ...
      },
      "routes": [
        ...,
        {
          "name": "suppliers",
          "pattern": "suppliers",
          "target": {
            "name": "suppliers",
            "prefix": "s"
          }
        },
        {
          "name": "categories",
          "pattern": "categories",
          "target": {
            "name": "categories",
            "prefix": "c"
          }
        },
        {
          "name": "products",
          "pattern": "products",
          "target": {
            "name": "products",
            "prefix": "p"
          }
        }
      ],
      "targets": {
        ...,
        "suppliers": {
          "type": "Component",
          "usage": "suppliersComponent"
        },
        "categories": {
          "type": "Component",
          "usage": "categoriesComponent"
        },
        "products": {
          "type": "Component",
          "usage": "productsComponent"
        },
        ...
      }
    }
  }
}

The following diagram shows how the entity components are integrated into the root component:

Navigation between Components

Navigation is not only needed within a component but also between different components, for example:

  • From Product detail view to Supplier detail view or Category detail view
  • There are two links on the product detail page, one pointing to its supplier and one to the category. Clicking on each link should allow the user to navigate to the detail page of the product’s supplier and category, respectively.
  • From Supplier detail view or Category detail view to the Product detail view
  • In the detail view of Supplier or Category, a further Products component is integrated in order to show a list of products that belong to a certain supplier or category. When a product is clicked on in the list, a navigation should be triggered to the detail page of the Products component, which is directly integrated under the root component.

The following diagram shows the navigation paths between the components mentioned above:

Inter-Components Communication

A child component shouldn’t be aware of the existence of a parent component and should behave in the same way no matter whether it’s integrated in a parent component or runs standalone. When inter-component navigation is needed, an event is fired by the child component with the necessary parameters. If a parent component exists, it registers a handler to the event after the child component is created within the parent component. In the event handler, the navigation to another component is triggered.

Event mappings

In our sample application, each component declares with its ‘eventMappings’ property what it does once a certain event is fired from one of its child components. The implementation of the BaseComponent class uses these event mappings to register listeners for the events that are fired by the child component. For example, the ‘eventMappings’ of the root component defines that an event with the name “toProduct” will be fired from the Suppliers component. Once this event is fired, it should navigate to the route “products”, which shows the Products component. The Products component should navigate to its “detail” route directly, with the given parameter “productID” assigned to an object under the key “id”.

{
  eventMappings: {
    suppliersComponent: [{
      name: "toProduct",
      route: "products",
      componentTargetInfo: {
        products: {
          route: "detail",
          parameters: {
            id: "productID"
          }
        }
      }
    }],
    ...
  }
}

Router.prototype.navTo

In the BaseComponent.js, the Router.prototype.navTo method is called with the route information for the current component, and if the route displays component target(s), further route information can be provided for the component(s) that are displayed by this route. Please see the API Documentation of the navTo method for the syntax information.

Because a parent component can give route information to its child components through the navTo method, a navigation that occurs within a child component can also be triggered by using the navTo method of the parent component’s router. This makes the navigation from the product list view to the product detail view independent from the integration level. No matter if the Products component is integrated directly under the root component or if it’s integrated in the Suppliers or Categories component, clicking on an item in the product list fires a “toProduct” event. This event is either handled by the root component directly or forwarded by the Suppliers or Categories component and then handled by the root component. The root component now calls its navTo method and navigates to the “products” route by giving the route name “detail” with the necessary parameters for the Products component which is displayed by the route.

Let’s summarize what has been achieved:

  • Create for each entity (Supplier, Category and Product) a component that shows a list of entities and a detail view of a selected entity.
  • Create a root component that can load and place all 3 above components through its routing.
  • Enable communications between parent and child components by using an eventing mechanism.
  • Enable navigations between different components based on the above-mentioned communication.

 

Author

Jiawei is a UI5 Core developer, photography enthusiast and riding bike to work everyday. He is currently focusing on improving the UI5 code base to make it be easier consumed by applications.

Assigned Tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jiawei Cao
      Jiawei Cao
      Blog Post Author

      Thanks to Graham Robinson for the contribution to the sample app!

       

      Author's profile photo Mike Doyle
      Mike Doyle

      Nice blog, Jiawei, thanks, and thanks Graham Robinson too

      This should be useful for a new requirement I have, however I won't be able to use version 1.72 yet, as it isn't a long term maintenance release (the latest of those is 1.71).  Could you please clarify how things would work on 1.71?  I assume the child component knows which of its routes matches.  Are you saying it won't have access to any routing parameters?  Not for the parent route either?  I assume in that case we could share the parameters either via an event or by a shared JSON model?

       

      Author's profile photo Jiawei Cao
      Jiawei Cao
      Blog Post Author

      One thing which is missing from version 1.71 is that all nested components that are loaded by routing can only have an empty string as its initial hash because the method sap.ui.core.routing.Router.prototype.navTo hasn't been extended with the parameter "oComponentTargetInfo" to provide further route name and parameters information for the "Component" targets which are loaded by this navTo step.

      Talking about this in the sample app scope, the "category" and "supplier" links on one product detail page won't work because clicking on them jumps to the detail page of the Categories or Suppliers component and the detail page has a non-empty string route pattern defined.

      Other than this, it works as expected. The router in parent and nested components have access to their route parameters.

      Author's profile photo Graham Robinson
      Graham Robinson

      Hi Mike,

      I describe a solution to this in an earlier blog - https://blogs.sap.com/2019/10/09/ui5-component-based-routing/.

      You can see sample code in Version 1 of my original project. https://github.com/Yelcho/UI5-Comp-Routing/releases/tag/v1.0

      Cheers

      Graham Robbo

       

      Author's profile photo Rahul Inamadar
      Rahul Inamadar

      Nice Blog. Jiawei.

      I had worked on this concept following the Documentation available in the demo kit. I am confused when it comes to building the application. The component preload file has to be created for each and every component, right? Does WebIDE is doing that or we need to write our scripts?

      Thanks

      Author's profile photo Graham Robinson
      Graham Robinson

      Good point Rahul,

      I rarely use WebIDE myself.

      I have setup my gulp build tasks so that I create a Component-preload.js file for each component I have in my project. I run these tasks in parallel.

      Remember also that some components could be included in a library as well.

      Cheers

      Graham Robbo

      Author's profile photo Andreas Ecker
      Andreas Ecker

      As another enhancement when leveraging Nested Components in UI5, please check-out the blog post https://blogs.sap.com/2020/03/23/ui5ers-buzz-48-consuming-title-changes-of-nested-components/.

      It shows how you could consume title changes in your application acc. to the propagation setup for your nested components.

      Enjoy,

      Andreas

       

      Author's profile photo Wolfram Knan
      Wolfram Knan

      Maybe you could provide more detailed coding and explanation like a tutorial?

      Author's profile photo Florian Vogt
      Florian Vogt

      Hi Wolfram,

      this site describes how to switch from a view based target to a component based target
      https://ui5.sap.com/#/topic/fb19f501b16e4e4991eb6a017770945b

      There are two samples available:

      All components in one project: https://ui5.sap.com/#/entity/sap.ui.core.routing.Router/sample/sap.ui.core.sample.RoutingNestedComponent

      Every component is a standalone application: https://flovogt.github.io/ui5con20-ui5-routing/

      Hope these links fit your needs?

       

      Author's profile photo Helmut Tammen
      Helmut Tammen

      Hi Jiawei, hi Florian,

      thank you for this great enhancement to UI5 and the presentation at UI5con 2020 which I remembered when I came to the below described use case.

      We would like to use your approach to integrate components into our application at runtime at customer site.

      So we don't know the component the user of our product wants to embed at design time. This makes it impossible to create a component usage entry in manifest.json.

      Is there a way to add the component usage at runtime?
      In the documentation we found the Component.createComponent method which uses a component usage entry from the manifest to create a child component. Would be nice if there was a way to dynamically create the component usage or call the Component.createComponent method with the necessary parameters at runtime or even better with an intent from FLP.

      We don't need interaction between the parent and child component. The child component thats available in a separate UI5 application just should be instantiated at runtime by us so that we can embed it into our application in a ComponentContainer.

      Currently we use the CrossApplicationNavigation service from FLP. It has an undocumented method createComponentInstance that allows us to create a Component for an FLP intent.
      The drawback of this approach is that the embedded component must not use a router cause this would overwrite the url (hash) in the address bar which would lead to errors because the router of the parent component cannot handle them.

      Side question

      As I understood the blog and the code not only the routers of the parent and child components are separated but also the data models. If there are models with the same name in both components the respective component always works with it's own model.
      Can you please confirm this?

      Best regards
      Helmut

      Author's profile photo Jiawei Cao
      Jiawei Cao
      Blog Post Author

      Hi Helmut,

      when the configuration of the embedded component is available only at the runtime, you can use the add a target to your routing's targets at runtime before you display this target manually. We have an article in development guide that explains how this can be done:

      https://sapui5.hana.ondemand.com/#/topic/856d6c6a408846b480ca177b9a1aab62

       

      To your side question:

      If you like to completely separate the binding scope between the parent and child components, there's a property on the ComponentContainer that is called "propagateModel" https://sapui5.hana.ondemand.com/#/api/sap.ui.core.ComponentContainer%23constructor. This property is set to false by default, which means that the model on the parent isn't available in the child component. Even when this property is set to true, a binding is resolved with the model that is at the nearest position to it. This means that a binding in the child component is resolved in its own model even when there's a model in the parent with the same name.

      Best regards,

      Jiawei

       

      Author's profile photo Helmut Tammen
      Helmut Tammen

      Perfect, will have a look at it later.

      Thanks so far Jiawei

      Author's profile photo George Younan
      George Younan

      Hello Jiawei

      Thanks for sharing. I was wondering how to reuse UI5 components deployed in BTP Cloud Foundry HTML5-App Repository. Can you please provide some insight into this use case?

      Best regards,

      George