angular: Dynamic standalone component can not resolve FormsModule (probably others from @angular/*)

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

No

Description

I have a standalone component defined like this:

import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

export const componentMetadata : Component = {
	jit: true,
	standalone: true,
	selector: '<dynamic-component>',
	imports: [
		FormsModule, CommonModule
	],
};
export class DynamicComponent {
	
	public constructor() {}
....
}

Then I have component that should create dynamically, as a child, this component, with template that is coming from DB. Sample code:

import ....

@Component({
	standalone: true,
	selector: '<container-component>',
	template: '<ng-container #content></ng-container>',
	providers: [ {
		provide: NG_VALUE_ACCESSOR,
		useExisting: forwardRef(() => ContainerComponent),
		multi: true
	}],
})

export class ContainerComponent implements OnInit, OnDestroy, ControlValueAccessor {
.....
    @ViewChild('content', { read: ViewContainerRef, static: true })
    contentContainer: ViewContainerRef;
.....
    private async createContent() {
	this.cleanComponentRef();
	const component = await this.setupContentComponent(this.content); // we have content when we reach this method
	this.componentRef = this.contentContainer.createComponent(component, {
		injector: this.contentContainer.injector,
	});
    }

    private async setupContentComponent(content: string = '') {
	const defs = await import('./dynamic.component');
	const currentMeta = { ...defs.componentMetadata };
	currentMeta.template = content;

	return Component(currentMeta)(defs.DynamicComponent);
    }

    private cleanComponentRef() {
	if (this.componentRef) {
	    this.componentRef.destroy();
	    delete this.componentRef;
        }
    }
}

The content is angular template with directives like ngModel. The problem is that i get a lot of Can't bind to 'ngModel' since it isn't a known property of...., but Forms module is imported in the dynamic component and should be compiled properly (IMO)

During investigation I reached a function in core.mjs function getStandaloneDefFunctions(type, imports) { when checking for exported directives in this code:

if (!!getNgModuleDef(dep)) {
  const scope = transitiveScopesFor(dep);
  for (const dir of scope.exported.directives) {

dep (FormsModule) doesn’t have any exported members

scope { “schemas”: null, “compilation”: { “directives”: {}, “pipes”: {} }, “exported”: { “directives”: {}, “pipes”: {} } }

Please provide a link to a minimal reproduction of the bug

StackBlitz

Please provide the exception or error you saw

NG0303: Can't bind to 'ngModel' since it isn't a known property of

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 14.2.9
Node: 19.0.1 (Unsupported)
Package Manager: npm 8.19.2 
OS: linux x64

Angular: 14.2.10
... animations, common, compiler, compiler-cli, core, forms
... localize, platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1402.9
@angular-devkit/build-angular   14.2.9
@angular-devkit/core            14.2.9
@angular-devkit/schematics      14.2.9
@angular/cdk                    14.2.7
@angular/cli                    14.2.9
@schematics/angular             14.2.9
rxjs                            7.5.7
typescript                      4.7.4

Anything else?

This is happening when aot: true in angular.json When aot: false, then all is working but then template translations (all tags in .html files having i18n attribute) are not expanded, but all @localize'text' messages are. Probably this is for another issue / discussion

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

I finally succeeded to get hybrid model working.
For our personal case we need not so much - just NgModel, NgIf and NgFor
I created a module with following code (we are at angular 15 where NgIf and NgFor are standalone, may be for older versions it should be tuned)

import { NgFor, NgIf } from "@angular/common";
import { NgModule } from "@angular/core";
import { NgModel } from "@angular/forms";

@NgModule({
	jit: true,
	imports: [ NgIf, NgFor ],
	declarations: [
		NgModel
	],
	exports: [
		NgModel, NgIf, NgFor
	]
})
export class JitModule {
}

Our project now is compiled with aot: true, dynamic components have jit: true and all they import the JitModule
I don’t know how long this will work and if it will change in the future versions, but it is a live-saver for the time of writing