1

Here a page, myPage.razor

@page "/myPage"
<div class="row">
    <div class="col">
        <ComponentA />
    </div>
</div>

In the ComponentA I use a ComponentB

<div class="row">
    <div class="col">
        <ComponentB />
    </div>
</div>

In the myPage.razor.cs there is an instance of a class Person. I'd like pass this instance to ComponentA and to ComponentB.

I tried with [Parameter] and [CascadingParameter] without any success.

Is there a way to do this ?

Thanks,

Update 1: The Microsoft sample below works but that's not do what I want (below the child code). Microsoft

@page "/parameter-parent"
<h1>Child component (without attribute values)</h1>
<ParameterChild />
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"  Body="@(panelBody)" />
@code {
    PanelBody panelBody = new PanelBody() { Text = "Set by parent.", Style = "italic" };
}

//Child
@using Testing.Pages
<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">@Title</div>
    <div class="card-body" style="font-style:@Body.Style">
        @Body.Text
    </div>
</div>

@code {
    [Parameter]
    public string Title { get; set; } = "Set By Child";

    [Parameter]
    public PanelBody Body { get; set; } =
        new()
        {
            Text = "Set by child.",
            Style = "normal"
        };
}

But I have to receive the object (not create a new one), display in an <input type="text"/> be able to change the text. The new value is update in the object in the ParameterParent. I tried the code below but Body is null all the time (the parent is the same then code above), I tried CascadingParameter too

@using Testing.Pages
<div class="card w-25" style="margin-bottom:15px">
    <div class="card-header font-weight-bold">@Title</div>
    <div class="card-body" >
        @Body.Text
    </div>
</div>

<InputText id="name" @bind-Value="Body.Text" />

@code {
    [Parameter]
    public string Title { get; set; } = "Set By Child";

    [Parameter]
    public PanelBody Body { get; set; }
}
6
  • 1
    Parameter works. Post what you actually tried. The code you posted has no parameters Commented Sep 29, 2022 at 9:33
  • I know, it's the code I would like to add parameter. Commented Sep 29, 2022 at 9:43
  • You wrote without success. What did you try and what didn't work? Have you checked the docs on component parameters? - add [Parameter] to the property you want to expose in ComponentB and use that as an attribute, eg <ComponentB MyProperty=@_whatever /> Commented Sep 29, 2022 at 9:48
  • I tried the same solution than Henk below, I got the same error described in the comments (compilation ok) but error at runtime Commented Sep 29, 2022 at 9:54
  • @Kris-I have you tried the documentation examples? Parameters work. All components, even the built-in ones, use [Parameter], not some internal black-box mechanism. I've been using component parameters for two years. People can't guess what's wrong with code you didn't post. I'd bet you didn't add the parameter in ComponentB. Or you tried to use a string literal instead of @(_someField) to pass the value Commented Sep 29, 2022 at 10:03

2 Answers 2

3

There are several ways to do multi-component two way communications.

You can create a notification service and subscribe components to notifications - here's a question and answer that shows you how to do that How can I trigger/refresh my main .RAZOR page from all of its sub-components within that main .RAZOR page when an API call is complete?.

Here is how to do it with a cascaded context object with notifications - similar in many ways to EditContext.

First the context object. We don't mix delegates and events with our data objects, so we create a specific object for the purpose that contains our edit model and it's associated events.

Here's PersonEditContext:

public class PersonEditContext
{
    public Person Person { get; set; } = new();
    public Action? Updated { get; set; }

    public void NotifyUpdated()
        => this.Updated?.Invoke();
}

Next ComponentA.

It:

  1. Picks up the cascaded context and create an EditContext from the Person instance.
  2. Wires up a handler to the edit context's OnFieldChanged event which raises the Action in the PersonEditContext cascaded instance.
  3. Wires up a handler to the PersonEditContext cascaded instance Updated Action to trigger a render event on the component.
  4. Implements IDisposable to tear down the handler wiring.
  5. itsMe prevents a render event if the component itself raises the event.
@implements IDisposable

<h3>ComponentA</h3>
<div>@personContext.Person.FirstName</div>
<div>@personContext.Person.LastName</div>
<p></p>
<hr />

