openui5 icon indicating copy to clipboard operation
openui5 copied to clipboard

`sap.ui.core.IAsyncContentCreation` leads to "Cannot read properties of null (reading `getId`)" on `Fragment` loading

Open pubmikeb opened this issue 3 years ago • 25 comments

OpenUI5 version: 1.95

Browser/version (+device/version): Chrome DEV

I'm trying to adopt recently introduced sap.ui.core.IAsyncContentCreation:

metadata: {
	interfaces: [
		"sap.ui.core.IAsyncContentCreation"
	],
	manifest: "json"
},

But once I've added sap.ui.core.IAsyncContentCreation, I face an issue with loading a Fragment:

27_002439

I've checked deeper and found out, that the problem is with oView, which is simply null:

this._oDialog = await Fragment.load({
	controller: fragmentController,
	id: oView.getId(),
	name: "webapp.view.AuthDialog"
});

The place where this._oView is initialized:

constructor: function constructor(oView) {
	this._oView = oView;
}

I assume, that the problem is that the system has not enough time to load and initialize oView.getId() due to async nature introduced by sap.ui.core.IAsyncContentCreation. I've tried to force waiting for the oView with async/await, but it didn't work. Once I remove sap.ui.core.IAsyncContentCreation, everything is working correctly but in a sync way.

My question is: How to tell UI5 to wait until the Fragment is loaded if using sap.ui.core.IAsyncContentCreation?

pubmikeb avatar Aug 26 '21 22:08 pubmikeb

And my questions are:

  • where do you create the fragment, in what hook / at what time?
  • and how do you get access to oView? Is it the result of a factory or a reference taken from a controller?

The error says that oView is null, not the ID.

codeworrior avatar Aug 26 '21 22:08 codeworrior

@codeworrior, thanks for your prompt response!

* where do you create the fragment, in what hook / at what time?

I create the authDialog fragment at Component.js's init():

init(...args) {
	const oDeviceModel = new JSONModel(Device);
	oDeviceModel.setDefaultBindingMode(BindingMode.OneWay);

	this.setModel(oDeviceModel, "device");

	UIComponent.prototype.init.apply(this, args);

	this.getRouter().initialize();

	this._authDialog = new AuthDialog(this.getRootControl(0));

},

AuthDialog is defined in sap.ui.define(["./controller/AuthDialog") and the AuthDialog constructor receives the oView, which is null:

constructor: function constructor(oView) {
	this._oView = oView;
},

Then, in BaseController.js, at the async onBeforeRendering() I run this fragment: this.getOwnerComponent().openAuthDialog();.

The BaseController.js is called from the another view:

(BaseController) => BaseController.extend("webapp.controller.loginBackground", {}));

* and how do you get access to oView? Is it the result of a factory or a reference taken from a controller?

In a BaseController.js I call for this.getOwnerComponent().openAuthDialog();, which calls this._authDialog.open();.

this._authDialog is defined in init(...args) {…} of BaseController.js, please, see the code snippet above.

Hopefully it was not so complicated explanation.

The error says that oView is null, not the ID.

Right, I've updated the initial post.

pubmikeb avatar Aug 27 '21 00:08 pubmikeb

To conclude, the problem is that this.getRootControl(0) is null due to sap.ui.core.IAsyncContentCreation.

If I remove sap.ui.core.IAsyncContentCreation, then this.getRootControl(0) is not null anymore and everything is OK.

pubmikeb avatar Aug 27 '21 00:08 pubmikeb

Exactly.

By implementing sap.ui.core.IAsyncContentCreation, your component claims that it is prepared to handle framework-controlled content creation in an async manner. As discussed in #3112 with @Thodd, this includes the root view. As the root view is created fully async (by the default implementation of createContent, called from within UIComponent#init), it won't be available in the same browser task in which the init call happened.

You could use rootControlLoaded before creating the fragment. Or you consider to make the dialog creation part of the root view's controller code (to me, the ID-coupling of view and authDialog suggests that the root view's controller could do the creation). But then you would have to use rootControlLoaded before accessing the fragment or you would have to ensure that the fragment is only accessed after the component creation finished (not sure if this is the case in your scenario).

