Henkolicious
Henkolicious

Reputation: 1401

Blazor button, use parent component @onclick

Is it possible to use the parent component method @onclick, or do I need to invoke it from the child?

Let's say I want to invoke parent method Foo().

Parent

@page "/"

// Custom component button
<Button Text="Some text" @onclick="Foo" Apperance="@Style.secondary" /> 


@code {
    async Task Foo() 
    {
        // ...
    }
}

Child

<button class="btn @_cssClass"><span>@Text</span></button>

@code {
    public enum Style
    {
        primary,
        secondary,
        tertiary,
        danger
    }

    [Parameter]
    public string Text { get; set; }
    [Parameter]
    public Style Apperance { get; set; }
    private string _cssClass { get; set; }

    protected override void OnInitialized()
    {
        _cssClass = Apperance switch
        {
            Style.secondary => "btn--secondary",
            Style.tertiary => "btn--tertiary",
            Style.danger => "btn--danger",
            _ => "btn--primary"
        };
    }
}

I get this error:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Object of type 'Tidrapport.Client.Components.Button' does not have a property matching the name 'onclick'.
System.InvalidOperationException: Object of type 'Tidrapport.Client.Components.Button' does not have a property matching the name 'onclick'.

I do want a child-button component, and when it's clicked (rendered in parent) - I want to invoke a method in the parent component.

Upvotes: 9

Views: 12653

Answers (4)

Craig Brown
Craig Brown

Reputation: 2431

You can use attribute splatting which lets you continue using the Razor @onclick attribute instead of declaring a callback.

Just add the following to your child class:

  1. A parameter that captures unmatched values
  2. The @attributes Razor attribute on the child element.

For example, have a Button.razor file that looks like this:

<button class="btn" @attributes="AdditionalAttributes">@Text</button>

@code {

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

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> AdditionalAttributes { get; set; }
}

Then you can call it like this:

<Button Text="Some text" @onclick="Foo" />

Note that this will apply all unmatched attributes, not just @onclick, and will overwrite any attributes that were declared before it. This may not be desirable in some situations, in which case you can just use an EventCallback.

Upvotes: 8

Henkolicious
Henkolicious

Reputation: 1401

So I've found the answer to my question, you design an EventCallback found in the docs.

So for me, it looks like this.

Parent

@page "/"

// Custom component button
<Button Text="Some text" OnClickCallback="@Foo" Apperance="@Style.secondary" /> 

@code {
    async Task Foo() 
    {
        // ...
    }
}

Child

<button class="btn @_cssClass" @onclick="OnClickCallback"><span>@Text</span></button>

@code {
    public enum Style
    {
        primary,
        secondary,
        tertiary,
        danger
    }

    [Parameter]
    public string Text { get; set; }
    [Parameter]
    public Style Apperance { get; set; }
    private string _cssClass { get; set; }
    [Parameter]
    public EventCallback OnClickCallback { get; set; } // this is where to you pass the parent function to be executed as a callback

    protected override void OnInitialized()
    {
        _cssClass = Apperance switch
        {
            Style.secondary => "btn--secondary",
            Style.tertiary => "btn--tertiary",
            Style.danger => "btn--danger",
            _ => ""
        };
    }
}

So the onclick event is bound to the eventhandler you design in the child component. It can be of type T if you want to use arguments, more on that in the docs.

Upvotes: 8

enet
enet

Reputation: 45626

So, I do want a child-button component, and when it's clicked - I want to invoke a method in the parent compent.

You ChildComponent should look like this:

<button type="button" @onclick="CallMethodParent">Call a method on parent component</button>


    @code {
    
        [Parameter]
        public EventCallback<string> ParentMethod { get; set; }
    
        private async Task CallMethodParent()
        {
            if(ParentMethod.HasDelegate)
            {
              await  ParentMethod.InvokeAsync("A message from child component...");
            }
        }
    
    }

Usage

<p>@message</p>

<ChildComponent ParentMethod="ParentMethod"/>

@code{
    private string message;

    private void ParentMethod(string message)
    {
        this.message = message;
    }

}

Note: The main thing to take from this is that you should define a parameter EventCallback property in the child component, which stores a method defined in the parent component.

Hope this helps...

Upvotes: 2

enet
enet

Reputation: 45626

You can't use the @onclick directive that way... This directive is used with Html elements, instructing the compiler to create event handler for the click event, as for instance:

<button type="button" @onclick="ClickEventHandler">Click me</button>

@code{
   private void ClickEventHandler()
   {
      Cosole.WriteLine("Hello Blazor");
    }
}

When you bind components, however, you should do something like this:

ChildComponent.razor

<input type="text" @bind="Text" @bind:event="oninput" />

@code {
        private string text { get; set; }

        [Parameter]
        public string Text
        {
            get { return text; }
            set
            {
                if (text != value) {
                    text = value;
                    if (TextChanged.HasDelegate)
                    {
                        TextChanged.InvokeAsync(value);
                    }
                }
            }
        }

        [Parameter]
        public EventCallback<string> TextChanged { get; set; }
    }

As you can see, I've defined a parameter Property named Text... In order to create two-way data binding, you should also define an EventCallback 'delegate' whose role is to update the bound object in the parent component. Thus, in the parent component you'll code:

<ChildComponent @bind-Text="Text" />

Note that the EventCallback delegate's name is composed of the Text word plus the word Changed.

The instruction @bind-Text binds the Text property defined in the child component to the Text property (see code below) defined in the parent component.

Thus the rule is @bind- plus the name of a property in the child component, as for instance Value which goes to @bind-Value and whose value should be the name of a property, say, MyValue and thus: @bind-Value="MyValue"

In the child component, however, you should define a parameter property named Value and an EventCallback named ValueChanged...

Here's a complete working code snippet demonstrating two-way data binding between a parent component and its child:

ParentComponent.razor

@page "/ParentComponent"

<h1>Parent Component</h1>

<input type="text" @bind="Text" @bind:event="oninput" />
<p></p>

<ChildComponent @bind-Text="Text" />


@code {
    [Parameter]
    public string Text { get; set; } = "Hello Blazor";
}

Hope this works...

Upvotes: 3

Related Questions