fix(core): Prevents trying to trigger incremental hydration on CSR (#58366)

hydrate triggers were firing in CSR cases and attempting to find parent defer blocks. This prevents that from happening. In these cases, the defer block id will be empty.

fixes: #58359

PR Close #58366
This commit is contained in:
Jessica Janiuk 2024-10-25 12:51:11 -04:00 committed by Alex Rickabaugh
parent 98f54cb134
commit fd7716440b
3 changed files with 62 additions and 9 deletions

View file

@ -78,15 +78,17 @@ export const sharedMapFunction = (rEl: RElement, jsActionMap: Map<string, Set<El
};
export function removeListenersFromBlocks(blockNames: string[], injector: Injector) {
let blockList: Element[] = [];
const jsActionMap = injector.get(BLOCK_ELEMENT_MAP);
for (let blockName of blockNames) {
if (jsActionMap.has(blockName)) {
blockList = [...blockList, ...jsActionMap.get(blockName)!];
if (blockNames.length > 0) {
let blockList: Element[] = [];
const jsActionMap = injector.get(BLOCK_ELEMENT_MAP);
for (let blockName of blockNames) {
if (jsActionMap.has(blockName)) {
blockList = [...blockList, ...jsActionMap.get(blockName)!];
}
}
const replayList = new Set(blockList);
replayList.forEach(removeListeners);
}
const replayList = new Set(blockList);
replayList.forEach(removeListeners);
}
export const removeListeners = (el: Element) => {

View file

@ -62,6 +62,9 @@ export async function hydrateFromBlockName(
deferBlock: DeferBlock | null;
hydratedBlocks: Set<string>;
}> {
if (blockName == null) {
return {deferBlock: null, hydratedBlocks};
}
const deferBlockRegistry = injector.get(DeferBlockRegistry);
// Make sure we don't hydrate/trigger the same thing multiple times
@ -116,4 +119,5 @@ export async function incrementallyHydrateFromBlockName(
// the hydration process has finished, which could result in problems
await whenStable(injector.get(ApplicationRef));
}
return Promise.resolve();
}

View file

@ -18,11 +18,17 @@ import {
ɵwhenStable as whenStable,
} from '@angular/core';
import {getAppContents, prepareEnvironmentAndHydrate, resetTViewsFor} from './dom_utils';
import {getAppContents, hydrate, prepareEnvironmentAndHydrate, resetTViewsFor} from './dom_utils';
import {getComponentRef, ssr, timeout} from './hydration_utils';
import {getDocument} from '@angular/core/src/render3/interfaces/document';
import {isPlatformServer} from '@angular/common';
import {withEventReplay, withIncrementalHydration} from '@angular/platform-browser';
import {
provideClientHydration,
withEventReplay,
withIncrementalHydration,
} from '@angular/platform-browser';
import {TestBed} from '@angular/core/testing';
import {PLATFORM_BROWSER_ID} from '@angular/common/src/platform_id';
describe('platform-server partial hydration integration', () => {
const originalWindow = globalThis.window;
@ -1393,6 +1399,47 @@ describe('platform-server partial hydration integration', () => {
}, 100_000);
});
describe('client side navigation', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
provideClientHydration(withIncrementalHydration()),
],
});
});
it('should not try to hydrate in CSR only cases', async () => {
@Component({
standalone: true,
selector: 'app',
template: `
<main (click)="fnA()">
@defer (hydrate when true) {
<article>
defer block rendered!
<span id="test" (click)="fnB()">{{value()}}</span>
</article>
} @placeholder {
<span>Outer block placeholder</span>
}
</main>
`,
})
class SimpleComponent {
value = signal('start');
fnA() {}
fnB() {
this.value.set('end');
}
}
const fixture = TestBed.createComponent(SimpleComponent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('Outer block placeholder');
});
});
describe('cleanup', () => {
it('should cleanup partial hydration blocks appropriately', async () => {
@Component({