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

Angular 2 到 Angular 4,使用 Angular Material UI 组件 - 第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (20投票s)

2017 年 6 月 12 日

CPOL

13分钟阅读

viewsIcon

168901

downloadIcon

2827

在本文中,我们将把我们的 Angular 2 应用程序转换为 Angular 4,并用 Angular Material 组件替换传统的 HTML 和第三方组件。

系列文章

引言

在本文中,我们将继续增强用户管理应用程序,将 Angular 版本从 Angular 2 升级到 Angular 4,并使用 Angular Material 2 组件代替传统的 HTML 和第三方组件(例如,我们在添加/更新/删除用户时使用的 `ng2-bs3-modal` 模态弹出框)。

Angular 4 有一些很酷的新功能,我不想一一赘述,因为你可以在网上轻松找到它们。快速回顾请查看 这里。我们将在用户管理应用程序中使用 **电子邮件验证器** 和 `If-else` 模板条件。

Angular Material 是与 Angular 兼容的组件,我们主要用它来设计 Web 应用程序的 UI,例如 `Input`、`Autocomplete`、`dropdown`、`checkbox` 等。点击 这里 查看 Angular Material 组件。我们将用 Angular Material 组件替换所有现有的 HTML 和第三方组件。

背景

本文是 Angular2 in ASP.NET MVC & Web API - Part 2 的第三部分。在之前的文章中,我们使用了 `ng3-bs3-modal` 第三方模态弹出组件和传统的 HTML 控件。在本文中,我们将用 **Angular Material 组件** 替换所有控件。强烈建议你在阅读本文之前先阅读 第一部分第二部分

让我们开始

在开始实际开发之前,我将向你展示本文的最终输出。你可以将其与 Angular2 in ASP.NET MVC & Web API - Part 2 进行比较,以了解我们要构建的内容。

你可以看到,我们在 **添加用户** 模态弹出框中添加了一些额外的表单控件,并且每个控件的外观与 第一部分第二部分 中相比有很大不同。让我们开始开发吧!

由于本文是 Angular2 in ASP.NET MVC & Web API - Part 2 文章的延续,让我们从 这里 下载附带的项目。

恢复 Angular 4 包

  1. 在 Visual Studio 2017 社区版中打开 `Angular2MVC_p2.sln`(最好),也建议重命名解决方案。
  2. 编辑 `package.json` 文件,并用以下包替换文件内容
    {
      "name": "angular-quickstart",
      "version": "1.0.0",
      "description": "QuickStart package.json from the documentation 
                      for Visual Studio 2017 & WebApi",
      "scripts": {
        "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
        "lint": "tslint ./app/**/*.ts -t verbose",
        "lite": "lite-server",
        "pree2e": "webdriver-manager update",
        "test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
        "test-once": "tsc && karma start karma.conf.js --single-run",
        "tsc": "tsc",
        "tsc:w": "tsc -w"
      },
      "keywords": [],
      "author": "",
      "license": "MIT",
      "dependencies": {
        "@angular/common": "4.0.2",
        "@angular/compiler": "4.0.2",
        "@angular/core": "4.0.2",
        "@angular/forms": "4.0.2",
        "@angular/http": "4.0.2",
        "@angular/platform-browser": "4.0.2",
        "@angular/platform-browser-dynamic": "4.0.2",
        "@angular/router": "4.0.2",
    
        "angular-in-memory-web-api": "0.2.4",
        "systemjs": "0.19.40",
        "core-js": "2.4.1",
        "rxjs": "5.0.1",
        "zone.js": "0.7.4"
      },
      "devDependencies": {
        "concurrently": "3.2.0",
        "lite-server": "2.2.2",
        "typescript": "2.0.10",
    
        "canonical-path": "0.0.2",
        "tslint": "3.15.1",
        "lodash": "4.16.4",
        "jasmine-core": "2.4.1",
        "karma": "1.3.0",
        "karma-chrome-launcher": "2.0.0",
        "karma-cli": "1.0.1",
        "karma-jasmine": "1.0.2",
        "karma-jasmine-html-reporter": "0.2.2",
        "protractor": "4.0.14",
        "rimraf": "2.5.4",
    
        "@types/node": "6.0.46",
        "@types/jasmine": "2.5.36",
        "@angular/material": "2.0.0-beta.6",
        "@angular/animations": "4.1.3"
      },
      "repository": {}
    }
  3. 在 `dependencies` 部分,你可以看到我们将 Angular 和其他辅助包的版本升级到了 `4.0.2`。

  4. 在 `devDependencies` 部分,你可以看到我们正在导入 Angular Material 包。

  5. 右键单击 `package.json` 文件,选择 **恢复包** 选项,这将需要几分钟时间来下载所有包。等待直到收到包恢复完成的消息。
  6. 保存文件,然后单击菜单 **生成** -> **重新生成解决方案** 选项。这将花费几秒钟或一分钟时间来下载所有包(.NET 和 Angular)。Angular 4 包恢复就完成了。

