Angular Material – 动态加载未正确渲染

huangapple go评论69阅读模式
英文:

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:

Angular Material – 动态加载未正确渲染

However, I set up the very same code in Stackblitz and it works perfectly fine there and this is what it displays:

Angular Material – 动态加载未正确渲染

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:

  1. ng new angular-dynamic-second
  2. npm install --save @angular/material @angular/cdk
  3. 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:
Angular Material – 动态加载未正确渲染

However, I setup the very same code in Stackblits and it works perfectly fine there and this is what it displays:

Angular Material – 动态加载未正确渲染

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的模板。

输出:

Angular Material – 动态加载未正确渲染

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&#39;t work. It creates a new instance of `Component` and passes it some metadata describing the component, but it doesn&#39;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**

    {
      &quot;name&quot;: &quot;angular-dynamic-second&quot;,
      &quot;version&quot;: &quot;0.0.0&quot;,
      &quot;scripts&quot;: {
        &quot;ng&quot;: &quot;ng&quot;,
        &quot;start&quot;: &quot;ng serve&quot;,
        &quot;build&quot;: &quot;ng build&quot;,
        &quot;watch&quot;: &quot;ng build --watch --configuration development&quot;,
        &quot;test&quot;: &quot;ng test&quot;
      },
      &quot;private&quot;: true,
      &quot;dependencies&quot;: {
        &quot;@angular/animations&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/cdk&quot;: &quot;^15.2.8&quot;,
        &quot;@angular/common&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/compiler&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/core&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/forms&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/material&quot;: &quot;^15.2.8&quot;,
        &quot;@angular/platform-browser&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/platform-browser-dynamic&quot;: &quot;^15.2.0&quot;,
        &quot;@angular/router&quot;: &quot;^15.2.0&quot;,
        &quot;rxjs&quot;: &quot;~7.8.0&quot;,
        &quot;tslib&quot;: &quot;^2.3.0&quot;,
        &quot;zone.js&quot;: &quot;~0.12.0&quot;
      },
      &quot;devDependencies&quot;: {
        &quot;@angular-devkit/build-angular&quot;: &quot;^15.2.4&quot;,
        &quot;@angular/cli&quot;: &quot;~15.2.4&quot;,
        &quot;@angular/compiler-cli&quot;: &quot;^15.2.0&quot;,
        &quot;@types/jasmine&quot;: &quot;~4.3.0&quot;,
        &quot;jasmine-core&quot;: &quot;~4.5.0&quot;,
        &quot;karma&quot;: &quot;~6.4.0&quot;,
        &quot;karma-chrome-launcher&quot;: &quot;~3.1.0&quot;,
        &quot;karma-coverage&quot;: &quot;~2.2.0&quot;,
        &quot;karma-jasmine&quot;: &quot;~5.1.0&quot;,
        &quot;karma-jasmine-html-reporter&quot;: &quot;~2.0.0&quot;,
        &quot;typescript&quot;: &quot;~4.9.4&quot;
      }
    }

