new usage and dynamic pop-up window of Blazor component

Posted by Lee on Tue, 04 Jan 2022 07:11:36 +0100

1. Preface

stay Stateless components in Blazor In this article, I mentioned stateless components. Some people mentioned that there is no diff, and the performance may be worse when rendering complex model s. Indeed, this does exist. Implementing stateless components in the way described above does render whenever the properties change. Whether a stateless component renders depends more on the parent component. If the parent component does not need to be updated, the stateless component will not render naturally. In addition, some requirements, such as maps, need to be rendered every time you drag and zoom. For components purely used for data display, it is better to use stateless components. If you want stateless components not to render every time, you can implement a ShouldRender function yourself.

2. Must the IComponent interface be implemented?

stay Stateless components in Blazor In, I mentioned that two conditions need to be met for a component to be successfully compiled and used:

  1. Implement IComponent interface
  2. A virtual function with the following Declaration: protected virtual void buildrendertree (rendertree builder);

If we remove the implementation declaration of IComponent interface (i.e. delete only IComponent), can it be used? Obviously not, the VS compiler will prompt you that the component cannot be found by error:

RZ10012 Found markup element with unexpected name 'xx.DisplayCount'. If this is intended to be a component, add a @using directive for its namespace.

But think again, vs will put all * razor files are compiled into a class. Can't we directly use this class to create a new component? Of course, it's no problem.

3. Let's talk about the rendering of Blazor components

stay Stateless components in Blazor In, I talked about the rendering of Blazor, which actually renders the RenderFragmentDOM tree generated in the component. When we create a * After the razor file, the compiler will automatically help us generate the DOM in the component as RenderFragment. So whatever a * Whether the razor file inherits the componentbase class, or whether it implements the IComponent interface, as long as the second condition above - with a virtual function of BuildRenderTree - is met, the DOM edited in the file can be transformed into a RenderFragmentDOM tree. In the previous article, the StatelessComponentBase base base class of stateless component is declared as follows:

public class StatelessComponentBase : IComponent
{
    private RenderFragment _renderFragment;

    public StatelessComponentBase()
    {
        // Set the component DOM tree (how it is created)
        _renderFragment = BuildRenderTree;
    }
    
    ...
}

To put it bluntly, it's just that we played a trick and used the compiler to correct * Based on the compilation method of razor, renderFragment is automatically generated. But no one said_ renderFragment must be private. We can do this:

public class StatelessComponentBase 
{
    public RenderFragment RenderFragment { get; private set; }

    public StatelessComponentBase()
    {
        RenderFragment = BuildRenderTree;
    }
    ...
}

In this way, we can get the RenderFragmentDOM tree outside the component.

4. Create a component

In 3, we have exposed the RenderFragment in the component to the outside. Naturally, we can obtain it through an instance after new:

new Componment().RenderFragment

Let's take an example. It is modified based on the Counter page:

// DisplayCount component
@inherits StatelessComponentBase

<h3>DisplayCount</h3>
<p role="status">Current count: @Count</p>


@code {
    public int Count{ get; set; }
}

==================

// index page
@page "/"
<PageTitle>Index</PageTitle>

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

@renderFragment

@code {
    RenderFragment? renderFragment = null;
    private int currentCount = 1;

    Components.DisplayCount displayCount = new Components.DisplayCount();
    private void IncrementCount()
    {
        currentCount++;
        displayCount.Count = currentCount;
    }


    public async override Task SetParametersAsync(ParameterView parameters)
    {
        displayCount.Count = currentCount;
        renderFragment = displayCount.RenderFragment;
        await base.SetParametersAsync(parameters);
    }
}

The code runs without any problems:

With this little trick, you can realize the dynamic rendering of Blazor. When it comes to Blazor's dynamic rendering, some people may talk about dynamic component. DynamicComponent can dynamically render components by receiving the Type of components - Type and the Parameters of components - Parameters. Here is a different idea from dynamic component. DynamicComponent needs to write the label in the component file to mount. This article uses a new component to obtain the internal RenderFragment. As an aside, DynamicComponent does not inherit the componentbase class, which is similar to the structure of stateless components I proposed earlier.

5. Dynamic pop-up window 1

Think about it. When using WinForm, we only need new and show to open a form. Now when you have time for dynamic rendering, the pop-up component in Blazor, new and show is no longer a dream.

In the above method, after a component is new, it must be mounted in a component. Without mount points, Blazor components cannot be rendered on the page. When we think of mount points, we naturally think of creating a global container to implement them. When we call show, render the RenderFragment in the container component.

Modal.razor: the base class of modal, for which show and close methods are added. Because all modal components are new, they need to be mounted in blazer to render. Here, modal rendering is performed through the static attribute ModalContainer in the Container.

@inherits StatelessComponentBase

@code {

    public void Show()
    {
        Container.ModalContainer?.AddModal(this);
    }

    public void Close()
    {
        Container.ModalContainer?.RemoveModal(this);
    }
}

Container.razor: global container for mounting Modal.

