Angular 4.0

一.课程简介 (注意:这里的AngularJS指的是2.0以下的版本)
AngularJS的优点:
  • 模板功能强大丰富
  • 比较完善的前端MVC框架
  • 引入了Java的一些概念:比如,依赖注入,单元测试等
AngularJS的一些问题:
  • 性能
  • 路由
  • 作用域
  • 表单验证
  • JavaScript语言本身问题
  • 学习成本高
Angular(指的4.0版本)新特性
  • 全新的命令行工具 AngularCLI: 比如,生成一个新项目的骨架,或者生成我们需要的组件的基础代码;或者作为一个开发服务器供我们调试、编译、构建、部署代码、运行自动化的单元测试,等等.
  • 服务器端渲染: 他可以使一个原本需要10s才能加载玩的单页应用,在1s之内呈现在用户面前,他可以使一个单页应用针对每一个视图去做seo优化.
  • 移动和桌面兼容: 可以使用Ionic,React-Native之类的框架
    AngularJS架构

    Angular架构
Angular与其他流行前端框架的比较
  • React优点
    • 速度快: 虚拟dom (Angular也采用了类似的算法,现在速度与react不相上下了)
    • FLUX架构 (Angular现在也已具备)
    • 服务器渲染 (Angular现在也已具备)
  • Vue对比 之 优点
    • 简单
    • 灵活
    • 性能:(大小只有十几kb,Angular得有50多kb)
  • Vue缺点
    • 个人主导
    • 只关注web
    • 只能依靠第三方框架来实现服务器渲染
Angular 继承了AngularJS原有的优点,并且吸收了React以及其他框架的优点,祛除了他们的缺点,形成了一套全新的框架.
Angular
二. 开始Angular开发
  • Angular程序架构
Angular程序架构
  • 搭建环境
    • 安装node.js node.js官网
    • 安装 angular-cli sudo cnpm install -g @angular/cli >>>>由于npm总是安装不成功,故使用了淘宝的镜像
    • 检查 angular-cli 是否安装成功 ng -v
      angular/cli 安装成功
    • 创建项目ng new xxx项目名字
    • 创建带路由的项目 ng new xxx --routing
    • 启动项目 ng serve --open
    • 创建组件 ng g component xxx组件
    • 创建一个文件夹并创建组件 ng g component stock/stockManage
      在stock文件夹下生成一个stockManage组件
项目目录
项目目录
component组件组成的必备元素
组件的必备元素
其他笔记
  • 声明的数组,在push之前要初始化下
