Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Root View and Controller is instantiated 2 times #1746

Closed
GKSoftwareDevelopment opened this issue Oct 27, 2017 · 18 comments
Closed

Root View and Controller is instantiated 2 times #1746

GKSoftwareDevelopment opened this issue Oct 27, 2017 · 18 comments

Comments

@GKSoftwareDevelopment
Copy link

GKSoftwareDevelopment commented Oct 27, 2017

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
Copy link
Contributor

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
Copy link
Contributor

bendkt commented Nov 22, 2017

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

@bendkt bendkt closed this as completed Nov 22, 2017
@GKSoftwareDevelopment
Copy link
Author

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
Copy link
Contributor

boghyon commented Nov 23, 2017

@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.

  • The rootView is a view that will be created when your app / component starts (no matter which value the hash has) and stays until the component gets destroyed.
  • The view on empty hash, however, can be avoided when the user performs deep link navigation.

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

@GKSoftwareDevelopment
Copy link
Author

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
Copy link
Contributor

boghyon commented Nov 24, 2017

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:

  • You could bind a single entity to the root control and make the binding context available for all descending views no matter which view was navigated. Then the descending views could bind data relative to the parent single entity.
  • You could add an element to the <dependents> aggregation of the root control such as the <App> so that the element can be accessed by all descending controllers as described in the past walkthrough step Reuse Dialogs.
  • Since the view on empty hash value can be skipped when the user visits your app via bookmark / deep link, you might want to have some logic in the root view's controller instead of somewhere else.

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

@bendkt
Copy link
Contributor

bendkt commented Nov 24, 2017

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

@INC01067
Copy link

INC01067 commented Sep 26, 2019

@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> <NavContainer id="crtNavCon" width="100%" height="100%"> <Page showHeader="false" class="crtPageBGClass" id="Master1"> <mvc:XMLView viewName="apiConsole.OutSystemApiConsole.view.Detail"/> </Page> </NavContainer> </tnt:mainContents>

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

Any solution to this?

@PabloMizzle
Copy link

PabloMizzle commented Oct 17, 2019

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" /> <HBox class="layoutBox" width="100%" height="100%"> <c:Fragment fragmentName="alerts.fragment.core.SideMenu" type="XML" /> <NavContainer id="alertsNavContainer"> <mvc:XMLView viewName="alerts.view.core.AlertsFirstLevel" /> <mvc:XMLView viewName="alerts.view.core.AlertsSecondLevel" /> </NavContainer> </HBox> </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
Copy link
Member

codeworrior commented Oct 17, 2019

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

@PabloMizzle
Copy link

PabloMizzle commented Oct 17, 2019

@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
Copy link
Member

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
Copy link

@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
Copy link

@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
Copy link

fwabendo commented Jul 1, 2020

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
Copy link

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
Copy link
Member

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
Copy link

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests