Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a child component as Parameter in Blazor

I would like to do in Blazor something that I normally do in React: create a reusable component that internally uses other child components, with the ability to pass those child components as parameters. I need that to be able to treat child components as a dependency that can be injected on demand with any custom implementation that could be needed in different contexts.

Imagine, for instance, a TextBox.razor component that gives you the ability to pass a custom component to render the label as you want, as long as it implements an ILabel interface. I tried something like this but the syntax doesn't seem to be valid:

TextBox.razor

enter image description here

As you see from the screenshot, Blazor doesn't allow me to use the Parameter Label as a component. Any idea of how to achieve this?

like image 897
tocqueville Avatar asked Nov 02 '19 11:11

tocqueville


2 Answers

You should be able to accomplish this with templated components.

Textbox.razor

@typeparam inputType
<div class="textbox">
    @if(LabelTemplate!=null && TItem!=null)
        @LabelTemplate(TItem)
    <input type="text"/>
</div>


    @code{
        [Parameter]
        public RenderFragment<inputType> LabelTemplate { get; set; }
        [Parameter]
        public inputType TItem { get; set; }
    }

In the code above, you are specifying that the component accepts a type using @typeparam inputType and receive an object of that type as a parameter TItem.

You are also accepting a LabelTemplate which accepts an object of type inputType. To render this fragment, we call @LabelTemplate and pass in our TItem parameter.

Now lets look at how to use our templated component in a new component called PersonForm.razor

PersonForm.razor

<Textbox TItem="myPerson">
    <LabelTemplate>
        @context.Name
    </LabelTemplate>
</Textbox>
<Textbox TItem="myPerson">
    <LabelTemplate>
        @context.PhoneNumber
    </LabelTemplate>
</Textbox>

@code{

  Person myPerson = new Person { Name = "Jane Doe", PhoneNumber = "999 999 9999" };
    public class Person
    {
        public string Name { get; set; }
        public string PhoneNumber { get; set; }
    }
}

I'm passing in my Person object to each Textbox component's TItem property, and accessing it in the LabelTemplate using the @context syntax.

This might seem confusing at first, so please read up on it here

Edited It just depends on what you want to accomplish. With the Verbose syntax comes flexibility on the "implementation" side of the component. Instead of forcing an interface that might not work with a wide variety of models/classes, you are letting the implementation specify what to do.

If you want something less verbose/more rigid, you can do the following as well.

@implements ILabel 
<div class="textbox"> 
    <label>@Text</label> 
    <input type="text"/> 
</div> 
@code
{ 
    [Parameter] 
    public string Text { get; set; } 
} 

ILabel.cs

    public interface ILabel
    {
        string Text { get; set; }
    }

like image 172
Eric Holland Avatar answered Nov 15 '22 11:11

Eric Holland


I realize this is probably late, but I just struggled through this and found out it is SUPER easy! Thought I would put an easy answer out there for people looking.

Here is my OrdersNavigation.razor file (which I want to embed into a header):

<div class="nav-strip">
    <NavLink href="orders">
        <Icon Name="@Icons.Cart" /> List
    </NavLink>
    <NavLink href="orders/create">
        <Icon Name="@Icons.Plus" /> Create
    </NavLink>
</div>

Now here is my PageHeader.razor:

<div class="page-header">
    <h3>@Title</h3>
    @Navigation
</h3>
<hr />

@code {
    [Parameter] public string Title { get; set; } = "[TITLE]";
    [Parameter] public RenderFragment Navigation { get; set; }
}

Notice that the Navigation property is a RenderFragment - this is key. Now in my page, I can simply add it like this:

<PageHeader Title="Orders">
    <Navigation>
        <OrderNavigation />
    </Navigation>
</PageHeader>

You see here that the Title parameter is entered like usual, but the Navigation parameter is entered as an element of PageHeader! Really, you can put anything in the tags and it will render where you have @Navigation.

Reference: https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/

Took a shot at your example:

Label.razor

<label>@Text</label>

@code {
    [Parameter] public RenderFragment Text { get; set; }
}

TextBox.razor

<div class="textbox">
    <Label>
        <Text>
            <div>
                Embedded label <br />
                You can even drop components in here!
            </div>
        </Text>
    </Label>
    <input  />
</div>
like image 37
naspinski Avatar answered Nov 15 '22 11:11

naspinski