export class StarsComponent implements OnInit {
  // 外面注入进来的
  @Input()
  rating: number = 0;
  stars: boolean[];
  constructor() {
  }
  ngOnInit() {
    // 数组要初始化一下
    this.stars = [];
    for (let i = 1; i <= 5; i++) {
      this.stars.push(i > this.rating);
    }
  }

}
三.使用Angular Route导航
  • 路由的基础知识
    新建一个导航的项目:ng new router --routing
  • 在路由时传递参数
    • 在查询参数中传递数据

      <a routerLink="/stock" [queryParams]="{id:1}">股票详情</a>
      

      当点击时,导航栏地址中就带上了id参数

      在路由时传递参数

      在跳转到组件中接收这个参数

      export class StockComponent implements OnInit {
      
        private stockId: number;
      
        constructor(private routeInfo: ActivatedRoute) {
      
        }
      
        ngOnInit() {
          this.stockId = this.routeInfo.snapshot.queryParams['id'];
        }
      
      }
      

      在html中调用

      <p>
        这里是股票信息组件
      </p>
      <p>
        股票ID是{{stockId}}
      </p>
      
      执行结果
    • 在路由路径中传递参数
      ①. 在路由中进行配置

      // 路由配置
      const routes: Routes = [
        {
          path:'',
          component: HomeComponent
        },
        {
          path:'stock/:id',
          component: StockComponent
        },
        // 注意: 通配符路由的配置一定要放在最下面
        {
          path:'**',// **(通配符)表示任何路径都可以来匹配
          component: Code404Component
        }
      ];
      

      ②.修改路由连接

      <a [routerLink]="['/stock',1]">股票详情</a>
      

      ③.修改要跳珠组件的ts文件

       export class StockComponent implements OnInit {
      
         private stockId: number;
      
          constructor(private routeInfo: ActivatedRoute) {
      
          }
      
        ngOnInit() {
            this.stockId = this.routeInfo.snapshot.params['id'];
        }
      
      }
      

      ④.显示效果


      在路由路径中传递参数
      注意:当两个跳转到的组件一样时,会因为路由快照的原因,导致路由传递的参数在组件内部不能显示出来;
      解决办法:在该组件的ts文件中
      export class StockComponent implements OnInit {
      
        private stockId: number;
      
        constructor(private routeInfo: ActivatedRoute) {
      
        }
      
        ngOnInit() {
          //解决路由快照的问题
          //每当参数改变时,下面的方法都会被调用一次
          this.routeInfo.params.subscribe((params: Params)=> this.stockId = params['id']);
          this.stockId = this.routeInfo.snapshot.params['id'];
        }
      
      }
      
    • 在路由配置中传递参数
      ①.在路由配置中设置静态数据

       const routes: Routes = [
       {
         path: '',
         component: HomeComponent
       },
       {
         path: 'stock/:id',
         component: StockComponent,
         // 配置一些静态数据
         data: [{
           isPro: true
         }]
       }
      ]
      

      ②.在要跳转到的组件ts文件中

      export class StockComponent implements OnInit {
      
        private stockId: number;
        private isPro: boolean;
      
        constructor(private routeInfo: ActivatedRoute) {
      
        }
      
        ngOnInit() {
          //解决路由快照的问题
          //每当参数改变时,下面的方法都会被调用一次
          this.routeInfo.params.subscribe((params: Params) => this.stockId = params['id']);
          this.stockId = this.routeInfo.snapshot.params['id'];
          this.isPro = this.routeInfo.snapshot.data[0]['isPro'];
        }
      
      }
      

      ③.html文件中

      <p>
        这里是股票信息组件
      </p>
      <p>
        股票ID是{{stockId}}
      </p>
      <p>
        isPro是{{isPro}}
      </p>
      

      ④.显示效果


      在路由配置中传递参数
  • 重定向路由
    在用户访问一个特定的地址时,将其重定向到另一个指定的地址.
    比如:将http://localhost:4200重定向到http://localhost:4200/home
    const routes: Routes = [
      {
        path: '',
        redirectTo: '/home',
        pathMatch: 'full'  // pathMatch: full,路由器将应用重定向,当且仅当导航到“/”。
      },
      {
        path: 'home',
        component: HomeComponent
      }
    ]
    
  • 子路由
    ①.在路由配置中,在children中配置子路由
    {
      path: 'stock/:id',
      component: StockComponent,
      // 配置一些静态数据
      data: [{
        isPro: true
      }],
      children:[
        {
          path:'',
          component:BuyerListComponent
        },
        {
          path:'seller/:id', // 传递参数
          component:SellerListComponent
        }
      ]
    },
    
    ②. 在子路的上一级路由的组件中,设置routerLink插槽
    <!-- ./ : 在当前路由下面去找 -->
    <a [routerLink]="['./']">买家列表</a>
    <a [routerLink]="['./seller', 100]">卖家列表</a>
    <!--插座-->
    <router-outlet></router-outlet>
    

    ③.显示效果


    子路由
  • 辅助路由
    具体参考路由demo
  • 路由守卫
    • CanActivate: 处理导航到某路由的情况.
      使用场景:是否有权限进入到某个路由组件
      ①.路由配置

      {
        path: 'stock/:id',
        component: StockComponent,
        // 路由守卫
        canActivate: [PermissionGuard]
      },
      

      ②.在permission.guard.ts文件中,配置权限

      import {CanActivate} from "@angular/router";
      
      export class PermissionGuard implements CanActivate {
      
         canActivate() {
          var hasPermission: boolean = Math.random() < 0.5;
          if (!hasPermission){
            console.log("用户无权限访问此股票详情");
          }
          return hasPermission; // true 可以进入, false不可以进入
        }
      }
      

      hasPermission为true时,可以跳转到stock路由,为false时,被拦截,不可以进入!

    • CanDeactivate: 处理从当前路由离开的情况.
      具体参考路由demo.

    • Resolve: 在路由激活之前获取路由数据.
      具体参考路由router2.

