@@ -28,12 +28,9 @@ import {
28
28
} from '@angular/platform-browser' ;
29
29
import { TestBed } from '@angular/core/testing' ;
30
30
import { PLATFORM_BROWSER_ID } from '@angular/common/src/platform_id' ;
31
- import { DEHYDRATED_BLOCK_REGISTRY , DehydratedBlockRegistry } from '@angular/core/src/defer/registry' ;
31
+ import { DEHYDRATED_BLOCK_REGISTRY } from '@angular/core/src/defer/registry' ;
32
32
import { JSACTION_BLOCK_ELEMENT_MAP } from '@angular/core/src/hydration/tokens' ;
33
- import {
34
- EventContractDetails ,
35
- JSACTION_EVENT_CONTRACT ,
36
- } from '@angular/core/src/event_delegation_utils' ;
33
+ import { JSACTION_EVENT_CONTRACT } from '@angular/core/src/event_delegation_utils' ;
37
34
38
35
describe ( 'platform-server partial hydration integration' , ( ) => {
39
36
const originalWindow = globalThis . window ;
@@ -1445,6 +1442,88 @@ describe('platform-server partial hydration integration', () => {
1445
1442
} ) ;
1446
1443
} ) ;
1447
1444
1445
+ describe ( 'control flow' , ( ) => {
1446
+ it ( 'should support hydration for all items in a for loop' , async ( ) => {
1447
+ @Component ( {
1448
+ standalone : true ,
1449
+ selector : 'app' ,
1450
+ template : `
1451
+ <main>
1452
+ @defer (on interaction; hydrate on interaction) {
1453
+ <div id="main" (click)="fnA()">
1454
+ <p>Main defer block rendered!</p>
1455
+ @for (item of items; track $index) {
1456
+ @defer (on interaction; hydrate on interaction) {
1457
+ <article id="item-{{item}}">
1458
+ defer block {{item}} rendered!
1459
+ <span (click)="fnB()">{{value()}}</span>
1460
+ </article>
1461
+ } @placeholder {
1462
+ <span>Outer block placeholder</span>
1463
+ }
1464
+ }
1465
+ </div>
1466
+ } @placeholder {
1467
+ <span>Outer block placeholder</span>
1468
+ }
1469
+ </main>
1470
+ ` ,
1471
+ } )
1472
+ class SimpleComponent {
1473
+ value = signal ( 'start' ) ;
1474
+ items = [ 1 , 2 , 3 , 4 , 5 , 6 ] ;
1475
+ fnA ( ) { }
1476
+ fnB ( ) {
1477
+ this . value . set ( 'end' ) ;
1478
+ }
1479
+ }
1480
+
1481
+ const appId = 'custom-app-id' ;
1482
+ const providers = [ { provide : APP_ID , useValue : appId } ] ;
1483
+ const hydrationFeatures = ( ) => [ withIncrementalHydration ( ) ] ;
1484
+
1485
+ const html = await ssr ( SimpleComponent , { envProviders : providers , hydrationFeatures} ) ;
1486
+ const ssrContents = getAppContents ( html ) ;
1487
+
1488
+ // <main> uses "eager" `custom-app-id` namespace.
1489
+ // <div>s inside a defer block have `d0` as a namespace.
1490
+ expect ( ssrContents ) . toContain ( '<article id="item-1" jsaction="click:;keydown:;"' ) ;
1491
+ // Outer defer block is rendered.
1492
+ expect ( ssrContents ) . toContain ( 'defer block 1 rendered' ) ;
1493
+
1494
+ // Internal cleanup before we do server->client transition in this test.
1495
+ resetTViewsFor ( SimpleComponent ) ;
1496
+
1497
+ ////////////////////////////////
1498
+ const doc = getDocument ( ) ;
1499
+ const appRef = await prepareEnvironmentAndHydrate ( doc , html , SimpleComponent , {
1500
+ envProviders : [ ...providers , { provide : PLATFORM_ID , useValue : 'browser' } ] ,
1501
+ hydrationFeatures,
1502
+ } ) ;
1503
+ const compRef = getComponentRef < SimpleComponent > ( appRef ) ;
1504
+ appRef . tick ( ) ;
1505
+ await whenStable ( appRef ) ;
1506
+
1507
+ const appHostNode = compRef . location . nativeElement ;
1508
+
1509
+ expect ( appHostNode . outerHTML ) . toContain ( '<article id="item-1" jsaction="click:;keydown:;"' ) ;
1510
+
1511
+ // Emit an event inside of a defer block, which should result
1512
+ // in triggering the defer block (start loading deps, etc) and
1513
+ // subsequent hydration.
1514
+ const article = doc . getElementById ( 'item-1' ) ! ;
1515
+ const clickEvent = new CustomEvent ( 'click' , { bubbles : true } ) ;
1516
+ article . dispatchEvent ( clickEvent ) ;
1517
+ await timeout ( 1000 ) ; // wait for defer blocks to resolve
1518
+
1519
+ appRef . tick ( ) ;
1520
+ expect ( appHostNode . outerHTML ) . not . toContain (
1521
+ '<article id="item-1" jsaction="click:;keydown:;"' ,
1522
+ ) ;
1523
+ expect ( appHostNode . outerHTML ) . not . toContain ( '<span>Outer block placeholder</span>' ) ;
1524
+ } ) ;
1525
+ } ) ;
1526
+
1448
1527
describe ( 'cleanup' , ( ) => {
1449
1528
it ( 'should cleanup partial hydration blocks appropriately' , async ( ) => {
1450
1529
@Component ( {
@@ -1585,7 +1664,6 @@ describe('platform-server partial hydration integration', () => {
1585
1664
1586
1665
await timeout ( 1000 ) ; // wait for defer blocks to resolve
1587
1666
1588
- appRef . tick ( ) ;
1589
1667
expect ( registry . size ) . toBe ( 1 ) ;
1590
1668
expect ( registry . has ( 'd0' ) ) . toBeFalsy ( ) ;
1591
1669
expect ( jsActionMap . size ) . toBe ( 1 ) ;
0 commit comments