Ant Design of Angular

来自ling
跳转至: 导航搜索

常见引用

antd 官方doc Angular 4.x 修仙之路 Angular CLI 终极指南 Angular 4 指令快速入门 ng-alain alain.git delon TypeScript 高级类型整理 TypeScript 类型兼容性整理 TypeScript 枚举使用整理 antd-mobile

cli

参考以下顺序有助于组织结构

ng generate interface base/models/ResposeInfo
ng generate enum base/models/ResultStatus
ng generate enum base/models/FailStatus
ng generate module builder
ng generate module builder/programe
ng generate component builder/programe
<router-outlet></router-outlet>
ng generate component builder/programe/views/programe-index
ng generate component builder/programe/components/programe-filter
ng generate component builder/programe/components/programe-grid
ng generate component builder/programe/components/programe-form
ng generate component builder/programe/components/programe-select
ng generate service builder/programe/services/programe
class
component
directive
enum
guard
interface
module
pipe
service


ng generate module builder/project
ng generate module builder/project/systemconfig
ng generate component builder/project/systemconfig/views/initconfig

事件

    <input #myInput type="text">
    <button (click)="onClick($event, myInput.value)">点击</button>

鼠标

<input #myInput type="text">
<button (click)="onClick($event, myInput.value)">点击</button>

键盘

    <input #myInput type="text" (keydown.enter)="onEnter($event, myInput.value)">
    <button (click)="onClick($event, myInput.value)">点击</button>

生命周期

https://segmentfault.com/a/1190000008716308

管道

{{address | json}}

接口

interface Address {
    province: string;
    city: string;
}

export class UserComponent {
    name: string;
    address: Address;
    // ...
}

指令

  • 使用 HostBinding 装饰器,实现元素的属性绑定
  • 利用 Input 装饰器,定义指令的输入属性
  • 通过 Attribute 装饰器来获取指令宿主元素的属性值

定义

import { Directive, HostBinding, HostListener, Input, Attribute } from '@angular/core';

@Directive({
    selector: '[greet]'
})
export class GreetDirective {
    @Input() greet: string;

    @HostBinding() get innerText() {
        return this.greet;
    }

    @HostListener('click',['$event']) 
    onClick(event) {
        this.greet = 'Clicked!';
        console.dir(event);
    }

    constructor(@Attribute('author') public author: string) {
        console.log(author);
    }
}

使用

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h2>Hello, Angular</h2>
    <h2 [greet]="'Hello, Semlinker!'" 
      author="semlinker">Hello, Angular</h2>
  `,
})
export class AppComponent { }
  • ngif相反指令
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
    selector: '[exeUnless]'
})
export class UnlessDirective {

    @Input('exeUnless')
    set condition(newCondition: boolean) {
        if (!newCondition) { 
            this.viewContainer.createEmbeddedView(this.templateRef);
        } else {
            this.viewContainer.clear();
        }
    }

    constructor(private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef) {
    }
}

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
   <h2 *exeUnless="condition">Hello, Semlinker!</h2> 
  `,
})
export class AppComponent {
  condition: boolean = false;
}

ngif

<div *ngIf="condition">...</div>

ng-for

我们使用 let item of items; 语法迭代数组中的每一项,另外我们使用 index as i 用来访问数组中每一项的索引值。除了 index 外,我们还可以获取以下的值:

    <ul>
      <li *ngFor="let message of mailService.messages; index as i;">
        {{i}} - {{message}}
      </li>
    </ul>
  • first: boolean - 若当前项是可迭代对象的第一项,则返回 true
  • last: boolean - 若当前项是可迭代对象的最后一项,则返回 true
  • even: boolean - 若当前项的索引值是偶数,则返回 true
  • odd: boolean - 若当前项的索引值是奇数,则返回 true

.map

data.map((i: STData, index: number) => {
      i.disabled = index === 0;
      return i;
    });

Object.keys(obj).forEach(function(key,i,v){
            console.log(key)
            console.log("---------")
            console.log(i);
            console.log("---------")
            console.log(v)
        })