四. 依赖注入
  • 什么是依赖注入模式及使用依赖注入的好处

    依赖注入: Dependency Injection 简称 DI 。
    控制反转: Inversion of Control 简称 IOC 。

    使用依赖注入的好处 : 依赖注入会帮你以一种松耦合的方式编写代码,使代码可测性和重用性更高!

  • 介绍Angular的依赖注入实现:注入器和提供器
  • 注入器的层级关系
  • <app-stars [rating]="stock?.rating"></app-stars> // stock?.rating => 当stock存在时,才去访问rating属性

五. 数据绑定、响应式编程和管道。
  • 数据绑定


    数据绑定
    • 事件绑定


      事件绑定
    HTML属性和DOM属性的关系
    • 少量 HTML 属性 和 DOM 属性 之间有着 1:1的映射,如 id
    • 有些 HTML 属性 没有对应的 DOM 属性, 如 colspan
    • 有些 DOM 属性 没有对应的 HTML 属性,如 textContent
    • 就算名字相同, HTML 属性 和 DOM属性 也不是同一样东西。
    • HTML 属性的值指定了初始值;DOM属性的值表示当前值。
      DOM 属性 的值可以改变;HTML 属性的值不能改变。
    • 模板绑定是通过 DOM属性 和事件来工作的,而不是 HTML 属性。
    HTML属性绑定
    • 基本Html属性绑定:


      基本Html属性绑定.png
    • CSS类绑定:


      CSS类绑定
    • 样式绑定


      样式绑定
  • 双向绑定[(ngModel)]="name" [()] 盒子里面装香蕉

 <input [(ngModel)]="name">
  • 响应式编程 : 异步数据流编程
    • 观察者模式与Rxjs
    Observable : 被观察者
    响应式编程

先导入

import {Observable} from "rxjs";
  //被观察者 
  Observable.from([1, 2, 3, 4])
    .filter(e => e % 2 === 0)
    .map(e => e * e)
    //订阅 (观察者)
    .subscribe(
      //观察者
      e => console.log(e),  // 4 ,  6
      err => console.error(err),
      () => console.log('结束了') // 结束了
    );

观察者后两个回调方法是可选的

err => console.log(err),
() => console.log('结束了') // 结束了
  • 模板本地变量 #xxxx#来声明
    <input #myField (keyup)="onKey(myField.value)">
    
    ts中代码
    onKey(value) {
      console.log(value);
    }
    

    等同下面的传统方式

    //$event 事件对象  keyup键盘点击
    <input (keyup)="onkey2($event)">
    
    ts中代码
    onkey2(event) {
      console.log(`我是来打怪兽的==${event.target.value}`);
    }
    
  • 响应式编程栗子
    html文件中

    <input [formControl]="searchInput">  // 绑定formControl属性
    

    app.module.ts中引入ReactiveFormsModule

    imports: [
        BrowserModule,
        ReactiveFormsModule
    ],
    

    ts文件中
    先导入

    import {Observable} from "rxjs";
    import {FormControl} from "@angular/forms";
    import 'rxjs/Rx';
    

    具体实现

    export class BindComponent implements OnInit {
    
      searchInput: FormControl = new FormControl();
    
      constructor() {
        this.searchInput.valueChanges
           // 500ms没有收到新的事件,才往下执行
          .debounceTime(500)
          .subscribe(stockCode => this.getStockInfo(stockCode));
      }
    
      ngOnInit() {
      }
    
      getStockInfo(value) {
        console.log(value);
      }
    
    }
    

    实现总结:当用户在搜索框中一直输入内容,此时输入框不会触发搜索功能,当用户输入停下来,超过500ms才会执行搜索功能!

  • 管道 : 负责处理原始值到显示值的转换.比如(GMT时间转换)

    管道符号: |

    Angular4中常用管道 摘抄参考文章
    • ①. 大小写转换管道
      uppercase: 将字符串转换为大写
      lowercase: 将字符串转换为小写

      <p>将字符串转换为大写{{str | uppercase}}</p>
      
      str:string = 'hello'
      

      页面上会显示
      将字符串转换为大写HELLO

    • ②. 日期管道
      date。日期管道符可以接受参数,用来规定输出日期的格式。

      <p>现在的时间是{{today | date:'yyyy-MM-dd HH:mm:ss'}}</p>
      
      today:Date = new Date();
      

      页面上会显示现在的时间是2017年12月05日14时57分53秒

    • ③. 小数管道
      number管道用来将数字处理为我们需要的小数格式
      接收的参数格式为{最少整数位数}.{最少小数位数}-{最多小数位数}
      其中最少整数位数默认为1
      最少小数位数默认为0
      最多小数位数默认为3
      当小数位数少于规定的{最少小数位数}时,会自动补0
      当小数位数多于规定的{最多小数位数}时,会四舍五入

      <p>圆周率是{{pi | number:'2.2-4'}}</p>
      
      pi:number = 3.14159;
      

      页面上会显示
      圆周率是03.1416

    • ④. async 管道:处理异步流

    • ⑤. 自定义管道
      执行命令ng g pipe pipe/xxx 创建xxx管道
      xxx.pipe.ts文件中:

      import {Pipe, PipeTransform} from '@angular/core';
      
      // Pipe 管道装饰器
      @Pipe({
        name: 'multiple' // 管道的名字,可以任意定义
      })
      export class MultiplePipe implements PipeTransform {
      
        transform(value: number, args?: number): any {
          // value: 管道输入进来的原始的值
          // args 可选参数,管道后面跟的 date:'yyyy-MM-dd HH:mm:ss'等
          // 返回 原始值 乘以 参数值
      
          if (!args) {
            args = 1;
          }
          return value * args;
        }
      }
      

      html中调用

      <p>自定义管道{{size | multiple:'4'}}</p>
      

      执行结果为: 28

  • indexOf() 包含

    return list.filter(item => {
        const itemFieldValue = item[field].toLowerCase();
        return itemFieldValue.indexOf(keyword) >= 0; // indexOf()包含
    });
    
