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.
Tabs with lazy loading
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 10 .
Folder Structure for this Step
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Resume"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page
title="{i18n>ResumeOf} {FirstName} {LastName}"
id="employeeResumePage"
showNavButton="true"
navButtonPress=".onNavBack">
<content>
<IconTabBar
id="iconTabBar"
headerBackgroundDesign="Transparent"
class="sapUiResponsiveContentPadding"
binding="{Resume}"
select=".onTabSelect"
selectedKey="{view>/selectedTabKey}">
<items>
<IconTabFilter id="infoTab" text="{i18n>tabInfo}" 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.
<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.
<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.
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.
{
"_version": "1.12.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"type": "View",
"viewType": "XML",
"path": "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": {
"id": "resume",
"name": "employee.Resume",
"level" : 4,
"transition": "flip"
},
"resumeTabHobbies": {
"id": "resumeHobbies",
"parent": "employeeResume",
"path": "sap.ui.demo.nav.view.employee",
"name": "ResumeHobbies",
"controlId": "hobbiesTab",
"controlAggregation": "content"
},
"resumeTabNotes": {
"id": "resumeNotes",
"parent": "employeeResume",
"path": "sap.ui.demo.nav.view.employee",
"name": "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 id
to a custom ID to illustrate how you could overrule a hard-coded ID inside a view.
Each target can define only one parent with its parent property. This is similar to the OpenUI5 control tree where each control can have only one parent control (accessed with the method
getParent()
ofsap.ui.base.ManagedObject
). ThecontrolId
property always references a control inside the parent view that is specified with theparent
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!
- Lazy-load content that is not initially displayed to the user
Related Information