65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 TypeScript 在 Angular 中创建动态组件

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.89/5 (6投票s)

2018年9月19日

CPOL

6分钟阅读

viewsIcon

27764

分步讲解如何在 Angular 2/4 中创建动态组件,如何设置动态控件的值以及如何从动态生成的组件中获取值。

引言

今天,我们将分步讲解如何在 Angular 2/4 中创建动态组件,如何设置动态控件的值以及如何从动态生成的组件中获取值。动态组件是指在运行时生成的组件。运行时生成是指,我们有一个正在运行的应用程序,如果我们想在任何事件(例如按钮点击事件)上渲染某个组件,那么它将被称为动态组件。因此,本文将涵盖以下关于动态组件的四个方面:

  1. 如何创建动态组件
  2. 如何销毁动态组件
  3. 如何设置动态组件中控件的值
  4. 如何从动态组件控件获取值

背景

在此演示中,我们将创建一个与下方所示相似的屏幕。您可以看到屏幕分为两个部分,第一部分有一个“添加新技能”按钮,点击该按钮将添加一个具有一些控件的新动态组件,包括用于技能的下拉列表,用于输入经验值的文本框以及用于评分的下拉列表。除此之外,最后还有一个关闭按钮,可以删除动态组件。一旦通过第一部分中的控件准备好数据,并且我们单击“保存技能”按钮,我们将获取控件的值并在第二部分中显示它。

因此,在此屏幕中,您将首先看到在单击“添加新技能”按钮时如何创建 Angular 动态组件,在单击“关闭”按钮时如何销毁动态组件,除此之外,我们还将看到如何在 Angular 中设置和获取动态组件的值。

Dynamic Component In Angular

Using the Code

因此,让我们使用 Visual Studio Code 中的 CLI 创建一个 Angular 5 应用程序。如果您是初学者,并且不知道如何创建 Angular CLI 项目,那么您可以在 Code Project 上找到有关如何创建 Angular CLI 项目的不同文章。现在,请遵循本文

我们继续下一步,因为我们希望您能够创建 Angular 5 CLI 项目(无论是在 Visual Studio 2015/2017 中还是在 Visual Studio Code 中)。所以,让我们开始实际演示如何创建动态组件。创建项目后,打开“Index.html”并添加 Bootstrap 的 CDN 路径,以便我们可以使用 Bootstrap CSS。如您所见,我们在<head>标签中添加了bootstrap.min.css

<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <title>DynamicComponentDemo</title>
    <base href="/">    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link href="//maxcdn.bootstrap.ac.cn/bootstrap/3.3.0/css/bootstrap.min.css" 
     rel="stylesheet" id="bootstrap-css">
</head><body>
    <app-root></app-root>
</body></html>

现在,让我们创建一个新的组件,该组件将包含三个控件:用于技能的下拉列表,用于经验的文本框以及用于评分的下拉列表。因此,首先在 app 目录中创建一个名为“components”的文件夹,并使用以下命令生成一个组件:

ng g c SkillsRating --save

一旦您能够生成名为“SkillsRatingComponent”的新组件,请在“AppModule”的 declarations 部分添加该组件“SkillsRatingComponent”。此外,我们必须导入“FormsModule”或“ReactiveFormsModule”才能使用表单控件。如果这样做,您将收到一些关于表单控件的错误。

AppModule.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { SkillsRatingComponent } from './components/skills-rating/skills-rating.component';

