0

How to use ChildContent to set service property and share between component that have same inject? i got empty collection. the first step, if Header component have property to set. It add item to collection. Inside Table component i check if service have items or not. I have problem to control flow which to render first.

Lifecycle Service

builder.Services.AddScoped<TableService>();

Header.cs

public class Header 
{
    public string? Name { get; set; }
}

TableService.cs

public class TableService
{
    public List<Header> Headers { get; set;} = new();
}

Header.razor

@inject TableService TableService

@code {
    [Parameter] public string? Name { get; set; }

    protected override void OnParametersSet()
    {
        TableService.Headers.Add(new Header { Name = this.Name });
    }
}

Table.razor

@inject TableService TableService

@foreach (var header in TableService.Headers)
{
   <span>@header.Name</span>
}

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

Index.razor

<Table>
  <Header Name="Id"/>
  <Header Name="Email/>
</Table>
david stephen
  • 329
  • 4
  • 12
  • See https://stackoverflow.com/a/76258228/13065781 and https://stackoverflow.com/a/75944088/13065781 – MrC aka Shaun Curtis Jul 24 '23 at 12:09
  • @MrCakaShaunCurtis i update my TableService to use ObservableCollection that have CollectionChanged event. Table component listen to that event on Table first Initialized if CRUD operation do StateHasChanged(). Still empty. [Check this](https://pastebin.com/NVWYVxZB) – david stephen Jul 24 '23 at 12:31

1 Answers1

0

This looks like code from a dynamic table.

First, do you really want the scope of your TableService to be for the lifetime of the SPA and shared by multiple instances of your table?

My guess is no, so you need to scope the your TableState object to the page holding the table. You can use a cascade to make it available to all the page components.

Here's my TableState. It controls access to the list to trigger HeadersHaveChanged events when things change. I've changed the Header object to a simple readonly struct to make it and the Headers property immutable.

public readonly record struct Header(string Name);

public class TableState
{
    private readonly List<Header> _headers = new();

    public IEnumerable<Header> Headers => _headers.AsEnumerable(); 
    public event EventHandler? HeadersHaveChanged;

    public void AddHeader(Header header)
    {
        _headers.Add(header);
        this.HeadersHaveChanged?.Invoke(this, EventArgs.Empty);
    }
}

My Index now looks like this:

<CascadingValue Value="_tableState" IsFixed>
    <Table>
        <Header Name="Id" />
        <Header Name="Email" />
    </Table>
</CascadingValue>

@code {
    private TableState _tableState = new();
}

Table looks like this. It renders the child content on the first render [which registers the columns] and renders the column code on subsequent renders.

@implements IDisposable

@if (_firstRender)
{
    @ChildContent
    _firstRender = false;
}
else
{
    <table class="table">
        <tr>
            @foreach (var header in _tableState.Headers)
            {
                <th>@header.Name</th>
            }
        </tr>
    </table>
}

@code {
    [CascadingParameter] private TableState _tableState { get; set; } = default!;
    [Parameter] public RenderFragment? ChildContent { get; set; }

    private bool _firstRender;

    protected override void OnInitialized()
    {
        ArgumentNullException.ThrowIfNull(_tableState);
        _firstRender = true;
        _tableState.HeadersHaveChanged += this.OnHeadersChanged;
    }

    // Do a yield to get the first render
    protected async override Task OnInitializedAsync()
        => await Task.Yield();

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

    // Isn't absolutely necessary in this casde as the _tableState object is of the same scope
    public void Dispose()
        => _tableState.HeadersHaveChanged -= this.OnHeadersChanged;
}

And Header

@code {
    [Parameter, EditorRequired] public string Name { get; set; } = "Not Set";
    [CascadingParameter] private TableState _tableState { get; set; } = default!;

    protected override void OnInitialized()
    {
        ArgumentNullException.ThrowIfNull(_tableState);
        _tableState.AddHeader(new(Name));
    }
}
MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31