升级数据库中的用户表

  1. 在之前的文章中,我们在 `User` 表中只有三个字段,现在让我们添加更多字段,以便更好地理解 Angular Material 组件。
  2. 转到 `App_Data` 文件夹,右键单击并选择 `打开` 或双击 `UserDB.mdf` 文件进行编辑。

  3. 在 **数据连接** -> **UserDBEntities** 层级中展开 `Tables`,右键单击 `TblUser` 并选择 **打开表定义** 选项。

  4. 你会看到只有四个字段(`Id`、`FirstName`、`LastName`、`Gender`),让我们根据以下屏幕截图手动更新表。

  5. 完成后,单击顶部的 **更新** 按钮。这将需要一些时间,然后你将看到以下屏幕,单击 **更新数据库** 按钮。

  6. 接下来,让我们更新 `User` 实体,转到 `DBContext` 文件夹,右键单击 `UserDBEntities.edmx` 并选择 **打开** 或双击进行编辑。

  7. 右键单击屏幕上的任意位置,然后选择 **从数据库更新模型...** 选项。

  8. 在 **更新向导** 屏幕上,转到 **刷新** 选项卡,选择 **表**,然后单击 **完成** 按钮。

  9. 片刻之后,你会看到 `TblUser` 已更新如下:

数据库更新已完成,让我们进入下一步。

Angular 4 & Angular Material 组件更新

