Skip to content

Commit 67ff884

Browse files
authored
Add component communication (#129)
* Add new content * Finalize Component Communication tutorial
1 parent 416be73 commit 67ff884

File tree

9 files changed

+197
-2
lines changed

9 files changed

+197
-2
lines changed

contents/trees/dotnet9-blazor-wasm.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
"Children":
7575
[
7676
]
77+
},
78+
{
79+
"ContentPath": "component-communication/v1",
80+
"GitHubLink": "https://github.com/Blazor-School/blazor-school-free-course-dotnet9-wasm/tree/master/ComponentCommunication",
81+
"Children":
82+
[
83+
]
7784
}
7885
]
7986
}
2.94 KB
Loading
9.41 KB
Loading
4.7 KB
Loading
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<h1>Component Communication</h1>
2+
<p>In a mechanical machine, there are many cogs. Each cog collaborates with the others to function as a machine. To build a system capable of accomplishing an interesting task, multiple components are needed. These components often need to work in coordination together and, thus, must be able to communicate with each other. Data must flow between them. There are multiple ways to share data between components and we will go through all possible ways to share data between components. With each method, you will not only learn how but also when to choose that method. Note that advanced techniques for each approach will be covered in later tutorials. In this tutorial, we will cover:</p>
3+
<ul>
4+
<li>Basic component tree structure.</li>
5+
<li>Communication between parent and child.</li>
6+
<li>Communication between components at the same level.</li>
7+
<li>Single source of truth.</li>
8+
</ul>
9+
<hr class="my-4" />
10+
<h1>Basic Component Tree Structure</h1>
11+
<p>In Blazor, the simplest component hierarchy is a 3-level structure, as shown in the image below:</p>
12+
<p><img src="tutorials/component-communication/v1/basic-structure.webp" alt="basic-structure.webp" width="1668" height="721" /></p>
13+
<p>This foundational setup consists of an outer-most component (Level 0), a middle component (Level 1), and an inner-most component (Level 2). More complex hierarchies are merely extensions of this model, and the same communication techniques can be applied. Data typically flows from the root (Level 0) down to the deepest level (Level 2). For clarity, this tutorial uses a colour-coded approach: Level 0 is blue, Level 1 is green, and Level 2 is red.</p>
14+
<hr class="my-4" />
15+
<h1>Communication Between Parent and Child</h1>
16+
<p><img src="tutorials/component-communication/v1/parent-child-communication-basic.webp" alt="parent-child-communication-basic.webp" width="1570" height="527" /></p>
17+
<p>In Blazor, the parent-child relationship is a fundamental aspect of component interaction, limited to 2 adjacent levels: a parent (Level 0) and its direct child (Level 1). Data can flow in 2 directions - parent to child or child to parent. This tutorial focuses solely on the parent to child flow, we will introduce more information in the next tutorials.</p>
18+
<p>Consider the following child component, <strong>SimpleChild.razor</strong>:</p>
19+
<pre language="razor">&lt;div&gt;
20+
&lt;h3&gt;SimpleChild (Level 1)&lt;/h3&gt;
21+
&lt;div&gt;@Parameter1&lt;/div&gt;
22+
&lt;/div&gt;
23+
24+
@code {
25+
[Parameter]
26+
public string Parameter1 { get; set; } = "";
27+
}</pre>
28+
<p>Here, <strong>SimpleChild.razor</strong> uses the <code>[Parameter]</code> attribute to define <code>Parameter1</code>, a <code>string</code> it renders within a div. This child can be reused in a parent component, <strong>SimpleParent.razor</strong>, which passes data to it:</p>
29+
<pre language="razor">&lt;div&gt;
30+
&lt;h3&gt;SimpleParent (level 0)&lt;/h3&gt;
31+
&lt;SimpleChild Parameter1="@Text" /&gt;
32+
&lt;/div&gt;
33+
34+
@code {
35+
public string Text { get; set; } = "Hello Blazor School!";
36+
}</pre>
37+
<p>In this example, <strong>SimpleParent.razor</strong> (Level 0) passes the value of <code>Text</code> to <strong>SimpleChild.razor</strong> (Level 1) via the <code>Parameter1</code>parameter. When rendered, <strong>SimpleChild.razor</strong> displays "Hello Blazor School!" within its <code>div</code>, demonstrating how parent to child communication enables the child to render dynamic data from the parent, aligning with the hierarchical structure previously introduced.</p>
38+
<h3>When Not to Use the <code>[Parameter]</code> Attribute?</h3>
39+
<p>While the <code>[Parameter]</code> attribute is effective for direct parent-child communication in Blazor, it becomes cumbersome when passing data beyond adjacent levels, such as from Level 0 to Level 2 in a 3-level hierarchy. In these cases, developers should opt for alternative communication methods to avoid unnecessary complexity.</p>
40+
<p>Consider the following scenario, illustrated in the diagram:</p>
41+
<p><img src="tutorials/component-communication/v1/parent-child-communication-bad-practice.webp" alt="parent-child-communication-bad-practice.webp" width="1359" height="460" /></p>
42+
<p>Here, <strong>Component3 </strong>needs a parameter from <strong>Component1</strong>, but<strong> Component2</strong> doesn&rsquo;t require it. Using <code>[Parameter]</code> would force the parameter to be passed through <strong>Component2</strong>: <strong>Component1&nbsp;</strong> passes to <strong>Component2</strong>, which must declare the parameter just to forward it to <strong>Component3</strong>. This "prop drilling" creates 2 issues: <strong>Component2</strong> becomes cluttered with unused parameters, and in larger hierarchies with 5 or more levels, managing multiple parameters this way becomes unmaintainable.</p>
43+
<p>In such cases, using <code>CascadingParameter</code> will allow developers to pass a parameter from Level 0 to Level 2 directly without flowing through Level 1.</p>
44+
<hr class="my-4" />
45+
<h1>Communication Between Components at the Same Level</h1>
46+
<p>Previously, we explored communication within nested components, such as parent-child relationships. In this section, we are discussing the communication of components at the same laevel, such as siblings or unrelated components. The following image shows an example of sibling components:</p>
47+
<p><img src="tutorials/component-communication/v1/basic-same-level-example.webp" alt="basic-same-level-example.webp" width="735" height="161" /></p>
48+
<p>In such a simple case, sibling components can communicate indirectly through a shared parent using parameters. Consider the following setup:</p>
49+
<p><strong>Component1.razor:</strong></p>
50+
<pre language="razor">&lt;div&gt;
51+
&lt;h3&gt;Component1 (level 0)&lt;/h3&gt;
52+
&lt;div&gt;@Message&lt;/div&gt;
53+
&lt;/div&gt;
54+
55+
@code {
56+
[Parameter]
57+
public string Message { get; set; } = "";
58+
}</pre>
59+
<p><strong>Component2.razor:</strong></p>
60+
<pre language="razor">&lt;div&gt;
61+
&lt;h3&gt;Component2 (level 0)&lt;/h3&gt;
62+
&lt;div&gt;@Message&lt;/div&gt;
63+
&lt;/div&gt;
64+
65+
@code {
66+
[Parameter]
67+
public string Message { get; set; } = "";
68+
}</pre>
69+
<p>To enable these siblings to share data, a parent component can pass the same parameter to both:</p>
70+
<pre language="razor">&lt;div&gt;
71+
&lt;Component1 Message="@HelloMessage" /&gt;
72+
&lt;Component2 Message="@HelloMessage" /&gt;
73+
&lt;/div&gt;
74+
75+
@code {
76+
public string HelloMessage { get; set; } = "Hello Blazor School!";
77+
}</pre>
78+
<p>In Blazor, components at the same level may not always share the same parent&mdash;sometimes one is nested while the other is not, as shown below:</p>
79+
<p><img src="tutorials/component-communication/v1/complex-same-level-example.webp" alt="complex-same-level-example.webp" width="898" height="323" /></p>
80+
<p>Here, <strong>Component2</strong> and <strong>Component3</strong> need to share data, despite <strong>Component2</strong> being nested within <strong>Component1</strong>. Using <code>CascadingParameter</code> and <code>CascadingValue</code>, you can pass data across these components without prop drilling through intermediate levels.</p>
81+
<p><strong>Component2.razor</strong></p>
82+
<pre language="razor">&lt;div&gt;
83+
&lt;h3&gt;Component2 (level 1)&lt;/h3&gt;
84+
&lt;div&gt;@HelloMessage&lt;/div&gt;
85+
&lt;/div&gt;
86+
87+
@code {
88+
[Parameter]
89+
public string HelloMessage { get; set; } = "";
90+
}</pre>
91+
<p><strong>Component1.razor</strong></p>
92+
<pre language="razor">&lt;div&gt;
93+
&lt;h3&gt;Component1 (level 0)&lt;/h3&gt;
94+
&lt;div&gt;@PersonName:&lt;/div&gt;
95+
&lt;Component2 /&gt;
96+
&lt;/div&gt;
97+
98+
@code {
99+
[CascadingParameter(Name = "CurrentUserName")]
100+
public string PersonName { get; set; } = "";
101+
}</pre>
102+
<p><strong>Component3.razor</strong></p>
103+
<pre language="razor">&lt;div&gt;
104+
&lt;h3&gt;Component3 (level 0)&lt;/h3&gt;
105+
&lt;div&gt;@PersonName: @HelloMessage I am @PersonName.&lt;/div&gt;
106+
&lt;/div&gt;
107+
108+
@code {
109+
[CascadingParameter(Name = "SayHelloMessage")]
110+
public string HelloMessage { get; set; } = "";
111+
112+
[CascadingParameter(Name = "CurrentUserName")]
113+
public string PersonName { get; set; } = "";
114+
}</pre>
115+
<hr class="my-4" />
116+
<h1>Single source of truth</h1>
117+
<p>In Blazor, using <code>Parameter</code> or <code>CascadingParameter</code> to pass data results in each component holding its own copy of the parameter. If those copies are not synchronised, they may become out of sync, and all the components will have a parameter with different values. We will walk you through how to synchronise those copies of parameters in the next tutorials. For now, this section introduces a single source of truth approach, ideal for scenarios where your application needs a single, consistent instance of data across all components. This method suits features like:</p>
118+
<ul>
119+
<li><strong>Login Information</strong>: Ensuring all components reflect the same user session state.</li>
120+
<li><strong>App&rsquo;s Current Settings</strong>: Maintaining uniform settings, such as theme or language, throughout the app.</li>
121+
</ul>
122+
<p>To implement a single source of truth in Blazor, you can follow 3 steps:</p>
123+
<ol>
124+
<li>
125+
<div><strong>Create the Transfer Service</strong>: Define a service class to hold shared properties, including a private backing field and an event to notify subscribers of changes:</div>
126+
</li>
127+
</ol>
128+
<pre language="csharp">public class BlazorSchoolTransferService
129+
{
130+
private string _helloMessage = "Hello Blazor School!";
131+
132+
public string HelloMessage
133+
{
134+
get =&gt; _helloMessage;
135+
set
136+
{
137+
_helloMessage = value;
138+
HelloMessageChanged?.Invoke(this, value);
139+
}
140+
}
141+
142+
public event EventHandler&lt;string&gt;? HelloMessageChanged;
143+
}</pre>
144+
<p>Here, <code>HelloMessage</code> uses a backing field (<code>_helloMessage</code>) rather than an auto-property, allowing the service to raise the <code>HelloMessageChanged</code> event whenever the value updates.</p>
145+
<ol start="2">
146+
<li>
147+
<div><strong>Register the Service</strong>: Add the service to the dependency injection container in <code>Program.cs</code>:</div>
148+
</li>
149+
</ol>
150+
<pre language="csharp">var builder = WebAssemblyHostBuilder.CreateDefault(args);
151+
builder.Services.AddScoped&lt;BlazorSchoolTransferService&gt;();</pre>
152+
<ol start="3">
153+
<li>
154+
<div><strong>Inject the Service into Components</strong>: Components can now inject the service to access or update the shared data::</div>
155+
</li>
156+
</ol>
157+
<pre language="razor">@inject BlazorSchoolTransferService BlazorSchoolTransferService
158+
@implements IDisposable
159+
160+
&lt;div&gt;@BlazorSchoolTransferService.HelloMessage&lt;/div&gt;
161+
162+
@code {
163+
protected override void OnInitialized()
164+
{
165+
BlazorSchoolTransferService.HelloMessageChanged += OnHelloMessageChanged;
166+
}
167+
168+
public void OnHelloMessageChanged(object? sender, string args) =&gt; StateHasChanged();
169+
170+
public void Dispose()
171+
{
172+
BlazorSchoolTransferService.HelloMessageChanged -= OnHelloMessageChanged;
173+
}
174+
}</pre>
175+
<h3>When Single Source of Truth is Not Suitable?</h3>
176+
<p>There are some scenarios where a single source of truth is not suitable:</p>
177+
<ul>
178+
<li><strong>Properties That Don&rsquo;t Need to Be Shared</strong>: When a component&rsquo;s state is isolated and only relevant to itself, like a counter in a single UI element or a form field&rsquo;s temporary value. Using a single source of truth in such cases introduces unnecessary overhead. </li>
179+
<li><strong>Simple Parent-Child Data Flow</strong>: If a property only needs to be shared between 2 adjacent levels, like a parent and its direct child, using <code>Parameter</code> or <code>CascadingParameter</code> is more efficient. For example, passing a message from <code>SimpleParent</code> to <code>SimpleChild</code> (as shown earlier) doesn&rsquo;t require a service, as the data flow is direct and doesn&rsquo;t benefit from app-wide centralisation.</li>
180+
</ul>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"Title": "Component Communication",
3+
"Slug": "component-communication",
4+
"Description": "Explore various ways to establish communication between components."
5+
}
4.37 KB
Loading
5.55 KB
Loading

