英文:
Angular Material - Dynamic Load not rendering properly
问题
I have a Component which is loaded dynamically using Angular 15 and Angular Material, the Component manages to load and is displayed on screen, however, it is not processing the Angular Material selectors and this is what is displayed on screen:
However, I set up the very same code in Stackblitz and it works perfectly fine there and this is what it displays:
https://stackblitz.com/edit/components-issue-rubl8b
Then I try a new Angular project from scratch on my desktop and the same code does not work. I also tried downloading the code from Stackblitz and it simply does not install the npm dependencies and if I force the installation, it does not compile.
Any idea why is this happening?
The complete code is in Stackblitz, if you want to reproduce the issue try creating a new project on your desktop using:
- ng new angular-dynamic-second
 - npm install --save @angular/material @angular/cdk
 - ng add @angular/material
 
英文:
I have a Component which is loaded dynamically using Angular 15 and Angular Material, the Component manages to load and is displayed on screen, however, it is not procesing the Angular Material selectors and this is what is displayed on screen:

However, I setup the very same code in Stackblits and it works perfectly fine there and this is what it displays:
https://stackblitz.com/edit/components-issue-rubl8b
Then I try a new Angular project from scratch on my desktop and the same code does not work. I also tried downloading the code from Stackblits and it simply does not install the npm dependencies and if I force the installation, it does not compile.
Any idea why is this happening?
The complete code is in Stackblits, if you want to reproduce the issue try creating a new project in your desktopm using:
1. ng new angular-dynamic-second
2. npm install --save @angular/material @angular/cdk
3. ng add @angular/material
答案1
得分: 3
I managed to solve the problem locally on my computer.
问题
您尝试使用一种不起作用的方法创建动态组件。它创建了一个新的Component实例,并传递了一些描述该组件的元数据,但实际上没有为组件类定义任何属性或方法。
解决方案
下面的代码使用@ViewChild装饰器获取对ViewContainerRef对象的引用,然后使用ComponentFactoryResolver创建DynamicComponent的新实例。DynamicComponent是一个简单的组件,具有inputValue属性和一个从Angular Material显示mat-form-field的模板。
输出:
package.json
{
  "name": "angular-dynamic-second",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^15.2.0",
    "@angular/cdk": "^15.2.8",
    "@angular/common": "^15.2.0",
    "@angular/compiler": "^15.2.0",
    "@angular/core": "^15.2.0",
    "@angular/forms": "^15.2.0",
    "@angular/material": "^15.2.8",
    "@angular/platform-browser": "^15.2.0",
    "@angular/platform-browser-dynamic": "^15.2.0",
    "@angular/router": "^15.2.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.12.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^15.2.4",
    "@angular/cli": "~15.2.4",
    "@angular/compiler-cli": "^15.2.0",
    "@types/jasmine": "~4.3.0",
    "jasmine-core": "~4.5.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "typescript": "~4.9.4"
  }
}
angular.json
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "angular-dynamic-second": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/angular-dynamic-second",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": ["zone.js"],
            "tsConfig": "tsconfig.app.json",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": [
              "@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "angular-dynamic-second:build:production"
            },
            "development": {
              "browserTarget": "angular-dynamic-second:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "angular-dynamic-second:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": ["zone.js", "zone.js/testing"],
            "tsConfig": "tsconfig.spec.json",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": [
              "@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import '@angular/compiler';
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch((err) => console.error(err));
index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularDynamicSecond</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
  <app-root></app-root>
</body>
</html>
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ExampleComponent } from './example/example.component';
@NgModule({
  declarations: [
    AppComponent,
    ExampleComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
<details>
<summary>英文:</summary>
I managed to solve the problem locally on my computer.
## Problem
You attempted to create a dynamic component using an approach that doesn't work. It creates a new instance of `Component` and passes it some metadata describing the component, but it doesn't actually define any properties or methods for the component class.
## Solution
The code below creates a dynamic component by using the `@ViewChild` decorator to get a reference to a `ViewContainerRef` object and then using the `ComponentFactoryResolver` to create a new instance of the `DynamicComponent`. The `DynamicComponent` is a simple component with an `inputValue` property and a template that displays a `mat-form-field` from Angular Material.
**Output:**
[![Output][2]][2]
**package.json**
    {
      "name": "angular-dynamic-second",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "watch": "ng build --watch --configuration development",
        "test": "ng test"
      },
      "private": true,
      "dependencies": {
        "@angular/animations": "^15.2.0",
        "@angular/cdk": "^15.2.8",
        "@angular/common": "^15.2.0",
        "@angular/compiler": "^15.2.0",
        "@angular/core": "^15.2.0",
        "@angular/forms": "^15.2.0",
        "@angular/material": "^15.2.8",
        "@angular/platform-browser": "^15.2.0",
        "@angular/platform-browser-dynamic": "^15.2.0",
        "@angular/router": "^15.2.0",
        "rxjs": "~7.8.0",
        "tslib": "^2.3.0",
        "zone.js": "~0.12.0"
      },
      "devDependencies": {
        "@angular-devkit/build-angular": "^15.2.4",
        "@angular/cli": "~15.2.4",
        "@angular/compiler-cli": "^15.2.0",
        "@types/jasmine": "~4.3.0",
        "jasmine-core": "~4.5.0",
        "karma": "~6.4.0",
        "karma-chrome-launcher": "~3.1.0",
        "karma-coverage": "~2.2.0",
        "karma-jasmine": "~5.1.0",
        "karma-jasmine-html-reporter": "~2.0.0",
        "typescript": "~4.9.4"
      }
    }
**angular.json**
    {
      "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
      "version": 1,
      "newProjectRoot": "projects",
      "projects": {
        "angular-dynamic-second": {
          "projectType": "application",
          "schematics": {},
          "root": "",
          "sourceRoot": "src",
          "prefix": "app",
          "architect": {
            "build": {
              "builder": "@angular-devkit/build-angular:browser",
              "options": {
                "outputPath": "dist/angular-dynamic-second",
                "index": "src/index.html",
                "main": "src/main.ts",
                "polyfills": ["zone.js"],
                "tsConfig": "tsconfig.app.json",
                "assets": ["src/favicon.ico", "src/assets"],
                "styles": [
                  "@angular/material/prebuilt-themes/indigo-pink.css",
                  "src/styles.css"
                ],
                "scripts": []
              },
              "configurations": {
                "production": {
                  "budgets": [
                    {
                      "type": "initial",
                      "maximumWarning": "500kb",
                      "maximumError": "1mb"
                    },
                    {
                      "type": "anyComponentStyle",
                      "maximumWarning": "2kb",
                      "maximumError": "4kb"
                    }
                  ],
                  "outputHashing": "all"
                },
                "development": {
                  "buildOptimizer": false,
                  "optimization": false,
                  "vendorChunk": true,
                  "extractLicenses": false,
                  "sourceMap": true,
                  "namedChunks": true
                }
              },
              "defaultConfiguration": "production"
            },
            "serve": {
              "builder": "@angular-devkit/build-angular:dev-server",
              "configurations": {
                "production": {
                  "browserTarget": "angular-dynamic-second:build:production"
                },
                "development": {
                  "browserTarget": "angular-dynamic-second:build:development"
                }
              },
              "defaultConfiguration": "development"
            },
            "extract-i18n": {
              "builder": "@angular-devkit/build-angular:extract-i18n",
              "options": {
                "browserTarget": "angular-dynamic-second:build"
              }
            },
            "test": {
              "builder": "@angular-devkit/build-angular:karma",
              "options": {
                "polyfills": ["zone.js", "zone.js/testing"],
                "tsConfig": "tsconfig.spec.json",
                "assets": ["src/favicon.ico", "src/assets"],
                "styles": [
                  "@angular/material/prebuilt-themes/indigo-pink.css",
                  "src/styles.css"
                ],
                "scripts": []
              }
            }
          }
        }
      }
    }
**main.ts**
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    import { AppModule } from './app/app.module';
    import '@angular/compiler';
    
    platformBrowserDynamic()
      .bootstrapModule(AppModule)
      .catch((err) => console.error(err));
**index.html**
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>AngularDynamicSecond</title>
      <base href="/">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
      <link rel="preconnect" href="https://fonts.gstatic.com">
      <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
      <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    </head>
    <body class="mat-typography">
      <app-root></app-root>
    </body>
    </html>
**app.module.ts**
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { ExampleComponent } from './example/example.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        ExampleComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
**app.component.ts**
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'angular-dynamic-second';
    }
**app.component.html**
    <app-example></app-example>
    
    <router-outlet></router-outlet>
**app-routing.module.ts**
    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    
    import { MatToolbarModule } from '@angular/material/toolbar';
    import { MatSlideToggleModule } from '@angular/material/slide-toggle';
    import { MatButtonModule } from '@angular/material/button';
    import { ReactiveFormsModule } from '@angular/forms';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { MatInputModule } from '@angular/material/input';
    
    const routes: Routes = [];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [
        RouterModule,
        MatToolbarModule,
        MatSlideToggleModule,
        MatButtonModule,
        ReactiveFormsModule,
        MatFormFieldModule,
        MatInputModule,
      ],
    })
    export class AppRoutingModule {}