让我们更新我们的用户管理应用程序,以使用 Angular Material 组件和 Angular 4 的一些功能。

  1. 编辑 `app -> app.module.ts` 文件,并为 Angular Material 添加以下 `import` 语句。同时在 `import` 部分添加 `BrowserAnimationsModule`、`MaterialModule`、`MdNativeDateModule` 模块。
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { MaterialModule, MdNativeDateModule } from '@angular/material';
  2. 接下来,在 `systemjs.config.js` 文件中添加 Material 和 Animation 的引用。
    '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
    '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
    '@angular/platform-browser/animations': 
    'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
    '@angular/material': 'npm:@angular/material/bundles/material.umd.js',
  3. 现在我们已经在 `AppModule` 中添加了 Angular Material 模块,我们的应用程序就可以使用 Angular Material 组件了。首先,让我们修改 `home` 页面。编辑 `app -> Components -> home.component.ts` 文件。
  4. 与其使用普通的图像,不如使用 Angular Material 的 `Card` 组件。转到 Angular Material Card 页面,了解 Card 是什么,在哪里以及如何使用它?
  5. 在 Angular Material `Card` 页面以及其他任何组件页面,你都可以看到三个选项卡:**概览**、**API 参考** 和 **示例**,它们为我们提供了以下信息:
    • **概览**:组件描述、架构及其用法。HTML、TS 和 CSS 代码以及工作 Plunker 链接。
    • **API 参考**:如何导入组件、模块和指令信息。
    • **示例**:包含该组件几乎所有功能的运行示例,以及包含 HTML、TS 和 CSS 代码的 Plunker 链接。
  6. 既然我们在 Card 页面,请单击 **示例** 选项卡,然后单击查看源代码 `< >` 链接。

  7. 你将进入以下视图,其中包含三个选项卡:**HTML**、**TS** 和 **CSS**。

  8. 我们将从这里复制 **HTML**、**TypeScript**(如果需要)和 **CSS** 到我们的应用程序中,所有我们需要它们的组件。当然,我们会根据我们的需求进行修改,但**不要**害怕复制,这真的可以帮助我们毫不费力地充分利用 Angular Material 组件。所以,复制整个 `md-card` HTML,并用 `home.component.ts` 模板的 HTML 替换它。

  9. 对于 Angular Material 组件,我们可能需要许多组件的 **CSS**,所以让我们创建一个样式表。右键单击 `Content` 文件夹,然后选择 `添加 -> 样式表`。

  10. 输入名称 `style.css`,然后单击 **确定** 按钮。

  11. 将 `style.css` 的内容替换为以下 **CSS**。
    .example-card {
      width: 400px;
    }
    .example-header-image {
      background-image: url('../images/users.png');
      background-size: cover;
    }
  12. 接下来,编辑 `App_Start -> BundleConfig.cs` 并按照以下方式更新 `Content/css` 包,以便在我们的应用程序中添加和使用新添加的 `style.css`。
    bundles.Add(new StyleBundle("~/Content/css").Include(
                          "~/Content/style.css",
                          "~/Content/bootstrap.css"));
  13. 现在,我们的样式表已准备就绪,随着我们开发进程的推进,我们将继续为其他组件添加 CSS 类。
  14. 让我们回到 `home.component.ts` 并进行修改。更改 `md-card-title`,例如“`Users`”,`md-card-subtitle` 为“`Sample Image`”等。在 `md-card-image` 中更新图像 URL 为 `src="../../images/users.png"`。最终的 `home.component.ts` 模板应如下所示:

  15. 编译并运行项目,你的主页应如下所示:

  16. 太好了,我们成功使用了第一个 Angular Material 组件。
  17. 接下来,让我们转到 `user.component.ts` 并用 Angular Material 组件进行更新。为了简化操作并使用 Dialog 组件,我们需要将其拆分为两个组件。`UserComponent` 将只包含用户列表,而 `Add`、`Update` 和 `Delete` 功能将移至我们将在后续步骤中创建的 `ManageUser` 组件。
  18. 在之前的文章中,我们使用了 `ng2-bs3-modal` 模态弹出控件来处理添加、更新和删除屏幕,现在不再需要它了,因此请从 `app.module.ts`、`systemjs.config.js` 和 `user.component.ts` 文件中删除所有对其的引用。
  19. 接下来,让我们创建 `ManageUser` 组件,然后回到 `UserComponent` 进行清理。右键单击 `app -> Components`,然后选择 `添加 -> TypeScript 文件`。

  20. 输入名称 `manageuser.component.ts`,然后单击 **确定** 按钮。同时创建 `manageuser.component.html` 文件。

  21. 在前面的步骤中,我们用几个额外的列更新了 `User` 表,让我们相应地更新我们的 `user.ts`。编辑 `app-> Model -> user.ts` 并进行如下更新:
    export interface IUser {
        Id: number,
        FirstName: string,
        LastName: string,
        Email:string,
        Gender: string,
        DOB: string,
        City: string,
        State: string,
        Zip: string,
        Country:string
    }
  22. 在 `manageuser.component.html` 中,我们将添加 Angular Material Dialog。我们将简单地访问 Dialog Plunker,并将 `dialog-result-example-dialog.html` 文件内容复制到 `manageuser.component.html`。从这个文件中,你可以看到 Dialog 的三个主要部分:`title`、`content` 和 `action` 按钮,这些都比较直观。其他 Angular Material 组件对应于 `user.ts` 模型字段,它们将放在 `md-dialog-content` div 内,如下所示:
    • mdInput:用于 `FirstName`、`LastName`、`Email`、`City` 和 `Zip`。
    • md-radio-button:用于 `Gender`(`Male`/Female)。
    • md-datepicker:生日(`DOB`)的日期选择器控件。
    • md-autocomplete:用于状态的自动完成下拉列表,只要我们开始输入就会过滤状态。(与我们通过管道开发的搜索用户功能相同)。
    • md-select:国家的下拉列表。
    • md-raised-button:取消和添加/更新/删除按钮。
  23. 你可以访问每个组件的 `Plunker` 链接来查看运行示例,我个人更喜欢 Plunker 来获取每个组件的代码。
  24. 将以下 HTML 复制到 `manageuser.component.html` 文件中。
    <form novalidate (ngSubmit)="onSubmit(userFrm)" [formGroup]="userFrm">
        <div>
            <h1 md-dialog-title><span>
            <md-icon>create</md-icon>{{modalTitle}}</span></h1>
        </div>
        <div style="padding-bottom:1px;background-color:#d8d8d8"></div>
        <div md-dialog-content class="md-dialog-container">
            <div class="frm-ctrl">
                <md-input-container>
                    <input mdInput placeholder="First Name" 
                    formControlName="FirstName">
                </md-input-container>
                <div *ngIf="formErrors.FirstName" class="text-danger">
                    {{ formErrors.FirstName }}
                </div>
            </div>
            <div class="frm-ctrl">
                <md-input-container>
                    <input mdInput placeholder="Last Name" 
                    formControlName="LastName">
                </md-input-container>
                <div *ngIf="formErrors.LastName" class="text-danger">
                    {{ formErrors.LastName }}
                </div>
            </div>
            <div class="frm-ctrl">
                <md-input-container>
                    <input type="email" 
                    mdInput placeholder="Email" formControlName="Email">
                </md-input-container>
                <div *ngIf="formErrors.Email" class="text-danger">
                    {{ formErrors.Email }}
                </div>
            </div>
            <div class="frm-ctrl">
                <md-radio-group formControlName="Gender">
                    <md-radio-button *ngFor="let gndr of gender" [value]="gndr">
                        {{gndr}}
                    </md-radio-button>
                </md-radio-group>
                <div *ngIf="formErrors.Gender" class="text-danger">
                    {{ formErrors.Gender }}
                </div>
            </div>
            <div class="frm-ctrl">
                <md-input-container style="width:50%">
                    <input mdInput [mdDatepicker]="picker" 
                    placeholder="Date of Birth" formControlName="DOB">
                    <button mdSuffix [mdDatepickerToggle]="picker"></button>
                </md-input-container>
                <md-datepicker #picker></md-datepicker>
                <div *ngIf="formErrors.DOB" class="text-danger">
                    {{ formErrors.DOB }}
                </div>
            </div>
            <div class="frm-ctrl">
                    <div class="line_ctrl">
                        <md-input-container class="example-full-width">
                            <input mdInput placeholder="City" formControlName="City">
                        </md-input-container>
                        <div *ngIf="formErrors.City" class="text-danger">
                            {{ formErrors.City }}
                        </div>
                    </div>
                    <div class="line_ctrl">
                        <md-input-container>
                            <input mdInput placeholder="State" 
                            [mdAutocomplete]="auto" formControlName="State">
                        </md-input-container>
                        <md-autocomplete #auto="mdAutocomplete">
                            <md-option *ngFor="let state of filteredStates | 
                            async" [value]="state">
                                {{ state }}
                            </md-option>
                        </md-autocomplete>
                        <div *ngIf="formErrors.State" class="text-danger">
                            {{ formErrors.State }}
                        </div>
                    </div>
                    <div class="line_ctrl">
                        <md-input-container class="example-full-width">
                            <input mdInput #postalCode maxlength="5" 
                            placeholder="Zip" formControlName="Zip">
                            <md-hint align="end">{{postalCode.value.length}} / 5</md-hint>
                        </md-input-container>
                        <div *ngIf="formErrors.Zip" class="text-danger">
                            {{ formErrors.Zip }}
                        </div>
                    </div>
            </div>
            <div class="frm-ctrl">
                <md-select placeholder="Country" 
                style="width:50%" formControlName="Country">
                    <md-option *ngFor="let ctry of country" [value]="ctry.value">
                        {{ ctry.viewValue }}
                    </md-option>
                </md-select>
                <div *ngIf="formErrors.Country" class="text-danger">
                    {{ formErrors.Country }}
                </div>
            </div>
        </div>
        <md-dialog-actions class="md-dialog-footer" align="end" >
            <button color="warn" type="button" 
            md-raised-button (click)="dialogRef.close()">Cancel</button>&nbsp;
            <button type="submit" color="primary" 
            [disabled]="userFrm.invalid" md-raised-button>{{modalBtnTitle}}</button>
        </ md-dialog-actions>
    </form>
  25. 这是我们在上一篇文章中使用的相同的响应式(模型驱动)表单,但增加了额外的字段以及 Angular Material 组件,我在上一步中已简要解释过。此表单将加载到 `Dialog` 框中,正如你所见,它位于 Dialog `content` 标签内。我们将在 `UserComponent` 中看到如何显示 Dialog 框。在 `style.css` 文件中添加以下 CSS。欢迎你根据自己的喜好更改 CSS。
    .md-dialog-container {
        width: 550px;
        height: 480px;
        padding-bottom: 20px;
        padding-top: 20px;
    }
    .md-dialog-footer {
        padding: 20px;
        width: 100%;
    }
    md-input-container {
        width: 100%;
    }
    .frm-ctrl {
        padding-top: 20px;
        width: 100%;
    }
    .line_ctrl {
        float: left;
        width: 145px;
        text-align: right;
        margin: 2px;
        display: inline;
    }
  26. 接下来,将以下代码复制到 `manageuser.component.ts` 文件中,让我们来理解它。
    import { Component, OnInit, ViewChild } from '@angular/core';
    import { UserService } from '../Service/user.service';
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    import { ModalComponent } from 'ng2-bs3-modal/ng2-bs3-modal';
    import { IUser } from '../Model/user';
    import { DBOperation } from '../Shared/enum';
    import { Observable } from 'rxjs/Rx';
    import { Global } from '../Shared/global';
    import { MdDialog, MdDialogRef } from '@angular/material';
    import { FormControl } from '@angular/forms';
    @Component({
        templateUrl: 'app/Components/manageuser.component.html',
    })
    export class ManageUser implements OnInit {
        msg: string;
        indLoading: boolean = false;
        userFrm: FormGroup;
        dbops: DBOperation;
        modalTitle: string;
        modalBtnTitle: string;
        listFilter: string;
        selectedOption: string;
        user: IUser;
        country = [
            { value: 'USA', viewValue: 'USA' },
            { value: 'Canada', viewValue: 'Canada' }
        ];
        gender = [
            'Male',
            'Female'
        ];
        states = ['Alabama','Alaska','Arizona','Arkansas',
        'California','Colorado','Connecticut','Delaware',
        'Florida','Georgia','Hawaii',
                  'Idaho','Illinois','Indiana','Iowa',
                  'Kansas','Kentucky','Louisiana','Maine',
                  'Maryland','Massachusetts','Michigan','Minnesota',
                  'Mississippi','Missouri','Montana','Nebraska',
                  'Nevada','New Hampshire','New Jersey',
                  'New Mexico','New York','North Carolina',
                  'North Dakota','Ohio','Oklahoma','Oregon',
                  'Pennsylvania','Rhode Island','South Carolina',
                  'South Dakota','Tennessee','Texas',
                  'Utah','Vermont','Virginia','Washington',
                  'West Virginia','Wisconsin','Wyoming'
                 ];
        stateCtrl: FormControl;
        filteredStates: any;
        constructor(private fb: FormBuilder, 
        private _userService: UserService, public dialogRef: MdDialogRef<ManageUser>) { }
        filterStates(val: string) {
            return val ? this.states.filter(s => new RegExp(`^${val}`, 'gi').test(s))
                : this.states;
        }
        ngOnInit(): void {
            this.userFrm = this.fb.group({
                Id: [''],
                FirstName: ['', [Validators.required, Validators.maxLength(50)]],
                LastName: ['', [Validators.required, Validators.maxLength(50)]],
                Email: ['', [Validators.required, Validators.email]],
                Gender: ['', Validators.required],
                DOB: ['', Validators.required],
                City: ['', Validators.required],
                State: ['', Validators.required],
                Zip: ['', Validators.required],
                Country: ['', Validators.required]
            });
            this.filteredStates = this.userFrm.controls["State"].
            valueChanges.startWith(null).map(name => this.filterStates(name));
            this.userFrm.valueChanges.subscribe(data => this.onValueChanged(data));
            this.onValueChanged();
            if (this.dbops == DBOperation.create)
                this.userFrm.reset();
            else
                this.userFrm.setValue(this.user);
            this.SetControlsState(this.dbops == DBOperation.delete ? false : true);
        }
        onValueChanged(data?: any) {
            if (!this.userFrm) { return; }
            const form = this.userFrm;
            for (const field in this.formErrors) {
                // clear previous error message (if any)
                this.formErrors[field] = '';
                const control = form.get(field);
                if (control && control.dirty && !control.valid) {
                    const messages = this.validationMessages[field];
                    for (const key in control.errors) {
                        this.formErrors[field] += messages[key] + ' ';
                    }
                }
            }
        }
        formErrors = {
            'FirstName': '',
            'LastName': '',
            'Email': '',
            'Gender': '',
            'DOB': '',
            'City': '',
            'State': '',
            'Zip': '',
            'Country': ''
        };
        validationMessages = {
            'FirstName': {
                'maxlength': 'First Name cannot be more than 50 characters long.',
                'required': 'First Name is required.'
            },
            'LastName': {
                'maxlength': 'Last Name cannot be more than 50 characters long.',
                'required': 'Last Name is required.'
            },
            'Email': {
                'email': 'Invalid email format.',
                'required': 'Email is required.'
            },
            'Gender': {
                'required': 'Gender is required.'
            }
            ,
            'DOB': {
                'required': 'DOB is required.'
            }
            ,
            'City': {
                'required': 'City is required.'
            }
            ,
            'State': {
                'required': 'State is required.'
            }
            ,
            'Zip': {
                'required': 'Zip is required.'
            }
            ,
            'Country': {
                'required': 'Country is required.'
            }
        };
        onSubmit(formData: any) {
            switch (this.dbops) {
                case DBOperation.create:
                    this._userService.post(Global.BASE_USER_ENDPOINT, 
                                           formData.value).subscribe(
                        data => {
                            if (data == 1) //Success
                            {
                                this.dialogRef.close("success");
                            }
                            else {
                                this.dialogRef.close("error");
                            }
                        },
                        error => {
                            this.dialogRef.close("error");
                        }
                    );
                    break;
                case DBOperation.update:
                    this._userService.put(Global.BASE_USER_ENDPOINT, 
                    formData._value.Id, formData._value).subscribe(
                        data => {
                            if (data == 1) //Success
                            {
                                this.dialogRef.close("success");
                            }
                            else {
                                this.dialogRef.close("error");
                            }
                        },
                        error => {
                            this.dialogRef.close("error");
                        }
                    );
                    break;
                case DBOperation.delete:
                    this._userService.delete(Global.BASE_USER_ENDPOINT, 
                                             formData._value.Id).subscribe(
                        data => {
                            if (data == 1) //Success
                            {
                                this.dialogRef.close("success");
                            }
                            else {
                                this.dialogRef.close("error");
                            }
                        },
                        error => {
                            this.dialogRef.close("error");
                        }
                    );
                   break;
            }
        }
        SetControlsState(isEnable: boolean) {
            isEnable ? this.userFrm.enable() : this.userFrm.disable();
        }
    }
  27. 为了提高可读性,你可以收起上面的代码以显示滚动条。
  28. 大部分代码是从上一篇文章的 `user.component.ts` 中复制的,例如用户表单的创建、初始化和错误消息。唯一区别是增加了几个字段和验证规则,包括 **电子邮件验证器**,这是 **Angular 4** 的一项功能。有用于 `Country`、`Gender` 和 `States` 的本地变量,它们分别填充 `md-select`、`md-radio-button` 和 `md-autocomplete` 控件。关于 `States` `md-autocomplete` 控件的一个有趣之处在于它是如何工作的?如果你在 `ngOnInit()` 事件中看到,在创建和初始化 `userFrm` 响应式表单后,我们添加了以下一行:
    this.filteredStates = this.userFrm.controls["State"].
    valueChanges.startWith(null).map(name => this.filterStates(name));
  29. 在上面的行中,每次用户在 `States md-autocomplete` 组件中开始键入任何字符时,都会发生 `valueChanges` 事件,该事件调用 `filterStates` 方法,该方法将用户输入的字符串与状态数组进行比较,并在运行时返回匹配结果。`filterStates` 方法的主体如下:
    filterStates(val: string) {
               return val ? this.states.filter
               (s => new RegExp(`^${val}`, 'gi').test(s)): this.states;
       }
    
  30. 其余代码已在之前的文章中解释过。
  31. 现在让我们转到 `user.component.ts` 文件,并将现有代码替换为以下内容:
    import { Component, OnInit, ViewChild } from '@angular/core';
    import { UserService } from '../Service/user.service';
    import { IUser } from '../Model/user';
    import { DBOperation } from '../Shared/enum';
    import { Observable } from 'rxjs/Rx';
    import { Global } from '../Shared/global';
    import { ManageUser} from './manageuser.component';
    import { MdDialog, MdDialogRef } from '@angular/material';
    @Component({
        templateUrl: 'app/Components/user.component.html'
    })
    export class UserComponent implements OnInit {
        users: IUser[];
        user: IUser;
        msg: string;
        dbops: DBOperation;
        modalTitle: string;
        modalBtnTitle: string;
        listFilter: string;
        searchTitle: string = "Search: ";
        selectedOption: string;
        constructor(private _userService: UserService, private dialog: MdDialog) { }
        openDialog() {
            let dialogRef = this.dialog.open(ManageUser);
            dialogRef.componentInstance.dbops = this.dbops;
            dialogRef.componentInstance.modalTitle = this.modalTitle;
            dialogRef.componentInstance.modalBtnTitle = this.modalBtnTitle;
            dialogRef.componentInstance.user = this.user;
            dialogRef.afterClosed().subscribe(result => {
                if (result == "success") {
                    this.LoadUsers();
                    switch (this.dbops) {
                        case DBOperation.create:
                            this.msg = "Data successfully added.";
                            break;
                        case DBOperation.update:
                            this.msg = "Data successfully updated.";
                            break;
                        case DBOperation.delete:
                            this.msg = "Data successfully deleted.";
                            break;
                    }
                }
                else if (result == "error")
                    this.msg = "There is some issue in saving records, 
                    please contact to system administrator!"
                else
                    this.msg = result;
            });
        }
        ngOnInit(): void {
             this.LoadUsers();
        }
        LoadUsers(): void {
            this._userService.get(Global.BASE_USER_ENDPOINT)
                .subscribe(users => { this.users = users; }
                //,error => this.msg = <any>error
                );
        }
        addUser() {
            this.dbops = DBOperation.create;
            this.modalTitle = "Add New User";
            this.modalBtnTitle = "Add";
            this.openDialog();
        }
        editUser(id: number) {
            this.dbops = DBOperation.update;
            this.modalTitle = "Edit User";
            this.modalBtnTitle = "Update";
            this.user = this.users.filter(x => x.Id == id)[0];
            this.openDialog();
        }
        deleteUser(id: number) {
            this.dbops = DBOperation.delete;
            this.modalTitle = "Confirm to Delete?";
            this.modalBtnTitle = "Delete";
            this.user = this.users.filter(x => x.Id == id)[0];
            this.openDialog();
        }
        criteriaChange(value: string): void {
            if (value != '[object Event]')
                this.listFilter = value;
        }
    }
  32. 由于大量代码已移至 `ManageUser` 组件,因此 `UserComponent` 现在变得非常精简。一个新方法是 `openDialog`,它打开 Dialog 框并发送参数。让我们一步一步地理解它:
    • ``let dialogRef = this.dialog.open(ManageUser)``:`dialog.open` 将我们刚刚在前面步骤中创建的 `ManageUser` 组件作为参数。
    • ``dialogRef.componentInstance.dbops = this.dbops;`` 用于将参数发送到 Dialog 组件(`ManageUser` 组件)。我们正在发送 `dpops`(我们要执行的操作类型,Add/Update/Delete 以相应地更改视图)。`modalTitle` 和 `modalBtnTitle` 是每个 DB 操作视图的标签。`user` 参数是我们为编辑和删除视图发送的单个用户记录。在参数语句之后,我们订阅 Dialog 的 `afterClosed` 事件,以检查从 `ManageUser` 组件发送的结果。检查 `ManageUser` 组件的 `onSubmit` 函数,我们明确发送了“success”和“error”字符串。根据相应 DB 操作的成功和失败,我们在用户列表下方显示消息。
  33. 在 `addUser`、`editUser` 和 `deleteUser` 方法中,我们只设置 `dbops`、`modalTitle` 和 `modalBtnTitle` 变量的值,然后调用 `openDialog` 函数,如前所述,这些变量被发送到 `ManageUser` 组件。其余代码不言自明。
  34. 接下来,编辑 `user.component.html` 并将代码替换为以下内容:
    <div class='panel panel-primary'>
        <div class='panel-heading'>
            User Management
        </div>
        <div class='panel-body'>
            <div>
                <search-list [title]='searchTitle' 
                (change)="criteriaChange($event)"></search-list>
            </div>
            <div class='table-responsive'>
                <div style="padding-bottom:10px">
                <button class="btn btn-primary" 
                (click)="addUser()">Add</button></div>
                <div *ngIf='users && users.length==0' 
                class="alert alert-info" role="alert">No record found!</div>
                <table class='table table-striped' *ngIf='users; else loadingScreen;'>
                    <thead>
                        <tr>
                            <th>First Name</th>
                            <th>Last Name</th>
                            <th>Gender</th>
                            <th></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr *ngFor="let user of users | userFilter:listFilter">
                            <td>{{user.FirstName}}</td>
                            <td>{{user.LastName}}</td>
                            <td>{{user.Gender}}</td>
                            <td>
                                <button title="Edit" class="btn btn-primary" 
                                (click)="editUser(user.Id)">Edit</button>
                                <button title="Delete" class="btn btn-danger" 
                                (click)="deleteUser(user.Id)">Delete</button>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <ng-template #loadingScreen><div class="alert alert-info" 
                role="alert"><md-progress-spinner mode="indeterminate" 
                style="width:50px; 
                height:50px"></md-progress-spinner>loading...</div></ng-template>
            </div>
            <div *ngIf="msg" role="alert" 
            class="alert alert-info alert-dismissible">
                <button type="button" class="close" 
                data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span></button>
                <span class="glyphicon glyphicon-exclamation-sign" 
                aria-hidden="true"></span>
                <span class="sr-only">Error:</span>
                {{msg}}
            </div>
        </div>
    </div>
  35. 由于其功能已移至 `ManageUser` 组件(我们正在使用 Angular Material 的 Dialog 组件),因此我们从 HTML 源中删除了 `ng2-bs3-modal`。这里,我们使用了另一个 Angular 4 功能,ngIf – else。找到 `<table class='table table-striped' *ngIf='users; else loadingScreen;'>` 语句。`*ngIf='users; else loadingScreen;'` 表示在加载用户列表之前,显示 `loadingScreen` 模板。`loadingScreen` 模板在后续语句中如下所示:
    <ng-template #loadingScreen><div class="alert alert-info" 
    role="alert"><md-progress-spinner mode="indeterminate" 
    style="width:50px; height:50px"></md-progress-spinner>loading...</div></ng-template>
  36. md-progress-spinner 是 Angular Material 的 spinner,我用它替换了加载图像。`ngIf-else` 是一个很棒的功能,有助于优化代码。因此,我删除了不再需要的 `isLoading` 变量。
  37. 现在 `ManageUser` 组件已完成,让我们将其添加到 `AppModule.ts` 中。编辑 `app-> app.module.ts`,导入 `ManageUser` 组件,并将其添加到 `declarations` 部分。
    import { ManageUser } from './components/manageuser.component';
    //Declaration Statement
    declarations: [AppComponent, UserComponent, 
                   HomeComponent, UserFilterPipe, SearchComponent, ManageUser],
  38. 还有一步是将 `ManageUser` 添加到 `entryComponents` 部分,如下所示:
    entryComponents: [ManageUser]
  39. Angular Material 还提供了 `预构建主题`,浏览 `node_modules\@angular\material\prebuilt-themes` 文件夹中的 `themes` 文件夹。你可以使用任何主题,我将使用 `indigo-pink.css`。将主题引用添加到 `Views -> Shared -> _Layout.cshtml` 页面。
    <link href="/node_modules/%40angular/material/prebuilt-themes/indigo-pink.css" 
     rel="stylesheet" />
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" 
     rel="stylesheet">
  40. 在第二条语句中,我从 Google CDN 导入 Material 图标,我们将在 `AppComponent` 中重新设计菜单时使用它们。Spinner、按钮和其他组件的颜色方案、可见性和布局取决于你选择的主题。删除主题并运行应用程序,你会看到应用程序的 UI 会被弄乱。
  41. 重新生成,清除浏览器缓存(浏览器历史记录),然后运行应用程序。通过添加、更新和删除用户来测试应用程序。
  42. 在最后一步,我想使用 Angular Material Menu 组件。编辑 `app -> app.component.ts` 并将其内容替换为以下内容:
    import { Component } from "@angular/core"
    import {
        Router,
        // import as RouterEvent to avoid confusion with the DOM Event
        Event as RouterEvent,
        NavigationStart,
        NavigationEnd,
        NavigationCancel,
        NavigationError
    } from '@angular/router';
    @Component({
        selector: "user-app",
        template: `
                    <div>
                      <nav class='navbar navbar-default'>
                           <div class='container-fluid'>
                          <button md-icon-button [mdMenuTriggerFor]="menu">
                             <md-icon>more_vert</md-icon> Menu
                           </button>
                           <md-menu #menu="mdMenu">
                             <button md-menu-item [routerLink]="['home']">
                               <md-icon>home</md-icon>
                               <span>Home</span>
                             </button>
                             <button md-menu-item [routerLink]="['user']">
                               <md-icon>group</md-icon>
                               <span>Users Management</span>
                             </button>
                           </md-menu>
                        </div>
                       </nav>
                       <div class='container'>
                           <router-outlet><div class="loading-overlay" 
                           *ngIf="loading">
                        <!-- show something fancy here, 
                        here with Angular 2 Material's loading bar or circle -->
                        <md-progress-bar mode="indeterminate"></md-progress-bar>
                         </div></router-outlet>
                       </div>
                    </div>
                    `
    })
    export class AppComponent {
        loading: boolean = true;
        constructor(private router: Router) {
            router.events.subscribe((event: RouterEvent) => {
                this.navigationInterceptor(event);
            });
        }
        // Shows and hides the loading spinner during RouterEvent changes
        navigationInterceptor(event: RouterEvent): void {
            if (event instanceof NavigationStart) {
               this.loading = true;
            }
            if (event instanceof NavigationEnd) {
                setTimeout(() => { this.loading = false; }, 1000)
               // this.loading = false;
            }
            // Set loading state to false in both of the 
            // below events to hide the spinner in case a request fails
            if (event instanceof NavigationCancel) {
                this.loading = false;
            }
            if (event instanceof NavigationError) {
                this.loading = false;
            }
        }
    }
  43. 在模板的 HTML 中,我们使用了 `md-menu`,它有 `md-menu-item` 按钮,带有指向相应视图(`home` 和 `user`)的 `routerLink`。另一件事是 `md-icon`,它会在菜单项之前显示,我们已经在 `_Layout.cshtml` 页面中添加了图标引用。要获取完整的图标列表,请单击 这里
  44. 我们在 `AppComponent` 中更新的另一件事是视图之间的加载进度条。这可以通过拦截路由更改并基于 `loading` 变量的布尔值显示 `md-progress-bar` 来实现。我特意添加了 `setTimeout(() => { this.loading = false; }, 1000)` 延迟,以便在切换视图时向你显示 spinner。
  45. 重新生成,清除浏览器缓存,然后运行应用程序,你应该会看到以下屏幕:

  46. 你可以点击顶部菜单的 **User Management** 按钮,在顶部菜单下方看到蓝色进度条。

关注点

我喜欢 Angular Material 组件,因为它节省了我使用许多第三方组件的时间和精力,所有组件都兼容模板驱动和模型驱动(响应式表单)。

Angular 4 还有一些更有用的功能,其中两个(`ngIf-else` 和 `Email Validator`)已在本篇文章中使用。其余部分可以根据适用情况使用。

你可以改进的地方

有一些功能可以用来改进这个应用程序。例如,出生日期不能是未来的日期,尝试添加 DOB 的验证规则,在用户列表中显示比名字、姓氏和性别更多的字段,直到用户列表完全加载后才显示搜索过滤器,并为成功的添加/更新/删除消息添加 snack-bar 组件,而不是当前的 Bootstrap 消息。试试看!

历史

  • 2017 年 6 月 12 日:文章创建
© . All rights reserved.