Our SaaS need to offer different home pages for different customers. Our approach is to have the different templates in the database. We also have components defined in a different module that must be used in the different home pages. So we're creating the home-page component dynamically, inheriting from the default one.
We're in Angular 13
It works in the dev, but not in prod with AOT. We don't get any error, and the interpolations in the dynamic component are processed, but the components coming from a different module are not rendered, it looks like they are not compiled, that is, treated as plane HTML tags.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterContentInit {
title = 'dynamic';
static factory: ComponentFactory<any> | undefined;
template = "<h1>Component loaded dynamically</h1> <div>Below should be rendered a component loaded from another module</div><app-widget-component #theComponent></app-widget-component>";
@ViewChild(AnchorDirective, { static: true }) anchorHost: AnchorDirective | undefined;
ngOnInit(): void {
}
constructor(public _compiler: Compiler,
public injector: Injector) {
}
ngAfterContentInit(): void {
this.loadContent(this.template, "");
}
loadContent(content: string, javascript: string) {
const cmpClass = class DynamicComponent extends DynamicComponentComponent {
text: string = "Text";
constructor(injector: Injector) {
super(injector)
}
};
(cmpClass as any).ctorParameters = () => [{ type: Injector }];
const metadata = new Component({
selector: "compiled-at-runtime",
template: content
// todo: check styles and other options
});
const decoratedCmp = Component(metadata)(cmpClass);
const moduleDef = NgModule({
imports: [WidgetModuleModule,CommonModule, RouterModule, FormsModule],
declarations: [decoratedCmp]
})(class DynamicHtmlModule { });
const moduleWithComponentFactory = this._compiler.compileModuleAndAllComponentsSync(
moduleDef
);
let factory = moduleWithComponentFactory.componentFactories.find(
x => x.selector === "compiled-at-runtime"
);
let viewContainerRef = this.anchorHost!.viewContainerRef;
const injector = Injector.create({
providers: [],
parent: viewContainerRef.injector
});
const componentRef = viewC
}
}
In prod we got :

And in dev:

My team got the same issue today (@angular/cli version 15.0.1), two of our custom components work well in the dev mode, but not rendered in the production mode.
It works after we removed all useless references\methods from the imports in these issue components.
/* It shall be the different behaviors between JIT and AOT, but we don't find out the root cause. :( */
UPDATE: Well, it turns out that there was a circular reference (just imported but not been used) in our components, and the imports removing we did before just breaks it and fixed the issue. Not sure why there's no warnings or errors in the neither dev mode nor prod mode when there's a circular reference in Angular. Need to dig more in the source code :-P