feat(common): add an 'outlet' injector option for ngTemplateOutlet · angular/angular@18003a3

@@ -6,7 +6,6 @@

66

* found in the LICENSE file at https://angular.dev/license

77

*/

889-

import {CommonModule, NgTemplateOutlet} from '../../index';

109

import {

1110

Component,

1211

ContentChildren,

@@ -17,12 +16,16 @@ import {

1716

Injector,

1817

NO_ERRORS_SCHEMA,

1918

OnDestroy,

19+

Optional,

2020

Provider,

2121

QueryList,

22+

SkipSelf,

2223

TemplateRef,

24+

inject,

2325

} from '@angular/core';

2426

import {ComponentFixture, TestBed} from '@angular/core/testing';

2527

import {expect} from '@angular/private/testing/matchers';

28+

import {CommonModule, NgTemplateOutlet} from '../../index';

26292730

describe('NgTemplateOutlet', () => {

2831

let fixture: ComponentFixture<any>;

@@ -49,6 +52,8 @@ describe('NgTemplateOutlet', () => {

4952

DestroyableCmpt,

5053

MultiContextComponent,

5154

InjectValueComponent,

55+

ProvideValueComponent,

56+

NestingCounter,

5257

],

5358

imports: [CommonModule],

5459

providers: [DestroyedSpyService],

@@ -360,6 +365,43 @@ describe('NgTemplateOutlet', () => {

360365

detectChangesAndExpectText('Hello world');

361366

});

362367368+

it('should be able to inherit outlet injector', () => {

369+

const template = `

370+

<ng-template #tpl><inject-value></inject-value></ng-template>

371+

<provide-value>

372+

<ng-container *ngTemplateOutlet="tpl; injector: 'outlet'"></ng-container>

373+

</provide-value>

374+

`;

375+

fixture = createTestComponent(template, [{provide: templateToken, useValue: 'root'}]);

376+

detectChangesAndExpectText('Hello provide-value');

377+

});

378+379+

it('should be able to inherit outlet injector in a deeply nested structure', () => {

380+

// This template should create the following rendered structure

381+

// (Spaces & newlines added for readability):

382+

// <nesting-counter> 1

383+

// <nesting counter> 2

384+

// <nesting-counter> 3

385+

// <nesting-counter> 4 </nesting-counter>

386+

// </nesting-counter>

387+

// </nesting-counter>

388+

// <nesting-counter> 2 </nesting-counter>

389+

// </nesting-counter>

390+

const template = `

391+

<ng-container *ngTemplateOutlet="node; context: {$implicit: [[[[]]], []]}" />

392+393+

<ng-template #node let-data>

394+

<nesting-counter>

395+

@for (item of data; track $index) {

396+

<ng-container *ngTemplateOutlet="node; context: {$implicit: item}; injector: 'outlet'" />

397+

}

398+

</nesting-counter>

399+

</ng-template>

400+

`;

401+

fixture = createTestComponent(template);

402+

detectChangesAndExpectText('12342');

403+

});

404+363405

it('should be available as a standalone directive', () => {

364406

@Component({

365407

selector: 'test-component',

@@ -443,6 +485,14 @@ class TestComponent {

443485

injector: Injector | null = null;

444486

}

445487488+

@Component({

489+

selector: 'provide-value',

490+

template: '<ng-content />',

491+

providers: [{provide: templateToken, useValue: 'provide-value'}],

492+

standalone: false,

493+

})

494+

class ProvideValueComponent {}

495+446496

@Component({

447497

selector: 'inject-value',

448498

template: 'Hello {{tokenValue}}',

@@ -466,6 +516,24 @@ class MultiContextComponent {

466516

context2: {name: string} | undefined;

467517

}

468518519+

const NESTING_DEPTH = new InjectionToken<number>('NESTING_DEPTH');

520+521+

@Component({

522+

selector: 'nesting-counter',

523+

template: '{{depth}}<ng-content />',

524+

providers: [

525+

{

526+

provide: NESTING_DEPTH,

527+

useFactory: (l: number) => (l ? l + 1 : 1),

528+

deps: [[new Optional(), new SkipSelf(), NESTING_DEPTH]],

529+

},

530+

],

531+

standalone: false,

532+

})

533+

class NestingCounter {

534+

depth = inject(NESTING_DEPTH);

535+

}

536+469537

function createTestComponent(

470538

template: string,

471539

providers: Provider[] = [],