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; }
}