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';
109import {
1110Component,
1211ContentChildren,
@@ -17,12 +16,16 @@ import {
1716Injector,
1817NO_ERRORS_SCHEMA,
1918OnDestroy,
19+Optional,
2020Provider,
2121QueryList,
22+SkipSelf,
2223TemplateRef,
24+inject,
2325} from '@angular/core';
2426import {ComponentFixture, TestBed} from '@angular/core/testing';
2527import {expect} from '@angular/private/testing/matchers';
28+import {CommonModule, NgTemplateOutlet} from '../../index';
26292730describe('NgTemplateOutlet', () => {
2831let fixture: ComponentFixture<any>;
@@ -49,6 +52,8 @@ describe('NgTemplateOutlet', () => {
4952DestroyableCmpt,
5053MultiContextComponent,
5154InjectValueComponent,
55+ProvideValueComponent,
56+NestingCounter,
5257],
5358imports: [CommonModule],
5459providers: [DestroyedSpyService],
@@ -360,6 +365,43 @@ describe('NgTemplateOutlet', () => {
360365detectChangesAndExpectText('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+363405it('should be available as a standalone directive', () => {
364406 @Component({
365407selector: 'test-component',
@@ -443,6 +485,14 @@ class TestComponent {
443485injector: 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({
447497selector: 'inject-value',
448498template: 'Hello {{tokenValue}}',
@@ -466,6 +516,24 @@ class MultiContextComponent {
466516context2: {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+469537function createTestComponent(
470538template: string,
471539providers: Provider[] = [],