contents/tutorials/component-reusability/v1/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</ul>
99
<hr class="my-4" />
1010
<h1>What is a Reusable Component?</h1>
11-
<p>A component is intended to be created once and used multiple times to eliminate the need for repeating code. A component does not always need to look the same.&nbsp;</p>
11+
<p>A component is intended to be created once and used multiple times to eliminate the need for repeating code. A component does not always need to look the same.</p>
1212
<p>For example, consider you have a textbox with a label to collect input from the user. The textbox will be used in many places to collect user data. To make a component that can be used in multiple scenarios, a meaningful label should be used in each place, making every textbox in your app "slightly different". You can declare these "slight differences" as component parameters to allow them to be changed when needed. Not only do these parameters allow changing the shape of a component, but they also provide a way to enable component communication.</p>
1313
<p>Consider a textbox to capture user input and validate user input as they type (logic). When the user inputs invalid data, the textbox appears with a red border (styling) and turns green when the data is valid. Both logic and styling of a component can be declared and reused in a component. The logic is preserved in the <code>.razor</code> file, where CSS styling is scoped in the <code>.razor.css</code> file.</p>
1414
<hr class="my-4" />
@@ -20,7 +20,10 @@ <h1>How to Build a Reusable Component?</h1>
2020
<p>Create a new Razor Component by right-clicking a folder within your project in Visual Studio and selecting <strong>Add</strong> &gt; <strong>Razor Component... </strong>and name it&mdash;e.g., <code>BlazorSchoolInputText.razor</code></p>
2121
<p><img src="tutorials/component-reusability/v1/create-razor-component.webp" alt="create-razor-component.webp" width="800" height="493" /></p>
2222
<p>The file name will serve as the component's tag.Here's an textbox component example with 2 parameters:</p>
23-
<pre language="razor">&lt;label&gt;@Label: &lt;input type="text" @bind-value="Value" @bind-value:event="oninput" class="@InputClass" /&gt;&lt;/label&gt;
23+
<pre language="razor">&lt;label&gt;
24+
@Label:
25+
&lt;input type="text" @bind-value="Value" @bind-value:event="oninput" class="@InputClass" /&gt;
26+
&lt;/label&gt;
2427

2528
@code {
2629
[Parameter]

0 commit comments

Comments
 (0)