**angular.json**

    {
      &quot;$schema&quot;: &quot;./node_modules/@angular/cli/lib/config/schema.json&quot;,
      &quot;version&quot;: 1,
      &quot;newProjectRoot&quot;: &quot;projects&quot;,
      &quot;projects&quot;: {
        &quot;angular-dynamic-second&quot;: {
          &quot;projectType&quot;: &quot;application&quot;,
          &quot;schematics&quot;: {},
          &quot;root&quot;: &quot;&quot;,
          &quot;sourceRoot&quot;: &quot;src&quot;,
          &quot;prefix&quot;: &quot;app&quot;,
          &quot;architect&quot;: {
            &quot;build&quot;: {
              &quot;builder&quot;: &quot;@angular-devkit/build-angular:browser&quot;,
              &quot;options&quot;: {
                &quot;outputPath&quot;: &quot;dist/angular-dynamic-second&quot;,
                &quot;index&quot;: &quot;src/index.html&quot;,
                &quot;main&quot;: &quot;src/main.ts&quot;,
                &quot;polyfills&quot;: [&quot;zone.js&quot;],
                &quot;tsConfig&quot;: &quot;tsconfig.app.json&quot;,
                &quot;assets&quot;: [&quot;src/favicon.ico&quot;, &quot;src/assets&quot;],
                &quot;styles&quot;: [
                  &quot;@angular/material/prebuilt-themes/indigo-pink.css&quot;,
                  &quot;src/styles.css&quot;
                ],
                &quot;scripts&quot;: []
              },
              &quot;configurations&quot;: {
                &quot;production&quot;: {
                  &quot;budgets&quot;: [
                    {
                      &quot;type&quot;: &quot;initial&quot;,
                      &quot;maximumWarning&quot;: &quot;500kb&quot;,
                      &quot;maximumError&quot;: &quot;1mb&quot;
                    },
                    {
                      &quot;type&quot;: &quot;anyComponentStyle&quot;,
                      &quot;maximumWarning&quot;: &quot;2kb&quot;,
                      &quot;maximumError&quot;: &quot;4kb&quot;
                    }
                  ],
                  &quot;outputHashing&quot;: &quot;all&quot;
                },
                &quot;development&quot;: {
                  &quot;buildOptimizer&quot;: false,
                  &quot;optimization&quot;: false,
                  &quot;vendorChunk&quot;: true,
                  &quot;extractLicenses&quot;: false,
                  &quot;sourceMap&quot;: true,
                  &quot;namedChunks&quot;: true
                }
              },
              &quot;defaultConfiguration&quot;: &quot;production&quot;
            },
            &quot;serve&quot;: {
              &quot;builder&quot;: &quot;@angular-devkit/build-angular:dev-server&quot;,
              &quot;configurations&quot;: {
                &quot;production&quot;: {
                  &quot;browserTarget&quot;: &quot;angular-dynamic-second:build:production&quot;
                },
                &quot;development&quot;: {
                  &quot;browserTarget&quot;: &quot;angular-dynamic-second:build:development&quot;
                }
              },
              &quot;defaultConfiguration&quot;: &quot;development&quot;
            },
            &quot;extract-i18n&quot;: {
              &quot;builder&quot;: &quot;@angular-devkit/build-angular:extract-i18n&quot;,
              &quot;options&quot;: {
                &quot;browserTarget&quot;: &quot;angular-dynamic-second:build&quot;
              }
            },
            &quot;test&quot;: {
              &quot;builder&quot;: &quot;@angular-devkit/build-angular:karma&quot;,
              &quot;options&quot;: {
                &quot;polyfills&quot;: [&quot;zone.js&quot;, &quot;zone.js/testing&quot;],
                &quot;tsConfig&quot;: &quot;tsconfig.spec.json&quot;,
                &quot;assets&quot;: [&quot;src/favicon.ico&quot;, &quot;src/assets&quot;],
                &quot;styles&quot;: [
                  &quot;@angular/material/prebuilt-themes/indigo-pink.css&quot;,
                  &quot;src/styles.css&quot;
                ],
                &quot;scripts&quot;: []
              }
            }
          }
        }
      }
    }

**main.ts**

    import { platformBrowserDynamic } from &#39;@angular/platform-browser-dynamic&#39;;
    
    import { AppModule } from &#39;./app/app.module&#39;;
    import &#39;@angular/compiler&#39;;
    
    platformBrowserDynamic()
      .bootstrapModule(AppModule)
      .catch((err) =&gt; console.error(err));


**index.html**

    &lt;!doctype html&gt;
    &lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
      &lt;meta charset=&quot;utf-8&quot;&gt;
      &lt;title&gt;AngularDynamicSecond&lt;/title&gt;
      &lt;base href=&quot;/&quot;&gt;
      &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
      &lt;link rel=&quot;icon&quot; type=&quot;image/x-icon&quot; href=&quot;favicon.ico&quot;&gt;
      &lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.gstatic.com&quot;&gt;
      &lt;link href=&quot;https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&amp;display=swap&quot; rel=&quot;stylesheet&quot;&gt;
      &lt;link href=&quot;https://fonts.googleapis.com/icon?family=Material+Icons&quot; rel=&quot;stylesheet&quot;&gt;
    &lt;/head&gt;
    &lt;body class=&quot;mat-typography&quot;&gt;
      &lt;app-root&gt;&lt;/app-root&gt;
    &lt;/body&gt;
    &lt;/html&gt;

