66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { ComponentRef , createNgModule , Directive , Injector , Input , NgModuleFactory , NgModuleRef , OnChanges , OnDestroy , SimpleChanges , Type , ViewContainerRef } from '@angular/core' ;
10-
9+ import { ComponentRef , createNgModule , Directive , DoCheck , Injector , Input , NgModuleFactory , NgModuleRef , OnChanges , OnDestroy , SimpleChanges , Type , ViewContainerRef } from '@angular/core' ;
1110
1211/**
1312 * Instantiates a {@link Component} type and inserts its Host View into the current View.
@@ -22,6 +21,9 @@ import {ComponentRef, createNgModule, Directive, Injector, Input, NgModuleFactor
2221 *
2322 * You can control the component creation process by using the following optional attributes:
2423 *
24+ * * `ngComponentOutletInputs`: Optional component inputs object, which will be bind to the
25+ * component.
26+ *
2527 * * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
2628 * the Component. Defaults to the injector of the current view container.
2729 *
@@ -42,6 +44,13 @@ import {ComponentRef, createNgModule, Directive, Injector, Input, NgModuleFactor
4244 * <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
4345 * ```
4446 *
47+ * With inputs
48+ * ```
49+ * <ng-container *ngComponentOutlet="componentTypeExpression;
50+ * inputs: inputsExpression;">
51+ * </ng-container>
52+ * ```
53+ *
4554 * Customized injector/content
4655 * ```
4756 * <ng-container *ngComponentOutlet="componentTypeExpression;
@@ -72,9 +81,10 @@ import {ComponentRef, createNgModule, Directive, Injector, Input, NgModuleFactor
7281 selector : '[ngComponentOutlet]' ,
7382 standalone : true ,
7483} )
75- export class NgComponentOutlet implements OnChanges , OnDestroy {
84+ export class NgComponentOutlet implements OnChanges , DoCheck , OnDestroy {
7685 @Input ( ) ngComponentOutlet : Type < any > | null = null ;
7786
87+ @Input ( ) ngComponentOutletInputs ?: Record < string , unknown > ;
7888 @Input ( ) ngComponentOutletInjector ?: Injector ;
7989 @Input ( ) ngComponentOutletContent ?: any [ ] [ ] ;
8090
@@ -87,45 +97,96 @@ export class NgComponentOutlet implements OnChanges, OnDestroy {
8797 private _componentRef : ComponentRef < any > | undefined ;
8898 private _moduleRef : NgModuleRef < any > | undefined ;
8999
100+ /**
101+ * A helper data structure that allows us to track inputs that were part of the
102+ * ngComponentOutletInputs expression. Tracking inputs is necessary for proper removal of ones
103+ * that are no longer referenced.
104+ */
105+ private _inputsUsed = new Map < string , boolean > ( ) ;
106+
90107 constructor ( private _viewContainerRef : ViewContainerRef ) { }
91108
109+ private _needToReCreateNgModuleInstance ( changes : SimpleChanges ) : boolean {
110+ // Note: square brackets property accessor is safe for Closure compiler optimizations (the
111+ // `changes` argument of the `ngOnChanges` lifecycle hook retains the names of the fields that
112+ // were changed).
113+ return changes [ 'ngComponentOutletNgModule' ] !== undefined ||
114+ changes [ 'ngComponentOutletNgModuleFactory' ] !== undefined ;
115+ }
116+
117+ private _needToReCreateComponentInstance ( changes : SimpleChanges ) : boolean {
118+ // Note: square brackets property accessor is safe for Closure compiler optimizations (the
119+ // `changes` argument of the `ngOnChanges` lifecycle hook retains the names of the fields that
120+ // were changed).
121+ return changes [ 'ngComponentOutlet' ] !== undefined ||
122+ changes [ 'ngComponentOutletContent' ] !== undefined ||
123+ changes [ 'ngComponentOutletInjector' ] !== undefined ||
124+ this . _needToReCreateNgModuleInstance ( changes ) ;
125+ }
126+
92127 /** @nodoc */
93128 ngOnChanges ( changes : SimpleChanges ) {
94- const {
95- _viewContainerRef : viewContainerRef ,
96- ngComponentOutletNgModule : ngModule ,
97- ngComponentOutletNgModuleFactory : ngModuleFactory ,
98- } = this ;
99- viewContainerRef . clear ( ) ;
100- this . _componentRef = undefined ;
101-
102- if ( this . ngComponentOutlet ) {
103- const injector = this . ngComponentOutletInjector || viewContainerRef . parentInjector ;
104-
105- if ( changes [ 'ngComponentOutletNgModule' ] || changes [ 'ngComponentOutletNgModuleFactory' ] ) {
106- if ( this . _moduleRef ) this . _moduleRef . destroy ( ) ;
107-
108- if ( ngModule ) {
109- this . _moduleRef = createNgModule ( ngModule , getParentInjector ( injector ) ) ;
110- } else if ( ngModuleFactory ) {
111- this . _moduleRef = ngModuleFactory . create ( getParentInjector ( injector ) ) ;
112- } else {
113- this . _moduleRef = undefined ;
129+ if ( this . _needToReCreateComponentInstance ( changes ) ) {
130+ this . _viewContainerRef . clear ( ) ;
131+ this . _inputsUsed . clear ( ) ;
132+ this . _componentRef = undefined ;
133+
134+ if ( this . ngComponentOutlet ) {
135+ const injector = this . ngComponentOutletInjector || this . _viewContainerRef . parentInjector ;
136+
137+ if ( this . _needToReCreateNgModuleInstance ( changes ) ) {
138+ this . _moduleRef ?. destroy ( ) ;
139+
140+ if ( this . ngComponentOutletNgModule ) {
141+ this . _moduleRef =
142+ createNgModule ( this . ngComponentOutletNgModule , getParentInjector ( injector ) ) ;
143+ } else if ( this . ngComponentOutletNgModuleFactory ) {
144+ this . _moduleRef =
145+ this . ngComponentOutletNgModuleFactory . create ( getParentInjector ( injector ) ) ;
146+ } else {
147+ this . _moduleRef = undefined ;
148+ }
149+ }
150+
151+ this . _componentRef = this . _viewContainerRef . createComponent ( this . ngComponentOutlet , {
152+ injector,
153+ ngModuleRef : this . _moduleRef ,
154+ projectableNodes : this . ngComponentOutletContent ,
155+ } ) ;
156+ }
157+ }
158+ }
159+
160+ /** @nodoc */
161+ ngDoCheck ( ) {
162+ if ( this . _componentRef ) {
163+ if ( this . ngComponentOutletInputs ) {
164+ for ( const inputName of Object . keys ( this . ngComponentOutletInputs ) ) {
165+ this . _inputsUsed . set ( inputName , true ) ;
114166 }
115167 }
116168
117- this . _componentRef = viewContainerRef . createComponent ( this . ngComponentOutlet , {
118- index : viewContainerRef . length ,
119- injector,
120- ngModuleRef : this . _moduleRef ,
121- projectableNodes : this . ngComponentOutletContent ,
122- } ) ;
169+ this . _applyInputStateDiff ( this . _componentRef ) ;
123170 }
124171 }
125172
126173 /** @nodoc */
127174 ngOnDestroy ( ) {
128- if ( this . _moduleRef ) this . _moduleRef . destroy ( ) ;
175+ this . _moduleRef ?. destroy ( ) ;
176+ }
177+
178+ private _applyInputStateDiff ( componentRef : ComponentRef < unknown > ) {
179+ for ( const [ inputName , touched ] of this . _inputsUsed ) {
180+ if ( ! touched ) {
181+ // The input that was previously active no longer exists and needs to be set to undefined.
182+ componentRef . setInput ( inputName , undefined ) ;
183+ this . _inputsUsed . delete ( inputName ) ;
184+ } else {
185+ // Since touched is true, it can be asserted that the inputs object is not empty.
186+ componentRef . setInput ( inputName , this . ngComponentOutletInputs ! [ inputName ] ) ;
187+ this . _inputsUsed . set ( inputName , false ) ;
188+ }
189+ }
129190 }
130191}
131192
0 commit comments