Reputation: 1401
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
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:
@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
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
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...");
}
}
}
<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
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:
<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:
@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