Dev Blog

Связывание дочерних компонентов с родительским через CascadingParameter в Blazor

Предположим, родительский компонент должен иметь определённое содержимое и взаимодействовать с этим:

<Panel>
  @foreach (var p in ChildPanels){
     <SubPanel></SubPanel>
  }

  <SubPanel></SubPanel>
</Panel>

Компонент Panel принимает в содержимое определенный тип компонентов: SubPanel. И они могут динамически меняться в зависимости от наличия в коллекции ChildPanels.

Если просто выводить содержимое параметра ChildContent в Panel, то мы ничего не узнаем о типе того, что передаем.

[Parameter]
public RenderFragment ChildContent { get; set; }
<div>
    @ChildContent
</div>

Чтобы контейнер знал о своем содержимом и было взаимодействие между ними, нужно передать объект контейнера через CascadingParameter:

Panel:

    <div>
        <CascadingValue Value=this>
            @ChildContent
        </CascadingValue>
    </div>

SubPanel:

        [CascadingParameter]
        public Panel Panel
        {
            get => _panel;
            set
            {
                if (_panel != value)
                {
                    _panel = value;
                    _panel.AddSubPane(this);
                }
            }
        }

Метод AddSubPane из Panel добавляет в коллекцию панелей дочернюю панель SubPanel. Таким образом ими можно будет управлять и отрисовывать по нужным правилам.

Поскольку панели могут появляться и исчезать динамически, то так же важен порядок дочерних панелей. Это можно решить через делегат Func<int>:

SubPanel:

        [Parameter]
        public Func<int> GetIndex { get; set; }

Panel:

        public void AddSubPane(Panel pane)
        {
            if (!pane.Visible)
            {
               if (Panes.Contains(pane))
                   Panes.Remove(pane);  
            
                return;
            }
        
            if (!Panes.Contains(pane))
                Panes.Add(pane);
                              
            UpdateIndexesViaDelegate();
        }
        
        
        private void UpdateIndexesViaDelegate()
        {
            foreach (var p in Panes)
                p.Index = p.GetIndex();

            Panes = Panes.OrderBy(static a => a.Index).ToList();
        }

Использование Panel:

<Panel>
  @foreach (var p in ChildPanels){
     <SubPanel GetIndex="() => Array.IndexOf(ChildPanels.ToArray(), p)"></SubPanel>
  }

  <SubPanel GetIndex="() => ChildPanels.Count"></SubPanel>
</Panel>

Логика тут простая: удаление невидимой панели и добавление новой, а затем перерасчет порядкового индекса.