数组操作

[1]

ngSwitchCase

  • ngSwitchCase。

按钮禁用

[nzLoading]="loading"

事件绑定

事件绑定语法

<date-picker (dateChanged)="statement()"></date-picker>

模板引用

<input #myInput type="text">
<button (click)="onClick(myInput.value)">点击</button>

注入服务

对于 Type 类型(函数类型) 的对象,我们一般使用 constructor(private mailService: MailService) 方式进行注入。而 Inject 装饰器一般用来注入非 Type 类型的对象。

  constructor(
    @Inject(MailService) private mailService,
    @Inject('apiUrl') private apiUrl
  ) {}
}

ng-content 指定子内容输出

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

@Component({
  selector     : '[nz-menu-group]',
  template     : `
    <div class="ant-menu-item-group-title">
      <ng-content select="[title]"></ng-content>
    </div>
    <ul class="ant-menu-item-group-list">
      <ng-content></ng-content>
    </ul>`,
})

export class NzMenuGroupComponent {
  @HostBinding('class.ant-menu-item-group') _nzMenuItemGroup = true;
}

使用

          <li nz-menu-group>
            <span title>Item 1</span>
            <ul>
              <li nz-menu-item>Option 1</li>
              <li nz-menu-item>Option 2</li>
            </ul>
          </li>

ng-template ng-container

[2]

  • 在 Angular 中,我们可以通过 ViewChild 装饰器来获取视图中定义的模板元素,然后利用 ViewContainerRef 对象的 createEmbeddedView() 方法,创建内嵌视图
  • ngTemplateOutlet 该指令用于基于已有的 TemplateRef 对象,插入对应的内嵌视图。在应用 NgTemplateOutlet 指令时,我们可以通过 [ngTemplateOutletContext] 属性来设置 EmbeddedViewRef 的上下文对象。绑定的上下文应该是一个对象,此外可通过 let 语法来声明绑定上下文对象属性名。
<ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
  • ngOutletContext
  • TemplateRef

TemplateRef 实例用于表示模板对象。用于表示内嵌的 template 模板元素,通过 TemplateRef 实例,我们可以方便创建内嵌视图(Embedded Views),且可以轻松地访问到通过 ElementRef 封装后的 nativeElement。需要注意的是组件视图中的 template 模板元素,经过渲染后会被替换成 comment 元素。

  • ViewContainerRef

ViewContainerRef 实例提供了 createEmbeddedView() 方法,该方法接收 TemplateRef 对象作为参数,并将模板中的内容作为容器 (comment 元素) 的兄弟元素,插入到页面中。 ViewContainerRef:用于表示一个视图容器,可添加一个或多个视图。通ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。

  • <ng-template> 用于定义模板,使用 * 语法糖的结构指令,最终都会转换为 <ng-template> 模板指令,模板内的内容如果不进行处理,是不会在页面中显示。
  • <ng-container> 是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment 元素,它可用于避免添加额外的元素来使用结构指令
  • 另外需要注意的是使用 let 语法创建模板局部变量,若未设置绑定的值,则默认是上下文对象中 $implicit 属性对应的值。为什么属性名是 $implicit 呢?因为 Angular 不知道用户会如何命名,所以定义了一个默认的属性名。 即 let-name="$implicit" 与 let-name 是等价的。
