7

In my UI5 app, I have a view with a table (sap.m.Table), populated by data coming from the back-end at onInit hook. The problem is that onInit is executed only once per view instance:

It is only called once per View instance, unlike the onBeforeRendering and onAfterRendering hooks.

And if a user decides to leave this view (e.g., back navigation) and to reopen it later, the onInit will not be recalled, and thus the data will not be retrieved again, and the table content will not reflect the possible changes.

To ensure that the data are retrieved every time the view is opened, I tried to get the data at onBeforeRendering, but this hook is also called just once. The only way, that I have found, to force onBeforeRendering to be called every time the view is opened, is to add the following code into onInit method:

onInit: function () {
    this.getView().addEventDelegate({
        onBeforeShow: this.onBeforeShow,
    }, this);
}

My questions:

  1. Why, without the code snippet above in onInit, is the onBeforeRendering not triggered every time the view is displayed?

  2. What does exactly the code snippet above do?

  3. The alternative technique: to use patternMatched and routeMatched. But which one of these three approaches is more common?

Mike
  • 14,010
  • 29
  • 101
  • 161

4 Answers4

15
  1. Why (...) is the onBeforeRendering not triggered every time the view is displayed?

I think there is a misconception of what "rendering" means. In UI5, when a control is "rendering", its corresponding HTML element is being modified or created in the DOM by the RenderManager. I.e. onBeforeRendering means literally "before the render function of the control (here: View) is called".

onBeforeRendering does not mean that it's called before the paint event from the browser (For that, modern browsers provide high-level APIs such as Intersection Observer).
Rendered controls can be in the DOM but not visible in the viewport at the same time.

Coming back to the question; the reason why on*Rendering is not triggered, is because the control has been already rendered before. This can be seen when user navigates via navTo and then back again. The corresponding view element is already in the DOM, so there is no need to call render again, meaning no on*Rendering triggered.


  1. What does the code snippet exactly do?
this.getView().addEventDelegate({
    onBeforeShow: this.onBeforeShow,
}, this);

addEventDelegate adds a listener to the events that are fired on the control (not by the control).

E.g.: The view contains events like afterInit, beforeExit, ...
Doing addEventDelegate({onAfterInit}) won't work since afterInit is fired by this control (view).
Doing addEventDelegate({onmouseover}) works since it's fired on this control.

The same applies to the onBeforeShow. The view doesn't contain any events like beforeShow, afterShow, etc.. Those are fired on the view by the NavContainer (e.g. by <App> on its child, view). Documentation about those events can be found in:

See also a similar question https://stackoverflow.com/questions/44882085/why-does-onbeforefirstshow-work/44882676


  1. The alternative technique: to use patternMatched (...). But which one of these three approaches is more common?

By "three approaches" I assume you mean:

  • on*Rendering (1st approach),
  • on*Show (2nd approach),
  • and the above mentioned routing events like patternMatched (3rd approach).

The answer is, as always, it depends on what you're trying to achieve. But usually, we:

  • Use the 2nd approach (NavContainerChild events) if the application does not have a routing concept (no sap.ui5/routing in manifest.json).

  • Use the 2nd approach with onAfterShow if the intent is to set initial focus after the view is displayed. See How to Set Initial Focus in a View?

  • Use the 3rd approach to get notified about the route pattern being matched. This approach is commonly used to do something every time the view (NavContainerChild) is displayed, for example, to do Context Binding after navigating to a detail page. How it works:

    1. When router.navTo() is called, the next corresponding view and controller are created.
    2. In onInit of the newly created controller, you assign a patternMatched handler.
    3. On navigation, the URL hash value will change. The router (internally the HashChanger) notices the URL change, leading to Route firing patternMatched by which your handler will be invoked. See the TL;DR below.
  • ⚠️ Personal opinion: avoid 1st approach as an application developer. Avoid doing anything in onBeforeRendering and in onAfterRendering since it's unpredictable how often the render function is called from the viewpoint of the application. For control developers, those hooks are absolutely necessary. But for application developers, there are often better alternatives.


TL;DR

Forget on(Before|After)Rendering. Use the (pattern)Matched event from the route instead:

{
    // Controller
    onInit() {
        const myRoute = this.getOwnerComponent().getRouter().getRoute("routeName");
        myRoute.attachPatternMatched(this.onMyRoutePatternMatched, this);
    },
    onMyRoutePatternMatched(event) {
        // your code when the view is about to be displayed ..
    },
}
Mike
  • 14,010
  • 29
  • 101
  • 161
