Skip to content

Commit 805c8f7

Browse files
committed
Avoid flicker when replacing component instance
1 parent 39e741f commit 805c8f7

File tree

6 files changed

+37
-23
lines changed

6 files changed

+37
-23
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.web.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webview.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Rendering/BrowserRenderer.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import { attachToEventDelegator as attachNavigationManagerToEventDelegator } fro
99
import { applyAnyDeferredValue, tryApplySpecialProperty } from './DomSpecialPropertyUtil';
1010
const sharedTemplateElemForParsing = document.createElement('template');
1111
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
12-
const elementsToClearOnRootComponentRender: { [componentId: number]: LogicalElement } = {};
12+
const elementsToClearOnRootComponentRender = new Set<LogicalElement>();
1313
const internalAttributeNamePrefix = '__internal_';
1414
const eventPreventDefaultAttributeNamePrefix = 'preventDefault_';
1515
const eventStopPropagationAttributeNamePrefix = 'stopPropagation_';
1616
const interactiveRootComponentPropname = Symbol();
17+
const preserveContentOnDisposalPropname = Symbol();
1718

1819
export class BrowserRenderer {
1920
public eventDelegator: EventDelegator;
@@ -47,7 +48,7 @@ export class BrowserRenderer {
4748
// If we want to preserve existing HTML content of the root element, we don't apply the mechanism for
4849
// clearing existing children. Rendered content will then append rather than replace the existing HTML content.
4950
if (!appendContent) {
50-
elementsToClearOnRootComponentRender[componentId] = element;
51+
elementsToClearOnRootComponentRender.add(element);
5152
}
5253
}
5354

@@ -58,15 +59,13 @@ export class BrowserRenderer {
5859
}
5960

6061
// On the first render for each root component, clear any existing content (e.g., prerendered)
61-
const rootElementToClear = elementsToClearOnRootComponentRender[componentId];
62-
if (rootElementToClear) {
63-
delete elementsToClearOnRootComponentRender[componentId];
64-
emptyLogicalElement(rootElementToClear);
62+
if (elementsToClearOnRootComponentRender.delete(element)) {
63+
emptyLogicalElement(element);
6564

66-
if (rootElementToClear instanceof Comment) {
65+
if (element instanceof Comment) {
6766
// We sanitize start comments by removing all the information from it now that we don't need it anymore
6867
// as it adds noise to the DOM.
69-
rootElementToClear.textContent = '!';
68+
element.textContent = '!';
7069
}
7170
}
7271

@@ -88,7 +87,12 @@ export class BrowserRenderer {
8887
// component was added.
8988
const logicalElement = this.childComponentLocations[componentId];
9089
markAsInteractiveRootComponentElement(logicalElement, false);
91-
emptyLogicalElement(logicalElement);
90+
91+
if (shouldPreserveContentOnInteractiveComponentDisposal(logicalElement)) {
92+
elementsToClearOnRootComponentRender.add(logicalElement);
93+
} else {
94+
emptyLogicalElement(logicalElement);
95+
}
9296
}
9397

9498
delete this.childComponentLocations[componentId];
@@ -377,6 +381,14 @@ export function isInteractiveRootComponentElement(element: LogicalElement): bool
377381
return element[interactiveRootComponentPropname];
378382
}
379383

384+
export function setShouldPreserveContentOnInteractiveComponentDisposal(element: LogicalElement, shouldPreserve: boolean) {
385+
element[preserveContentOnDisposalPropname] = shouldPreserve;
386+
}
387+
388+
function shouldPreserveContentOnInteractiveComponentDisposal(element: LogicalElement): boolean {
389+
return element[preserveContentOnDisposalPropname] === true;
390+
}
391+
380392
export interface ComponentDescriptor {
381393
start: Node;
382394
end: Node;

src/Components/Web.JS/src/Rendering/DomMerging/DomSync.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
import { AutoComponentDescriptor, ComponentDescriptor, ServerComponentDescriptor, WebAssemblyComponentDescriptor, canMergeDescriptors, discoverComponents, mergeDescriptors } from '../../Services/ComponentDescriptorDiscovery';
5-
import { isInteractiveRootComponentElement } from '../BrowserRenderer';
5+
import { isInteractiveRootComponentElement, setShouldPreserveContentOnInteractiveComponentDisposal } from '../BrowserRenderer';
66
import { applyAnyDeferredValue } from '../DomSpecialPropertyUtil';
77
import { LogicalElement, getLogicalChildrenArray, getLogicalNextSibling, getLogicalParent, getLogicalRootDescriptor, insertLogicalChild, insertLogicalChildBefore, isLogicalElement, toLogicalElement, toLogicalRootCommentElement } from '../LogicalElements';
88
import { synchronizeAttributes } from './AttributeSync';
@@ -331,7 +331,8 @@ function upgradeComponentCommentsToLogicalRootComments(root: Node): ComponentDes
331331
if (existingDescriptor) {
332332
allDescriptors.push(existingDescriptor);
333333
} else {
334-
toLogicalRootCommentElement(descriptor);
334+
const logicalElement = toLogicalRootCommentElement(descriptor);
335+
setShouldPreserveContentOnInteractiveComponentDisposal(logicalElement, true);
335336

336337
// Since we've already parsed the payloads from the start and end comments,
337338
// we sanitize them to reduce noise in the DOM.

src/Components/Web.JS/src/Services/WebRootComponentManager.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,17 +341,18 @@ export class WebRootComponentManager implements DescriptorHandler, RootComponent
341341
component.assignedRendererId = rendererId;
342342
component.uniqueIdAtLastUpdate = component.descriptor.uniqueId;
343343
return { type: 'add', ssrComponentId: component.ssrComponentId, marker: descriptorToMarker(component.descriptor) };
344-
}
344+
} else {
345+
// The component has already been added for interactivity.
346+
if (component.uniqueIdAtLastUpdate === component.descriptor.uniqueId) {
347+
// The descriptor has not changed since the last update.
348+
// Nothing to do.
349+
return null;
350+
}
345351

346-
if (component.uniqueIdAtLastUpdate === component.descriptor.uniqueId) {
347-
// The descriptor has not changed since the last update.
348-
// Nothing to do.
349-
return null;
352+
// The descriptor has changed since it was last updated, so we'll update the component's parameters.
353+
component.uniqueIdAtLastUpdate = component.descriptor.uniqueId;
354+
return { type: 'update', ssrComponentId: component.ssrComponentId, marker: descriptorToMarker(component.descriptor) };
350355
}
351-
352-
// The descriptor has changed since it was last updated, so we'll update the component's parameters.
353-
component.uniqueIdAtLastUpdate = component.descriptor.uniqueId;
354-
return { type: 'update', ssrComponentId: component.ssrComponentId, marker: descriptorToMarker(component.descriptor) };
355356
} else {
356357
// The descriptor was removed from the document.
357358
this.unregisterComponent(component);

0 commit comments

Comments
 (0)