import { Component, TemplateRef, ViewContainerRef, ViewChild, 
  AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <ng-template #tpl>
      Hello, Semlinker!
    </ng-template>
  `,
})
export class AppComponent implements AfterViewInit{
  @ViewChild('tpl')
  tplRef: TemplateRef<any>;

  constructor(private vcRef: ViewContainerRef) {}

  ngAfterViewInit() {
    this.vcRef.createEmbeddedView(this.tplRef);
  }
}


@Component({
  selector: 'ng-template-outlet-example',
  template: `
    <ng-container *ngTemplateOutlet="greet"></ng-container>
    <hr>
    <ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
    <hr>
    <ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container>
    <hr>
    <ng-template #greet><span>Hello</span></ng-template>
    <ng-template #eng let-name><span>Hello {{name}}!</span></ng-template>
    <ng-template #svk let-person="localSk"><span>Ahoj {{person}}!</span></ng-template>
`
})
class NgTemplateOutletExample {
  myContext = {$implicit: 'World', localSk: 'Svet'};
}

Input 装饰器

定义

export class SimpleFormComponent implements OnInit {
  //@Input('message') msg: string;
  @Input() message: string;

  /** async data */
  @Input()
  get nzAjaxData() {
    return this.data;
  }

  set nzAjaxData(data) {
    this._isAjax = true;
    this.data = data;
  }
  // ...
}

传值

 template: `
    <h3>{{title}}</h3>
    <app-simple-form *ngFor="let message of mailService.messages;"
      [message]="message">
    </app-simple-form>
  `

使用 Output 装饰器

  • Output 装饰器的作用是用来实现子组件将信息,通过事件的形式通知到父级组件。
  • 只能通过事件形式,将内组件的值传给外组件 ,void类型,不能有返回值
  • 内组件update.emit({text: message})-->外组件onUpdate(message.id, $event.text)

在介绍 Output 属性装饰器前,我们先来介绍一下 EventEmitter 这个幕后英雄:

let numberEmitter: EventEmitter<number> = new EventEmitter<number>(); 
numberEmitter.subscribe((value: number) => console.log(value));
numberEmitter.emit(10);

定义

import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'app-simple-form',
  template: `
    <div>
     {{message}}
     <input #myInput type="text" [(ngModel)]="message">
     <button (click)="update.emit({text: message})">更新</button>
    </div>
  `,
  styles: []
})
export class SimpleFormComponent implements OnInit {
  @Input() message: string;
  @Output() update = new EventEmitter<{text: string}>();

  ngOnInit() { }
}

使用

@Component({
  selector: 'app-root',
  template: `
    <h3>{{title}}</h3>
    <ul>
      <li *ngFor="let message of mailService.messages;">
        {{message.text}}
      </li>
    </ul>
    <app-simple-form *ngFor="let message of mailService.messages;"
      [message]="message.text"
      (update)="onUpdate(message.id, $event.text)">
    </app-simple-form>
  `
})

自定义双向绑定

父组件和子组件引用同一个对象.达到双向绑定目的

  • dict-select
  • 子组件定义一个变量childata
  • 使用@input定义get data(){return childata} set(data){this.childdata=data}
  • 父组件使用 [(data)]=xxx 做双向绑定

定义组件

<label for="">UserName:</label>
<input type="text"  [(ngModel)]="userName" (ngModelChange)="change()">

视图层只有一个label和一个input标签,这是一个最简单的表单。

其中ngModelChange是当input发生变化时,触发的事件。

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'binding-test',
  templateUrl: './binding-test.component.html',
  styleUrls: ['./binding-test.component.css']
})

/**
 * 自定义组件双向数据绑定
 */
export class BindingTestComponent implements OnInit {



  @Input() public userName;

  @Output() public userNameChange = new EventEmitter();
  constructor() { }

  ngOnInit() {
  }


  /**
   * change
   */
  public change(userName: string) {
    this.userNameChange.emit(this.userName);
  }
}

@Input() public userName;
@Output() public userNameChange = new EventEmitter();
注意:Output中EventEmitter类型属性的名字必须为 Input属性对应名字+Change

使用

<binding-test [(userName)]="testBinding"></binding-test>

app.component:模板:TestBinding

其中binding-test标签是自定义组件,利用[()]符号进行双向绑定,下面p标签显示所绑定的值。

@ViewChild @ContentChild @ViewChildren @ContentChildren

  • @ViewChild @ContentChild @ViewChildren @ContentChildren 又是什么
  • @ViewChild 选择组件模板内的节点
  • @ContentChild 选择当前组件引用的子组件 @ContentChild(组件名)
  • @ViewChildren 和 @ContentChildren 则为对应的复数


import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
    selector: 'exe-parent',
    template: `
      <p>Parent Component</p>  
      <ng-content></ng-content>
    `
})
export class ParentComponent implements AfterContentInit {
    @ContentChild(ChildComponent)
    childCmp: ChildComponent;

    ngAfterContentInit() {
        console.dir(this.childCmp);
    }
}



import { Component } from '@angular/core';

@Component({
    selector: 'exe-child',
    template: `
      <p>Child Component</p>  
    `
})
export class ChildComponent {
    name: string = 'child-component';
}



import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-parent>
      <exe-child></exe-child>
    </exe-parent>
  `,
})
export class AppComponent { }

