Show TOC

Step 10: Implement “Lazy Loading”Locate this document in the navigation structure

In the previous steps, we have implemented a Resume view that uses tabs to display data. The complete content of the tabs is loaded once, no matter which tab is currently displayed. We can increase the performance of our app by avoiding to load content that is not visible. Therefore, we implement a “lazy loading” feature that only loads the view and data when requested by the user.

Preview
Figure 1: Tabs with lazy loading
Coding

You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 10.

Figure 2: Folder Structure for this Step
webapp/view/employee/Resume.view.xml
<mvc:View
	controllerName="sap.ui.demo.nav.controller.employee.Resume"
	xmlns="sap.m"
	xmlns:mvc="sap.ui.core.mvc"
	xmlns:f="sap.ui.layout.form"
	busyIndicatorDelay="0">
	<Page
		title="{i18n>ResumeOf} {FirstName} {LastName}"
		id="employeeResumePage"
		showNavButton="true"
		navButtonPress="onNavBack"
		class="sapUiResponsiveContentPadding">
		<content>
			<IconTabBar
				id="iconTabBar"
				class="sapUiResponsiveContentPadding"
				binding="{Resume}"
				select="onTabSelect"
				selectedKey="{view>/selectedTabKey}">
				<items>
					<IconTabFilter id="infoTab" text="{i18n>Info}" key="Info">
						<Text text="{Information}" />
					</IconTabFilter>
					<IconTabFilter id="projectsTab" text="{i18n>Projects}" key="Projects">
						<mvc:XMLView viewName="sap.ui.demo.nav.view.employee.ResumeProjects"></mvc:XMLView>
					</IconTabFilter>
					<IconTabFilter id="hobbiesTab" text="{i18n>Hobbies}" key="Hobbies">
						<!-- place content via lazy loading -->
					</IconTabFilter>
					<IconTabFilter id="notesTab" text="{i18n>Notes}" key="Notes">
						<!-- place content via lazy loading -->
					</IconTabFilter>
				</items>
			</IconTabBar>
		</content>
	</Page>
</mvc:View>

To illustrate lazy loading, we implement that the content is loaded only when the user selects the corresponding tab for two of our tabs from the IconTabBar: Hobbies and Notes. The IconTabFilter controls each have a hard-coded ID so that we can address them later in our routing configuration. In real use cases, you would do this for tabs that contain a lot of content or trigger expensive service calls to a back-end service.

In the resume view we remove the content of the Hobbies and Notes tabs as we will now fill it dynamically with navigation features.

webapp/view/employee/ResumeHobbies.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
	<Text text="{Hobbies}" />
</mvc:View>

Create the file ResumeHobbies.view.xml in the webapp/view/employee folder. Move the content for the tab that was previously in the resume view to that view. We don’t need a controller for this view as there is no additional logic involved. This view will be lazy-loaded and placed into the content of the Hobbies tab with navigation features.

webapp/view/employee/ResumeNotes.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
	<Text text="{Notes}" />
</mvc:View>

Create the file ResumeNotes.view.xml in the webapp/view/employee folder similar to the Hobbies view to transform this tab to a separate view as well.

webapp/controller/employee/Resume.controller.js
sap.ui.define([
	"sap/ui/demo/nav/controller/BaseController",
	"sap/ui/model/json/JSONModel"
], function (BaseController, JSONModel) {
	"use strict";
	var _aValidTabKeys = ["Info", "Projects", "Hobbies", "Notes"];
	return BaseController.extend("sap.ui.demo.nav.controller.employee.Resume", {
		...
		_onRouteMatched : function (oEvent) {
			var oArgs, oView, oQuery;
			oArgs = oEvent.getParameter("arguments");
			oView = this.getView();
			oView.bindElement({
				...
			});
			oQuery = oArgs["?query"];
			if (oQuery && _aValidTabKeys.indexOf(oQuery.tab) > -1){
				oView.getModel("view").setProperty("/selectedTabKey", oQuery.tab);
				// support lazy loading for the hobbies and notes tab
				if (oQuery.tab === "Hobbies" || oQuery.tab === "Notes"){
					// the target is either "resumeTabHobbies" or "resumeTabNotes"
					this.getRouter().getTargets().display("resumeTab" + oQuery.tab);
				}

			} else {
				// the default query param should be visible at all time
				this.getRouter().navTo("employeeResume", {
					employeeId : oArgs.employeeId,
					query: {
						tab : _aValidTabKeys[0]
					}
				},true /*no history*/);
			}
		},
		...
	});
});