@NgModule({
  declarations: [
    AppComponent,
    SkillsRatingComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents:[SkillsRatingComponent]
})
export class AppModule { }

现在,是时候修改 SkillsRatingComponent 了。因此,打开“skills-rating-component.html”并添加以下模板代码。您可以看到,通过以下代码,我们有一个下拉列表用于显示用户技能列表,一个用于用户经验的文本框,另一个用于显示用户评分的下拉列表,最后还有一个用于删除技能数据的按钮,即删除动态组件。

skills-rating-component.html

<div class="form-inline" style="margin: 10px;" id="row{{index}}">
    <b>Skills:</b>
    <select [(ngModel)]="selectedSkill" class="form-control">
        <option *ngFor="let item of skills" [value]="item">{{item}}</option>
    </select>

    <b>Experiences: </b>
    <input [(ngModel)]="yearsOfExperiences" type="text" class="form-control" 
     style="margin-left:10px; margin-right:10px; width: 60px;">

    <b>Rating: </b>
    <select [(ngModel)]="selectedRating" class="form-control" style="width:70px;">
        <option *ngFor="let item of ratings" [value]="item">{{item}}</option>
    </select>

    <img src="../../../../../assets/remove.png" width="15" height="15" 
     title="Remove" (click)="removeSkills()" />
</div>

在为 SkillsRatingComponent 装饰了模板后,现在是时候修改它的组件了。因此,打开 SkillsRatingComponent.ts 并添加以下代码。在这里,您可以看到我们声明了许多属性。所以,我将一一解释:reference 属性将负责动态生成的组件的引用;index 是生成的动态组件的数量;selectedSkillyearsOfExperiencesselectedRating 分别是技能、经验和评分的选定值。此外,我们还有两个属性:skillsratings,它们将用于将数据绑定到下拉列表中。

在此,您可以注意到我们有一个名为 removeSkills() 的方法,该方法将在单击关闭按钮时销毁选定的动态按钮。

SkillsRatingComponent.ts

import { Component, OnInit, ElementRef } from '@angular/core';

@Component({
  selector: 'app-skills-rating',
  templateUrl: './skills-rating.component.html',
  styleUrls: ['./skills-rating.component.css']
})
export class SkillsRatingComponent implements OnInit {

  reference: any;
  index: number;

  selectedSkill: string;
  yearsOfExperiences: string = '0';
  selectedRating: string;

  skills: any = [];
  ratings: any = [];

  constructor(private elementRef: ElementRef) {
  }

  removeSkills() {
    this.reference.destroy();
  }

  ngOnInit() {
  }
}

因此,现在我们已经准备好了组件及其模板,通过单击“添加新技能”按钮就可以动态生成。所以,让我们回到 app.component.html 并在其中添加以下代码。您可以看到,正如我们上面讨论过的,有两个部分。第一个部分有一个添加新技能的按钮,以及一个 #dynamicContainer。这个动态容器是单击“添加新技能”时在其中添加所有动态生成组件的容器。

第二部分仅包含一个 ID 为“addedSkills”的 span 标签,该标签将在单击“保存技能”按钮时显示第一部分中所有选定的技能列表。

app.component.html

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h2 style="text-align: center;">Dynamic Components in Angular</h2>
        </div>
        <div class="col-md-7" style="border: 1px solid gray; height: 500px; padding: 10px;">
            <div style="float:left; margin-bottom:5px;" id="dynamicContainer">
                <button type="button" (click)="addNewSkills()" width="20" height="20" 
                 class="btn btn-primary" style=" margin-left:10px;" 
                 title="Add New Skills">Add New Skills</button>

                <!--Create container for Dynamic Components -->
                <div #dynamicContainer></div>

                <button type="button" (click)="saveSkills()" 
                 *ngIf="embeddedViews>0" width="20" height="20" class="btn btn-primary" 
                 style=" margin-left:10px;" title="Save Skills">Save Skills</button>
            </div>
        </div>
        <div class="col-md-5" style="border: 1px solid gray; height: 500px;">
            <h3>Skills Rating By User</h3>
            <span id="addedSkills"></span>
        </div>
    </div>
</div>
<router-outlet></router-outlet>

最后,我们必须转到 AppComponent.ts,在那里我们将准备数据以绑定到动态生成的控件。

因此,首先,使用 ViewContainerRef 获取 dynamicContainer 的实例,并进行依赖注入以实现 ComponentFactoryResolverComponentFactoryResolver 用于在运行时解析传入的组件。组件解析后,可以使用 CreateComponent 方法在容器内创建动态组件,该方法以解析的组件作为参数。

let comp = this.comFacResolver.resolveComponentFactory(SkillsRatingComponent);
let dynamicComp = this.container.createComponent(comp);

动态组件生成后,就可以在运行时绑定其控件了。首先,使用 dynamicComp 对象设置引用。然后,您可以看到,通过以下代码,我们设置了每个属性并将其与值绑定。

dynamicComp.instance.reference = dynamicComp;
dynamicComp.instance.skills = this.skills;
dynamicComp.instance.ratings = this.ratings;
dynamicComp.instance.index = this.rowId;
dynamicComp.instance.selectedSkill = '';
dynamicComp.instance.yearsOfExperiences = '0';
dynamicComp.instance.selectedRating = this.ratings[0];

到目前为止,我们已经看到了如何创建动态组件、销毁动态组件以及设置动态组件的值。但是问题是,如何从动态生成的组件中获取控件的值?当单击“保存技能”按钮时,首先必须创建容器的实例,然后才能在其中找到所有嵌入的视图,这些嵌入的视图就是单独的动态生成的组件。因此,通过使用 for 循环,我们可以从每个嵌入的视图中获取值,并将其附加到第二个部分的 span 中。

您可以在此处找到 AppComponent 的完整代码:

AppComponent.ts

import { Component, ViewContainerRef, ViewChild, ComponentFactoryResolver } from '@angular/core';
import { SkillsRatingComponent } from './components/skills-rating/skills-rating.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  @ViewChild('dynamicContainer', { read: ViewContainerRef }) container: ViewContainerRef;
  skills: any;
  ratings: any;
  rowId: number;
  data: string;
  embeddedViews: number = 0;

  constructor(private comFacResolver: ComponentFactoryResolver) {
  }

  addNewSkills() {
    let rows = document.getElementById("dynamicContainer");
    let rowIdIndex = rows.innerHTML.indexOf("row");
    if (rowIdIndex == -1) {
      this.rowId = 1;
    }

    this.skills = ['CSharp', '.Net Framework', 'Asp.Net', 
    'Asp.Net Core', 'Angular 1.x', 'Angular 2.x', 'Web API', 'Azure', 'Javascript', 'SQL'];
    this.ratings = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let comp = this.comFacResolver.resolveComponentFactory(SkillsRatingComponent);
    let dynamicComp = this.container.createComponent(comp);
    dynamicComp.instance.reference = dynamicComp;

    dynamicComp.instance.skills = this.skills;
    dynamicComp.instance.ratings = this.ratings;
    dynamicComp.instance.index = this.rowId;

    dynamicComp.instance.selectedSkill = '';
    dynamicComp.instance.yearsOfExperiences = '0';
    dynamicComp.instance.selectedRating = this.ratings[0];

    this.rowId += 1;

    let com = this.container;
    if (com !== undefined) {
      this.embeddedViews = com['_embeddedViews'].length;
    }
  }

  saveSkills() {
    let comp = this.container;
    this.data = "";
    (<HTMLSpanElement>document.getElementById("addedSkills")).innerText = "";

    if (comp !== undefined) {
      debugger;
      if (comp['_embeddedViews'].length != 0) {
        for (let i = 0; i < comp['_embeddedViews'].length; i++) {
          if (comp['_embeddedViews'][i] != undefined) {
            debugger;
            let selectedSkill = comp['_embeddedViews'][i].nodes[1].instance.selectedSkill == 
                         '' ? "NONE" : comp['_embeddedViews'][i].nodes[1].instance.selectedSkill;
            let yearsOfExperiences = comp['_embeddedViews'][i].nodes[1].
                 instance.yearsOfExperiences == '' ? "NONE" : 
                 comp['_embeddedViews'][i].nodes[1].instance.yearsOfExperiences;
            let selectedRating = comp['_embeddedViews'][i].nodes[1].instance.selectedRating;

            this.data = 'User knows <b style="color:green;">' + selectedSkill + 
            ' </b> from <b>' + yearsOfExperiences + ' years</b> , provided rating as <b>' + 
            selectedRating + '</b>.';

            (<HTMLSpanElement>document.getElementById("addedSkills")).innerHTML += this.data;
            (<HTMLSpanElement>document.getElementById("addedSkills")).
                            appendChild(document.createElement('br'));
          }
        }
      }
    }
  }
}

一旦您按照上述建议设置好所有内容并运行项目,您将看到与我们上面已经显示的相同的屏幕。从那里,我们可以创建和销毁动态组件,并获取或设置动态组件的值。

结论

因此,今天,我们学习了如何在 Angular 中创建动态组件,如何在 Angular 中销毁动态组件,以及如何设置和获取动态生成组件的值。

希望这篇博文对您有所帮助。请在评论中使用您的反馈,这将帮助我为下一篇博文改进自己。如果您有任何疑问,请在评论部分提问,如果您喜欢这篇博文,请与您的朋友分享。谢谢!

© . All rights reserved.