Aspnetcore: text is not updated when in a ref interface

Created on 30 Jul 2018  Â·  9Comments  Â·  Source: dotnet/aspnetcore

The ref of a component is set after Onit or after OnParametersSet.

Example, I have a demo Component like:

@using Microsoft.AspNetCore.Blazor.Components
@implements IDemo

@DemoText

@functions
{
    [Parameter]
    public string DemoText { get; set; } = "";
}

It implements interface:

  interface IDemo
    {
        string DemoText { get; set; }
    }

On Index.cshtml:

@page "/"

<Demo ref="@demo"></Demo>

@functions
{
    IDemo demo;

    protected override void OnParametersSet()
    {                
            demo.DemoText = "This fails";        
    }

}

Demo isn’t created after OnInit or after OnParametersSet and the line:

     `   demo.DemoText = "This fails";       `

fails.

Is this the right way?

Maybe OnInit can be called after the initiation of refs or can there be a override which is called before rendering but after the initiation of ref, something like OnPreRender?

area-blazor

Most helpful comment

Try it like this

    protected override void OnAfterRender()
    {
        if (firstrender)
        {
            Task.Run(()=>
            {
                demo.DemoText = "Yeahah";
                demo.Update();
             });
            firstrender = false;
        }
    }

I have done something similar in my blazor component: https://github.com/stavroskasidis/BlazorContextMenu/blob/develop/BlazorContextMenu/Components/Item.cshtml , line: 197.

I think this will work because it moves the execution in the next rendering loop, because it is too late to make changes in the current iteration. (Someone from the team correct me if I am wrong).

All 9 comments

Here:
https://blazor.net/docs/components/index.html#lifecycle-methods
we can read:

OnAfterRenderAsync and OnAfterRender are called each time after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps using the rendered content, such as activating third-party JavaScript libraries that operate on the rendered DOM elements.

It looks that it works as documented. For element references it is impossible to do this earlier. I have no idea if it is technically possible to initialize component references earlier.

The real question is: why do you need this? Your minimal code shows the problem but can easily be converted to something like this:

<Demo DemoText="This doesn't fail"></Demo>

Could you explain you real problem which cannot be solved without valid component reference in OnParametersSet?

Hello Andrzej-W,

mij real work is indeed different. I am creating an html text editor, so in the real code you can write and read to the demotext property.
So:

<Demo DemoText="This doesn't fail"></Demo>

Doesn't work for me because the real code has to read and write to DemoText.

I updated the demo a little bit to narrow down my problem.

My Interface is now:

 interface IDemo
    {
        string DemoText { get; set; }
        void Update();
    }

My component is now:

@using Microsoft.AspNetCore.Blazor.Components
@implements IDemo

@DemoText

@functions
{
    [Parameter]
    public string DemoText { get; set; } = "";

    public void Update()
    {        
        this.StateHasChanged();
    }

}

And my index.cshtml is now:

@page "/"

<Demo ref="@demo"></Demo>


@functions
{
    IDemo demo;

    private bool firstrender = true;

    protected override void OnAfterRender()
    {
        if (firstrender)
        {
            demo.DemoText = "Yeahah";
            demo.Update();
            firstrender = false;
        }
    }

}

In this version nothing gets to the screen.

Am I doing something wrong?

Try it like this

    protected override void OnAfterRender()
    {
        if (firstrender)
        {
            Task.Run(()=>
            {
                demo.DemoText = "Yeahah";
                demo.Update();
             });
            firstrender = false;
        }
    }

I have done something similar in my blazor component: https://github.com/stavroskasidis/BlazorContextMenu/blob/develop/BlazorContextMenu/Components/Item.cshtml , line: 197.

I think this will work because it moves the execution in the next rendering loop, because it is too late to make changes in the current iteration. (Someone from the team correct me if I am wrong).

Hello stavroskasidis,

when I use your code it works! Thank you.

Personally I believe there should be a better solution.

For instance an override when it's certain you can set data in a interface like

protected override void OnAfterRefSet{
                demo.DemoText = "Yeahah";
                demo.Update();
}

But it works now, thank you!!

I agree, there should be a better solution. This is more like a workaround

demo.DemoText = "Yeahah";

We generally recommend against using component references as a way to mutate component state. Instead we recommend using declarative parameters to pass data to child components. This will allow child components to re-render at the correct times automatically.

<Demo DemoText="This doesn't fail"></Demo>
Doesn't work for me because the real code has to read and write to DemoText.

Could you please provide a bit more context on why you have this constraint? Instead of mutating a component property, could you instead mutate a service that the component consumes?

Hello danroth27,

My project is an educational system where you can edit lessons. A lesson can be just plain tekst (html) or something upload a pdf .
To edit this lesson I have a page where I load the Editor component.
This page loads the text with a webapi component. I want to pass the text from the page to the component and after it is edit and the user press save, I want to read the text from the component and save it to the database (through webapi).

In the component I have an html editor like:

<div id="mytextarea" class="CoreEditor" contenteditable="true" spellcheck="false"></div>

I get and set the inner text (html) with (javascript with interop) like:

$(selector).html(html);

In my component I want 2 things:

  1. Read and write the text(html) of de editor
  2. Set the state of the component, for instance I want to set the kind of menu to show in front of the editor.

2 can be done with properties but to my knowledge it is not possible to use custom twoway binding to a property in a component like:

<editor bind=”@someproperty”></editor>

Currently I use the jquery html function to get the innerhtml, but maybe there is a better way, maybe with binding?

To sum it up:

-two way binding to a component
-Two way binding to innerhtml

@Knudel you can read about two-way data binding in this blog post http://flores.eken.nl/blazor-custom-component-with-2-way-databinding/ by @floreseken

General recommendation is:
Whenever you want to share some data between different components and you have some related business logic like reading or writing data from/to WebAPI use service and implement all business logic in that service.

Good example is here:
https://github.com/aspnet/samples/tree/master/samples/aspnetcore/blazor/FlightFinder
http://blazor-flight-finder.azurewebsites.net/

Related doc:
https://blazor.net/docs/dependency-injection.html

Make note that singleton service is created only once and remains in memory. If you have a lot of services and they have a lot of data it is not always the best solution. You can register service as transient, inject it into the page and pass as parameter to components on this page.

Andrzej-W thank you for your help, I didn't know about bind-Value. I will implement this in my component, I believe this will solve my problem.

Was this page helpful?
0 / 5 - 0 ratings