ContentChild 与 ViewChild 的异同点

相同点

都是属性装饰器

都有对应的复数形式装饰器:ContentChildren、ViewChildren

都支持 Type<any>|Function|string 类型的选择器

不同点

ContentChild 用来从通过 Content Projection 方式 (ng-content) 设置的视图中获取匹配的元素

ViewChild 用来从模板视图中获取匹配的元素

在父组件的 ngAfterContentInit 生命周期钩子中才能成功获取通过 ContentChild 查询的元素

在父组件的 ngAfterViewInit 生命周期钩子中才能成功获取通过 ViewChild 查询的元素

平行组件间传递

https://segmentfault.com/a/1190000008886598#articleHeader8 Subject 其实是观察者模式的实现,所以当观察者订阅 Subject 对象时,Subject 对象会把订阅者添加到观察者列表中,每当有 subject 对象接收到新值时,它就会遍历观察者列表,依次调用观察者内部的 next() 方法,把值一一送出。

Subject 之所以具有 Observable 中的所有方法,是因为 Subject 类继承了 Observable 类,在 Subject 类中有五个重要的方法:

  • next - 每当 Subject 对象接收到新值的时候,next 方法会被调用
  • error - 运行中出现异常,error 方法会被调用
  • complete - Subject 订阅的 Observable 对象结束后,complete 方法会被调用
  • subscribe - 添加观察者
  • unsubscribe - 取消订阅 (设置终止标识符、清空观察者列表)

我们可以利用 RxJS Subject 来实现组件通信,具体示例如下:

  • message.service.ts
import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MessageService {
    private subject = new Subject<any>();

    sendMessage(message: string) {
        this.subject.next({ text: message });
    }

    clearMessage() {
        this.subject.next();
    }

    getMessage(): Observable<any> {
        return this.subject.asObservable();
    }
}
  • home.component.ts
import { Component } from '@angular/core';

import { MessageService } from '../_services/index';

@Component({
    moduleId: module.id,
    templateUrl: 'home.component.html'
})

export class HomeComponent {
    constructor(private messageService: MessageService) {}
    
    sendMessage(): void { // 发送消息
        this.messageService.sendMessage('Message from Home Component to App Component!');
    }

    clearMessage(): void { // 清除消息
        this.messageService.clearMessage();
    }
}
  • app.component.ts
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { MessageService } from './_services/index';

@Component({
    moduleId: module.id,
    selector: 'app',
    templateUrl: 'app.component.html'
})

export class AppComponent implements OnDestroy {
    message: any;
    subscription: Subscription;

    constructor(private messageService: MessageService) {
        this.subscription = this.messageService.getMessage()
              .subscribe(message => { this.message = message; });
    }

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
}
  • ProgrameService
@Injectable()
export class ProgrameService {

  filterSubject = new Subject();
  filterObservable = this.filterSubject.asObservable();

}
  • ProgrameFilterComponent 推送事件,观察者执行
 flushData() {
   // 推送数据更新
   this.programeService.filterSubject.next(this.getParams());
 }
  • ProgrameGridComponent 注册为观察者
 constructor(private programeService: ProgrameService) {
   super();
   programeService.filterObservable.subscribe(params => {
     this.params = params;
     this.flushData();
   });
 }

子组件中使用@Inject调用父组件中的变量和方法

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

