FFB
FFB

Reputation: 87

How to dynamically build parent and child components with rendertreebuilder?

I need help with dynamically building/generating multistep component with rendertreebuilder.

I have a two components: MultistepComponent (parent) and MultiStepNavigation (child). I also have a component builder class to build these components.

Rendering components does not work, I get an error:

Object reference not set to an instance of an object.

That is because the MultiStepComponent property in MultiStepNavigation is empty. Using a component in razor page does not give this error.

I will be posting a lot of code, but you don't have to read every detail. I just want to give all the information.

MultiStepComponent.razor

<CascadingValue Value="this">
    <div id="@Id">
        <ul class="nav nav-pills nav-justified">
            @foreach (var step in Steps)
            {
                <li id="step-@(StepsIndex(step) + 1)" class="nav-item">
                    <a class="nav-link @((ActiveStep == step) ? "active" : "")" href="javascript: void(0)"
                       @onclick="@(e=> SetActive(step))">@step.Name</a>
                </li>
            }
        </ul>
        <div id="container-fluid">
            <div class="navigatingBtns">
                <button class="btn btn-primary btn-lg" type="button"
                        disabled="@(ActiveStepIndex == 0)" @onclick="GoBack">
                    Previous
                </button>
                <button class="btn btn-primary btn-lg"
                        type="@(IsLastStep ? "submit" : "button")" @onclick="GoNext">
                    @(IsLastStep ? "Submit" : "Next")
                </button>
            </div>
            @ChildContent
        </div>
        </div>
</CascadingValue>

MultiStepComponent.razor.cs

public partial class MultiStepComponent { 

        protected internal List<MultiStepNavigation> Steps = new List<MultiStepNavigation>();

        [Parameter]
        public string Id { get; set; }

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

        [Parameter]
        public MultiStepNavigation? ActiveStep { get; set; }

        [Parameter]
        public int ActiveStepIndex { get; set; }

        public bool IsLastStep { get; set; }

        protected internal void GoBack()
        {
            if (ActiveStepIndex > 0)
            {
                SetActive(Steps[ActiveStepIndex - 1]);
            }
        }

        protected internal void GoNext()
        {
            if (ActiveStepIndex < Steps.Count - 1)
            {
                SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]);
            }
        }

        protected internal void SetActive(MultiStepNavigation step)
        {
            ActiveStepIndex = StepsIndex(step);
            if (ActiveStepIndex == Steps.Count - 1)
            {
                IsLastStep = true;
            }
            else
            {
                IsLastStep = false;
            }
        }

        public int StepsIndex(MultiStepNavigation step) => StepsIndexInternal(step);
        protected int StepsIndexInternal(MultiStepNavigation step)
        {
            return Steps.IndexOf(step);
        }

        protected internal void AddStep(MultiStepNavigation step)
        {
            Steps.Add(step);
        }

        protected override void OnAfterRender(bool firstRender)
        {
            if (firstRender)
            {
                SetActive(Steps[0]);
                StateHasChanged();
            }
        }
    }

MultiStepNavigation.razor

@if (MultiStepComponent.ActiveStep == this) { 
    <div id="step-@(MultiStepComponent.StepsIndex(this) + 1)">
        @ChildContent
    </div>
}

MultiStepNavigation.razor.cs

public partial class MultiStepNavigation
    {
        [CascadingParameter]
        protected internal MultiStepComponent MultiStepComponent { get; set; }

        [Parameter]
        public RenderFragment ChildContent { get; set; } = default!;

        [Parameter]
        public string Name { get; set; } = "";

        protected override void OnInitialized()
        {
            if(MultiStepComponent != null)
            {
                MultiStepComponent.AddStep(this);
            }
        }
    }

code to build componnts:

renderFragment = b =>
            {
                b.OpenComponent<MultiStepComponent>(0);
                b.AddAttribute(1, "id", "MultiStepContainer");
                b.OpenComponent<MultiStepNavigation>(2);
                b.AddAttribute(3, "Name", "First Step");
                b.CloseComponent();
                b.CloseComponent();
            };

Making a component on razor page myself does not give an error. It looks like this:

<MultiStepComponent Id="MultiStepContainer">
                <MultiStepNavigation Name="First Step"></MultiStepNavigation>    
            </MultiStepComponent>

What did I do wrong??

Upvotes: 0

Views: 1699

Answers (2)

DEV EG
DEV EG

Reputation: 11

You have to create a new builder if you want to add attributes on the child component :

renderFragment = b1 =>
{
    b1.OpenComponent<MultiStepComponent>(0);
    b1.AddAttribute(1, "id", "MultiStepContainer");
    b1.AddAttribute(2, "ChildContent", (RenderFragment)((b2 => 
    { 
        b2.OpenComponent<MultiStepNavigation>(3);
        b2.AddAttribute(4, "Name", "First Step");
        b2.CloseComponent();
    }));
    b1.CloseComponent();
};

Notice that a component can only have one childContent, so if the component have 2 childs components you have to code a builder2 and builder3 inside the same ChildContent,

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30046

You code doesn't take account of the fact that MultiStepNavigation is child content of MultiStepComponent.

Look in /obj/debug/net5.0/razor/..., find your razor version of your component and look at the code.

You probably need to do something like this:

private RenderFragment _MultiStepNavigationFragment => b =>
{
        b.OpenComponent<MultiStepNavigation>(0);
        b.AddAttribute(1, "Name", "First Step");
        b.AddAttribute(2, "ChildContent", this.ChildContent);
        b.CloseComponent();
};
private RenderFragment _MultiStepComponentFragment => b =>
{
        b.OpenComponent<MultiStepComponent>(0);
        b.AddAttribute(1, "id", "MultiStepContainer");
        b.AddAttribute(2, "ChildContent", this._MultiStepNavigationFragment);
        b.CloseComponent();
};

Upvotes: 4

Related Questions