**app.module.ts**

    import { NgModule } from &#39;@angular/core&#39;;
    import { BrowserModule } from &#39;@angular/platform-browser&#39;;
    
    import { AppRoutingModule } from &#39;./app-routing.module&#39;;
    import { AppComponent } from &#39;./app.component&#39;;
    import { BrowserAnimationsModule } from &#39;@angular/platform-browser/animations&#39;;
    import { ExampleComponent } from &#39;./example/example.component&#39;;
    
    @NgModule({
      declarations: [
        AppComponent,
        ExampleComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

**app.component.ts**

    import { Component } from &#39;@angular/core&#39;;
    
    @Component({
      selector: &#39;app-root&#39;,
      templateUrl: &#39;./app.component.html&#39;,
      styleUrls: [&#39;./app.component.css&#39;]
    })
    export class AppComponent {
      title = &#39;angular-dynamic-second&#39;;
    }

**app.component.html**

    &lt;app-example&gt;&lt;/app-example&gt;
    
    &lt;router-outlet&gt;&lt;/router-outlet&gt;

**app-routing.module.ts**

    import { NgModule } from &#39;@angular/core&#39;;
    import { RouterModule, Routes } from &#39;@angular/router&#39;;
    
    import { MatToolbarModule } from &#39;@angular/material/toolbar&#39;;
    import { MatSlideToggleModule } from &#39;@angular/material/slide-toggle&#39;;
    import { MatButtonModule } from &#39;@angular/material/button&#39;;
    import { ReactiveFormsModule } from &#39;@angular/forms&#39;;
    import { MatFormFieldModule } from &#39;@angular/material/form-field&#39;;
    import { MatInputModule } from &#39;@angular/material/input&#39;;
    
    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 &#39;@angular/core&#39;;
    import { NgIf } from &#39;@angular/common&#39;;
    import { ReactiveFormsModule } from &#39;@angular/forms&#39;;
    import { MatFormFieldModule } from &#39;@angular/material/form-field&#39;;
    import { MatInputModule } from &#39;@angular/material/input&#39;;
    
    @Component({
      selector: &#39;app-example&#39;,
      templateUrl: &#39;./example.component.html&#39;,
      styleUrls: [&#39;./example.component.css&#39;],
    })
    export class ExampleComponent implements OnInit {
      @ViewChild(&#39;container&#39;, { 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 = &#39;Hello World!&#39;;
      }
    }
    
    @Component({
      selector: &#39;app-dynamic-component&#39;,
      template: `
        &lt;mat-form-field class=&quot;example-full-width&quot;&gt;
          &lt;mat-label&gt;First name&lt;/mat-label&gt;
          &lt;input matInput maxlength=&quot;10&quot; /&gt;
          &lt;mat-hint&gt;Errors appear instantly!&lt;/mat-hint&gt;
          &lt;mat-error *ngIf=&quot;true&quot;&gt;
            First Name is &lt;strong&gt;required&lt;/strong&gt;
          &lt;/mat-error&gt;
        &lt;/mat-form-field&gt;
      `,
      styles: [&#39;&#39;],
      imports: [NgIf, MatFormFieldModule, MatInputModule, ReactiveFormsModule],
      standalone: true,
    })
    export class DynamicComponent {
      inputValue: string = &#39;&#39;;
    }

**example.component.html**

    &lt;mat-toolbar color=&quot;primary&quot;&gt; Angular Material App &lt;/mat-toolbar&gt;
    
    &lt;button mat-raised-button (click)=&quot;loadModule()&quot;&gt;Load Module&lt;/button&gt;
    
    &lt;ng-container #container&gt;&lt;/ng-container&gt;
    
    &lt;mat-slide-toggle color=&quot;primary&quot;&gt;Slide Toggle&lt;/mat-slide-toggle&gt;

**Folder structure:**

[![Structure][1]][1]


  [1]: https://i.stack.imgur.com/thOIH.png
  [2]: https://i.stack.imgur.com/xSWuD.gif

</details>



huangapple
  • 本文由 发表于 2023年4月17日 06:56:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76030712.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定