六.组件间通讯
  • 组件的输入输出属性

    • 输入属性 : 适用于父传子
      需要使用@Input() 修饰
      export class StockSearchComponent implements OnInit {
      
        // @Input() 修饰 keyWord 属性是个输入属性
        @Input()
        private keyWord: string;
        constructor() {
        }
        ngOnInit() {
          setInterval(() => {
            this.keyWord = 'xxxx';
          }, 3000);
        }
      }
      
      这样在父子组件嵌套的情况下,父组件给子组件传值
      <div>
        <input placeholder="请输入搜索关键字" [(ngModel)]="search">
        <app-stock-search [keyWord]="search" ></app-stock-search>
      </div>
      
    • 输出属性: 适用于子传父
      在子组件中先引入
      import { EventEmitter } from '@angular/core';
      
      声明属性
      @Output() // 装饰器没有名字
      searchResult: EventEmitter<StockInfo> = new EventEmitter();
      
      @Output('lastPrice') // 也可以给装饰器起名字
      searchResult: EventEmitter<StockInfo> = new EventEmitter();
      
      发射事件
      this.searchResult.emit(stockInfo);
      
      在父组件中 事件绑定(lastPrice)="xxx($event)"
      如果装饰器没有名字
      <app-stock-search  (searchResult)="searchResultHandle($event)"></app-stock-search>
      
      装饰器有名字 lastPrice
      <app-stock-search (lastPrice)="searchResultHandle($event)"></app-stock-search>
      
      在父组件ts文件中实现
      searchResultHandle(stockInfo: StockInfo) {
        this.currentPrice = stockInfo.price;
      }
      
      在父组件中显示
      <div>
           当前价格是{{currentPrice | number:'2.2-2'}}
      </div>
      
  • 使用中间人模式传递数据
    两兄弟间的数据传递,由这两个兄弟所在的父组件充当中间人的角色

    中间人模式

    如上图,son1的一个事件想把值传递到兄弟组件son2中,
    现在son1组件中声明事件属性

    @Output()
    addCart: EventEmitter<StockInfo> = new EventEmitter();
    

    然后发送事件

    buyStock() {
        this.addCart.emit(new StockInfo(this.keyWord, this.price));
    }
    

    在父组件father中,调用该组件,并实现addCartHandler方法

    <son1 (addCart)="addCartHandler($event)"></son1 >
    
    private stockInfo: StockInfo;
    addCartHandler(stockInfo: StockInfo) {
      this.stockInfo = stockInfo;
    }
    

    father组件再将数据传递给son2

    <son2 [stockInfo]="stockInfo"></son2 >
    

    以上就是中间人模式

  • 组件生命周期以及angular的变化发现机制


    生命周期: 红色部分只会被调用一次
  • 生命周期的执行顺序


    生命周期的执行顺序
  • ngOnChanges钩子: 输入属性发生变化的时候调用;
    当组件有@Input()属性时,该属性的初始化是在ngOnChanges中执行的.所以当组件依赖外部输入进来的属性时,它的初始化最好在ngOnInit钩子里面,而不要写在构造函数里面.