@Component({
  selector: 'my-parent',
  templateUrl: './parent.component.html',
  styleUrls: [ './parent.component.css' ],
})
export class ParentComponent implements OnInit {
  
  constructor() {}
  
  ngOnInit(): void {
    
  }
  sayHello(){
    console.log("Hello!")
  }
}

import { Component, OnInit, Inject, forwardRef} from '@angular/core';
import { ParentComponent } from './parent.component';

@Component({
  selector: 'my-child',
  templateUrl: './child.component.html',
  styleUrls: [ './child.component.css' ],
})
export class ChildComponent implements OnInit {
  
  constructor(
    @Inject(forwardRef(()=>ParentComponent)) public parent:ParentComponent
  ) {}
  
  ngOnInit(): void {
    this.parent.sayHello();   //"Hello!"
  }
}

ngClass

<!-- 使用布尔值 -->
<div [ngClass]="{bordered: false}">This is never bordered</div>
<div [ngClass]="{bordered: true}">This is always bordered</div>

<!-- 使用组件实例的属性 -->
<div [ngClass]="{bordered: isBordered}">
   Using object literal. Border {{ isBordered ? "ON" : "OFF" }}
</div>

<!-- 样式名包含'-' -->
<div[ngClass]="{'bordered-box': false}">
   Class names contains dashes must use single quote
</div>

<!-- 使用样式列表 -->
<div class="base" [ngClass]="['blue', 'round']"> 
  This will always have a blue background and round corners
</div>

ngStyle

 <div [ngStyle]="{color: 'white', 'background-color': 'blue'}">
   Uses fixed white text on blue background
 </div>

需要注意的是, background-color 需要使用单引号,而 color 不需要。这其中的原因是,ng-style 要求的参数是一个 Javascript 对象,color 是一个有效的 key,而 background-color 不是一个有效的 key ,所以需要添加 对于一些场合,我们也可以直接利用 Angular 属性绑定的语法,来快速设置元素的样式。

设置元素的背景颜色

 <div [style.background-color="'yellow'"]>
  Use fixed yellow background
 </div>

设置元素的字体大小

<!-- 支持单位: px | em | %-->
 <div>
   <span [ngStyle]="{color: 'red'}" [style.font-size.px]="fontSize">
      Red Text
   </span>
 </div>

@ts-ignore

        private notification: NzNotificationService,
        private personcheckService: PersoncheckService,
        private translate: TranslateService) {
      personinfoService.filterObservable.subscribe(params => {
        // console.log(params);
        // @ts-ignore
        let {apportionmentType}=params;
       this.apportionmentType=apportionmentType;
      });
    }

offsetHeight

内容高度

this.viewHeight = this.elementView.nativeElement.offsetHeight;

NgNonBindable

ngNonBindable 指令用于告诉 Angular 编译器,无需编译页面中某个特定的HTML代码片段。

viewContainerRef.createComponent const childInjector = Injector.create技巧

定义未初始化值
  picker: ComponentRef<DatePickerComponent>;


初始化触发
  @HostListener('click')
  togglePicker(): void {
    if (!this.picker) {
      this.showPicker();
    } else {
      this.hidePicker();
    }
  }

  showPicker(): void {
.......
      const options = new DatePickerOptions();
      Object.assign(options, this._defaultOptions, {
        hidePicker: (event): void => {
          this.hidePicker();
        }
      });

      const optionalParams: Array<keyof DatePickerDirective> = [
        'mode',
        'minDate',
        'maxDate',
        'value',
        'mask',
        'title',
        'okText',
        'dismissText',
        'disabled',
        'locale',
        'appendToBody',
        'onOk',
        'onDismiss',
        'onValueChange'
      ];
      optionalParams.forEach(param => {
        if (typeof this[param] !== 'undefined') {
          (options as any)[param] = this[param];
        }
      });
      const componentFactory: ComponentFactory<DatePickerComponent> = this._cfr.resolveComponentFactory(
        DatePickerComponent
      );

#初始化控件并传递值到构造函数

      const childInjector = Injector.create([
        {
          provide: DatePickerOptions,
          useValue: options
        }
      ]);
      this.picker = this._viewContainerRef.createComponent(
        componentFactory,
        this._viewContainerRef.length,
        childInjector
      );
......
}

