Blazor - How to make child component show validation messages?

7.1k Views Asked by At

I need to display validation messages if a nested-component is not properly filled in. The component is consumed by other parent-components and they need to get feedback on whether there are validation issues.

I have tried the following code for the nested-component and used the CanSubmit method. While the method correctly tells if there are validation problems the validation messages are not showing.

All the code below can be tested on blzorrepl: https://blazorrepl.com/repl/GvOQlvvv1789ra1G37

@if(editContext != null) {
    <EditForm EditContext="@editContext">
        <input type="text" @bind="testModel.Name" />
        <DataAnnotationsValidator />
        <ValidationSummary />
    </EditForm>
}

@code {
    [Parameter]
    public TestModel testModel
    {
        get { return (TestModel)editContext?.Model; }
        set { editContext = new EditContext(value); }
    }

    EditContext editContext;

    public bool CanSubmit()
    {
        return editContext.Validate();
    }
}

This is my parent-component code, a bit reduced but reproducing the problem:

<ChildComponent @ref="myTestComponent" testModel="testModel" />
<input type="button" @onclick="buttonClick" value="validate programmatically" />
<div>@testMessage</div>

@code {
    TestModel testModel = new TestModel();
    ChildComponent myTestComponent;
    string testMessage;

    void buttonClick()
    {
        testMessage = "not passed validation";

        if (myTestComponent.CanSubmit())
        {
            testMessage = "passed validation!";
        }
    }
}

The testMessage is only used to show the validation status.

What can I do to make parent-component cause the nested-component to show validation messages? I can only place the submit in the parent-component.

As it was requested, here is a more complete example of what I am doing, a list of items which can be edited inline as well as a form to add more instances. https://blazorrepl.com/repl/mlYwlQPm34bekYE824

3

There are 3 best solutions below

0
On BEST ANSWER

What was the original problem was assigning the EditContext. It was necessary to access it but it can be done via editForm.EditContext as in the example below.

Working example https://blazorrepl.com/repl/Glkmbcbr323MP1VS55

Parent component:

<ChildComponent @ref="myTestComponent" testModel="testModel" />
<input type="button" @onclick="buttonClick" value="validate programmatically" />
<div>@testMessage</div>

@code {
    TestModel testModel = new TestModel();
    ChildComponent myTestComponent;
    string testMessage;

    void buttonClick()
    {
        testMessage = "not passed validation";

        if (myTestComponent.CanSubmit())
        {
            testMessage = "passed validation!";
        }
    }
}

Child component:

@if(testModel != null) {
    <EditForm @ref="editForm" Model="@testModel">
        <input type="text" @bind="testModel.Name" />
        <DataAnnotationsValidator />
        <ValidationSummary />
    </EditForm>
}

@code {
    [Parameter]
    public TestModel testModel
    {
        get; set;
    }

    EditForm editForm;

    public bool CanSubmit()
    {
        return editForm.EditContext.Validate();
    }
}
4
On

I'll try to explain why your approach is not working and then suggest ways to solve it. I hope I understand your intentions correctly.

First you need to change the <input type="text" ...> to <InputText @bind-Value="..." />

When your method buttonClick is finished in your parent component, Blazor will call StateHasChanged on your component. It's part of the built-in logic of an EventHandler. This will trigger the component life cycle of your child component. During that cycle, the setter of your child component property testModel will be called, again. Blazor doesn't make any test for equality. (The only mighty check engine is the DiffierentialRenderTree at the end of a render cycle). That means a new EditContext will be created. This context, though, doesn't know about the validation error. Hence the message disappears. To prove that point, set a counter variable inside the setter and display it on the page. You will see this result.

enter image description here.

To avoid this scenario, you create the EditContext once, when the parameters are set, for instance.

@code {

    [Parameter]
    public TestModel testModel { get; set; }

    EditContext editContext;

   protected override void OnParametersSet()
   {
       base.OnParametersSet();
       if(editContext == null)
       {
           editContext = new EditContext(testModel);
       }
   }

    public bool CanSubmit()
    {
        return editContext.Validate();
    }
}

If you need to update the model but preserve the validation state, write a comment, and we can go from there.

enter image description here

4
On

I have a different approach.. Your Parent have to look like this

<CascadingValue Value="testModel">
    <EditForm Model="testModel">
        <ChildComponent @ref="myTestComponent" />
        <ObjectGraphDataAnnotationsValidator />
        <ValidationMessage For="@(()=>testModel.Name)" />
        <br />
        <button type="submit">test</button>
    </EditForm>
</CascadingValue>

the only thing you need to do now in your Child is like this

@if (testModel != null)
{
  <InputText @bind-Value="testModel.Name" />
}

@code {

    [CascadingParameter]
    public TestModel testModel { get; set; }
}

I personally think it's less code...