在父组件中调用子组件的方法

  • 方法一

在父组件中 ,声明一个模板变量

<app-child #child1></app-child>

在父组件ts文件中,使用@ViewChild()装饰器

export class AppComponent implements OnInit {
  /**
   * 在父组件 AppComponent 里面,获得子组件的引用
   */
  @ViewChild('child1') // 通过模板变量的名字child1,找到了对应的子组件,并将child1赋值给了下面的child1变量
  child1: ChildComponent;

  ngOnInit(): void {
    // 调用子组件的方法
    this.child1.greeting('Tom');
  }
}
  • 方法二

在父组件的模板上调用子组件的方法

<!-- 先声明模板变量-->
<app-child #child2></app-child>
<!--在父组件的模板上调用子组件的方法-->
<button (click)="child2.greeting('Jerry')">调用child2的greeting方法</button>
  • ngAfterViewInit()ngAfterViewChecked()钩子
    ngAfterViewInit()ngAfterViewChecked() 生命周期是在组件的视图被组装完毕以后调用的;
    如果组件有子组件, 那么只有当所有子组件的视图组装完毕以后,父组件的这两个方法才会被调用;
    不要在这两个方法里改变视图中绑定的东西.如果想改他,也要写在一个定时器(setTimeout)里面,不然会抛出异常;
    ngAfterViewInit() 先调用, ngAfterViewChecked()后调用

  • ng-content 投影

    类似于Vue的插槽
    在子组件定义一个投影

    <div style="border:1px solid red">
      <!--使用 ng-content 组件标记投影点-->
      <ng-content></ng-content>
    </div>
    

    当其父组件调用该子组件时,可以往投影上传递任意内容

    <app-red-border>
      <div>
        我是投影里的内容
      </div>
    </app-red-border>
    
    当然也传递多个投影,并指定每个投影的位置

    在父组件调用子组件时,给投影设置class

    <app-red-border>
      <div class="red">
        我是红框里面的内容
      </div>
      <div class="green">
        我是绿框里面的内容
      </div>
    </app-red-border>
    

    子组件中给设置多个投影位置,并给ng-content设置select属性为父组件中投影的class,

     <div style="border:1px solid red">
      <!--使用 ng-content 组件标记投影点-->
      <ng-content select=".red"></ng-content>
     </div>
    
     <div style="border:1px solid green">
       <ng-content select=".green"></ng-content>
     </div>
    
  • ngAfterContentInit()ngAfterContentChecked()钩子
    这两个钩子针对的是视图中父组件投影进来的那部分内容的;
    ngAfterContentInit():投影进来的内容初始化完毕调用;
    ngAfterContentChecked():专门针对投影进来的内容,做变更检测的;

  • ngOnDestroy 组件销毁
    当切换路由时,会调用该组件销毁钩子;

  • 小结:
    ①.父子组件之间应该避免直接访问彼此的内部,而应该通过输入输出属性来通讯;
    ②.组件可以通过输出属性发射自定义事件,这些事件可以携带任何你想携带的数据;
    ③.在没有父子关系的组件之间,尽量使用中间人模式进行通讯;
    ④.父组件可以在运行时投影一个或多个模板片段到子组件中;
    每个Angular组件都提供了一组生命周期钩子,供你在某些特定的事件发生时执行相应的逻辑;
    ⑤.Angular的变更检测机制会监控组件属性的变化并自动更新视图.这个检测非常频繁并且默认是针对整个组件树的,所以实现相关钩子时要慎重;
    ⑥.你可以标记你的组件树中的一个分支,使其被排除在变更检测机制之外.

