diff --git a/packages/core/src/event_delegation_utils.ts b/packages/core/src/event_delegation_utils.ts index 6e8184e3249..73830204572 100644 --- a/packages/core/src/event_delegation_utils.ts +++ b/packages/core/src/event_delegation_utils.ts @@ -78,15 +78,17 @@ export const sharedMapFunction = (rEl: RElement, jsActionMap: Map 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) => { diff --git a/packages/core/src/hydration/blocks.ts b/packages/core/src/hydration/blocks.ts index 4037ee3cd74..869f0760bb8 100644 --- a/packages/core/src/hydration/blocks.ts +++ b/packages/core/src/hydration/blocks.ts @@ -62,6 +62,9 @@ export async function hydrateFromBlockName( deferBlock: DeferBlock | null; hydratedBlocks: Set; }> { + 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(); } diff --git a/packages/platform-server/test/incremental_hydration_spec.ts b/packages/platform-server/test/incremental_hydration_spec.ts index 0ce4fa6f2cd..f8aa41d7add 100644 --- a/packages/platform-server/test/incremental_hydration_spec.ts +++ b/packages/platform-server/test/incremental_hydration_spec.ts @@ -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: ` +
+ @defer (hydrate when true) { +
+ defer block rendered! + {{value()}} +
+ } @placeholder { + Outer block placeholder + } +
+ `, + }) + 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({