Динамические компоненты Razor (DynamicComponent)

Динамические компоненты Razor удобно использовать в том случае, когда мы заранее не знаем как будет строится пользовательский интерфейс. Например, наше приложение может использовать систему виджетов и пользователь сам определяет какие виджеты необходимо вывести на главной странице. В этом случае нам потребуется обеспечить динамическое создание компонентов.

Компонент <DynamicComponent>

<DynamicComponent> — это встроенный компонент Razor, использование которого в разметке выглядит следующим образом:

<DynamicComponent Type="componentType" Parameters="parameters" />

@code {
    private Type componentType = ...;
    private IDictionary<string, object> parameters = ...;
}

Параметр Type определяет тип компонента, который будет показан пользователю, а Parameters — список параметров, которые будут переданы в компонент.  Посмотрим на работу этого компонента на примере.

Пример использования DynamicComponent

В качестве примера, будем размещать на главной странице приложения (компонент Home.razor) различные дочерние компоненты, которые будет выбирать пользователь. Итак, создадим новое приложение Blazor Hybrid и добавим в папку Components следующие компоненты:

1. Компонент Clock.razor (часы)

<h4>Часы</h4>
@if (timer != null)
{
    <p>Дата: @time.ToString("D")</p>
    <p>Время: @time.ToString("T")</p>
}
@code {
    System.Threading.Timer? timer;
    
    DateTime time = DateTime.Now;
    private async void SetClock(object stateInfo)
    {
        time = DateTime.Now;
        await InvokeAsync(StateHasChanged);
    }
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        timer = new System.Threading.Timer(SetClock, new System.Threading.AutoResetEvent(false), 1000, 1000);
    }
}

2. Компонент Message.razor (сообщение)

<div class="alert alert-danger" role="alert">
    @Text
</div>
@code{
    [Parameter]
    public string Text { get; set; } = "Важное сообщение";
}

Третьим компонентов будет выступать уже имеющийся в приложении счётчик (Counter.razor) с небольшими изменениями.

3. Шаблонный компонент Counter.razor (счётчик)

@page "/counter"

<h4>Counter</h4>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    [Parameter]
    public EventCallback OnMaxValue { get; set; }

    private async Task IncrementCount()
    {
        if (currentCount == 10)
            await OnMaxValue.InvokeAsync();
        else
          currentCount++;
    }
}

Вот эти три компонента мы и будем динамически отображать на главной странице приложения. Теперь перейдем в файл Home.razor и изменим его код следующим образом:

@page "/"

<div class="d-flex justify-content-between">
    <div>
        <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" />
        <label class="form-check-label" for="clock">Часы</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="message" name="message" @bind-value="showMessage" />
        <label class="form-check-label" for="message">Сообщение</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" />
        <label class="form-check-label" for="counter">Счётчик</label>
    </div>
</div>

    @if (showClock)
    {
        <DynamicComponent Type="typeof(Clock)"></DynamicComponent>
    }

    @if (showMessage)
    {
        <DynamicComponent Type="typeof(Message)"></DynamicComponent>
    }


    @if (showCounter)
    {
        <DynamicComponent Type="typeof(Counter)"></DynamicComponent>
    }


@code{
    bool showCounter = false;
    bool showClock = false;
    bool showMessage = false;
}

Рассмотрим подробнее этот код. В разметке мы определили три html-элемента типа checkbox которым мы сделали привязку к их свойству value, используя три переменные в коде компонента:

bool showCounter = false; 
bool showClock = false; 
bool showMessage = false;

Например, для первого чекбокса код разметки выглядит так:

<div>
    <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" />
    <label class="form-check-label" for="clock">Часы</label>
</div>

В зависимости от состояния чекбокса мы либо показываем компонент на главной странице приложения, либо прячем:

@if (showClock)
{
    <DynamicComponent Type="typeof(Clock)"></DynamicComponent>
}

@if (showMessage)
{
    <DynamicComponent Type="typeof(Message)"></DynamicComponent>
}


@if (showCounter)
{
    <DynamicComponent Type="typeof(Counter)"></DynamicComponent>
}

Запустим приложение и посмотрим на результат:

Соответственно, когда вы отмечаете чекбокс — компонент появляется, убираете — компонент скрывается. Так в самом простом случае работает встроенный компонент <DynamicComponent>. Как вы могли заметить, наш компонент «Сообщение» выводит сообщение по умолчанию, хотя у этого компонента определен параметр Text, с помощью которого мы можем задавать собственный текст сообщения. Рассмотрим как мы это можем делать с использованием <DynamicComponent>.

Передача параметров в динамические компоненты Razor

Для передачи параметров в динамические компоненты Razor используется такой же механизм, как и при передаче произвольного набора параметров в html-элементы. То есть, мы должны сформировать словарь вида Dictionary<string, object>, где ключом является имя параметра динамического компонента и передать этот словарь в параметре Parameters <DynamicComponent>. Попробуем передать в наш компонент «Сообщение» произвольный текст — перепишем код Home.razor следующим образом:

@page "/"

<div class="d-flex justify-content-between">
    <div>
        <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" />
        <label class="form-check-label" for="clock">Часы</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="message" name="message" @bind-value="showMessage" />
        <label class="form-check-label" for="message">Сообщение</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" />
        <label class="form-check-label" for="counter">Счётчик</label>
    </div>
</div>


@if (showClock)
{
    <DynamicComponent Type="typeof(Clock)"></DynamicComponent>
}

