I am having 2 components A and B , B being a child of A.Is there anything like the EventEmitter (Angular) in Blazor ? How can i attach an event handler in the parent that can respond to the output of its child ?
Child
<button onclick="Emit">Press me</button>
#functions(){
[Parameter] // i do not know how the `Output` is called
private string Pressed {get;set;}
public void Emit()
{
//somehow emit `Pressed` to the parent , make him respond
}
}
Parent component
<Child [Pressed]="doSomething"> </Child>
#functions{
doSomething(string value){
Console.WriteLine("Child sent me : "+value);
}
}
P.S Sorry for syntax errors ,i am quite new to blazor.
You can use an Action parameter
<button onclick="Emit">Press me</button>
#functions(){
[Parameter] protected Action<string> Pressed {get;set;}
public void Emit()
{
Pressed?.Invoke("Some String");
}
}
In Emit, you use a conditional to check if anyone is subscribed to the Action and Invoke it, passing in the parameters.
Don't forget, in the Parent, if you want the page to update, call StateHasChanged() in the "doSomething" method.
Component A.cshtml
// Define a method in the parent component which will be called
// from the child component when the user tap the button residing
// in the child component. This method has a string parameter passed
// from the child component
public void GetValueFromChild(string value)
{
// Do somethig with value
}
Component B.cshtml
// When the user click the button the method GetValueFromChild
// defined on the parent component is called
<button class="btn" onclick=#(() => OnAddValue("some string value"))>Add</button>
#functions
{
// Define an Action delegate property which stores a reference
// to A.GetValueFromChild
// Parameters
[Parameter] Action<string> OnAddValue{ get; set; }
}
A.cshtml
// Here (in the parent component) you place the child component and
// set its OnAddValue to the name of the method to be called
<B OnAddValue = "GetValueFromChild"></B>
Please mark my answer as accepted if it helped you out
Hope this helps...
Related
It would take me days to list all the solutions I've found and tried, suffice to say I have been looking into this issue for over 8 hours now and tried everything I could find. I have no doubt that I have probably SEEN the pieces of the answer and simply lack the acumen to connect it all together.
SCENARIO
In my MainLayout.razor (which I have renamed AppLayout.razor) I have included a TopNavMenu component (red border) with some filter ability. I need any page that gets loaded to be able to respond to changes in the filter.
AppLayout.razor
<div class="page">
<TopNavMenu OnFiscalYearChange="TopNavMenu_OnFiscalYearChange"
OnSiteIDChange="TopNavMenu_OnSiteIDChange"></TopNavMenu>
<main>
<article class="content px-4">
<CascadingValue Value="#OnFiscalYearChange" Name="TopNavMenu_OnFiscalYearChange">
<CascadingValue Value="#OnSiteIDChange" Name="TopNavMenu_OnSiteIDChange">
#Body
</CascadingValue>
</CascadingValue>
</article>
</main>
In order to make the even accessible to the #page component as well as any child components of the #page I have ...
Created EventCallbacks in the AppLayout.razor.cs base class (think Code Behind)
component
[Parameter]
public EventCallback<int> OnFiscalYearChange { get; set; }
[Parameter]
public EventCallback<int> OnSiteIDChange { get; set; }
Created event handlers which will Invoke/Fire these
events when called.
public async Task TopNavMenu_OnFiscalYearChange(int fiscalYear)
{
await OnFiscalYearChange.InvokeAsync(fiscalYear);
}
public async Task TopNavMenu_OnSiteIDChange(int siteID)
{
await OnSiteIDChange.InvokeAsync(siteID);
}
Assigned the event handlers from step #2 to the correspnding events
of the TopNavMenu component.
<TopNavMenu OnFiscalYearChange="TopNavMenu_OnFiscalYearChange"
OnSiteIDChange="TopNavMenu_OnSiteIDChange">
</TopNavMenu>
Then, for each EventCallback I pass a reference to all it's children
via the CascadingValues
<CascadingValue Value="#OnFiscalYearChange" Name="TopNavMenu_OnFiscalYearChange">
<CascadingValue Value="#OnSiteIDChange" Name="TopNavMenu_OnSiteIDChange">
#Body
</CascadingValue>
</CascadingValue>
So now those EventCallbacks are available to all the descendent components
In code behind the "page" component Grid.razor I have 2 methods that will handle the events when fired.
protected async Task FiscalYearDropDown_OnChange(int selectedYear)
{
... Doing something
}
protected async Task SiteIDDropDown_OnChange(int selectedSiteID)
{
... Doing something
}
In code behind the "page" component Grid.razor I have also added 2 CascadingParameter properties to assign these methods to the Events so that they will be called when raised.
[CascadingParameter(Name = "TopNavMenu_OnFiscalYearChange")]
public EventCallback<int> OnFiscalYearChange
{
set
{
EventCallback.Factory.Create<int>(this, this.FiscalYearDropDown_OnChange);
}
}
[CascadingParameter(Name = "TopNavMenu_OnSiteIDChange")]
public EventCallback<int> OnSiteIDChange
{
set
{
EventCallback.Factory.Create<int>(this, this.SiteIDDropDown_OnChange);
}
}
This simply does not work. The methods never get executed when the event occurs. I have added several breakpoints and can see the properties from Step #7 getting set but after that when the event is fired execution never makes it into the event handlers (step#6).
Why aren't these event handlers getting called? Am I not assigning them correctly?
SOLUTION
Thanks to #fixmeinfortyfive for dusting off this suggestion. I had tried passing the TopNavMenu control reference as a CascadingValue before but, unlike in WebForms, you don't simply add an event handler method to the event (eg. btnTest.OnClick += btnTest_OnClick). In order for me to assign an event handler to the Event I needed to associate the EventCallbacks that I was originally attempting to use to capture the events passed down from the AppLayout.razor page, to the events defined directly on the TopNavmenu reference.
In Grid.razor "page" I removed the "CascadingParameter" attributes from the Event Callbacks, created a call variable to hold the references in and defined a GETTER for each ...
private EventCallback<int> _onFiscalYearChange;
public EventCallback<int> OnFiscalYearChange
{
get
{
if (!_onFiscalYearChange.HasDelegate)
{
_onFiscalYearChange = EventCallback.Factory.Create<int>(this, this.FiscalYearDropDown_OnChange); ;
}
return _onFiscalYearChange;
}
set
{
EventCallback.Factory.Create<int>(value, this.FiscalYearDropDown_OnChange);
}
}
private EventCallback<int> _onSiteIDChange;
public EventCallback<int> OnSiteIDChange
{
get
{
if(!_onSiteIDChange.HasDelegate)
{
_onSiteIDChange = EventCallback.Factory.Create<int>(this, this.SiteIDDropDown_OnChange); ;
}
return _onSiteIDChange;
}
set
{
EventCallback.Factory.Create<int>(value, this.SiteIDDropDown_OnChange);
}
}
I also added a CascadingParameter to hold the reference to the TopNavMenu component...
[CascadingParameter(Name = "TopNavMenu")]
public TopNavMenu TopNavMenu { get; set; }
And finally, in the OnInitializeAsync method I assigned the EventCallbacks to the event handlers of the TopNavMenu and voila! It works!
protected override async Task OnInitializedAsync()
{
// If the TopNav is passed in, then assign the event handlers.
//
if(TopNavMenu != null)
{
TopNavMenu.OnFiscalYearChange = OnFiscalYearChange;
TopNavMenu.OnSiteIDChange = OnSiteIDChange;
}
}
The EventCallback parameters defined in the AppLayout - what purpose do they serve? Surely they would never receive values, as the Layout is supposed to be wrapping your application, hence no component above them will ever pass said handlers.
As for the question - you should be passing the method that invokes the event itself, rather than the EventCallback parameters I mentioned earlier.
As in, change your cascading declarations to this:
<CascadingValue Value="#TopNavMenu_OnFiscalYearChange" Name="TopNavMenu_OnFiscalYearChange">
#Body
</CascadingValue>
or perhaps even better, instead of defining multiple values like this, simply pass a reference of the TopNav component and invoke its methods - this would remove the need to define handlers in the AppLayout
<TopMav #ref="#TopNavRef" .../>
<CascadingValue Value="#TopNavRef">
#Body
</CascadingValue>
Hope this helps.
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?
Index.razor
#page "/"
<button #onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
PetDog.razor
#inherits PetTemplate
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate.razor
<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();
}
}
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:
PetTemplate.razor
<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());
}
}
PetDog.razor
#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);
}
}
Index.razor
#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.
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:
Declare an event in the base component that is raised when the UI state is changed from within the component. Also let the variable that controls the state be a parameter. In you case, something like
PetTemplate.razor:
[Parameter]
public EventCallback OnToggleRequested {get;set;}
[Parameter]
public string Display {get;set;}
protected async Task RaiseToggle()
{
await OnToggleRequested.InvokeAsync();
}
In your PetDog, simple call the toggle method when inside click is raised
PetDog.razor:
<button #onclick="RaiseToggle">Show Details (Inside)</button>
In your container (in this case, index.razor) listen to the event and make changes. Also wire the outside button to the same method:
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.
I'm new to Blazor and I'm currently working on a Blazor Webassembly .net 5.0 application.
I try to figure out the correct way to
render a child component
from a parent component
on button click (form submit)
pass parameters from the parent component to => the child component
My current solution seems to work, but unfortunately it ends in an infinite rendering loop: I use the OnParametersSetAsync method in the child component to handle the data loading.
Side note: I use Telerik Blazor components, but it should have no impact.
My parent component looks like this:
View (parent)
// I want to submit a form to set a bool = true, and then to rend the child component - is that ok?
<EditForm OnValidSubmit="#(async () => await StartEverything())">
<label for="OrderNumber">OrderNumber: </label>
<TelerikTextBox #bind-Value="OrderNumber" Id="OrderNumber" />
<TelerikButton ButtonType="#ButtonType.Submit">Start Everything</TelerikButton>
</EditForm>
#if (ShowChild)
{
<MyChildComponent OrderNumber="OrderNumber"/>
}
else
{
<div>Please enter an order number.</div>
}
Code Behind (parent)
public class MyParentComponent : ComponentBase {
protected int OrderNumber { get; set; }
protected bool ShowChild { get; set; }
protected async Task StartEverything()
{
if (OrderNumber > 0)
{
await Task.FromResult(ShowChild = true);
}
}
}
My child component looks like this:
View (child)
#if (Customer != null)
{
<p>#Customer.CustomerName</p>
<p>#Customer.AgencyName</p>
}
Code Behind (child)
public class MyChildComponent : ComponentBase {
// I need this Parameter sent from my parent component
[Parameter]
public int OrderNumber { get; set; }
protected CustomerViewModel Customer { get; set; }
protected override async Task OnParametersSetAsync()
{
var parameterForQuery = OrderNumber; // this should hold the value sent from the parent component
// Load Customer ViewModel Data here - is this the correct event? What is the best approach?
}
}
Item ViewModel
public class CustomerViewModel
{
public string CustomerName { get; set; }
public string AgencyName { get; set; }
}
Do you know how to correctly render a Child Component within a Parent Component and pass parameters from the Parent Component to the child component - then render the child component ONLY ON BUTTON CLICK (form submit, no infinite render loop)?
Do you know how to solve this problem?
I recommend going through https://blazor-university.com. It's the site that kind of got me kick-started when I first started with Blazor.
In regard to your question, I recommend the following:
https://blazor-university.com/components/component-lifecycles/
In particular, the following statement should prove useful in your case (from that link):
OnInitialized / OnInitializedAsync
This method is only executed once when the component is first created.
If the parent changes the component’s parameters at a later time, this
method is skipped.
It seems likely that simply changing which method you override will solve your problem, since OnParametersSetAsync behaves as you've described, and 'OnInitializedAsync' behaves as you want. :D
Let's say I have a collection of data in my page loaded via OnInitializedAsync(). The data is shown graphically in a table but later on also in more detail in another table further down on the page.
Since the rows in the detailed table has a lot of controls and logic I decided to make a component for the row e.g. <RowData Data="#rowdata" /> and bound each row data.
The problem is that if the data gets changed in my child controller (RowData) it won't reflect in my first table in the "parent" component where the same data is also listed.
Is there an easy way to signal change or should I avoid making child components?
I have sovled it by making an EventCallback in my child component and updating via callback in my parent component. But I have the feeling I'm missing something.
The following sample shows how to perform two-way data-binding between a parent
component and its child component. In each of these two component is a text box controls. When you type text in the parent component's text box, the text in the child component's text box changes to reflect the changes made in the parent,
and vice versa...
ChildComponent.razor
<div style="border:solid 1px red">
<h2>Child Component</h2>
<input type="text" #bind="Text" #bind:event="oninput" />
</div>
#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; }
}
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";
}
I have sovled it by making an EventHandler in my child component and updating via callback. But I have the feeling I'm missing something
What you've been missing is the existence of the EventCallback 'delegate' used in this sample to call the parent component and pass it the value entered in the child component. This is how we define the 'delegate'
[Parameter]
public EventCallback<string> TextChanged { get; set; }
And this is how we invoke it, when the value of the Text property changes:
TextChanged.InvokeAsync(value);
What delegate did you use ? Note that the EventCallback's target is not the child component, but the parent component...
Good luck... If something is not clear, don't hesitate to ask...
If you have a root component with N levels of nested components (children within children ad-nauseam) then you can use a cascading value. Try something like this
public class MyState
{
public List<MyObject> Objects { get; set; }
public Action OnModified { get; }
public MyState(List<MyObject> objects, Action onModified)
{
Objects = objects;
OnModified = onModified;
}
}
In your parent component
MyState State;
protected override OnInitialized()
{
State = new MyState(your objects, () => InvokeAsync(StateHasChanged));
}
In your parent markup
<CascadingValue Value=State>
All your child content here
</CascadingValue>
In your various child components that need access
[CascadingParameter]
public MyState State { get; set; }
protected void SomeEditWasMade()
{
State.Objects[23].Name = "Bob";
State.OnModified();
}
That should call the () => InvokeAsync(StateHasChanged) in the parent, and then that component and every component that consumes the MyState cascading value will get rerendered.
Or you could use something like Fluxor :)
Just hoping to get some help with some of this Blazor functionality. I'm building a true SPA app with no navigation, which means I'm going to need a fair bit of linkage.
Im going to follow some fundamentals of say Winforms or UWP were you have access to controls. And treat Blazor components as if they were controls. Do as much work in C# as I can.
So far I know:
For a child component, I can create an event callback, and register to it in the parent.
I can store a reference to the child component with the #ref tag. Then access functions of the child component after OnRender has been completed.
I can dynamically build a component using the builder.
But how do I pass a reference of the parent to the child? Like set a parameter of the child and pass "this".
The idea is that every child component of Index has a reference to the Index component. And the Index component has a reference to every child of Index.
So that way I can do things like when I click a button in the Header component.
I can call parent.PopupComponent.Show("Title");
I know it can be achieved with callbacks, but I would like to be able to make any call I need, access any variable etc. Through the linkage of components. Without needing to set up an extra callback function for each step.
Thank you in advance :)
You can pass a reference to the parent component to a child component as a regular parameter, as for instance:
Child.razor
#code {
[Parameter]
public Parent Parent { get; set; }
}
Parent.razor
#* Passing a parent reference to the child component*#
<Child Parent="this" />
#code {
}
You can also pass a CascadingParameter to child components. This is useful when you want to pass a reference to the parent component to all the children, as for instance:
Child.razor
#code {
[CascadingParameter]
public Parent Parent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Parent.razor
#* Passing a parent reference to the child components in the form of
CascadingValue*#
<CascadingValue Value=this>
#ChildContent
</CascadingValue>
#code {
}
And this is bonus:
The following code snippet illustrate how to add a child component to a parent component, from which you can call properties of the child component :
protected override void OnInitialized()
{
Parent.AddChild(this);
}
Note: The OnInitialized method is implemented on the child component, right ?
And the word this refers to the current object; that is the child component, right ?
Hope this helps...
I just wanted to add this in as some people may find it useful when trying to build a Blazor app the same way I am.
Here is how I found a way to control what content is being loaded into a component, in my case I made an empty Flyout/Window component, with the content set as a render fragment. Note that the render fragment is private, the is because the content will be a BlazorComponent, defined in the ShowPopup() call.
Also worth noting that the component builder stuff will most likely become obsolete as they build on Blazor. This is low-level API, and as mentioned by the Developers. They will have something more useful in the future.
Child Component Code
<div>
...
#childContent
</div>
#code
{
private RenderFragment childContent { get; set; }
public void ShowPopup(Type ComponentType)
{
childContent = CreateDynamicComponent(ComponentType);
//Show popup logic....
}
RenderFragment CreateDynamicComponent(Type T) => builder =>
{
builder.OpenComponent(0, T);
builder.CloseComponent();
};
}
Then the parent Code
<div>
...
<button #onclick="#(e => PopupWindow.ShowPopup(typeof(BlazorComponentX)))">Load BlazorComponentX into Popup</button>
<PopupWindow #ref="PopupWindow"></PopupWindow>
...
</div>
#code
{
Public PopupWindow PopupWindow {get; set;}
}