Skip to Content
Technical Articles

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.

9 Comments
You must be Logged on to comment or reply to a post.