Boghyon Hoffmann
  • 17,103
  • 12
  • 72
  • 170
  • BTW, what's better to use `this.getOwnerComponent().getRouter().getRoute("routeName");` or `sap.ui.core.UIComponent.getRouterFor(this);`? – Mike Oct 22 '21 at 13:50
  • 1
    @Mike Generally, referencing modules via global names (`sap.ui.core...`) should be avoided. The 1st approach with the `getOwnerComponent` assumes that the dependent modules are already loaded, while requiring the `UIComponent` module in the 2nd approach and then calling `UIComponent.getRouterFor(this)` would be a cleaner way. Not sure tbh. IMHO, one of the APIs (`oComponent.getRouter` or `UIComponent.getRouterFor`) should be deprecated since they're doing the same thing. – Boghyon Hoffmann Oct 22 '21 at 19:20
  • Regarding _«referencing modules via global names (`sap.ui.core`…) should be avoided.»_, could, you, please, elaborate it. In many samples I see something like `sap.m.InputType.Password`. What's wrong with it? In my case, `UIComponent` is already imported/loaded to the controller via `sap.ui.define`. I've checked the API, `UIComponent.getRouterFor(oControllerOrView)` returns `sap.ui.core.routing.Router` of the `oControllerOrView`, so it's just yet another way to work with router. – Mike Oct 22 '21 at 20:45
  • 1
    @Mike `sap.m.InputType` is an enum (data type) attached to the `sap.m` lib, not a dedicated module you can require. But some enums *are* real modules (e.g. `sap/ui/model/FilterType`). Which module to require is shown in the corresponding API reference of the enum. Here is a sample commit that corrects declaring enum dependency: https://github.com/SAP/openui5/commit/9882c524f5215d350a15e04b4312bfa02169d4f7. Same applies to namespaces such as `sap.m.URLHelper`: https://github.com/SAP/openui5/issues/3216#issuecomment-816920583. Require the module that is documented in the API reference. – Boghyon Hoffmann Oct 24 '21 at 14:47
  • 1
    @Mike Declaring module dependencies properly is all part of the modernization efforts for UI5 applications and UI5 itself. I'd suggest to [run `ui5-migration migrate`](https://github.com/SAP/ui5-migration/) for all UI5 apps. As part of its `amdCleanup` task, `sap.ui.core.UIComponent.getRouterFor` will be replaced with `UIComponent.getRouterFor` for example. – Boghyon Hoffmann Oct 24 '21 at 14:51
  • Great! Thanks for the link to [`ui5-migration migrate`](https://github.com/SAP/ui5-migration/) tool, will have a look on it! – Mike Oct 25 '21 at 06:47
3

You should use

onInit: function() {
    let route = this.getOwnerComponent().getRouter().getRoute("yourroutename");
    route.attachPatternMatched(this.onRoutePatternMatched, this);
    // ...
},

This route event will be triggered every time the route pattern is matched in your routing config. In the above example, the function onRoutePatternMatched will be called every time the route is loaded through navigation.

onRoutePatternMatched: function(event) {
    // this logic will repeat itself every time the route pattern is matched
},
Mike
  • 14,010
  • 29
  • 101
  • 161
Bernard
  • 1,208
  • 8
  • 15
2

Question #3:

patternMatched is going to get hit when your router is matched on the URL or on the router.navTo method.

routeMatched is going to get hit on the same occasion as patternMatched and when its child routes get navigated to.

Imagine, you have a master view on route A and it’s detail on route B. If the user navigates directly to route B, it makes sense to render the target associated to route B and also the target associated to route A.

To conclude:

  • patternMatched: direct route match

  • routeMatched:

    1. The pattern of a route in this router.
    2. The pattern of its sub-route.
    3. The pattern of its nested route. When this occurs, the nestedRoute parameter is set with the instance of nested route.
Mike
  • 14,010
  • 29
  • 101
  • 161
Geraldo Megale
  • 363
  • 1
  • 10
0

The answers of Boghyon Hoffmann and Bernard are currently the best solution to the problem of needing an event to perform some action when the page is navigated to.

There is an option where it's possible to configure the target of a route to clear the control aggregation and force it to be re-rendered every time it is navigated to. With this configurantion the events onBeforeRendering and onAfterRendering will be called when navigating to the page. However, it is not a recommended solution as it will cause performance issues with the constant re-rendering and it should set to false when working with sap.m.NavContainer or it would cause transitions stop happening and other issues. If you are working on a legacy project that uses sap.ui.ux3.Shell, which is currently deprecated, it is a possible solution.

So to configure this option we need to set the option clearControlAggregation to true in the manifest.json, it is false by default when using sap.m.routing.Router.

In this link you can check all the options you have for the route targets: https://sapui5.hana.ondemand.com/#/api/sap.ui.core.routing.Targets

"config": {
    "routerClass": "sap.ui.core.routing.Router",
    "viewPath": "TestApp.view",
    "viewType": "XML",
    "clearControlAggregation": true,
    "controlId": "Main",
    "controlAggregation": "pages",
    "transition": "slide"
}

It is also possible to add this option only for a specific target like this:

"targets": {
    "Test1": {
        "viewType": "XML",
        "viewName": "Test1",
        "clearControlAggregation": true,
        "title": "Test1"
    },
    "Test2": {
        "viewType": "XML",
        "viewName": "Test2",
        "title": "Test2"
    }
}
Mike
  • 14,010
  • 29
  • 101
  • 161
  • @Boghyon Hoffmann, here is an alternative opinion to your original answer, I'm curious to know your opinion about using `clearControlAggregation` approach. – Mike Jun 09 '20 at 16:25
  • 1
    @MikeB. I **strongly** discourage from using `clearControlAggregation`. _**1.**_ It destroys all previously loaded views and forces the target to rerender every time on navigation which is an expensive task (re-writing view's HTML text to the DOM, re-allocating memory, propagating models again, etc.). _**2.**_ API reference **explicitly warns _not_ to use it** if the target parent is `sap.m.NavContainer` (applies to `sap.m.App`, `sap.m.SplitApp`, and `sap.f.FlexibleColumnLayout`). – Boghyon Hoffmann Jun 10 '20 at 10:57
  • _> As in the official documentation of SAP, the `onAfterRendering` and `onBeforeRendering` should be called every time you go back to the page._ --> Could you add a link that supports the claim? – Boghyon Hoffmann Jun 10 '20 at 10:59
  • _> it also brings some other complications when listening to it and having to filter when other routes have matched_ --> Please elaborate more on those _complications_. It certainly doesn't justify going against documentation guidelines and using `clearControlAggregation` instead with all the [drawbacks](https://stackoverflow.com/questions/55082731/onbeforerendering-or-onafterrendering-is-not-called-every-time-the-view-is-o/62286980#comment110186428_62286980). Adding *[optional query parameters](https://ui5.sap.com/#/topic/b8561ff6f4c34c85a91ed06d20814cd3)* might be helpful in your case. – Boghyon Hoffmann Jun 10 '20 at 11:16
  • 1
    I downvoted your answer to let others know that this approach is not recommended. Pease don't take it personally. Thank you for contributing. – Boghyon Hoffmann Jun 10 '20 at 11:17
  • @BoghyonHoffmann Thank you for taking your time to comment it, the exchange of ideas is a great way to learn, I'll be answering the questions you posed in this comment and in the next ones. About the SAP Documentation stating these events should be called "when going back to the page" it is not right and I'll edit it and explain better as it should be called on re-rendering the page and as you said before it is exactly what the `clearControlAggregation` is for, to force a re-renderization of the page and it has its downsides. – Vinícius Ribeiro Jun 10 '20 at 14:33
  • @BoghyonHoffmann About the complexity using the `routeMatched` or `routePatternMatched`, it is really simple when you have it for one controller, but as you develop new controllers with this solution it will grow in complexity. Let's say you have 5 controllers listening to the routeMatched event. If somehow the user visit all these controllers this event will trigger the 5 listeners when any route in the router is matched and then you need to treat it in each listener. – Vinícius Ribeiro Jun 10 '20 at 14:40
  • @BoghyonHoffmann The idea of having it on a `BaseController` is good but adds even more complexity on having to figure out how each route link to a controller in a generic way. It obviously doesn't justify going against their official recommendation, but it is a knowledge worth having to be able to ponder between the pros and cons. – Vinícius Ribeiro Jun 10 '20 at 14:42
  • @ViníciusRibeiro I reread your answer and realized that you were talking about `route(Pattern)Matched` from the Router class. That obviously won't scale well as you described. But none of the answers here were actually actively promoting it as a solution. Or which *answers* did you mean in the sentence? _> The previous **answers** relied on the method of listening to the `routeMatched` event_ --> Do you mean [this answer](https://stackoverflow.com/a/25150448/5846045)? (I actually downvoted that also few years ago) – Boghyon Hoffmann Jun 10 '20 at 15:22
  • @BoghyonHoffmann no, in fact, I meant the answers on this post, but now I see I misunderstood your answer and Bernard's. I'll edit my answer to only talk about the `clearControlAggregation` option warning about its downsides. Thank you for this discussion. – Vinícius Ribeiro Jun 11 '20 at 10:55
  • @ViníciusRibeiro Thank you for improving the answer. I reverted my downvote and hope future readers understand the implication before considering to use `clearControlAggregation`. – Boghyon Hoffmann Jun 11 '20 at 13:50
  • 1
    Here is a recent issue that is caused by `clearControlAggregation: true`: https://github.com/SAP/openui5/issues/3368. At this point, it is no longer a mere performance issue, it **will** break the app if that attribute is enabled without using `sap.ui.ux3.Shell`. Also @Mike – Boghyon Hoffmann Oct 21 '21 at 22:58