Blazor seems aware of object changes within components without any external user intervention, for example if I have:
<p>@Data.SomeProperty</p>
<input type="text" @bind="Data.SomeProperty" />
@code {
[Parameter]SomeModel Data {get;set;} // How do I know when this/its internals have changed?
}
Whenever the input changes it seems to understand that it needs to refresh the UI (not sure if its whole UI or just the specific parts changed). So is there a way for a developer to hook into this and be notified when something has changed?
My use case here is I want to be able to have a title
field for a component (ComponentA) but the title is computed within a child component (ComponentB) so I want to be able to have ComponentB raise an event or notify in some way to indicate the computed property has changed then ComponentA can take that change and update its title.
So is there a way to be able to notify externally that the models changed WITHOUT altering the models internals? (Happy for it to be a custom EventCallback
I somehow trigger as long as logic for it lives inside the component not the model)
CodePudding user response:
You already have [Parameter]SomeModel Data {get;set;}
which is oneway communication from parent (ComponentA) to the child (ComponentB).
For the reverse direction you can use an [Parameter]EventCallback<string> OnChange { get set;}
. Or use a StateManager object as a cascading value.
Your main questions seems to be
but how do I KNOW in ComponentB that data has changed?
That is easy, just use
override onParametersSet()
{
// ... compute title
OnChange.InvokeAsync(title);
}
But you could easily get into a loop here, depending on how you handle this in the Parent.
CodePudding user response:
It is really not clear what you mean by "altering the models internals." But let's suppose that you mean you don't want to notify the parent component that the model passed to the child component has changed. Note that the model named Data
is bound to an input html element, so it is modified.
Suppose this is your ComponentA:
ComponentA.razor
<ComponentB Data="model" />
@code
{
private SomeModel model = new SomeModel {SomeProperty = "Blazor"};
}
And this is your model definition SomeModel.cs
public class SomeModel
{
public string SomeProperty {get; set;}
}
Note that when you instantiate ComponentB like this:
<ComponentB Data="model" />
You create a one-way databinding between ComponentA and ComponentB, which would look like this:
ComponentB.razor
<p>@Data.SomeProperty</p>
<input type="text" @bind="Data.SomeProperty" />
@code {
// Note: A [Parameter] property must be public
[Parameter] public SomeModel Data {get;set;}
}
Now, when you type text in the input element, and then navigate out of the input element, the new text will appear in the
element. In other words, componentB is rendered. However, the parent component (ComponentA) is not aware of the change, and its model object remain unchanged, as this is one way binding. If you wish to notify ComponentA of the change, that is, if you want to create a two-way databinding, please let me know, and I'll show how to do that for the same price.
Now, if you want to pass a title string to ComponentA when Data.SomeProperty
changes, you can do that in many ways. The following code snippet demonstrates how to do that...
Change <input type="text" @bind="Data.SomeProperty" />
To <input type="text" value="@Data.SomeProperty" @onchange="OnChange" />
And define a method named OnChange
like this:
private void OnChange(ChangeEventArgs)
{
// Gets the new value and assign it to Data.SomeProperty property
// Note that what we're doing here is creating a two-way data-binding
// between the input element and the Data.SomeProperty property
// instead of using the `@bind` directive. This is how you know that
// the data has changed, and you can also access the new value.
Data.SomeProperty = ChangeEventArgs.Value.ToString();
// Now, if you want to pass a computed title to ComponentA, you'll
// have to add a method to ComponentA, let's name it `GetTitle`, which
// has a string parameter:
// private void GetTitle (string title)
// {
// // Do something with title
// }
// How to let ComponentB know that she should pass the title value
// to ComponentA.GetTitle ?
// Do the following:
// Define an EventCallback<string> property in ComponentB, like this:
// [Parameter] public EventCallback<string> Notifier {get;set;}
// The Notifier delegate property will store the call back to be
// triggered from the current method (OnChange), like this:
var title = "computed title";
Notifier.InvokeAsync(title);
// Least but not last, you should instantiate ComponentB in ComponentA
// like this:
// <ComponentB Data="model" Notifier="GetTitle" />
}
Note that you can also keep the @bind directive as is, and use a setter property to invoke the call back Notifier.