Reputation: 540
I have a base component PetTemplate
and a second PetDog
that inherits and uses the template of PetTemplate
. PetTemplate
has a method named ToggleDisplay
. My goal is when I click the button on the Index
page that invokes the PetDog.ToggleDisplay
method and show/hide the PetDog details on the page.
The "Inside" button in the sample code below works but "Outside" button don't. How can I invoke the ToggleDisplay
method from a page or a parent component correctly?
@page "/"
<button @onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog @ref="dog" />
@code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
@inherits PetTemplate
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
<div class="mt-3">
<button @onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: @Name</h3>
<div style="display:@display">
@ChildContent
</div>
</div>
@code {
string display = "none";
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
StateHasChanged();
}
}
Upvotes: 1
Views: 5899
Reputation: 45754
When you use
<PetDog @ref="dog" />
@code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
You actually create a reference to the PetDog component, and then try to call a derived method, dog.ToggleDisplay(), on object you have no reference to ( the instance of the PetTemplate). In order to make it work, you'll have to get a reference to the parent component (PetTemplate), and provide it to the derived component (PetDog), like this:
<div class="mt-3">
<button @onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: @Name</h3>
<div style="display:@display">
@ChildContent
</div>
</div>
@code {
string display = "none";
string val;
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
InvokeAsync(() => StateHasChanged());
}
}
@inherits PetTemplate
<PetTemplate @ref="petTemplate" Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
@code
{
PetTemplate petTemplate;
public PetTemplate PetTemplateProp { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
{
PetTemplateProp = petTemplate;
}
base.OnAfterRender(firstRender);
}
}
@page "/"
<button @onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog @ref="dog" />
@code {
PetDog dog;
void ShowPetDetails()
{
dog.PetTemplateProp.ToggleDisplay();
}
}
Note: Though Razor components are C# classes, you cannot treat them as normal classes. They behave differently. As for instance, you can't define a variable instance, and set its parameters, etc. outside of the component. At best, you can capture a reference to a component as well as call public methods on the component instance, as is done in the current sample. In short, component objects differ from normal classes.
It's also important to remember that each component is a separate island that can render independently of its parents and children.
But just wondering how can I change a component parameter value from outside of it, that inherited/uses a template. I tried the methods in the documentation or the resources I found, but it didn't work for my case
You should not (it was a warning) and probably cannot ( it may be now an error) change a component parameter's value outside of the component. As for instance, you can't capture a reference to a component and assign a value to its parameter property:
<PetTemplate @ref="petTemplate">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate petTemplate;
This is not allowed: petTemplate.Name="Dog"
as this is changing the parameter outside of its component. You can only do that like this:
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
Furthermore, modification of a parameter property from within the component itself is deprecated ( currently you should get a warning, at least that is what Steve Sanderson suggested to the Blazor team).
To make it clear, you should not modify the parameter property Name from within the PetTemplate component. A parameter property should be automatic property; that is, having a get and set accessors like this: [Parameter] public string Name { get; set; }
And you should not use it like this:
private string name;
[Parameter]
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
// Code to a method or whatever to do something
}
}
}
This is deprecated as it may have side effects. Component parameters should be treated as DTO, and should not be modified. If you wish to perform some manipulation of the parameter value, then copy it to a local variable, and do your thing.
Upvotes: 5
Reputation: 2080
As pointed out by @enet Blazor component inheritance doesn't behave exactly as one would intuitively expect. This is a cleaner approach when you want to control a UI functionality that can be controlled both internally and externally:
PetTemplate.razor
:
[Parameter]
public EventCallback OnToggleRequested {get;set;}
[Parameter]
public string Display {get;set;}
protected async Task RaiseToggle()
{
await OnToggleRequested.InvokeAsync();
}
PetDog.razor
:
<button @onclick="RaiseToggle">Show Details (Inside)</button>
Index.razor
:
<button @onclick="ToggleDisplay">Show Details (Outside)</button>
<PetDog OnToggleRequested="ToggleDisplay" Display="@display"/>
string display = "block";
void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
}
Note that the event can be used at level of hierarchy and you don't need to capture any references anywhere.
Upvotes: 1