DatePickerComponent的构造函数


  constructor(
    public elementRef: ElementRef,
    public options: DatePickerOptions,
    public localeProviderService: LocaleProviderService
  ) {
  }

获取页面上的元素

@Component({
  selector: 'my-comp',
  template: `
    <input type="text" />
    <div> Some other content </div>
  `
})
export class MyComp {
  constructor(el: ElementRef) {
    el.nativeElement.querySelector('input').focus();
  }
}

推荐的方式

@Component({
  selector: 'my-comp',
  template: `
    <input #myInput type="text" />
    <div> Some other content </div>
  `
})
export class MyComp implements AfterViewInit {
  @ViewChild('myInput') input: ElementRef;

  constructor(private renderer: Renderer) {}

  ngAfterViewInit() {
    this.renderer.invokeElementMethod(
        this.input.nativeElement, 'focus');
    }
}

应该如何引入第三方 UI 库,如 bootstrap

若要引入第三方 UI 库,可以在 .angular-cli.json 文件中,配置对应的样式文件地址,具体如下:

{
  "apps": {
     "styles": [
         "styles.css",
         "../node_modules/bootstrap/dist/css/bootstrap.min.css"
      ]
  }
}

数据绑定

模板:Title

双向

[(ngModel)]="message"

单项

[ngModel]="message"

下载

let url = environment.BASE_SERVER_URL + 'excelExport/export?processor=personInfoExcelExportProcessor';
window.open(url);
URL.revokeObjectURL(url);

表单

[3]

Angular 中有两种表单:

Template-driven Forms (类似于 AngularJS 1.x 中的表单 )

Reactive Forms

  • Template-driven 表单的特点
  1. 使用方便
  2. 适用于简单的场景
  3. 通过 [(ngModel)] 实现数据双向绑定
  4. 自动生成 Form Model (异步)
  5. 最小化组件类的代码
  6. 不易于单元测试
  • Reactive 表单的特点
  1. 比较灵活
  2. 适用于复杂的场景
  3. 简化了HTML模板的代码,把验证逻辑抽离到组件类中
  4. 手动创建 Form Model (同步)
  5. 方便的跟踪表单控件值的变化
  6. 易于动态添加表单控件
  7. 易于单元测试

Template-driven Forms

  • 在 Angular 表单中,我们通过 ngModel 指令来实现双向绑定。
  • 目前 Angular 支持的内建 validators 如下:
  1. required - 设置表单控件值是非空的
  2. email - 设置表单控件值的格式是 email
  3. minlength - 设置表单控件值的最小长度
  4. maxlength - 设置表单控件值的最大长度
  5. pattern - 设置表单控件的值需匹配 pattern 对应的模式
  • 在 Angular 中,我们可以通过 #userName="ngModel" 方式获取 ngModel 对象,然后通过 userName.valid 判断表单控件是否通过验证。
  • 需要注意的是,在使用 <form> 标签后,我们的 username 输入框,必须添加 name 属性。
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
  <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
    <input 
     type="text" 
     required
     minlength="3"
     name="username"
     [(ngModel)]="username"
     #userName="ngModel">
    <hr>
    {{userName.valid}}
    <div *ngIf="userName.errors?.required">请您输入用户名</div>
    <div *ngIf="userName.errors?.minlength">
      用户名的长度必须大于 {{userName.errors?.minlength.requiredLength}},当前的长度为
        {{userName.errors?.minlength.actualLength}}
    </div>
    <button type="submit">提交</button>
    {{loginForm.value | json}}
  </form>
  `,
})
export class AppComponent {
  username = 'semlinker';

  onSubmit(value) {
    console.dir(value);
  }
}

ngModelGroup

ngModelGroup 指令是 Angular 表单中提供的另一特殊指令,可以对表单输入内容进行分组,方便我们在语义上区分不同性质的输入。例如联系人的信息包括姓名及住址,现在需对姓名和住址进行精细化信息收集,姓名可精细化成姓和名字,地址可精细化成城市、区、街等。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
  <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
   <fieldset ngModelGroup="user">
    <input 
     type="text" 
     required
     minlength="3"
     name="username"
     [(ngModel)]="username"
     #userName="ngModel">
    <hr>
    <div *ngIf="userName.errors?.required">请您输入用户名</div>
    <div *ngIf="userName.errors?.minlength">
      用户名的长度必须大于 {{userName.errors?.minlength.requiredLength}},当前的长度为
        {{userName.errors?.minlength.actualLength}}
    </div>
    <input type="password" ngModel name="password">
   </fieldset>
    <button type="submit">提交</button>
    <hr>
    {{loginForm.value | json}}
  </form>
  `,
})
export class AppComponent {
  username = 'semlinker';

