I have a similar component and I whish to save the instance of the InnerComponent the first time it is rendered and render the same instance every time without reinstanceiating it.
#if(isVisible)
{
<InnerComponent #ref="#_InnerComponent" #key="#("InnerComponentKey")">
#ChildContent
</InnerComponent>
}
#code{
[Parameter] public InnerComponent _InnerComponent { get; set; }
private bool IsVisible { get; set; }
}
When the inner component is visible the user can manipulate its state.
But if IsVisible gets set to false and then true again, the inner component gets rerendered overriding _InnerComponent and thus we lose track of the changes that the user made to that InnerComponent instance.
Adding #key does not seem to help preserve the instance either. It just gets rerendered and overwritten :/ I am sure, that I am feeding it the same key both times it gets rendered, but I don't know how to inspect the keys that it gets compared to.
If it is possible to render a component instance I could do something like the following, but I can't seem to find a way to do that.
#if(isVisible)
{
#if(_InnerComponent == null)
{
<InnerComponent #ref="#_InnerComponent" #key="#("InnerComponentKey")">
#ChildContent
</InnerComponent>
}
else
{
#_InnerComponent.Render()
}
}
I am taking criticism on my question since I haven't asked many :)
Thanks in advance!
Simplified example:
Let's say we have the following component that I am going to call `CounterContainer` , where `<Counter>` is the counter component from the default Blazor project template.
#if(CounterIsVisible)
{
<Counter #ref="#_Counter" #key="#("CounterKey")" />
}
<button #onclick="() => CounterIsVisible = !CounterIsVisible">
Show/Hide counter
</button>
#code{
[Parameter] public Counter _Counter { get; set; }
private bool CounterIsVisible { get; set; } = true;
}
I want to save the _Counter instance, so I can see the correct _Counter.currentCount that I counted to. I could save it using a method from this article, but I find them all unpractical, because
the component I am building has much more data than just a single variable
I need the data only as long as the CounterContainer exists and only for visualisation
It is just too complicated for my use case
I already have the Counter reference stored. I just want to view it instead of reinstanceiating it and overwriting the whole thing.
Hope that made it a bit clearer (:
I'm surprised it compiles, since you've misspelled your component class:
[Parameter] public **InnerCopmonent** _InnerComponent { get; set; }
Why do you have an #ref to your InnerComponent at all? What does InnerComponent do, and why do you want to reference it? Can you share that component's code?
best option: separate View and Data. The state you want to preserve should not be kept inside the component but in a separate (ViewModel) object.
short solution: always render the component. Use #isVisible to turn a css-class on/off to hide it.
The way I like to do this kind of thing is to create a carrying class which I instantiate in the parent and pass to the child as a Parameter. Since a class is passed by reference, I don't need to worry about events or anything like that. (I just made up a class for demo to show you can have whatever you want in it)
CounterData.cs
public class CounterData
{
public int counter { get; set; }
public string Title { get; set; } = "Counter";
public List<int> CounterHistory { get; set; } = new List<int>();
}
CounterContainer.razor
#page "/counter"
<h3>Counter Container</h3>
<button #onclick="()=> ShowCounter=!ShowCounter">Toggle</button>
#if (ShowCounter)
{
<Counter CarryingInstance="PersistentData" />
}
#code {
CounterData PersistentData = new CounterData();
bool ShowCounter = false;
}
Counter.razor
<h3>#CarryingInstance.Title</h3>
<input #bind="CarryingInstance.Title" /><br />
<button #onclick="()=> Increment(1)">Add</button>
<button #onclick="()=>Increment(-1)">Sub</button>
#CarryingInstance.counter <br />
History:
#foreach (var item in CarryingInstance.CounterHistory)
{
<span> #item</span>
}
#code {
[Parameter]
public CounterData CarryingInstance { get; set; }
void Increment (int amount)
{
CarryingInstance.counter += amount;
CarryingInstance.CounterHistory.Add(CarryingInstance.counter);
}
}
I've seen the option of hiding the component with CSS and the unexpected problems it can cause because components then aren't going through normal life cycle events when they are legitimately rerendered/reinstantiated. So I'm busy updating several components to fix that exact thing. My advice would be to avoid doing that if possible.
Another way, similar to BennyBoys carrying instance method, is you could probably do it is by utilizing dependency injection and registering a type as a scoped or singleton service and having it injected into the component.
You can read here on Chris Sainty's blog about how the different registrations work in blazor and their behaviour and if it'll fit your needs.
https://chrissainty.com/service-lifetimes-in-blazor/
I would double check via official docs that these still behave the same way though just in case but it might be a nice way to do it. I think it depends on what your actual use case is whether it's a good idea to do it like this, but it's another option :)
Related
I am building a dashboard which is having tiles that loads data on start up. The loading is shown with a loading animation and to prevent repeatable code, I want to manage this in a wrapper tile. This wrapper tile instance is passing itself to the child so the child can set the loading state to false when the data is retrieved. This is the code I have so far:
wrapper tile code
WrapperTile.razor
<MudItem xs="12" sm="6" md="4" lg="3" xl="2" xxl="1">
<MudPaper Height="250px" Elevation="3" Class="pa-4 mud-width-full">
#if (IsLoading)
{
<MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
}
else
{
}
#if (ChildContent is not null)
{
#ChildContent(this)
}
</MudPaper>
</MudItem>
Side-note: The second if statement (checking the ChildContent) is currently outside the else statement for testing purposes but will be replaced as soon as the loading state switch process is working.
WrapperTile.razor.cs
public partial class WrapperTile
{
[Parameter]
public RenderFragment<WrapperTile>? ChildContent { get; set; }
public bool IsLoading { get; set; } = true;
}
test tile 1
TestTile1.razor
<WrapperTile>
Tile 1<br/>
<MudButton OnClick="Test">Test</MudButton>
</WrapperTile>
TestTile1.razor.cs
public partial class TestTile1
{
[Parameter]
public WrapperTile? Wrapper { get; set; }
private void Test()
{
ArgumentNullException.ThrowIfNull(Wrapper);
Wrapper.IsLoading = false;
}
}
test tile 2
Is the same as test tile 1, only the tile text is different (tile 2).
What I think that should happen is when calling the ChildContent(this) the wrapper tile is passed to the tile itself and the tile itself can change the IsLoading on the WrapperTile instance, changing the loading animation to the data that must be shown.
What works is the loading animation, it is shown as well as the tile text and the button. But as soon as the button is clicked, an exception is thrown by the ArgumentNullException.Throw() method, meaning that the Wrapper is null.
I cannot figure out why the Wrapper is null in this situation. Do I miss a constructor? Am I using the RenderFragment wrong this way? I also tried to use the CascadingValue component:
#if (ChildContent is not null)
{
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
}
This also has the same issue, the Wrapper property is not set and causing the null reference exception.
What do I need to do to make this work the way I would like?
EDIT:
I have put both scenario's in a blazor fiddle.
RenderFragment with parameter: https://blazorfiddle.com/s/ozatfeb0
Using the CascadeValue/CascadingParameter: https://blazorfiddle.com/s/s6m4anbu
In both situations the Wrapper property is not set.
I have a class MealsQueryInputs that I would like to use as a component parameter with two-way binding capabilities.
All of the demos and sample code I can find are using built-in primitive types and never a class. I can get the MS demos to work but I cannot get binding to a class to work. Is it even possible to do this?
My component FilterSortOptions.razor:
using WhatIsForDinner.Shared.Models
<MudCheckBox Checked="#QueryInputs.Favorite"
Color="Color.Inherit"
CheckedIcon="#Icons.Material.Filled.Favorite"
UncheckedIcon="#Icons.Material.Filled.FavoriteBorder"
T="bool"/>
<MudRating SelectedValue="#QueryInputs.Rating"/>
<MudButton OnClick="#(async () => await OnPropertyChanged())">Apply</MudButton>
#code {
[Parameter]
public MealsQueryInputs QueryInputs { get; set; }
[Parameter]
public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }
private async Task OnPropertyChanged()
{
await QueryInputsChanged.InvokeAsync(QueryInputs);
}
}
As MrC said, you should avoid directly binding to the data being supplied as a parameter.
Here is a simple working sample (not MudBlazor) to show the concept
https://blazorrepl.telerik.com/QQEnQjaO54LY3MYK35
You bind to a local variable/property and try not to modify the incoming data directly.
MyComponent
<h1>MyComponent</h1>
<label for="choice">Choose</label>
<input id="choice" type="checkbox" #bind-value=localValue />
#code
{
bool localValue
{
get => Data.SomeChoice;
set {
if (value != localValue)
{
localData = Data with { SomeChoice = value };
InvokeAsync(ValueChanged);
}
}
}
ComplexObject localData;
[Parameter] public ComplexObject Data { get; set; }
[Parameter] public EventCallback<ComplexObject> DataChanged { get; set; }
Task ValueChanged() => DataChanged.InvokeAsync(localData);
}
ComplexObject
public record ComplexObject(bool SomeChoice, string SomeText);
Main
#code
{
ComplexObject data = new(false,"");
}
<MyComponent #bind-Data=data />
You have chosen #data.SomeChoice
Here is how you can bind class objects to a custom razor component
This is FilterSortOptions component
<div>
<label>Rating:</label>
<input type="text" value=#QueryInputs.Rating #oninput=#(val=> {
QueryInputs.Rating=val.Value.ToString();
QueryInputsChanged.InvokeAsync(QueryInputs);
}) />
</div>
<div>
<label>Favourite:</label>
<input type="checkbox" value=#QueryInputs.Rating #onchange=#(val=> {
QueryInputs.Favourite=(bool)val.Value;
QueryInputsChanged.InvokeAsync(QueryInputs);
}) />
</div>
#code {
[Parameter]
public MealsQueryInputs QueryInputs { get; set; }
[Parameter]
public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }
}
This is the model to bind, for simplicity Rating is is string type
public class MealsQueryInputs
{
public bool Favourite { get; set; } = false;
public string Rating { get; set; } = "0";
}
Here is the razor page
<h3>Rating: #QueryInputs.Rating</h3>
<h3>Favourite: #QueryInputs.Favourite</h3>
<FilterSortOptions #bind-QueryInputs=#QueryInputs></FilterSortOptions>
#code {
public MealsQueryInputs QueryInputs = new();
}
Updated Answer
Firstly, if your using an object then you are passing around references to the same object. So when you update the object in the sub-component, you're updating the same object the parent is using. You don't need to pass the object back in the callback unless you create a noew copy of it.
Secondly, your not binding the mud controls to the object.
Let's look at your code:
<MudCheckBox Checked="#QueryInputs.Favorite"
Color="Color.Inherit"
CheckedIcon="#Icons.Material.Filled.Favorite"
UncheckedIcon="#Icons.Material.Filled.FavoriteBorder"
T="bool"/>
Checked="#QueryInputs.Favorite" doesn't bind the control to the field. It just sets the initial value.
I think (I don't use Mudblazor and it's a little different from standard Blazor Form Controls) you need to do this:
<MudCheckBox #bind-Checked="#QueryInputs.Favorite"></MudCheckBox>
The same is true for MudRating.
<MudRating #bind-SelectedValue="#QueryInputs.Rating" />
Then the button:
<MudButton OnClick="#(async () => await OnPropertyChanged())">Apply</MudButton>
can be simplified to this. You're wrapping an async method within an async method.
<MudButton OnClick="OnPropertyChanged">Apply</MudButton>
// or
<MudButton OnClick="() => OnPropertyChanged()">Apply</MudButton>
Original Answer
There are a couple of issues here:
QueryInputs is a Parameter and therefore should never be modified by the code within the component. You end up with a mismatch between what the Renderer thinks the value is and what it actually is.
When the parent component renders it will always cause a re-render of any component that is passed a class as a parameter. The Renderer has no way of telling if a class has been modified, so it applies the heavy handed solution - call SetParametersAsync on the component.
A solution is to use a view service to hold the data and events to notify changes. One version of the truth! Search "Blazor Notification Pattern" for examples of how to implement this. I'll post some code if you can't find what you want.
I have a blazor application where I have the main window and other files which are the child windows.
I'm looking for a way for the main window button to open the child window, but even though the EnableConf value is true, the window won't open, I tried using StateHasChanged() and it still doesn't work.
CheckState.cs
public class CheckState {
public bool EnableConf { get; set; } = false;
}
MainWindow.cs in MainWindow()
Collect.AddScoped<CheckState>();
Conf.razor
#inject AppBlaz.CheckState CheckState
if(CheckState.EnableConf) {
<style>
...
</style>
...
<button #onclick="()=> CloseConf()">
...
}
#code {
private void CloseConf() {
CheckState.EnableConf = false;
}
}
main.razor
#inject AppBlaz.CheckState CheckState
...
<button #onclick="()=> OpenConf()">
...
#code {
private void OpenConf() {
CheckState.EnableConf = true;
}
}
The child window close button works perfectly, the only problem is that the main window cannot open the child window.
Does anyone know how I can make this work correctly
Presuming that somewhere in the main.razor you actually have placed the child window component (Conf.razor) to be hidden or displayed and there is no java script / third party lib that popups this window:
Add
[Parameter]
public bool CheckState {get; set;}
[Parameter]
public EventCallback<bool> CheckStateChanged { get; set; }
to your Conf.razor. Use this property as replacement for CheckState.EnableConf - if needed, then in OnAfterRender() override assign value to new property from currently used service (CheckState).
if(CheckState.EnableConf) becames if(CheckState).
In main.razor:
Repeat the above, but ommit [parameter] attribute for CheckState and drop whole CheckStateChanged including the [Parameter].
In place where the Conf.razor is initialized / put into actual ui code add
<Conf #Bind-CheckState=CheckState /> - now you will pass this newly created property down the line to child window.
In OpenConf() method change the newly created CheckState parameter instead of / in addition to CheckState.EnableConf.
CloseConf now should re
This should trigger the UI update. In case of emergency, add StateHasChanged() at the end of OpenConf() method.
Again, this should work (more or less, I typed in from memory) for standard blazor components.
I've got a bit of a strange situation going on. I've created a very simple class as such.
[Serializable]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class StockSyncCollection : ObservableCollection<StockSyncDatum>
{
}
public class StockSyncDatum
{
public StockSyncDatum() { }
public StockSyncDatum(int posStockId, int posStockCatalogueId, DateTime lastUpdated)
{
this.PosStockId = posStockId;
this.PosStockCatalogueId = posStockCatalogueId;
this.LastUpdated = lastUpdated;
}
public int PosStockId { get; set; }
public int PosStockCatalogueId { get; set; }
public DateTime LastUpdated { get; set; }
}
I have then created a custom setting through the designer for which the generated code looks like this
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::Sync.Config.StockSyncCollection StockSyncCollection {
get {
return ((global::Sync.Config.StockSyncCollection)(this["StockSyncCollection"]));
}
set {
this["StockSyncCollection"] = value;
}
}
However the problem I am running into is that later in my code (after adding some items to the collection) I will call
Settings.Default.Save();
But what happens is that only some subset of the items that have been added to the collection up until the point the save method is called is actually persisted. So for example, I might add 50 items but only 10 of them will be saved.
Now note that I am calling Settings.Default.Save() in a loop. My gut instinct is telling me that there is some sort of hash that is generated of the object that isn't being updated.
So what I am wondering is how the hell do you debug Application Settings in C# apps? I can see various events like SettingsChanging and SettingsSaving but I do not see an event for SettingsSaveError or similar.
Anyone have any idea what might be going on or how to debug this?
EDIT: The loop code basically looks like this.
foreach (var partition in stockTransfers.Partition(PartitionCount))
{
IList<StockTransferContract> stockTransferContracts = partition.ToStockTransferContracts();
//Do all the magic syncing stuff...
foreach (var item in stockTransferContracts)
_StockSyncCollection.Add(new StockSyncDatum(posStockId, posStockCatalogueId, DateTime.UtcNow));
Settings.Default.Save();
}
Keep in mind that I need to call save because I expect errors to occasionally arise and need to ensure that the previously "Sync'd" items are noted.
Let's say I have a WriteItem class that looks like this:
public class WriteItem
{
public string Name { get; set; }
public object Value { get; set; }
public int ResultCode { get; set; }
public string ErrorMessage { get; set;}
}
I need to process each item and set its ResultCode and ErrorMessage properties and I though about defining a method similar to this:
public void ProcessItems(WriteItemCollection items)
{
foreach(var item in items)
{
// Process each item and set its result.
}
}
The processing of each item is done by another class.
Is this the best way to do it?
Or is it better to have the method return a collection of a custom Result class?
Both options have their advantages and disadvantages. Both are "fine" in the sense that there is nothing wrong with them and they are commonly used in C#.
Option 1 has the big advantage of being simple and easy. You can even keep a reference to a WriteItem instance and check its status after processing.
Option 2 has a clearer separation of concerns: In Option 1, you need to add comments to your WriteItem class to define which are "input" and which are "output" properties. Option 2 does not need that. In addition, Option 2 allows you to make WriteItem and ProcessingResult immutable, which is a nice property.
Option 2 is also more extensible: If you want to process something else than WriteItems (with the same return options), you can define a class
class ProcessingResult<T>
{
public T Item { get; set; }
public int ResultCode { get; set; }
public string ErrorMessage { get; set; }
}
and use it as ProcessingResult<WriteItem> as well as ProcessingResult<SomeOtherItem>.
What you wrote will work. You can modify the object properties without having side effects while iterating in the collection.
I wouldn't return a new collection unless you need to keep a copy of the original collection untouched.
I think it all comes down to readability.
When you call ProcessItems, is it obvious that the collection has changed? If you call the method like this:
var items = GetItemsFromSomewhere();
ProcessItems(items);
versus calling it like this:
var items = GetItemsFromSomewhere();
items = ProcessItems(items);
or simply changing your methodname:
var items = GetItemsFromSomewhere();
items = UpdateItemStatuses(items);
In the end there is no right answer to this question in my book. You should do what feels right for your application. And consider: what if another developer was looking at this piece of code? Can he surmise what is happening here, or would he have to dive into the ProcessItems-function to get the gist of the application.
It is better to return a new results class.
why?
As others have said you are modifying the collection and its not really clear. But for me this is not the main reason. You can have processes which modify objects.
For me its because you have had to add extra properties to your WriteItem object in order to support the processor. This in effect creates a strong coupling between the model and a processor which should not exist.
Consider you have another method ProcessItems_ForSomeOtherPurpose(List<WriteItem> items) do you expand your ResultCode int to have more meaningful values? do you add another property ResultCode_ForSomeOtherPurpose? what if you need to process the same item mutiple times with multiple processors?
I would give your Model an Id. Then you can log multiple processes against it
eg.
item 1 - loaded
item 1 - picking failed!
item 1 - picked
item 1 - delivered