Skip to content

Commit c4b21ac

Browse files
authored
Add cascading parameter tutorial (#130)
* Init tutorial * Finish the tutorial
1 parent 67ff884 commit c4b21ac

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed

contents/trees/dotnet9-blazor-wasm.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@
8080
"GitHubLink": "https://github.com/Blazor-School/blazor-school-free-course-dotnet9-wasm/tree/master/ComponentCommunication",
8181
"Children":
8282
[
83+
{
84+
"ContentPath": "cascading-parameter/v1",
85+
"GitHubLink": "https://github.com/Blazor-School/blazor-school-free-course-dotnet9-wasm/tree/master/CascadingParameterInDetail",
86+
"Children":
87+
[
88+
]
89+
}
8390
]
8491
}
8592
]
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
<h1>Cascading Parameter</h1>
2+
<p>In Blazor, cascading parameters enable developers to pass data across multiple levels of a component tree without explicitly passing it through each intermediate component (prop drilling). This tutorial introduces the following aspects of using cascading parameters:</p>
3+
<ul>
4+
<li>Different approaches to using cascading parameters.</li>
5+
<li>Passing and updating a parameter with the root level technique.</li>
6+
<li>Passing, updating, and overriding a parameter with the <code>CascadingValue</code> component</li>
7+
</ul>
8+
<hr class="my-4" />
9+
<h1>Different Approaches to Using Cascading Parameters</h1>
10+
<p>In Blazor, cascading parameters can be implemented in 2 primary ways: registering them at the root level in <code>Program.cs</code> (root-level technique) or using the <code>CascadingValue</code> component within specific parts of the component tree. Each approach has distinct characteristics, as outlined in the comparison table below:</p>
11+
<div class="table-responsive">
12+
<table class="table table-hover table-bordered">
13+
<thead>
14+
<tr>
15+
<th class="text-center" scope="col">Aspect</th>
16+
<th class="text-center" scope="col">Root Level Technique</th>
17+
<th class="text-center" scope="col"><code>CascadingValue</code> Component</th>
18+
</tr>
19+
</thead>
20+
<tbody>
21+
<tr>
22+
<td scope="col"><strong>Overriding Parameter Value</strong></td>
23+
<td scope="col">No</td>
24+
<td scope="col">Yes</td>
25+
</tr>
26+
<tr>
27+
<td scope="col"><strong>Refresh UI When the Value Changes</strong></td>
28+
<td scope="col">Automatically</td>
29+
<td scope="col">Manual</td>
30+
</tr>
31+
<tr>
32+
<td scope="col"><strong>Declaration Location</strong></td>
33+
<td scope="col"><code>Program.cs</code></td>
34+
<td scope="col">Any Component</td>
35+
</tr>
36+
</tbody>
37+
</table>
38+
</div>
39+
<ul>
40+
<li><strong>Root-Level Technique</strong>: By providing a cascading parameter in <code>Program.cs</code>, it becomes available to all components in the app. This method automatically updates the UI when the value changes but doesn&rsquo;t allow overriding at lower levels.</li>
41+
<li><strong><code>CascadingValue</code> Component</strong>: Using <code>CascadingValue</code> within a component allows for more control, enabling you to override the parameter in specific branches of the tree. However, UI updates require manual triggering (e.g., via <code>StateHasChanged</code>).</li>
42+
</ul>
43+
<blockquote>Do not use the root level technique as a way to register a singleton/scoped service. The root level cascading parameter is designed only for passing parameters, not for passing services. If you need a service, use the dependency injection feature instead.</blockquote>
44+
<hr class="my-4" />
45+
<h1>Passing and Updating a Parameter with the Root Level Technique</h1>
46+
<p>You can register cascading parameters in Program.cs to provide site-wide data, it is particularly effective for scenarios like theme settings or account information, where a single value needs to be shared across the entire app.</p>
47+
<ol>
48+
<li>Define the parameter model (if any). Keep in mind that while this example uses a class (<code>RootLevelSampleModel</code>) as the cascading parameter, you're not limited to classes. The root level technique also supports registering struct-typed values, such as <code>string</code>, <code>int</code>, <code>bool</code>, and more.</li>
49+
</ol>
50+
<pre language="csharp">public class RootLevelSampleModel
51+
{
52+
public string Message { get; set; } = "";
53+
}</pre>
54+
<ol start="2">
55+
<li>Register Parameters in <code>Program.cs</code>.</li>
56+
</ol>
57+
<pre language="csharp">builder.Services.AddCascadingValue&lt;RootLevelSampleModel&gt;(sp =&gt; new RootLevelSampleModel
58+
{
59+
Message = "Hello Blazor School!"
60+
});</pre>
61+
<p>To register 2 parameters of the same type, use the <code>Name</code> parameter to differentiate them:</p>
62+
<pre language="csharp">builder.Services.AddCascadingValue&amp;lt;RootLevelSampleModel&amp;gt;(&amp;quot;Variant2&amp;quot;, sp =&amp;gt; new RootLevelSampleModel
63+
{
64+
Message = &amp;quot;Hello Blazor School! (Variant2)&amp;quot;
65+
});</pre>
66+
<ol start="3">
67+
<li>Access parameter in a component</li>
68+
</ol>
69+
<pre language="razor">&lt;div&gt;Message: @Sample?.Message&lt;/div&gt;
70+
&lt;div&gt;Variant2 Message: @SampleVariant2?.Message&lt;/div&gt;
71+
72+
@code {
73+
[CascadingParameter]
74+
public RootLevelSampleModel? Sample { get; set; }
75+
76+
[CascadingParameter(Name = "Variant2")]
77+
public RootLevelSampleModel? SampleVariant2 { get; set; }
78+
}</pre>
79+
<h3>Update Root Level Parameter</h3>
80+
<div class="ratio ratio-16x9"><iframe width="560" height="315" src="https://www.youtube.com/embed/c6o9B3A22pY?si=55M2ehqClBut2wQ6" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"></iframe></div>
81+
<ol>
82+
<li>Define the parameter model: In the parameter model, add <code>CascadingValueSource&lt;T&gt;</code> where <code>T</code> is the parameter model itself.</li>
83+
</ol>
84+
<pre language="csharp">public class RootLevelUpdatingModel
85+
{
86+
public string Message { get; set; } = "";
87+
public CascadingValueSource&lt;RootLevelUpdatingModel&gt;? Source { get; set; }
88+
}</pre>
89+
<ol start="2">
90+
<li>Register in <code>Program.cs</code>.</li>
91+
</ol>
92+
<pre language="csharp">builder.Services.AddCascadingValue(sp =&gt;
93+
{
94+
var value = new RootLevelUpdatingModel()
95+
{
96+
Message = "Hello Blazor School! (With Notifying)",
97+
};
98+
99+
value.Source = new CascadingValueSource&lt;RootLevelUpdatingModel&gt;(value, false);
100+
101+
return value.Source;
102+
});</pre>
103+
<ol start="3">
104+
<li>Update the parameter and call <code>NotifyChangeAsync</code> from <code>CascadingValueSource</code>.</li>
105+
</ol>
106+
<pre language="razor">&lt;h3&gt;Component2 (Level 1)&lt;/h3&gt;
107+
&lt;div&gt;Message: @Sample?.Message&lt;/div&gt;
108+
&lt;button type="button" @onclick="UpdateValueAsync"&gt;Update Value&lt;/button&gt;
109+
110+
@code {
111+
[CascadingParameter]
112+
public RootLevelUpdatingModel? Sample { get; set; }
113+
114+
public async Task UpdateValueAsync()
115+
{
116+
if (Sample is not null &amp;&amp; Sample.Source is not null)
117+
{
118+
Sample.Message = "Overridden from Component2";
119+
await Sample.Source.NotifyChangedAsync();
120+
}
121+
}
122+
}</pre>
123+
<hr class="my-4" />
124+
<h1>Passing, Updating, and Overriding a Parameter with the <code>CascadingValue</code> Component</h1>
125+
<p>The <code>CascadingValue</code> component allows you to pass parameters across a component tree, with the added flexibility of updating and overriding them at different levels. This technique is particularly useful for scenarios like multilingual websites, where different sections might display distinct languages simultaneously.</p>
126+
<ol>
127+
<li>Define the parameter model (optional). While you can use simple types like string or int, a class provides more structure for complex data.</li>
128+
</ol>
129+
<pre language="csharp">public class UsingCascadingValueModel
130+
{
131+
public string Message { get; set; } = "";
132+
}</pre>
133+
<ol start="2">
134+
<li>Pass the Parameter Using <code>CascadingValue</code> in an ancestor component.</li>
135+
</ol>
136+
<pre language="razor">&lt;h3&gt;Ancestor (Level 0)&lt;/h3&gt;
137+
&lt;CascadingValue Value="Model" IsFixed="true"&gt;
138+
&lt;Component1 /&gt;
139+
&lt;/CascadingValue&gt;
140+
141+
@code {
142+
public UsingCascadingValueModel Model { get; set; } = new()
143+
{
144+
Message = "Hello from SimpleCascadingValueExample"
145+
};
146+
}</pre>
147+
<p>In this example, we are not updating the parameter, so we set <code>IsFixed</code> to <code>true</code>. Setting <code>IsFixed</code> to <code>true</code> will not allow the parameter to be updated but will yield better performance.</p>
148+
<ol start="3">
149+
<li>Access the parameter in a descendant component.</li>
150+
</ol>
151+
<pre language="razor">&lt;h3&gt;Component1 (Level 0)&lt;/h3&gt;
152+
&lt;div&gt;Message: @Sample?.Message&lt;/div&gt;
153+
154+
@code {
155+
[CascadingParameter]
156+
public UsingCascadingValueModel? Sample { get; set; }
157+
}</pre>
158+
<p>In case you are passing multiple parameters of the same type, set a name when passing them with <code>CascadingValue</code>:</p>
159+
<pre language="razor">&lt;CascadingValue Value="Model" IsFixed="true" Name="blazorschool_parameter"&gt;
160+
&lt;!-- ... --&gt;
161+
&lt;/CascadingValue&gt;</pre>
162+
<p>Then, in your descendant component, use the name to differentiate the parameters:</p>
163+
<pre language="razor">@code {
164+
[CascadingParameter(Name = "blazorschool_parameter")]
165+
public UsingCascadingValueModel? Sample { get; set; }
166+
}</pre>
167+
<h3>Update Parameter</h3>
168+
<p>A parameter passed by <code>CascadingValue</code> can be updated at any level in the hierarchy.</p>
169+
<div class="ratio ratio-16x9"><iframe width="560" height="315" src="https://www.youtube.com/embed/ceNixnOlR30?si=3Ilhm6KRpCSumgmg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"></iframe></div>
170+
<ol>
171+
<li>Define the parameter and update logic in the ancestor component. Pass both the parameter and the component instance (<code>this</code>) using nested <code>CascadingValue</code>.</li>
172+
</ol>
173+
<pre language="razor">&lt;CascadingValue Value="this"&gt;
174+
&lt;CascadingValue Value="Model"&gt;
175+
&lt;Component1 /&gt;
176+
&lt;/CascadingValue&gt;
177+
&lt;/CascadingValue&gt;
178+
179+
@code {
180+
public UsingCascadingValueModel Model { get; set; } = new()
181+
{
182+
Message = "Hello from UpdateCascadingValueExample"
183+
};
184+
185+
public void UpdateModelMessage(string message)
186+
{
187+
Model.Message = message;
188+
StateHasChanged();
189+
}
190+
}</pre>
191+
<ol start="2">
192+
<li>Update the parameter in a descendant component. Access both the parameter and the ancestor instance to call the update method.</li>
193+
</ol>
194+
<pre language="razor">&lt;h3&gt;Component2 (level 1)&lt;/h3&gt;
195+
&lt;div&gt;Message: @Sample?.Message&lt;/div&gt;
196+
&lt;button type="button" @onclick="UpdateValueAsync"&gt;Update Value&lt;/button&gt;
197+
198+
@code {
199+
[CascadingParameter]
200+
public UsingCascadingValueModel? Sample { get; set; }
201+
202+
[CascadingParameter]
203+
public UpdateCascadingValueExample? Source { get; set; }
204+
205+
public async Task UpdateValueAsync()
206+
{
207+
if (Source is not null)
208+
{
209+
Source.UpdateModelMessage("Update from Component2");
210+
}
211+
}
212+
}</pre>
213+
<h3>Overriding Parameter</h3>
214+
<p>A parameter passed by <code>CascadingValue</code> can be overridden to have different values in different branches. Furthermore, a descendant component can override the parameter value in any ancestor component.</p>
215+
<p>We will set up the example with 4 levels: <code>OverrideCascadingValueExample</code> as the ancestor component, <code>Component1</code> as Level 0, <code>Component2</code> as Level 1, and <code>Component3</code> as Level 2. The parameter will be passed from <code>OverrideCascadingValueExample</code> and will be overridden in <code>Component2</code>, and then updated in <code>Component3</code>.</p>
216+
<div class="ratio ratio-16x9"><iframe width="560" height="315" src="https://www.youtube.com/embed/OaTx-bUkvd8?si=oPnShFOnLpb36ojS" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"></iframe></div>
217+
<ol>
218+
<li>Define the parameter and update logic in the ancestor component. Pass the component instance and parameter using nested <code>CascadingValue</code>.</li>
219+
</ol>
220+
<pre language="razor">&lt;CascadingValue Value="this"&gt;
221+
&lt;CascadingValue Value="Model"&gt;
222+
&lt;Component1 /&gt;
223+
&lt;/CascadingValue&gt;
224+
&lt;/CascadingValue&gt;
225+
226+
@code {
227+
public UsingCascadingValueModel Model { get; set; } = new()
228+
{
229+
Message = "Hello from OverrideCascadingValueExample"
230+
};
231+
232+
public void UpdateModel(string message)
233+
{
234+
Model.Message = message;
235+
StateHasChanged();
236+
}
237+
}</pre>
238+
<ol start="2">
239+
<li>In the descendant component where you want to override the parameter, declare the parameter and the update logic. Pass the current instance (<code>this</code>) and the overridden parameter using <code>CascadingValue</code>.</li>
240+
</ol>
241+
<pre language="razor">&lt;h3&gt;Component2 (level 1)&lt;/h3&gt;
242+
&lt;div&gt;Message: @OverridingSample.Message&lt;/div&gt;
243+
&lt;CascadingValue Value="this"&gt;
244+
&lt;CascadingValue Value="OverridingSample"&gt;
245+
&lt;Component3 /&gt;
246+
&lt;/CascadingValue&gt;
247+
&lt;/CascadingValue&gt;
248+
249+
@code {
250+
public UsingCascadingValueModel OverridingSample { get; set; } = new UsingCascadingValueModel()
251+
{
252+
Message = "Overridden from Component2"
253+
};
254+
255+
public void UpdateModel(string message)
256+
{
257+
OverridingSample.Message = message;
258+
StateHasChanged();
259+
}
260+
}</pre>
261+
<ol start="3">
262+
<li>Update the parameter from a descendant component. Access both the overridden parameter and ancestor instances to update either.</li>
263+
</ol>
264+
<pre language="razor">&lt;h3&gt;Component3 (level 2)&lt;/h3&gt;
265+
&lt;div&gt;Message: @Sample?.Message&lt;/div&gt;
266+
&lt;button type=&quot;button&quot; @onclick=&quot;UpdateAtLevel1&quot;&gt;Update at level 1&lt;/button&gt;
267+
&lt;button type=&quot;button&quot; @onclick=&quot;UpdateAtRoot&quot;&gt;Update at root&lt;/button&gt;
268+
269+
@code {
270+
[CascadingParameter]
271+
public UsingCascadingValueModel? Sample { get; set; }
272+
273+
[CascadingParameter]
274+
public Component2? Component2 { get; set; }
275+
276+
[CascadingParameter]
277+
public OverrideCascadingValueExample? Root { get; set; }
278+
279+
public void UpdateAtLevel1()
280+
{
281+
Component2?.UpdateModel(&quot;Updated from Component3&quot;);
282+
}
283+
284+
public void UpdateAtRoot()
285+
{
286+
Root?.UpdateModel(&quot;Updated from Component3&quot;);
287+
}
288+
}</pre>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"Title": "Cascading Parameter",
3+
"Slug": "cascading-parameter",
4+
"Description": "Passing a parameter to multiple components without prop drilling"
5+
}

0 commit comments

Comments
 (0)