  onSubmit(value) {
    console.dir(value);
  }
}

以上代码成功运行后,模板:LoginForm.value 的输出结果:

{ "user": { "username": "semlinker", "password": "123" } }

表单添加验证状态样式

在 Angular 表单中,若验证通过则会在表单控件上添加 ng-valid 类,若验证失败则会在表单控件上添加 ng-invalid 类。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  styles: [`
   input.ng-invalid {
       border: 3px solid red;
    }
   input.ng-valid {
       border: 3px solid green;
    }
  `
  ],
  template: `
  <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
   <fieldset ngModelGroup="user">
    <input 
     type="text" 
     required
     minlength="3"
     name="username"
     [(ngModel)]="username"
     #userName="ngModel">
    <hr>
    <div *ngIf="userName.errors?.required">请您输入用户名</div>
    <div *ngIf="userName.errors?.minlength">
      用户名的长度必须大于 {{userName.errors?.minlength.requiredLength}},当前的长度为
        {{userName.errors?.minlength.actualLength}}
    </div>
    <input type="password" required ngModel name="password">
   </fieldset>
    <button type="submit">提交</button>
    <hr>
    {{loginForm.value | json}}
  </form>
  `,
})
export class AppComponent {
  username = 'semlinker';

  onSubmit(value) {
    console.dir(value);
  }
}

表单控件的状态

表单控件除了 valid 状态外,还包含哪些状态? 在 Angular 中表单控件有以下 6 种状态,我们可以通过 #userName="ngModel" 方式获取 ngModel 对象,进而获取控件的状态信息。具体状态如下:

  • valid - 表单控件有效
  • invalid - 表单控件无效
  • pristine - 表单控件值未改变
  • dirty - 表单控件值已改变
  • touched - 表单控件已被访问过
  • untouched - 表单控件未被访问过
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  styles: [`
   input.ng-invalid {
       border: 3px solid red;
    }
   input.ng-valid {
       border: 3px solid green;
    }
  `
  ],
  template: `
  <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
   <fieldset ngModelGroup="user">
    <input 
     type="text" 
     required
     minlength="3"
     name="username"
     [(ngModel)]="username"
     #userName="ngModel">
    <hr>
    <p>Name控件的valid状态:{{userName.valid}} - 表示控件有效</p>
    <p>Name控件的invalid状态:{{userName.invalid}} - 表示控件无效</p>
    <p>Name控件的pristine状态:{{userName.pristine}} - 表示控件值未改变</p>
    <p>Name控件的dirty状态:{{userName.dirty}} - 表示控件值已改变</p>
    <p>Name控件的touched状态:{{userName.touched}} - 表示控件已被访问过</p>
    <p>Name控件的untouched状态:{{userName.untouched}} - 表示控件未被访问过</p>
    <div *ngIf="userName.errors?.required">请您输入用户名</div>
    <div *ngIf="userName.errors?.minlength">
      用户名的长度必须大于 {{userName.errors?.minlength.requiredLength}},当前的长度为
        {{userName.errors?.minlength.actualLength}}
    </div>
    <input type="password" required ngModel name="password">
   </fieldset>
    <button type="submit">提交</button>
    <hr>
    {{loginForm.value | json}}
  </form>
  `,
})
export class AppComponent {
  username = 'semlinker';

