How can I declare an array for ChildContent RenderFragment

Viewed 63

I have wrote 2 components:

  • MyLine.razor
  • MyTable.razor

Here is how I want to use this components:

<MyTable>
  <MyLine />
  <MyLine />
  <MyLine />
</MyTable>

Here is what I've wrote for MyTable.razor:

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

It works, but I want to count all "MyLine" components.

Here is what I've tried:

   [Parameter]
   public RenderFragment<MyLine> ChildContent { get; set; }

   [Parameter]
   public RenderFragment<List<MyLine>> ChildContent { get; set; }

But it does not work.

Any idea ?

Thanks

2 Answers

Inside MyTable component define a list that will keep track of MyLine children and two methods for adding and removing items from the list. Also cascade MyTable component to the child components.

MyTable.razor:

<CascadingValue Value="this" IsFixed>
    @ChildContent 
</CascadingValue>

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

    private readonly List<MyLine> _lines = new();

    public void AddLine(MyLine line)
    {
        if (!_lines.Contains(line))
        {
            _lines.Add(line);
            StateHasChanged();
        }
    }

    public void RemoveLine(MyLine line)
    {
        if (_lines.Contains(line))
        {
            _lines.Remove(line);
            StateHasChanged();
        }
    }
}

Then inside MyLine component you can use the cascading parameter to get a reference to the parent component. MyLine will pass itself to the parent list when it's initialized and will remove itself from the parent list when it's disposed.

MyLine.razor:

@implements IDisposable

...

@code {
    [CascadingParameter]
    public MyTable MyTable { get; set; }

    protected override void OnInitialized()
    {
        MyTable.AddLine(this);
    }

    public void Dispose()
    {
        MyTable.RemoveLine(this);
    }
}

Now inside parent component you can do _lines.Count to get the result you want.

BlazorFiddle: https://blazorfiddle.com/s/v6uj2o6m

Also I would like to point to an example in the documentation for cascading values and parameters where it states:

TabSet component can provide itself as a cascading value that is then picked up by the descendent Tab components.

https://docs.microsoft.com/en-us/aspnet/core/blazor/components/cascading-values-and-parameters?view=aspnetcore-6.0#pass-data-across-a-component-hierarchy

Sometimes it is useful to cascade the parent component to its child components and there is nothing wrong in doing it.

Not sure who needs to know what, so I've wired in an event to raise when lines are added or removed from the collection.

First a data object to hold the data you want to cascade (don't cascade a component):

public class MyTableData
{
    private List<MyLine> _lines { get; set; } = new List<MyLine>();

    public IEnumerable<MyLine> Lines => _lines;
    public event EventHandler? LinesChanged;

    public void AddLine(MyLine line)
    {
        if (!_lines.Contains(line))
        {
            _lines.Add(line);
            this.LinesChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public void RemoveLine(MyLine line)
    {
        if (_lines.Contains(line))
        {
            _lines.Remove(line);
            this.LinesChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

Now MyLine. I'm not sure if your doing anything dynamically with the adding and removing lines so I implemented a remove in Dispose. Dispose should get called by the render if it removes a line instance from the render tree.

@implements IDisposable
<h3>MyLine</h3>

@code {
    [CascadingParameter] private MyTableData? TableData { get; set; }

    protected override void OnInitialized()
    {
        if (this.TableData is not null)
            TableData.AddLine(this);
    }

    public void Dispose()
    {
        if (this.TableData is not null && this.TableData.Lines.Contains(this))
            TableData.RemoveLine(this);
   }
}

And MyTable. It registers an event handler to ensure the component gets rendered (displays the correct number of lines) when this.TableData.Lines.Count() changes.

@implements IDisposable

<h3>MyTable</h3>
<CascadingValue Value=TableData>
    @this.ChildContent
</CascadingValue>
<div>
    Lines: @this.TableData.Lines.Count();
</div>

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

    public MyTableData TableData { get; set; } = new MyTableData();

    protected override void OnInitialized()
        => TableData.LinesChanged += this.OnLinesChanged;

    private void OnLinesChanged(object? sender, EventArgs e)
        => this.InvokeAsync(StateHasChanged);

    public void Dispose()
        => TableData.LinesChanged -= this.OnLinesChanged;
}
Related