**example.component.ts**
    import {
      Component,
      ComponentFactoryResolver,
      OnInit,
      ViewChild,
      ViewContainerRef,
    } from '@angular/core';
    import { NgIf } from '@angular/common';
    import { ReactiveFormsModule } from '@angular/forms';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { MatInputModule } from '@angular/material/input';
    
    @Component({
      selector: 'app-example',
      templateUrl: './example.component.html',
      styleUrls: ['./example.component.css'],
    })
    export class ExampleComponent implements OnInit {
      @ViewChild('container', { read: ViewContainerRef, static: true })
      container!: ViewContainerRef;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
    
      ngOnInit(): void {}
    
      loadModule() {
        this.container.clear();
        const componentFactory =
          this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
        const componentRef = this.container.createComponent(componentFactory);
        componentRef.instance.inputValue = 'Hello World!';
      }
    }
    
    @Component({
      selector: 'app-dynamic-component',
      template: `
        <mat-form-field class="example-full-width">
          <mat-label>First name</mat-label>
          <input matInput maxlength="10" />
          <mat-hint>Errors appear instantly!</mat-hint>
          <mat-error *ngIf="true">
            First Name is <strong>required</strong>
          </mat-error>
        </mat-form-field>
      `,
      styles: [''],
      imports: [NgIf, MatFormFieldModule, MatInputModule, ReactiveFormsModule],
      standalone: true,
    })
    export class DynamicComponent {
      inputValue: string = '';
    }
**example.component.html**
    <mat-toolbar color="primary"> Angular Material App </mat-toolbar>
    
    <button mat-raised-button (click)="loadModule()">Load Module</button>
    
    <ng-container #container></ng-container>
    
    <mat-slide-toggle color="primary">Slide Toggle</mat-slide-toggle>
**Folder structure:**
[![Structure][1]][1]
  [1]: https://i.stack.imgur.com/thOIH.png
  [2]: https://i.stack.imgur.com/xSWuD.gif
</details>
				通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。



评论