七. 表单处理
  • 模板式表单 (需要引入:FormsModule)
    表单的数据模型是通过组件模板的相关指令来定义的,因为使用这种方式定义表单的数据模型时,我们会受限于HTML的语法,所以,模板驱动方式只适用于一些简单的场景.

  • 响应式表单 (需要引入:ReactiveFormsModule)
    使用响应式表单时,你通过编写TypeScript代码而不是Html代码来创建一个底层的数据模型,在这个模型定义好以后,你使用一些特定的指令,将模板上的html元素与底层的数据模型连接在一起.

  • 响应式表单 和 模板式表单的不同点

    • 不管是哪种表单,都有一个对应的数据模型来存储表单的数据. 在模板式表单中,数据模型是由angular基于你组件模板中的指令隐式创建的. 而在响应式表单中,你通过编码明确的创建数据模型,然后将模板上的html元素与底层的数据模型连接在一起.
    • 数据模型并不是一个任意的对象, 它是一个由angular/forms模块中的一些特定类,如:FormControl, FormGroup, FormArray等组成的. 在模板式表单中, 你是不能直接访问到这些类的.
    • 响应式表单并不会替你生成HTML, 模板仍然需要你自己来编写.
  • 模板式表单例子
    模板式表单的指令都是ng开头的,如:ngForm,ngModel

<form #myForm="ngForm" (ngSubmit)="createUser(myForm.value)">
  <div>用户名:<input #myNickName="ngModel" ngModel name="nickname" type="text" pattern="[a-zA-Z0-9]+"></div>
  <div>邮箱:<input ngModel name="email" type="email"></div>
  <div>手机号:<input ngModel name="mobile" type="number"></div>
  <div ngModelGroup="passwordInfo">
    <div>密码:<input ngModel name="password" type="password"></div>
    <div>确认密码:<input ngModel name="passwordConfirm" type="password"></div>
  </div>

  <button type="submit">注册</button>
</form>

<br>
<hr>
<br>

<div>
  {{myForm.value | json}}
</div>
<br>
<div>
  昵称的值是{{myNickName.value}}
</div>
模板式表单栗子
  • 响应式表单
    响应式表单的指令都是form开头的,如:formGroup,formControl
    html中代码
<!--
与后台的formModel绑定;
createUser() 不用传值;
-->
<form [formGroup]="formModel" (submit)="createUser()">
  <div>昵称:<input formControlName="nickname" type="text" pattern="[a-zA-Z0-9]+"></div>
  <div>邮箱:
    <ul formArrayName="emails">
      <li *ngFor="let email of formModel.get('emails').controls;let i = index">
        <input [formControlName]="I">
      </li>
    </ul>
    <input type="button" (click)="addEmail()" value="添加Email">
  </div>
  <div>手机号:<input formControlName="mobile" type="number"></div>
  <!--与后台的passwordInfo绑定-->
  <div formGroupName="passwordInfo">
    <div>密码:<input formControlName="password" type="password"></div>
    <div>确认密码:<input formControlName="passwordConfirm" type="password"></div>
  </div>

  <button type="submit">注册</button>
</form>

ts中代码

import {Component, OnInit} from '@angular/core';
import {FormArray, FormControl, FormGroup} from "@angular/forms";

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

  // 整个表单所有数据
  private formModel: FormGroup;

  constructor() {
    this.formModel = new FormGroup({
      nickname: new FormControl(),
      emails: new FormArray([
        new FormControl()
      ]),
      mobile: new FormControl(),
      passwordInfo: new FormGroup({
        password: new FormControl(),
        passwordConfirm: new FormControl()
      })
    });
  }

  addEmail() {
    const emails = this.formModel.get('emails') as FormArray;
    emails.push(new FormControl());
  }
  createUser() {
    console.log(this.formModel.value);
  }
}
响应式表单

打印结果:


响应式表单打印结果
使用FormBuilder可简化上面ts文件中大代码
import {Component, OnInit} from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup} from "@angular/forms";

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

  // 整个表单所有数据
  private formModel: FormGroup;

  private fb: FormBuilder = new FormBuilder();

  constructor() {
    this.formModel = this.fb.group({
      nickname: ['xxx'],
      emails: this.fb.array([
        ['']
      ]),
      mobile: [''],
      passwordInfo: this.fb.group({
        password: [''],
        passwordConfirm: ['']
      })
    });
  }

  addEmail() {
    const emails = this.formModel.get('emails') as FormArray;
    emails.push(new FormControl());
  }

  createUser() {
    console.log(this.formModel.value);
  }

  ngOnInit() {
  }

}

推荐阅读更多精彩内容