<EditForm EditContext=this.editContext OnValidSubmit=HandleValidSubmit>
    <!-- Input2 -->
    <InputText id="name" @bind-Value="personContext.Person.FirstName" />
</EditForm>

@code {
    private EditContext? editContext;
    private bool itsMe;
    private Person person => personContext.Person;
    [CascadingParameter] private PersonEditContext personContext { get; set; } = new();

    protected override void OnInitialized()
    {
        editContext = new EditContext(person);
        editContext.OnFieldChanged += OnFieldChanged;
        personContext.Updated += Updated;
        base.OnInitialized();
    }

    private void HandleValidSubmit()
    {
    }

    private void OnFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        itsMe = true;
        personContext.NotifyUpdated();
    }

    private void Updated()
    {
        if (!itsMe)
            StateHasChanged();

        itsMe = false;
    }

    public void Dispose()
    {
        if (editContext is not null)
            editContext.OnFieldChanged -= OnFieldChanged;

        if (personContext is not null)
            personContext.Updated -= Updated;
    }
}

And MyPage which is very similar:

@page "/"
@implements IDisposable

<EditForm EditContext=this.editContext OnValidSubmit=HandleValidSubmit>
    <InputText id="name" @bind-Value="person.FirstName" />
</EditForm>

<CascadingValue Value="personContext" IsFixed="true">
    <ComponentA />
    <ComponentB />
</CascadingValue>

@code {
    private EditContext? editContext;
    private PersonEditContext? personContext;
    private Person person => personContext?.Person ?? new();
    private bool itsMe;

    protected override void OnInitialized()
    {
        personContext = new PersonEditContext { Person = new Person { FirstName = "Nancy", LastName = "Dvolio" } };
        editContext = new EditContext(person);
        editContext.OnFieldChanged += OnFieldChanged;
        personContext.Updated += Updated;
    }

    private void HandleValidSubmit()
    {
        // Whatever
    }

    private void OnFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        itsMe = true;
        personContext?.NotifyUpdated();
    }

    private void Updated()
    {
        if (!itsMe)
            StateHasChanged();

        itsMe = false;
    }

    public void Dispose()
    {
        if (editContext is not null)
            editContext.OnFieldChanged -= OnFieldChanged;

        if (personContext is not null)
            personContext.Updated -= Updated;
    }
}

Here's ComponentB to demonstrate the multi-component behaviour:

@implements IDisposable
<div class="bg-primary text-white p-2 m-2" >
    <h3>ComponentB</h3>
    <div>@person.FirstName</div>
    <div>@person.LastName</div>
</div>

@code {
    private Person person => personContext.Person;
    [CascadingParameter] private PersonEditContext personContext { get; set; } = new();

    protected override void OnInitialized()
    {
        personContext.Updated += Updated;
        base.OnInitialized();
    }

    private void Updated()
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        if (personContext is not null)
            personContext.Updated -= Updated;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

When I copy the @code {} section to MyPage.razor.cs, I get plenty of error 'editContext' does not exists, personContext does not exist, StateHasChanged does not exists. Any idea why ? The files are named ComponantA.razor and ComponantA.razor.cs with a partial class in named with the same name than the component
Can you show your MyPage.razor and MyPage.razor.cs. Your errors suggest you probably have a typo somewhere.
1

It is rather straightforward:

@page "/myPage"
<div class="row">
    <div class="col">
        <ComponentA Item="myItem" />
    </div>
</div>

@code { ItemType myItem = new(); }

and

<div class="row">
    <div class="col">
        <ComponentB Item="Item" />
    </div>
</div>

@code
{
  [Parameter] public ItemType Item { get; set; }
}

Component B should have exactly the same Parameter as Component A.

3 Comments

Error: System.InvalidOperationException: Object of type 'ComponentA' does not have a property matching the name 'Item'. By the way the intelliSense does not set Item property
@Kris-I [Parameter] works. I've been using components with parameters for the last 2 years. Post the actual code you tried. Something others can copy and try to compile
The compilation is ok it's when I load the page the problem. Is it normal than 'Item" property does not appear in the intellisense ? I tried with a simple string and not an object I get the same error 'MyPage' does not have a property matching the name 'Item'.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.