Now we extend the resume controller a little and add additional logic to the part of the _onRouteMatched function where a new tab has been selected and validated. In case the selectedKey matches Hobbies or Notes we call this.getRouter().getTargets().display("resumeTab" + oQuery.tab) to display the corresponding target manually. Here the valid targets are resumeTabHobbies and resumeTabNotes as we have changed the behavior for these two tabs by creating separate views.

These lines of code make sure that the targets are only loaded when they are needed (“lazy loading”). But the router does not know the new targets yet, so let’s create them in our routing configuration.

webapp/manifest.json
{
	"_version": "1.1.0",
	"sap.app": {
		...
	},
	"sap.ui": {
		...
	},
	"sap.ui5": {
		...
		"routing": {
			"config": {
				"routerClass": "sap.m.routing.Router",
				"viewType": "XML",
				"viewPath": "sap.ui.demo.nav.view",
				"controlId": "app",
				"controlAggregation": "pages",
				"transition": "slide",
				"bypassed": {
					"target": "notFound"
				}
			},
			"routes": [{
				...
			}, {
				"pattern": "employees/{employeeId}/resume:?query:",
				"name": "employeeResume",
				"target": "employeeResume"
			}],
			"targets": {
				...
				"employeeResume": {
					"viewName": "employee.Resume",
					"viewLevel" : 4,
					"transition": "flip"
				},
				"resumeTabHobbies": {
					"parent": "employeeResume",
					"viewPath": "sap.ui.demo.nav.view.employee",
					"viewName": "ResumeHobbies",
					"viewId": "thisIsMyCustomIdToBeUsedForResumeHobbies",
					"controlId": "hobbiesTab",
					"controlAggregation": "content"
				},
				"resumeTabNotes": {
					"parent": "employeeResume",
					"viewPath": "sap.ui.demo.nav.view.employee",
					"viewName": "ResumeNotes",
					"controlId": "notesTab",
					"controlAggregation": "content"
				}
			}
		}
	}
}

We add the resumeTabHobbies and resumeTabNotes targets to the descriptor file with additional fields that override the default configuration as we now want to display the targets locally inside the IconTabBar control and not as pages of the app.

The resumeTabHobbies target sets the parent property to employeeResume. The parent property expects the name of another target. In our case, this makes sure that the view from the parent target employeeResume is loaded before the target resumeTabHobbies is displayed. This can be considered as a “view dependency”. By setting the controlId and controlAggregation properties the router places the view ResumeHobbies into the content aggregation of the IconTabFilter control with ID hobbiesTab. We also set a parameter viewId to a custom ID to illustrate how you could overrule a hard-coded ID inside a view.
Note

Each target can define only one parent with its parent property. This is similar to the SAPUI5 control tree where each control can have only one parent control (accessed with the method getParent() of sap.ui.base.ManagedObject). The controlId property always references a control inside the parent view that is specified with the parent target.

Now we add the resumeTabNotes target similar to the Hobbies target. The resumeTabNotes target defines the parent target employeeResume as well, because they share the same parent view. We place the ResumeNotes view into the content aggregation of the IconTabFilter control with ID notesTab.

We have now implemented lazy loading for the tabs Hobbies and Notes. These two tabs are now managed by the routing configuration and only loaded when we click on them the first time.

Try it out yourself: Open the Network tab of your browser's developer tools and click on the tabs of your app. In the network traffic you will see that ResumeHobbies.view.xml file is only loaded when the Hobbies tab is displayed the first time. The same applies for the Notes tab. Mission accomplished!

Conventions
  • Lazy-load content that is not initially displayed to the user