I see that the documentation of getRootControl needs to be adapted to reflect the behavior when async content creation is used.

BTW: getRootControl does not expect any parameters.

codeworrior avatar Aug 27 '21 06:08 codeworrior

Thanks, I'll try to figure out how to implement such async behavior. But up-to-dated documentation with real-life code snippets would be welcomed.

Regarding getRootControl, which does not expect any parameters.

I've added 0 as an input parameter according to the IDEA documentation:

deprecated UIArea.getRootControl(idx: int): Control (since 1.1) - use function #getContent instead Returns the content control of this UIArea at the specified index. If no index is given the first content control is returned. openui5 getRootControl(): Control

Is it a correct info and #getContent should be used instead of deprecated #getRootControl?

pubmikeb avatar Aug 27 '21 06:08 pubmikeb

No, UIArea and UIComponent are two different things and there's no getContent for the UIComponent.

Maybe IDEA only recognizes the method name getRootControl and mistakenly shows the documentation for UIArea?

codeworrior avatar Aug 27 '21 07:08 codeworrior

Maybe IDEA only recognizes the method name getRootControl and mistakenly shows the documentation for UIArea?

IDEA relies on the https://github.com/DefinitelyTyped/types/openui5 project, there you can find:

/**
 * @deprecated (since 1.1) - use function {@link #getContent} instead
 *
 * Returns the content control of this `UIArea` at the specified index. If no index is given the first content
 * control is returned.
 */
getRootControl(
  /**
   * index of the control in the content of this `UIArea`
   */
  idx: int
): Control;

I don't know if it's a right or a wrong resolving, but UIComponent comes from class sap.ui.core.UIComponent.

pubmikeb avatar Aug 27 '21 07:08 pubmikeb

BTW, is it a right assumption that with sap.ui.core.IAsyncContentCreation implemented there is no more need in "async": true in routing/config and rootView of manifest.json?

pubmikeb avatar Aug 27 '21 07:08 pubmikeb

You could use rootControlLoaded before creating the fragment.

@codeworrior, thanks a lot, it looks like I solved the issue by turning init(…) into async function and awaiting for the rootControlLoaded :

async init(...args) {

	const oDeviceModel = new JSONModel(Device);
	oDeviceModel.setDefaultBindingMode(BindingMode.OneWay);

	this.setModel(oDeviceModel, "device");

	UIComponent.prototype.init.apply(this, args);

	this.getRouter().initialize();

	await this.rootControlLoaded();

	this._authDialog = new AuthDialog(this.getRootControl());

},

I'm not sure if it's the best possible solution but now my fragment displayed correctly.

Two questions:

  1. Is calling await this.rootControlLoaded(); what you meant or you actually proposed something else?
  2. Although I'm using sap.ui.core.IAsyncContentCreation, rootView/"async": true with routing/config/"async": true in manifest.json, and data-sap-ui-async = "true" in HTML-bootstrapper, I still get:

[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/. warning in DevTools

27_145314

My view has only one authDialog fragment and the bootstrap configuration in HTML is:

<script data-sap-ui-async = "true"
	data-sap-ui-compatVersion = "edge"
	data-sap-ui-excludejquerycompat = "true"
	data-sap-ui-frameOptions = "deny"
	data-sap-ui-onInit = "module:sap/ui/core/ComponentSupport"
	data-sap-ui-resourceroots = '{
		"webapp": "./"
	}'
	data-sap-ui-theme = "sap_fiori_3"
	data-sap-ui-xx-componentpreload = "off"
	data-sap-ui-xx-nosync = "true"
	id = "sap-ui-bootstrap"
	src = "https://openui5nightly.hana.ondemand.com/resources/sap-ui-core.js">
</script>

So, what could be a reason for «Synchronous XMLHttpRequest on the main thread» warning?

Regarding:

Or you consider to make the dialog creation part of the root view's controller code (to me, the ID-coupling of view and authDialog suggests that the root view's controller could do the creation).

The whole idea to be able reusing and showing this dialog on any view, that's why to achieve the maximal degree of reuse, I followed the Walkthrough tutorial recommendation and decoupled this fragment and it's controller from any view's controller, including the root view. I'm not sure if's the best practice, but i remember that the Walkthrough tutorial advised doing so.

Again, @codeworrior, thanks a lot for the providing explanation and possible ways to solve the issue!

pubmikeb avatar Aug 27 '21 12:08 pubmikeb

I think one possible reason for the remaining syncXHR might be the loading of CLDR data. Or do you see any *.js XHRs in the network tab (there should be none, we use them only in the sync loading case)?

codeworrior avatar Aug 27 '21 13:08 codeworrior

Reg. async init: please be aware that the framework will not wait for the promise returned by your init method. The purpose of IAsyncContentCreation rather is to allow createContent to become async.

codeworrior avatar Aug 27 '21 13:08 codeworrior

@pubmikeb About the remaining sync XHR calls, here I documented what I'm currently aware of: https://blogs.sap.com/2018/04/26/ui5ers-buzz-29-asynchronify-your-app/comment-page-1/#comment-545788

The CLDR is one of them.

boghyon avatar Aug 27 '21 13:08 boghyon

Or do you see any *.js XHRs in the network tab (there should be none, we use them only in the sync loading case)?

Since it's a sandbox project for testing modern UI5 features, the entire Network tab content is: 27_150652

I don't see any *.js XHRs.

Regarding «syncXHR might be the loading of CLDR data», is there any way to identify if it's a case and then to get rid of these sync requests?

pubmikeb avatar Aug 27 '21 13:08 pubmikeb

Reg. async init: please be aware that the framework will not wait for the promise returned by your init method. The purpose of IAsyncContentCreation rather is to allow createContent to become async.

Without async init I can't await for this.rootControlLoaded();. So, does it mean that my solution will work only occasionally and can't guarantee the stable work?

pubmikeb avatar Aug 27 '21 13:08 pubmikeb

https://blogs.sap.com/2018/04/26/ui5ers-buzz-29-asynchronify-your-app/comment-page-1/#comment-545788

@boghyon , among the described by you reasons, I had only "preload": true for the i18n model, I've removed it but still have the warning.

pubmikeb avatar Aug 27 '21 13:08 pubmikeb

@pubmikeb The issue https://github.com/SAP/openui5/issues/3134 refers rather to the v2.ODataModel requiring sap/ui/thirdparty/datajs.js synchronously if "preload": true. Not sure about the current state of i18n resources.

boghyon avatar Aug 27 '21 13:08 boghyon

BTW, some lifehack how to see who is leading to sync requests is to use a Permissions-Policy feature aka Feature-Policy:

res.setHeader("permissions-policy", "sync-xhr=()");

then you'll get in DevTools a detailed report about every case of sync. request:

27_171817

pubmikeb avatar Aug 27 '21 15:08 pubmikeb

@pubmikeb The issue #3134 refers rather to the v2.ODataModel requiring sap/ui/thirdparty/datajs.js synchronously if "preload": true. Not sure about the current state of i18n resources.

There is also a known issue wrt. sync loading of i18n files. This can happen if you use {{placeholder}} texts in the manifest.json, e.g. for "sap.app"/description. Additionally, we have seen that this issue also occurs if you have multiple Components extending each other. So each parent Component might cause an additional sync XHR for the i18n file.

