How to pass value from Child component (RenderFragment) to Parent page in C# Blazor?

I have a Blazor project that loads DLL that contain razor components. To render these dynamic components at client-side, I use render tree builder to create a RenderFragment from dynamic component and place it on the page to show the component. However, I can't find a way to bind a value while creating the render tree. Normally, I can pass data using the example below of parent and child component, but not sure how to do it when converting dynamic components to RenderFragment.

Parent component

@page "/ParentComponent"

<h1>Parent Component</h1>

<ChildComponent @bind-Password="_password" />

@code {
    private string _password;
 }

Child component

<h1>Child Component</h1>

Password:

<input @oninput="OnPasswordChanged"
       required
       type="@(_showPassword ? "text" : "password")"
       value="@Password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

@code {
    private bool _showPassword;

    [Parameter]
    public string Password { get; set; }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        Password = e.Value.ToString();
        return PasswordChanged.InvokeAsync(Password);
    }
    private void ToggleShowPassword()
    {
        _showPassword = !_showPassword;
    }
}

Dynamic Component to RenderFragment

The code below shows how I am able to show the component on the page by converting it to RenderFragment. Do you know how I can bind a variable at the render tree builder, to pass data from dynamic component to the parent page?

Note: This Dynamic Component @dynamicComponent is same as the Child component above and I want the password data from it.

@page "/{ComponentName}"
@inject IComponentService ComponentService

@if (render)
{
    @dynamicComponent()
    
    @_password;
}
    
@code{
    private string _password;
    
    [Parameter]
    public string ComponentName { get; set; }
    bool render = false;
    
    protected override void OnInitialized()
    {
        render = true;
        base.OnInitialized();
    }
    
    RenderFragment dynamicComponent() => builder =>
    {
        RazorComponentModel component = ComponentService.GetComponentByPage(ComponentName);
        builder.OpenComponent(0, component.Component);
        for (int i = 0; i < component.Parameters.Count; i++)
        {
            var attribute = component.Parameters.ElementAt(i);
            builder.AddAttribute(i + 1, attribute.Key, attribute.Value);
        }
        builder.CloseComponent();
    };
}

1 answer

  • answered 2021-02-19 11:55 enet

    Let us first see if I understood you right... You want to create a sort of component renderer, which gets a component's name as a parameter, and by means of the IComponentService you gets the component's Type, and then render it. I also guess that the component's Type may be Type Child, and you want to know how to render it in the Index page, and gets the password entered in the Child component, right ? The following code describes how to do that, but of course I cannot use the IComponentService service to discover the type of the component, but instead assume that the type is Child.

    Copy and run the code snippet. Test it... It should work...

    @page "/{ComponentName?}"
    
    @using Microsoft.AspNetCore.Components.CompilerServices;
    
    @if (render)
    {
        @dynamicComponent
       
    }
    
    <p>Your password: @_password</p>
    @code{
        private string _password;
    
        [Parameter]
        public string ComponentName { get; set; }
    
        bool render = false;
    
        protected override void OnInitialized()
        {
            render = true;
            base.OnInitialized();
        }
    
        RenderFragment dynamicComponent => builder =>
        {
            builder.OpenComponent(0, typeof(Child));
            builder.AddAttribute(1, "Password", _password);
            builder.AddAttribute(2, "PasswordChanged", 
                EventCallback.Factory.Create<string>(this, 
                RuntimeHelpers.CreateInferredEventCallback(this, __value => _password = __value, _password)));
            builder.CloseComponent();
    
        };
    }
    

    Note: You should not modify the values of the parameter properties in your Child component. Parameter properties in Blazor are auto properties, meaning that you can access their values but you must not change them.

    Also, you shouldn't create sequence number by a loop. Sequence numbers should be hard-coded. Googlize this subject discussed in an article by Steve Anderson.