<div style="position: absolute; top:0; width: 100%; height: 100%; z-index: 9999; pointer-events: none;
            display: flex; align-items:center; justify-content:center;">
    @foreach(var modal in Modals)
    {
        <div @key="modal" style="border: 1px solid #efefef; border-radius: 4px;">
            @modal.RenderFragment
        </div>
    }
</div>

@code {
    /// <summary>
    ///Because you need to be able to obtain the container instance in each new Modal,
    ///So here you need to use a static variable to reference itself during component initialization
    /// </summary>
    internal static Container? ModalContainer { get; private set; }


    private List<Modal> Modals { get; init; } = new List<Modal>();

    protected override Task OnInitializedAsync()
    {
        ModalContainer = this;
        return base.OnInitializedAsync();
    }

    internal void AddModal(Modal modal)
    {
        if (!Modals.Contains(modal))
        {
            Modals.Add(modal);
            StateHasChanged();
        }
    }

    internal void RemoveModal(Modal modal)
    {
        if (Modals.Contains(modal))
        {
            Modals.Remove(modal);
            StateHasChanged();
        }
    }

}

Next, when we need to add a Modal component, we only need to inherit Modal.

// Modal1.razor
@inherits Modal

<h3>Modal1</h3>

When using, we can use new and show:

@page "/modalDemo"
@using Instantiation.Components.DyModal1


<PageTitle>Modal Demo</PageTitle>

<button class="btn btn-primary" @onclick="()=>{modal1.Show();}">OpenModal1</button>
<button class="btn btn-primary" @onclick="()=>{modal1.Close();}">CloseModal1</button>

@code {
    Modal modal1 = new Modal1();
}

The effects are as follows:

Please note that in the last part, after Modal2 is closed and then opened, the count value of count does not change, that is, the internal state of Modal can be retained in this way. However, some interactions between Modal sub components and Modal root components (such as Modal1.razor) cannot be used, such as * * EventCallback * *.

See [Pages/ModalDemo.razor] for the code of this part( BlazorTricks/ModalDemo2.razor at main · zxyao145/BlazorTricks (github.com)).

6. Dynamic pop-up window 2

Of course, it can also be realized by using DynamicComponent, so you don't have to use new and show, you can directly use generic add < T > (), and you can directly use ComponentBase without exposing RenderFragment.

Container2.razor: you also need to define a global container:

<div style="position: absolute; top:0; width: 100%; height: 100%; z-index: 9999; pointer-events: none;
            display: flex; align-items:center; justify-content:center;">
    @foreach(var modal in Modals)
    {
        <div @key="modal" style="border: 1px solid #efefef; border-radius: 4px; pointer-events: all;">
            <DynamicComponent Type="modal" />
        </div>
    }
</div>

@code {
    /// <summary>
    ///Because you need to be able to obtain the container instance in each new Modal,
    ///So here you need to use a static variable to reference itself during component initialization
    /// </summary>
    internal static Container2? ModalContainer { get; private set; }


    private List<Type> Modals { get; init; } = new List<Type>();

    protected override Task OnInitializedAsync()
    {
        ModalContainer = this;
        return base.OnInitializedAsync();
    }

    internal void AddModal<T>()
    {
        var type = typeof(T);
        if (!Modals.Contains(type))
        {
            Modals.Add(type);
            StateHasChanged();
        }
    }

    internal void RemoveModal<T>()
    {
        var type = typeof(T);
        if (Modals.Contains(type))
        {
            Modals.Remove(type);
            StateHasChanged();
        }
    }
}

Modal3.razor: specific pop-up modal. Note that there is no need to write any inheritance

<h3>Modal3</h3>

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

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
        StateHasChanged();
    }
}

use:

@page "/modalDemo2"
@using Instantiation.Components.DyModal2

<PageTitle>Modal Demo2</PageTitle>

<button class="btn btn-primary" @onclick="()=>{Container2.ModalContainer?.AddModal<Modal3>();}">Open DynamicComponentModalIns</button>
<button class="btn btn-primary" @onclick="()=>{Container2.ModalContainer?.RemoveModal<Modal3>();}">Close DynamicComponentModalIns</button>

The effects are as follows:

Please note that after the Modal is closed and reopened in this way, the count value of count is reset to 0, that is, the internal state of Modal cannot be preserved in this way.

See [Pages/ModalDemo2.razor] for the code of this part( BlazorTricks/ModalDemo2.razor at main · zxyao145/BlazorTricks (github.com)).

7. Summary

This article describes how Blazor is used through new instantiation, which leads to the use of dynamic pop-up windows. This paper describes two methods of dynamic pop-up: one is new and show mentioned earlier, which requires more coding, and the other is to use the built-in component DynamicComponent of Blazor. Both methods have their own disadvantages. The first method can retain the internal state, but some functions of Modal will be lost in the interaction with components and sub components (not limited to the child components of Modal); The second method can retain all the functions of the component, but when Modal is opened again after it is closed, it cannot retain the previous internal state (in fact, this part can be solved, prompt: Render method of DynamicComponent).

Example code: BlazorTricks/02-Instantiation at main · zxyao145/BlazorTricks (github.com)

Topics: .NET Blazor