Specifically you should see that the same i18n files is loaded twice, once async and once sync :(

Though it's hard to say if this is the case here, but judging from @pubmikeb's last screenshot, I think it is. This is a problem which we might tackle in the future, so for now I don't think there is anything you can do.

Thodd avatar Sep 07 '21 10:09 Thodd

Guess I forgot to mention this earlier, the i18n issue I described is already tracked internally via a backlog item: CPOUI5FRAMEWORK-286

I can't say when this will be tackled, but in a productive (bundled) scenario, the issue should not be visible. So at the moment it's not planned in the near future.

PS: Let's keep this open until the issue is solved. @pubmikeb: can you please look into your manifest file to see if you use {{placeholder}} strings? Also if possible can you isolate the issue in a jsbin?

Thodd avatar Oct 12 '21 09:10 Thodd

It seems that the discussion ended as there was no answer from the author for more than a month. Thanks to Thodd for detailed information on the topic. I'm closing the ticket.

Shtilianova avatar Oct 12 '21 10:10 Shtilianova

I forgot to mention this earlier, the i18n issue I described is already tracked internally via a backlog item: CPOUI5FRAMEWORK-286

That's a great news.

Let's keep this open until the issue is solved.

@thodd, I agree!

Can you please look into your manifest file to see if you use {{placeholder}} strings?

Yes, I have {{placeholder}}, e.g.:

"sap.app": {
	"_version": "1.16.0",
	"applicationVersion": {
		"version": "1.0.0"
	},
	"description": "{{APP_DESCRIPTION}}",
}

Also if possible can you isolate the issue in a jsbin?

I need to repro this case in my code-sandbox and in case I can repro, I'll try to share the sample project.

@Shtilianova, please, consider re-opening this issue.

pubmikeb avatar Oct 12 '21 16:10 pubmikeb

I've took a Shop Administration Tool as a sample app for the base, and implemented sap.ui.core.IAsyncContentCreation in Component.js. Now I don't experience xhr/sync requests in case of i18n.

pubmikeb avatar Oct 12 '21 22:10 pubmikeb

I'll have a look into the Shop Admin app, but I would have expected to see a sync XHR for already asynchronously loaded i18n files. Still, it's present without the Interface anyway, which is bad in itself...

Edit: Yep I can reproduce the problem with the linked Shop Admin Tool, this app also already implements the sap.ui.core.IAsyncContentCreation interface. At least one i18n file is loaded first async, and then sync again.

@pubmikeb: Any idea why you did not see the sync XHRs?

Thodd avatar Oct 26 '21 12:10 Thodd

@pubmikeb: Any idea why you did not see the sync XHRs?

@Thodd, sorry for the very long delay with the response. I've rechecked the Shop Administration Tool, and I do observe the XHR-requests.

Under

Now I don't experience xhr/sync requests in case of i18n.

I meant that my own playground app, based on the Shop Administration Tool, doesn't have XHRs anymore. But in my test app I've tried to follow the latest async guidelines, including the modern manifest configuration.

pubmikeb avatar Feb 02 '22 13:02 pubmikeb

@pubmikeb: Thanks for rechecking this, so at least it's consistently wrong ;)

Thodd avatar Feb 02 '22 14:02 Thodd

Guess I forgot to mention this earlier, the i18n issue I described is already tracked internally via a backlog item: CPOUI5FRAMEWORK-286

@Thodd, I'm just curious, if is there any update on the subject?

pubmikeb avatar Jan 12 '23 20:01 pubmikeb

Hi @pubmikeb,

I recently stumbled upon this again, and yes there is indeed an update now. We are currently fixing another issue wrt. synchronous ResourceBundle loading.

In the wake of UI5 2.0 we will have to address this too. Not sure when though... still, the issue came up again and needs to be fixed before the 2.0 release.

BR, T

Thodd avatar Oct 20 '23 14:10 Thodd

needs to be fixed before the 2.0 release

Such a great news!

pubmikeb avatar Oct 20 '23 14:10 pubmikeb

Hi @pubmikeb,

the sync loading of the i18n file of a component is now replaced by an asynchronous request with change ad5cf65. This will be available with UI5 version 1.121 and upwards.

Best regards, Jiawei

stopcoder avatar Jan 09 '24 16:01 stopcoder

the sync loading of the i18n file of a component is now replaced by an asynchronous request

@stopcoder, are any changes required to the end user UI5-app codebase?

pubmikeb avatar Jan 10 '24 09:01 pubmikeb