SAP / openui5

OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on almost any browser of your choice.
http://openui5.org
Apache License 2.0
2.95k stars 1.23k forks source link

Root View and Controller is instantiated 2 times #1746

Closed GKSoftwareDevelopment closed 6 years ago

GKSoftwareDevelopment commented 6 years ago

OpenUI5 version: 1.44.15

Browser/version (+device/version): Google Chrome Version Version 61.0.3163.100 (Official Build) (64-bit) FAIL

Any other tested browsers/devices(OK/FAIL): Google Chrome Version 64.0.3249.0 (Official Build) canary (64-bit) FAIL

URL (minimal example if possible): com.gk-software.demo.nav-one-component.zip

User/password (if required and possible - do not post any confidential information here):

Steps to reproduce the problem: Please debug the example. Place breakpoint in ProcessBaseController, onInit() method. Start the example, onInit() is called 2 times before showing the view. In the DOM structure App is also present 2 times, specifically included in itself:

screenshot_duplicated_elements

What is the expected result? In manifest.xml should be possible to define rootView and use it as a target. For example:

"sap.ui5": {
        "_version": "1.1.0",
        "rootView": "RootView",
...
"routes": [
            {
                "pattern": "component",
                "name": "componentName",
                "target": "component"
            },
            {
                "pattern": "component/docs",
                "name": "componentDocs",
                "target": "componentDocs"
            }],

            "targets": {
                "component": {
                    "viewName": "RootView",
                    "viewLevel" : 1
                },
                "componentDocs": {
                    "parent": "component",
                    "viewName": "Docs",
                    "transition": "show",
                    "viewLevel" : 2
                }
            }

What happens instead? When we use rootView as a target in manifest.xml, rootView and its controller are instantiated 2 times, there are 2 instances in the memory as well as in the DOM structure. Because of that we do not have possibility where to do things that should run only once during the lifecycle, i.e. in the onInit() method.

Any other information? (attach screenshot if possible) The reason why it is happening is that in Component.js RootView is instantiated during component UIComponent.prototype.init.apply(this, arguments); and then the Router matches the target with RootView and thus the second instance of RootView is created.

init: function () {
              // call the init function of the parent
              UIComponent.prototype.init.apply(this, arguments);

              // create the views based on the url/hash
              this.getRouter().initialize();
                }

Is it possible to correct this behaviour and enable the possibility to use root view as a target?

Shtilianova commented 6 years ago

Hello @GKSoftwareDevelopment , Thank you for sharing this finding. I've created an internal incident 1780415610. The status of the issue will be updated here in GitHub. Regards, Diana

bendkt commented 6 years ago

Hello,

I am not quite sure, but from a quick look at the sources provided in the .zip I would suggest you trying to not declare both, rootView in Manifest and the same view on the route with the empty hash "". Then both may be created, the rootView by the Component and the empty hash View by routing.

Best regards Benedikt

GKSoftwareDevelopment commented 6 years ago

Hello Benedikt, sorry to say that but it seems to me like you are trying to close this issue by providing workaround which will prevent it. I don't think that the code we wrote is anything what is forbidden. And I think it should not create two instances in such case.

boghyon commented 6 years ago

@GKSoftwareDevelopment Hope this answer also helps: https://stackoverflow.com/q/47457769/5846045


I don't think that the code we wrote is anything what is forbidden.

It's not forbidden but also not seen as best-practice.

These two have different purposes. It doesn't make sense to assign one view to appear in both cases.

GKSoftwareDevelopment commented 6 years ago

Ok. Can you please propose me the best-practice how to handle situation when I want to have the same page after component creation and after empty hash call? Does it mean that I have to create dummy view for rootView which does nothing? If I understand it well the rootView is mandatory field but it doesn't handle the case when user call empty hash. So when I need the empty hash route then the rootView is for nothing.

Maybe I don't see anything so please fix me if I'm wrong.

boghyon commented 6 years ago

In most cases, the rootView has nothing but a root control (such as App). And as its aggregation, it contains the navigated view corresponding to the current hash value via the router whereas the navigated view is not always the view on empty hash. But the root view is always there as parent. Whether it does nothing else or not, depends on your use case.

Here are some examples of how such a root view could be useful:

For the current best practice, check out the tutorial Navigation and Routing.

bendkt commented 6 years ago

There was someone faster. Great explanation, thanks @boghyon!

INC01067 commented 4 years ago

@bendkt / @boghyon -

First sorry to be replying to an old thread - but just wanted to check something on the same issue:

I have Root View as "view.App", "" as "Home" and "view.detail" as "view.Detail" - basically nothing is repeatedly assigned.

In my Home.view I am calling "view.Detail" as an aggregated item of Page.

`

                    </tnt:mainContents>`

I believe because of this, my Detail.controller method onInit and onAfterRendering are being called twice.

Any solution to this?

PabloMizzle commented 4 years ago

Hi Everyone,

I have a similar problem with nested views. There is a top level view and two subordinate views in a NavContainer: `<c:View id="alerts" viewName="Alerts" controllerName="alerts.controller.core.Alerts" xmlns="sap.m" xmlns:c="sap.ui.core" xmlns:lp="shared.controls" xmlns:mvc="sap.ui.core.mvc"> <lp:AppToolbar id="appToolbar" title="{i18n>APP_COMPONENT_TITLE}" showHomeButton="true" logonName="{userInfo>/logonName} ({userInfo>/roles/0})" topLevelRouteName="Alerts" />

</c:View>`

The two subordinate views in the NavContainer are created twice which leads to 4 sub-views instead of 2 as I would expect it.

Is there a solution to this issue?

codeworrior commented 4 years ago

@PabloMizzle you didn't mention how your routing configuration looks like and for which hash you encounter the issue?

PabloMizzle commented 4 years ago

@codeworrior thanks for caring, here's my routing config:

The two sub-ordinate views are created twice. Once upon initialization and once when you first navigate to the respective routing target.

When landing on the "AlertsFirstLevel" (via hash ../#/alerts/ or via bypassed), there are three sub-views: AlertsFirstLevel, AlertsSecondLevel, AlertsFirstLevel. Then when you navigate to AlertsSecondLevel, a 4th sub-view is created in the "pages" aggregation of the NavContainer...

"routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "viewPath": "alerts.view.core", "controlId": "alertsApp", "controlAggregation": "pages", "transition": "slide", "bypassed": { "target": "AlertsFirstLevel" }, "async": true }, "routes": [ { "pattern": "alerts", "name": "AlertsFirstLevel", "target": "AlertsFirstLevel" }, { "pattern": "alerts/:selectedEntity:/transactions", "name": "AlertsSecondLevel", "target": "AlertsSecondLevel" } ], "targets": { "Alerts": { "viewName": "Alerts", "viewLevel": 1 }, "AlertsFirstLevel":{ "viewName": "AlertsFirstLevel", "parent": "Alerts", "controlId": "alertsNavContainer", "controlAggregation": "pages", "transition": "fade", "viewLevel": 2 }, "AlertsSecondLevel":{ "viewName": "AlertsSecondLevel", "parent": "Alerts", "controlId": "alertsNavContainer", "controlAggregation": "pages", "viewLevel": 3 } } }

Posted the same question on SCN: https://answers.sap.com/questions/12872252/ui5-root-view-and-controller-instantiated-twice.html?childToView=12887688#answer-12887688

codeworrior commented 4 years ago

To my understanding, the current Router implementation assumes that it is the only creator for the configured views / targets. It does not reuse already existing views.

@stopcoder can you confirm and maybe point out a configuration that avoids the duplication? I guess the views must not be embedded in the root view and instead be created by the home route.

INC01067 commented 4 years ago

@stopcoder can you confirm and maybe point out a configuration that avoids the duplication? I guess the views must not be embedded in the root view and instead be created by the home route.

That would be great as I still don't have any solution to this.

Thanks @PabloMizzle for reviving my question and @codeworrior for some direction. Awaiting guidance.

PabloMizzle commented 4 years ago

@codeworrior Hey again. I am still facing this problem. May I ask if there plans to address this issue? Shall I open a new issue one as this one is actually closed?

fwabendo commented 4 years ago

After several attempts to solve multiple instantiations thinking it was my fault , i decided to look it up. something needs to be done on this issue for convenience purposes and cut on the time of unaware developers about underlying design "best practices".

chris-nos commented 2 years ago

I would also be interested if there is a way to solve this issue without creating a separate root view. I would have expected, that the router checks if the target view with a given Id have been already created (eg. as rootView ) and in this cases reuses it's reference. A clear statement like: "No, it's not possible" would be sufficient for me so I can stop researching.

stopcoder commented 2 years ago

Hi @tanzhaus,

I put the answer first: "No, it's not possible" and try to explain why it's not possible.

Both "rootView" and "router" belong directly to the Component. "Router" and "rootView" don't know about each other. The component builds up a connection between them by telling the router to use "rootView.byId(CONTROL_ID)" (CONTROL_ID is the routing configuration "controlId") to find the container where the router places the target(s). Therefore the creation and instance management of the "rootView" isn't in the scope of the router. When the rootView is configured as a routing target, another instance is created because router doesn't know the existence of the rootView in the component and shouldn't know about it.

However, there's solution for the original problem. It can be solved by having a very simple rootView where only the container control is created. The View that should be shown with the empty hash "" should be declared as a target which is then displayed by the route with empty hash.

Best regards, Jiawei

chris-nos commented 2 years ago

Thank's a lot Jiawei @stopcoder. Yes, creating a simple "root view" container works fine but some customer are not very happy about that due to increasing number of files and reorganizing existing hierarchies. Thanks to you I can now justify that :)