Matthew Layton
Matthew Layton

Reputation: 42229

How to render a component passed as an attribute

Assume Row is a component like so:

<div class="row">
    <div class="col-6">
        @Column1
    </div>
    <div class="col-6">
        @Column2
    </div>
</div>

@code
{
    [Parameter]
    public ComponentBase Column1 { get; set; }

    [Parameter]
    public ComponentBase Column2 { get; set; }
}

Then assume that we use this like so:

<Row Column1="foo" Column2="foo"></Row>

@code {
    private readonly SomeComponent foo = new();
    private readonly SomeComponent bar = new();
}

I'd expect a row containing two columns containing the rendered components for foo and bar, but instead I get two columns containing "Example.Shared.Components.SomeComponent"

How then do you render sub-components passed into other components? Is there a better way to do this (i.e. something like ng-content in Angular?

As a side note, I'm looking for solutions that allow this programatically (as above), and using markup (as below):

<Row>
    <Column>
        <Foo></Foo>
    </Column>
    <Column>
        <Bar></Bar>
    </Column>
</Row>

Upvotes: 1

Views: 361

Answers (4)

Henk Holterman
Henk Holterman

Reputation: 273179

What you want is templated components

You still can't use SomeComponent foo = new();.

Instantiation is left to the renderer. But consider that a good thing, it lets you slip in an (item) context.

You'll also want to look at Cascading values, to expose the Row to the Columns.

And finally, there are plenty of commercial and Open versions of a Blazor DataGrid out there. Don't reinvent the whole wheel.

Upvotes: 2

Bennyboy1973
Bennyboy1973

Reputation: 4208

I'd recommend not thinking of filling objects with data and passing the objects around. I'd think of Components as the expression of existing data into the html space. Note how for each layer of data, I have nested Razor components. So you pass DATA into the Table component, not RenderFragments or any other kind of object. The following is simplified for clarity.

class TableData {
    public List<RowData> Rows;
}
class RowData {
    public List<ColumnData> Columns;
}
class ColumnData {
    public string DisplayString;
}

TableComponent.razor

@foreach (var row in TableData.Rows){
    <DataRowComponent Data=row />
}
@code {
    [Parameter]
    public TableData TableData{get; set;}
}

DataRowComponent.razor

@foreach (var column in Data.Columns ){
    <DataColumnComponent Data=column />
}
@code {
    [Parameter]
    public RowData Data {get; set;}
}

DataColumnComponent.razor

<div>
    @Data.DisplayString
</div>

@code {
    [Parameter]
    public ColumnData Data {get; set;}
}

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30001

How about:

<div class="row">
    <div class="col-6">
        @Column1
    </div>
    <div class="col-6">
        @Column2
    </div>
</div>

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

    [Parameter]
    public RenderFragment Column2 { get; set; }
}

And then:

<Row>
  <Column1>
    <SomeComponent value="@somevalue"/>
  </Column1>
  <Column2>
   <SomeOtherComponent/>
  </Column2>
</Row>

As already stated, the Renderer instantiates all components and attaches them to the RenderTree.

Also note that all components don't necessarily inherit from ComponentBase, but they do all implement IComponent.

Upvotes: 2

Mayur Ekbote
Mayur Ekbote

Reputation: 2080

Blazor is mostly declarative. So you should:

<Row>
    <Column1>
        <Foo></Foo>
    </Column1>
    <Column2>
        <Bar></Bar>
    </Column2>
</Row> 

You don't need to do new Foo() / new Bar()

Of course, you can take the very circuitous route of programmatically populating the RenderFragment. But it is generally not worth it. Instead, use Generic components and Polymorphism.

Upvotes: 1

Related Questions