@if (showMessage)
{
    <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent>
}


@if (showCounter)
{
    <DynamicComponent Type="typeof(Counter)"></DynamicComponent>
}


@code{
    bool showCounter = false;
    bool showClock = false;
    bool showMessage = false;

    Dictionary<string, object> messageParameters = new()
    {
        { "Text","Это текст для сообщения, переданный из Home.razor"}
    };
}

В коде компонента мы сформировали словарь с параметрами для компонента:

Dictionary<string, object> messageParameters = new()
{
    { "Text","Это текст для сообщения, переданный из Home.razor"}
};

А в разметке передали этот словарь компоненту:

@if (showMessage)
{
    <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent>
}

Результат будет следующим:

Здесь мы передали в параметрах обычную строку, однако, при необходимости, мы можем передавать в динамические компоненты различные объекты и методы обратного вызова для обработки событий компонента.

Обработка событий в динамических компонентах

Чтобы продемонстрировать передачу методов обратного вызова в динамические компоненты, воспользуемся компонентом Counter у которого определено следующее событие:

[Parameter]
public EventCallback OnMaxValue { get; set; }

Изменим код Home.razor следующим образом:

@page "/"

<div class="d-flex justify-content-between">
    <div>
        <input class="form-check-input" type="checkbox" id="clock" name="clock" @bind-value="showClock" />
        <label class="form-check-label" for="clock">Часы</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="message" name="message" @bind:get="showMessage" @bind:set="(value)=>{showMessage=value;}" />
        <label class="form-check-label" for="message">Сообщение</label>
    </div>

    <div>
        <input class="form-check-input" type="checkbox" id="counter" name="counter" @bind-value="showCounter" />
        <label class="form-check-label" for="counter">Счётчик</label>
    </div>
</div>


@if (showClock)
{
    <DynamicComponent Type="typeof(Clock)"></DynamicComponent>
}

@if (showMessage)
{
    <DynamicComponent Type="typeof(Message)" Parameters="messageParameters"></DynamicComponent>
}


@if (showCounter)
{
    <DynamicComponent Type="typeof(Counter)" Parameters="counterParameters"></DynamicComponent>
}

@code{
    bool showCounter = false;
    bool showClock = false;
    bool showMessage = false;

    private Dictionary<string, object> counterParameters = new();

    public Dictionary<string, object> messageParameters = new()
    {
        { "Text","Это текст для сообщения, переданный из Home.razor"}
    };

    private void OnCounterMaxValue()
    {
        messageParameters.Clear();
        messageParameters.Add("Text", "Счётчик достиг максимального значения");
        showMessage = true;
    }

    protected override void OnInitialized()
    {
        counterParameters[nameof(Counter.OnMaxValue)] = EventCallback.Factory.Create(this, OnCounterMaxValue);
    }
}

Первое, что мы изменили — это установили двухстороннюю привязку для чекбокса, отвечающего за вывод сообщения:

<div>
    <input class="form-check-input" type="checkbox" id="message" name="message" @bind:get="showMessage" @bind:set="(value)=>{showMessage=value;}" />
    <label class="form-check-label" for="message">Сообщение</label>
</div>

Зачем мы это сделали — показано далее. Второй момент — это мы добавили новый словарь

private Dictionary<string, object> counterParameters = new();

В котором мы будем передавать параметры для компонента Counter.

Третий момент — мы переопределили метод OnInitialized() где создали объект словаря и записали в него передаваемый параметр — метод обратного вызова:

protected override void OnInitialized()
{
    counterParameters[nameof(Counter.OnMaxValue)] = EventCallback.Factory.Create(this, OnCounterMaxValue);
}

Здесь стоит обратить внимание на то как передается обработчик события — мы воспользовались методом Create свойства Factory у EventCallback. Свойство Factory представлено объектом типа EventCallbackFactory — фабрика для создания экземпляров EventCallback и EventCallback<T>.

В метод EventCallbackFactory.Create() мы передаем два параметра:

  • receiver — приемник сообщений — любой объект, обрабатывающий событие
  • callback — действие Action<Object> — собственно, сам метод обратного вызова

Метод обратного вызова у нас выглядит следующим образом:

private void OnCounterMaxValue()
{
    messageParameters.Clear();
    messageParameters.Add("Text", "Счётчик достиг максимального значения");
    showMessage = true;
}

Здесь мы назначаем компоненту Message новое значение параметра Text и программно включаем чекбокс для показа сообщения, изменяя значение у showMessage . Именно по этому мы изменили простую привязку к свойству value чекбокса на двухстороннюю. Если бы мы этого не сделали, то программное изменение значения showMessage никак бы не влияло на состояние чекбокса.

Теперь запустим приложение и включим счётчик. Вид приложения будет следующим:

Как только счётчик достигнет значения 10, на одиннадцатом клике вид приложения изменится следующим образом:

Таким образом, можно сказать, что используя параметры динамического компонента, мы успешно передали в него метод обратного вызова для обработки событий. При этом, вы можете сразу показать сообщение послед запуска приложения, а потом прокликать счётчик и на одиннадцатом клике сообщение автоматически поменяется.

Итого

Динамические компоненты Razor в разметке представлены компонентами <DynamicComponent> и позволяют отображать обычные компоненты Razor в зависимости от различных факторов, которые трудно или невозможно предугадать заранее. В динамические компоненты можно передавать как обычные параметры, представленные простыми типами или целыми объектами, так и методы обратного вызова для обработки событий в дочерних компонентах.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии