Skip to content

Commit 379163f

Browse files
authored
Fix sequence number in the component sample (#20093)
1 parent 562f868 commit 379163f

1 file changed

Lines changed: 1 addition & 217 deletions

File tree

aspnetcore/blazor/components/virtualization.md

Lines changed: 1 addition & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: ASP.NET Core Blazor component virtualization
33
author: guardrex
44
description: Learn how to use component virtualization in ASP.NET Core Blazor apps.
5-
monikerRange: '>= aspnetcore-3.1'
5+
monikerRange: '>= aspnetcore-5.0'
66
ms.author: riande
77
ms.custom: mvc
88
ms.date: 10/02/2020
@@ -15,8 +15,6 @@ By [Daniel Roth](https://github.com/danroth27)
1515

1616
Improve the perceived performance of component rendering using the Blazor framework's built-in virtualization support. Virtualization is a technique for limiting UI rendering to just the parts that are currently visible. For example, virtualization is helpful when the app must render a long list of items and only a subset of items is required to be visible at any given time. Blazor provides the `Virtualize` component that can be used to add virtualization to an app's components.
1717

18-
::: moniker range=">= aspnetcore-5.0"
19-
2018
Without virtualization, a typical list might use a C# [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop to render each item in the list:
2119

2220
```razor
@@ -130,220 +128,6 @@ The size of each item in pixels can be set with `ItemSize` (default: 50px):
130128
</Virtualize>
131129
```
132130

133-
::: moniker-end
134-
135-
::: moniker range="< aspnetcore-5.0"
136-
137-
For example, a grid or list that renders hundreds of rows containing components is processor intensive to render. Consider virtualizing a grid or list layout so that only a subset of the components is rendered at any given time.
138-
139-
The following `Virtualize` component (`Virtualize.cs`) implements <xref:Microsoft.AspNetCore.Components.ComponentBase> to render child content based on user scrolling:
140-
141-
```csharp
142-
using System;
143-
using System.Collections.Generic;
144-
using System.Linq;
145-
using System.Threading.Tasks;
146-
using Microsoft.AspNetCore.Components;
147-
using Microsoft.AspNetCore.Components.Rendering;
148-
using Microsoft.JSInterop;
149-
150-
public class Virtualize<TItem> : ComponentBase
151-
{
152-
[Parameter]
153-
public string TagName { get; set; } = "div";
154-
155-
[Parameter]
156-
public RenderFragment<TItem> ChildContent { get; set; }
157-
158-
[Parameter]
159-
public ICollection<TItem> Items { get; set; }
160-
161-
[Parameter]
162-
public double ItemHeight { get; set; }
163-
164-
[Parameter(CaptureUnmatchedValues = true)]
165-
public Dictionary<string, object> Attributes { get; set; }
166-
167-
[Inject]
168-
IJSRuntime JS { get; set; }
169-
170-
ElementReference contentElement;
171-
int numItemsToSkipBefore;
172-
int numItemsToShow;
173-
174-
protected override void BuildRenderTree(RenderTreeBuilder builder)
175-
{
176-
// Render actual content
177-
builder.OpenElement(0, TagName);
178-
builder.AddMultipleAttributes(1, Attributes);
179-
180-
var translateY = numItemsToSkipBefore * ItemHeight;
181-
builder.AddAttribute(2, "style", $"transform: translateY({ translateY }px);");
182-
builder.AddAttribute(2, "data-translateY", translateY);
183-
builder.AddElementReferenceCapture(3, @ref => { contentElement = @ref; });
184-
185-
// As an important optimization, *don't* use builder.AddContent(seq, ChildContent, item)
186-
// because that implicitly wraps a new region around each item, which in turn means that
187-
// @key does nothing (because keys are scoped to regions). Instead, create a single
188-
// container region and then invoke the fragments directly.
189-
190-
builder.OpenRegion(4);
191-
192-
foreach (var item in Items.Skip(numItemsToSkipBefore).Take(numItemsToShow))
193-
{
194-
ChildContent(item)(builder);
195-
}
196-
197-
builder.CloseRegion();
198-
199-
builder.CloseElement();
200-
201-
// Also emit a spacer that causes the total vertical height to add up to
202-
// Items.Count()*numItems
203-
204-
builder.OpenElement(5, "div");
205-
var numHiddenItems = Items.Count - numItemsToShow;
206-
builder.AddAttribute(6, "style",
207-
$"width: 1px; height: { numHiddenItems * ItemHeight }px;");
208-
builder.CloseElement();
209-
}
210-
211-
protected override async Task OnAfterRenderAsync(bool firstRender)
212-
{
213-
if (firstRender)
214-
{
215-
var objectRef = DotNetObjectReference.Create(this);
216-
var initResult = await JS.InvokeAsync<ScrollEventArgs>(
217-
"VirtualizedComponent._initialize", objectRef, contentElement);
218-
OnScroll(initResult);
219-
}
220-
}
221-
222-
[JSInvokable]
223-
public void OnScroll(ScrollEventArgs args)
224-
{
225-
var relativeTop = args.ContainerRect.Top - args.ContentRect.Top;
226-
numItemsToSkipBefore = Math.Max(0, (int)(relativeTop / ItemHeight));
227-
228-
var visibleHeight = args.ContainerRect.Bottom -
229-
(args.ContentRect.Top + numItemsToSkipBefore * ItemHeight);
230-
numItemsToShow = (int)Math.Ceiling(visibleHeight / ItemHeight) + 3;
231-
232-
StateHasChanged();
233-
}
234-
235-
public class ScrollEventArgs
236-
{
237-
public DOMRect ContainerRect { get; set; }
238-
public DOMRect ContentRect { get; set; }
239-
}
240-
241-
public class DOMRect
242-
{
243-
public double Top { get; set; }
244-
public double Bottom { get; set; }
245-
public double Left { get; set; }
246-
public double Right { get; set; }
247-
public double Width { get; set; }
248-
public double Height { get; set; }
249-
}
250-
}
251-
```
252-
253-
The following `FetchData` component (`FetchData.razor`) uses the preceding `Virtualize` component to display 25 rows of weather data at a time:
254-
255-
```razor
256-
@page "/"
257-
@page "/fetchdata"
258-
@inject HttpClient Http
259-
260-
<h1>Weather forecast</h1>
261-
262-
<p>This component demonstrates fetching data from a service.</p>
263-
264-
@if (forecasts == null)
265-
{
266-
<p><em>Loading...</em></p>
267-
}
268-
else
269-
{
270-
<h2>Using <code>table-layout: fixed</code></h2>
271-
<div style="height:300px; overflow-y: auto;">
272-
<Virtualize ItemHeight="25" Items="@forecasts">
273-
<tr @key="@context.GetHashCode()"
274-
style="display: table; table-layout: fixed; width: 100%;">
275-
<td>@context.Date.ToShortDateString()</td>
276-
<td>@context.TemperatureC</td>
277-
<td>@context.TemperatureF</td>
278-
<td>@context.Summary</td>
279-
</tr>
280-
</Virtualize>
281-
</div>
282-
283-
<h2>Using <code>position: sticky</code></h2>
284-
<div style="height: 300px; overflow-y: auto; position: relative;">
285-
<table>
286-
<thead class="sticky">
287-
<tr>
288-
<th>Date</th>
289-
<th>Temperature (C)</th>
290-
<th>Temperature (F)</th>
291-
<th>Summary</th>
292-
</tr>
293-
</thead>
294-
295-
<Virtualize TagName="tbody" ItemHeight="25" Items="@forecasts">
296-
<tr @key="@context.GetHashCode()">
297-
<td>@context.Date.ToShortDateString()</td>
298-
<td>@context.TemperatureC</td>
299-
<td>@context.TemperatureF</td>
300-
<td>@context.Summary</td>
301-
</tr>
302-
</Virtualize>
303-
</table>
304-
</div>
305-
306-
<style type="text/css">
307-
thead.sticky th {
308-
position: sticky;
309-
top: 0;
310-
}
311-
tr td {
312-
height: 25px;
313-
}
314-
</style>
315-
}
316-
317-
@code {
318-
private WeatherForecast[] forecasts;
319-
320-
protected override async Task OnInitializedAsync()
321-
{
322-
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>(
323-
"sample-data/weather.json");
324-
}
325-
326-
public class WeatherForecast
327-
{
328-
public DateTime Date { get; set; }
329-
330-
public int TemperatureC { get; set; }
331-
332-
public string Summary { get; set; }
333-
334-
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
335-
}
336-
}
337-
```
338-
339-
In the preceding example, the approach is about avoiding absolute positioning for each individual item. Absolute positioning would have some advantages (we can be sure the items do take up the specified amount of Y space) but has the bad disadvantage that you lose the normal widths and can't get table columns to line up across rows/header when based on content sizing.
340-
341-
The concept behind the design of the `Virtualize` component is that the component doesn't change how the items are laid out within the DOM. There are no added wrapper elements, besides the single one whose `TagName` you specify.
342-
343-
The best approach is to avoid even the `TagName` wrapper element. Have the `Virtualize` component emit no elements of its own. All the component does is render however many of the template instances are required to fill up any remaining visible space in the closest scrollable ancestor, plus add a following spacer element that makes the scrollable ancestor have the right scrolling range. As far as the layout is concerned, it's the same as if the full range of children were physically in the DOM. It does require you to specify an accurate `ItemHeight` though. If you get it wrong (for example, because you're confused and think it's valid to specify `style.height` on a `<tr>`), then the component ends up rendering the wrong number of template instances and either not fill up the UI or inefficiently render too many. Additionally, the scroll range on the parent won't be correct.
344-
345-
::: moniker-end
346-
347131
## State changes
348132

349133
When making changes to items rendered by the `Virtualize` component, call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> to force re-evaluation and rerendering of the component.

0 commit comments

Comments
 (0)