Open musictopia2 opened 3 years ago
@musictopia2 thanks for contacting us.
Can you give us examples where and how you would use something like this?
We are incredibly reluctant to add additional lifecycle methods so unless there's something that can't be achieved in any other way, we are not eager to add new methods.
An example of where it would be used is in something like this.
You need an async call that runs just before it renders content. There are many times when because either statehaschanged or the parent control caused the child components to render, it does not fire onparametersset because parameters did not change. However, the state of something may have changed but it can only refresh async. A good example of where async is required is if javascript interop had to be used. This is a case where if it runs afterrender, its too late. Obviously a person would be responsible to make sure if it requires it be rendered at least once they handle that case though. I know that at the beginning, they can run a method. The bad news is they don't allow await methods to be done and if the method was async void, then it finishes rendering before the method was done which is bad.
@musictopia2 I'm struggling to understand your sample, could you be a bit more concrete?
An actual "mockup sample situation" would help us better understand what the value here is.
Here is a mock now https://github.com/musictopia2/BeforeRenderLifeCycleSamples
For this case, you can see that if I had to run an async method before it renders, it even gives compile errors. Its also possible for a base class to have an overridable method that needs to run before anything can render. Needs async in addition to regular versions of the method or just async version. If a person always calls statehaschanged after running the async method, then you get never ending loop which is bad.
Since the demo has compile errors to demostrate the problem, in order to run it, you have to comment out the sample for the async method at the beginning of the razor file. Another case is allows more to be done from code behind instead of having to do the @{ } block as well.
@musictopia2 thanks for the additional details.
I now better understand what you are trying to accomplish but it is not possible to do so today nor it will be possible in the future unless we decide to write a different framework that operates in a vastly different way.
Rendering in Blazor is synchronous and that's not something that is going to change. When a component renders we produce a RenderTree out of it, as a result of a component rendering, other child components can also be rendered. We put those in a queue and process them until no more components want to render.
When we have all the render trees we diff the current trees against the old trees and produce a render batch. Then we send that render batch to the browser (JS side of things) to be applied.
It is only at that point where the changes are actually applied to the DOM and element references "Materialize".
There is no option to have something like OnBeforeRender
because all the changes to the DOM happen simultaneously. If the goal of doing something like OnBeforeRender
is to invoke some JS interop to get some DOM element information, it's not possible because the element has not yet been inserted into the DOM.
In a similar way, it is not possible to get a reference to a component before the render happens, because its the rendering process what triggers the creation and initialization of the child component.
As I mentioned, this is not possible with the way the current rendering system in Blazor is designed and that is not likely something we are planning to change. It would be a massive behavior change that would break pretty much every app out there and it would also have potential performance implications.
I hope this helps clarify why something like this is not possible today and why we wouldn't want to change the rendering system to make it possible, at that point Blazor would be a completely different framework.
/cc: @dotnet/aspnet-blazor-eng in case anyone wants to add something.
Is it possible to have at least a non async version. Would be useful in cases where it uses services that may have updated information. That would at least make it so instead of having to start with the @{ } then that can be code behind as well. I do understand why the async version would not be possible.
If you want me to create a sample of where state can be different that requires extra variable initialization, i can do so as well.
@musictopia2 I'm not sure I completely understand your follow up, but I believe it falls on to this
In a similar way, it is not possible to get a reference to a component before the render happens, because its the rendering process what triggers the creation and initialization of the child component.
If you are rendering a parent component the child component doesn't exist until after the parent render has been produced, so no information can be changed.
Would be useful in cases where it uses services that may have updated information.
You can already do this today and you don't need any additional lifecycle method:
StateHasChanged()
.If you want to clarify things further that will help us, but it is likely we have alternative recommendations on how to achieve the same scenario.
PD: You don't need to create an entire repo, if you create snippets with the relevant code and an explanation that likely works for us and is less work for you.
Hope this helps
I did find it easier to explain with a repo instead of figuring out how to do a code snippet. Often times a service will call StateHasChanged on the parent component. That in turns calls it on its children. I do know that ElementReference it would not have. The cases I am talking about now is a case where there is no elementreference so that would not be a problem. I know it can currently be done by using @{ }
However, if that event was done, then even that would not be necessary in that case. I can update the repo to show a case where a service may update something and only the parent updates statehaschanged.
@musictopia2 thanks for taking the time to create a repo.
If you update your sample to describe what you are suggesting that'll help us understand.
I updated the sample now. It was fast creating the repo anyways. Especially with the latest version of visual studio where through visual studio, you can create a repo and upload to github. Used to be much harder.
For the sample its a case where there is a service the parent subscribes to statehaschanged. Then the parent calls statehaschanged which all the children updates. As you can see even though i got the desired results, i had to have code like this.
@{ RunProcess(); }
If the new life cycle event was done, then it gives more ways to organize the code so if you have any cleanup work to do before rendering you can do. For this case, its easier doing this way and more efficient than all the children having to subscribe to the event which means has to be event and not simple delegate and then has to dispose as well. Its also possible another component changed the value as well.
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
One scenario I can think of is having to access device hardware such as geo-location, compass, etc.
At the moment we'd have to wait until after firstRender
(in case we are server-side rendering), then call the JS to grab any values we will need to render in the UI (long/lat for example), and then call StateHasChanged to render a second time and make those retrieved values get rendered.
I see you live in @LocationInfo.LocationDescription - which is at longitude @LocationInfo.Longitude and latitude @LocationInfo.Latitude
@code {
private LocationInfo LocationInfo;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
LocationInfo = Js.InvokeAsync<MyStats>("GetGeoStats");
StateHasChanged();
}
}
}
I have had one scenario where I've needed to do this, and have had to force a second render.
@mrpmorris You can already do that today using a service and a circuit handler to initialize the value when the circuit starts. Then your component can just read the location data from the service. Alternatively you can use JS interop within a try..catch
inside OnInitializedAsync/OnParametersSetAsync
@mrpmorris As for avoiding a second render, I somewhat see where you're going, but it's not hugely meaningful in most cases. It is meaningful for prerendering (as Javier mentions above) but not really once the application has become interactive.
Once the application is running, conceptually there has to be something in the space where each component goes. There are some pixels on the user's display to fill in, and something has to get painted there, even if it's emptiness. If you do want to render "nothingness" until you have obtained some data, you can put an @if (ready) { ... }
around your markup. But I'm not keen on creating new implicit ways to render nothing, when there's already a perfectly good way to do that, and when it's not really a good UX pattern anyway. It's nearly always better to render something like "Loading..." or "I see you live in ... which is at longitude ..., latitude ..." until the data is ready, as then the UI isn't flashing and jumping around so much.
My purposes are not to avoid a second render. my purposes is to run some process before it renders and being able to do all in code behind. However, the results of the process can influence what will actually get rendered.
@SteveSandersonMS I see what you mean. My gut said calling a render after a render is bad, but this is effectively what happens with OnInitializedAsync implicitly anyway :)
@SteveSandersonMS I'm trying to find a way to automatically find what parts of my state have been read during render so it can automatically subscribe for updates. The only way I can see to achieve this is by having a BeforeRender
or being able to override StateHasChanged
to know when rendering is taking place. I'm trying to find ways to hack w/ Fody or find some other integration point to achieve this.
@giulianob I'm not sure how general you want your solution to be, but it sounds like you not only need a synchronous call before rendering, you also need a synchronous call after rendering (which also doesn't exist in general, e.g., because Blazor Server's post-render notification has to be async, because it's telling you when the remote client has confirmed the UI is updated).
If you're OK with dropping some unusual code into your components, you could put a @{ ... }
block at the top and bottom of your markup, e.g.:
@{ MyThing.RenderingIsStarting(); }
<h1>Hello, world!</h1>
...
@{ MyThing.RenderingIsFinishing(); }
@code {
// ... logic here ...
}
Even if you could override StateHasChanged
, that wouldn't help you because that's not where the rendering happens. That method only enqueues the rendering, which may happen later if there are already other things on the queue.
If you did some private reflection, you could technically overwrite the _renderFragment
field on ComponentBase
to give your components some other RenderFragment
delegate that wraps your own logic around the default _renderFragment
callback. That would actually happen at the right time during rendering. But of course we don't support private reflection, so I couldn't possibly advise you to do that :)
@giulianob Sounds to me like it's not the rendering you need to change, but your data source.
If your data source has a way of recording which properties are read, then you'll know. Am I missing something?
If your data source has a way of recording which properties are read, then you'll know. Am I missing something?
I was guessing they are trying to do something like Knockout.js-style dependency detection. This involves knowing when to start collecting a list of the reads, and when to stop collecting the list.
If your data source has a way of recording which properties are read, then you'll know. Am I missing something?
I was guessing they are trying to do something like Knockout.js-style dependency detection. This involves knowing when to start collecting a list of the reads, and when to stop collecting the list.
That's right. There are a bunch of projects that have done this. I actually ended up hacking the render fragment like you said. Issue is that only works for my components or if I do it in IComponentActivator then for any components that extend ComponentBase. Would be great if we could subscribe to render events to know when a component is starting/finished rendering. Thanks for the replies!
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
There are cases where there can be code that a person wants ran before the rendering just like there is AfterRender.
The purpose would be if a person wants to use code behind and run some methods to prepare for rendering. I have found many cases where StateHasChanged was called but did not invoke propertyset but the code should run in the code behind instead of razor file.