  onSubmit(value) {
    console.dir(value);
  }
}

Reactive Form

要使用 Reactive Form 需要以下几个步骤:

  • 导入 ReactiveFormsModule
import { ReactiveFormsModule } from "@angular/forms";
  • 在 NgModule 的 imports 属性值对应的数组中,添加 ReactiveFormsModule
@NgModule({
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
})
export class AppModule { }
  • 绑定 form 元素的 formGroup 属性
<form (ngSubmit)="save()" [formGroup]="signupForm"></form>
  • 关联 input 元素对应的 FormControl 实例
<input type="text" name="userName" placeholder="请输入用户名" formControlName="userName">
  • 使用 FormGroup
  1. 创建 FormGroup 属性
  2. 创建 FormGroup 实例
  3. 创建每个 FormControl 实例
signupForm: FormGroup; 

this.signupForm = new FormGroup({
  userName: new FormControl('', [Validators.required, Validators.minLength(3)]),
  email: new FormControl('', [Validators.required, Validators.email]),
  ...
});
  • 使用 FormBuilder

要使用 FormBuilder 需要以下几个步骤:

  1. 导入 FormBuilder
  2. 注入 FormBuilder 实例
  3. 使用 FormBuilder 实例

FormBuilder 创建 Form Control 语法

方式一

this.signupForm = this.fb.group({
  userName: 'semlinker',
  hasAddress: false
});

方式二

this.signupForm = this.fb.group({
  userName: {value: 'semlinker', disabled: true}
  hasAddress: {value: true, disabled: false}
});

方式三

this.signupForm = this.fb.group({
  userName: [''],
  hasAddress: [{value: true, disabled: false}]
})

路由

https://blog.csdn.net/weixin_42706227/article/details/81543694 (八)、Angular4.0 路由守卫

router.navigate

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

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Our app</h3>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}
  ngOnInit() {
    setTimeout(() => {
      this.router.navigate(['/settings']);
    }, 5000);
  }
}
constructor(
    private router: Router,
    private routerParams: ActivatedRoute
  ) { }
ngOnInit() {
    this.routerParams.snapshot.paramMap.get('id')  // 此时我可以通过这个快照拿到这个id,但是仅仅只能拿到一次
}

constructor(private route: ActivatedRoute) {}
ngOnInit() {
  this.route.paramMap.subscribe((params: ParamMap)=>{
    const id = params.get('id')
    })
}

安装

cd xxx 
npm install -g @angular/cli@latest
ng new PROJECT-NAME
cd PROJECT_NAME
cnpm install ng-zorro-antd --save
  • 直接用下面的代码替换 /src/app/app.module.ts 的内容

注意:在根 module 中需要使用 NgZorroAntdModule.forRoot(),在子 module 需要使用 NgZorroAntdModule

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    BrowserAnimationsModule,
    NgZorroAntdModule.forRoot()
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

调试

ng serve --port 0 --open

如果需要实时调试AoT效果,请使用以下命令启动

ng serve --port 0 --open --aot

5. 构建和部署

ng build
ng build --prod #会报错,不知道为啥

Angular官方教程

https://www.angular.cn/tutorial

cli

[4]

ng g component hero-detail
ng g service Hero #不用加Service,会自动以Service结尾

问题解决

Cannot assign to a reference or variable

问题原因是,ngModel 会去查找 ts文件中的 this.item ,然而我们并没有在ts文件中定义 item。

换句话说,ngModel中传入的值必须是文件的本地的变量,临时的item不会被接受。

 <nz-collapse-panel *ngFor="let item of childs; let i=index" nzHeader="子女{{childs[i].name}}"
                             [nzActive]="panelactive">
            <personinfo-child-edu-form *ngIf="childs[i].editStatus !== 'DELETE'"
                                       [(item)]="childs[i]"></personinfo-child-edu-form>
          </nz-collapse-panel>