Angular
创建一个Angular项目
前提准备
安装node、npm
安装Angular CLI
npm install -g @angular/cli
创建新项目
ng new my-app
运行
cd my-app
ng serve --open
小插曲:第一次在vscode上运行可能会出现"ng无法加载文件..."的情况,这是因为
第一次使用 VSCode,默认使用的 powershell 执行命令,在 powershell 中禁止执行
ng.ps1脚本文件。使用 windows 的 cmd 时,可以正常执行
ng serve --open命令,正常启动hello-angular应用。将默认的 powershell 执行方式修改为 windows 的 cmd 即可正常执行。

创建组件
ng generate compoent <cpn-name>
创建成功会生成一个文件夹里面有四个文件:

其中.ts文件内容具体如下:

也可以用template: '<h1>Hello World!</h1>',替换templateUrl: './app.component.html',只能使用一个。
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',//自定义标签名
  templateUrl: './app.component.html',//模板路径,或者使用 ` ` 直接编写模板
  styleUrls: ['./app.component.less']//存放相关样式文件
})
//在vue中组件有自己的data、methods等选项,在Angular中也有
export class AppComponent {
  title = 'mydemo';//相当于data
  curItem= "Television";
  increase = function () {
    console.log(this.title);//在组件方法中通过this访问组件成员
  }
}
使用组件
创建一个组件ng generate component cpn-Name。此时app.modules会自动更新把组件加入declarations中。
在modules.ts里面的NgModules:组件模块资源:组件、指令、服务
1、import { MycpnComponent } from './mycpn/mycpn.component';依赖模块
2、declarations:[MycpnComponent]组件
3、bootstrap: [MycpnComponent]指定启动的根组件
NgModules的作用:可以帮你把应用组织成一些紧密相关的代码块。
declarations:属于该模块的可声明对象(组件、指令和管道)的列表。
imports:要折叠(Folded)进本模块的其他模块
bootstrap:应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性
举个例子,你有三个积分卡,A卡在a店用,B卡在b店用,C卡在c店用你要在a店用A卡,首先a店需要声明一下我用到了A卡,这就是declarations。你忽然想在a店用B卡,那么你就要让a店声明一下,我这个店也可以用B卡了,这就是imports。这是a店忽然和bc店合作,让A卡在三个店都能用,这个时候就需要exports一下A卡,告诉大家,我的A卡你们都能用了。
输入与输出
@Input() 和 @Output() 为子组件提供了一种与其父组件通信的方法。
@Input() 允许父组件更新子组件中的数据。
相反,@Output() 允许子组件向父组件发送数据。

@Input
要使用 @Input() 装饰器,首先要导入 Input,然后用 @Input() 装饰该属性。
子组件:
template:
`
<p>Today's item: {{item}}</p>
`                   
//2、模板可以使用该属性           
export class ItemDetailComponent {
  @Input() item: string; 
//  1、用@Input()装饰item属性
}
父组件:
template:
`
<app-child [item]="currentItem"></app-child>
`                   
//3、父组件中使用该属性=某值
export class AppComponent {
  currentItem = 'Television';//4、赋值
}
过程:
- 子组件使用 @Input()装饰器装饰一属性item
- 在父组件中使用子模板<app-child>,并把子组件的item绑定到父组件的currentItem上:[item]="currentItem"
- 父组件为currentItem指定一个值。
最终结果是currentItem的值通过@Input传递给了子组件的item。
另外,要想监视 @Input() 属性的变化,你可以使用 Angular 的生命周期钩子OnChanges 。
具体代码:

@Output
要使用 @Output() 装饰器,首先要导入 Output和EventEmitter,然后用 @Input() 装饰一个自定义事件。
子组件
import { Output, EventEmitter } from '@angular/core';
...
template:
`
<label>Add an item: <input #news></label>
<button (click)="addNewItem(news.value)">Add to parent's list</button>
`
...
export class ItemOutputComponent {
  @Output() myEvent = new EventEmitter<string>();
  addNewItem(value: string) {
    this.myEvent.emit(value);
  }
}
父组件
...
template:
`
<app-child (myEvent)="fun($event)"></app-child>
<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>
`
...
export class AppComponent {
  items = ['item1', 'item2', 'item3', 'item4'];
  fun(newItem: string) {
    this.items.push(newItem);
  }
}
过程:
- 子组件使用@Output() myEvent = new EventEmitter<string>()自定义一个事件myEvent。
- 通过已有的事件(click,focus等)触发myEvent.emit(value),把事件连同参数发送给父组件。
- 事件绑定 (myEvent)='fun($event)'会把子组件中的myEvent事件连接到父组件的fun()方法,$event就是携带的参数。
最终结果是:子组件输入的内容会添加到父组件的ul上。
具体代码:

总结
@Input是子组件用来接收父组件传来的数据的,@Output是子组件用来给父组件发送数据的,并且需要通过事件触发。
组件交互
通过输入型绑定把数据从父组件传到子组件
代码逻辑:
在子组件标签上使用数据绑定[h]=hero和[m]="master"
子组件通过@Input() h:object和@Input() m:string接收父组件传来的数据
使用h和m。


父组件监听子组件的事件
代码逻辑:
子组件通过@Output() 自定义事件 = new EventEmitter<boolean>()搞一个自定义事件voted
通过voted.emit(参数)把参数和事件发送出去
父组件通过触发自定义事件(voted)="onVoted($event)"获取参数,$event就是参数(可省略)


双向绑定
组件间数据的双向绑定
原理:
1、为了使双向数据绑定有效,@Output() 属性的名字必须遵循一个规则,如果 @Input() 属性为 size ,则 @Output() 属性必须为 sizeChange 。
2、子组件具有值属性 size 和自定义事件属性 sizeChange。 size 属性是 @Input()可以接收父组件的数据 。 sizeChange 事件是一个 @Output()发送数据到父组件。
3、接下来,有两个方法, dec() 用于减小字体大小, inc() 用于增大字体大小。这两种方法使用 resize() 在最小/最大值的约束内更改 size 属性的值,并发出带有新 size 值的事件。

<app-father [(size)]="fontSizePx"></app-father>其实是下面的简写↓↓↓
<app-father [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-father>
$event 变量包含sizeChange事件的数据。当用户单击按钮时,$event 赋值给 fontSizePx。
表单中的双向绑定
NgModel 指令允许你显示数据属性并在用户进行更改时更新该属性。使用 ngModel 指令,必须先导入 FormsModule 并将其添加到 NgModule 的 imports 列表中。
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel">
模板引用变量
在模板中用#声明一个模板变量phone,可以在组件模板中任意使用这个phone变量。
  <input #phone placeholder="phone number" />
  <button (click)="callPhone(phone.value)">Call</button>
在input上输入数据,点击button后数据会传到callPhone函数中。
  <p #p>look!<p/>
  <button (click)="console.log(p)">Call</button>
打印整个p标签,说明p是一个节点元素。
Angular 根据你所声明的变量的位置给模板变量赋值:
- 如果在组件上声明变量,该变量就会引用该组件实例。
- 如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。
- 如果你在 <ng-template>元素上声明变量,该变量就会引用一个TemplateRef实例来代表此模板。
- 如果该变量在右侧指定了一个名字,比如 #var="ngModel",那么该变量就会引用所在元素上具有这个exportAs名字的指令或组件。???
指令
在 Angular 中有三种类型的指令:
- 组件 — 拥有模板的指令
- 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
- 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。
内置属性型指令
属性型指令会监听并修改其它 HTML 元素和组件的行为、Attribute 和 Property。 它们通常被应用在元素上,就好像它们是 HTML 属性一样,因此得名属性型指令。
许多 NgModule(例如 RouterModule 和 FormsModule都定义了自己的属性型指令。最常见的属性型指令如下:
- 
NgClass—— 添加和删除一组 CSS 类。
- 
NgStyle—— 添加和删除一组 HTML 样式。
- 
NgModel—— 将数据双向绑定添加到 HTML 表单元素。
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">This div is x-large or smaller.</div>
<div [ngStyle]="currentStyles">This div is initially italic, normal weight, and extra large (24px).</div>
内置结构型指令
结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,这通常是通过添加、移除和操纵它们所附加到的宿主元素来实现的。
本节会介绍常见的内置结构型指令:
- 
NgIf—— 从模板中创建或销毁子视图。(相当于vue的v-if)
- 
NgFor—— 为列表中的每个条目重复渲染一个节点。(相当于vue的v-for)
- 
NgSwitch—— 一组在备用视图之间切换的指令。
为什么要用*?,结构性指令,直接接触 DOM。
ngIf
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
ngFor
<div *ngFor="let item of items">{{item.name}}</div>
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
<div *ngFor="let item of items; trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
ngFor微语法:
"let item of items"的意思是:将items数组中的每个条目存储在局部循环变量item中,并使其可用于每次迭代的模板 HTML 中。
"let i=index"表示i为每次循环的索引值。
trackBy: trackByItems":用于解决:如果将NgFor与大型列表一起使用,则对某个条目的较小更改(例如删除或添加一项)就会触发一系列 DOM 操作。
ngSwitch
类似于 JavaScript switch 语句。它根据切换条件显示几个可能的元素中的一个。Angular 只会将选定的元素放入 DOM。
NgSwitch 实际上是三个协作指令的集合: NgSwitch,NgSwitchCase 和 NgSwitchDefault,如以下范例所示。
<div [ngSwitch]="currentItem.feature">
  <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>
  <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>
  <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>
  <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>
  <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item>
</div>
ngSwtich 是属性指令,他下面的*ngSwitchCase是结构指令
属性型指令
属性型指令用于改变一个 DOM 元素的外观或行为,而不会改变它的dom tree属性。
创建一个属性型指令
ng generate directive highlight创建一个指令类文件,会生成一个highlight.directive.ts文件
进行编写后:
import { Directive,ElementRef,HostListener } from '@angular/core';
//导入的Directive提供@Directive装饰器,导入了一个 ElementRef用于修改样式
@Directive({
    selector: '[appHighlight]'//表示的是属性选择器
})
export class HighlightDirective {
   constructor(el:ElementRef) {
        el.nativeElement.style.backgroundColor = "yellow"
    }
}
然后就可以在标签上使用appHighlight属性了。
响应用户事件
import { Directive,ElementRef,HostListener } from '@angular/core';
//HostListener用于响应用户引发的事件,让你订阅某个属性型指令所在的宿主 DOM 元素的事件
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) {}
  //用户使用此属性时就会触发
  
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight('yellow');
    //鼠标进入时触发
  }
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight("black");
    //鼠标离开时触发
  }
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
通过@Input传值

ngNonBindable
当你把 ngNonBindable 应用在元素上时,它会阻止元素及其所有子元素的绑定。不过,ngNonBindable 仍然允许指令作用于受 ngNonBindable 影响的元素上。下面的例子中,appHighlight 指令仍然会生效,但是 Angular 不会对表达式 {{ 1 + 1 }} 进行求值。
<h3>ngNonBindable with a directive</h3>
<div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.
</div>
结构型指令
结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,比如添加、移除或维护这些元素。即会改变dom tree。
*微语法
结构型指令都会有一个*开头。这个*是一个微语法而不是模板表达式。
<div *ngIf="hero" class="name">{{hero.name}}</div>
            |这个语法糖可以解析为下面的模板
            ↓微语法就是把 * 翻译成ng-template的样子
<ng-template [ngIf]="hero">
  <div class="name">{{hero.name}}</div>
</ng-template>
三个常用的内置结构型指令 —— NgIf、NgFor、NgSwitch...。
指令同时具有两种拼写形式大驼峰
UpperCamelCase和小驼峰lowerCamelCase,比如你已经看过的NgIf和ngIf。 这里的原因在于,NgIf引用的是指令的类名,而ngIf引用的是指令的属性名。指令的类名拼写成大驼峰形式(
NgIf),而它的*属性名则拼写成小驼峰形式(ngIf)。 本章会在谈论指令的属性和工作原理时引用指令的类名,在描述如何在 HTML 模板中把该指令应用到元素时,引用指令的属性名。
ng-template
<ng-template>是一个 Angular 元素,用来渲染 HTML。 它永远不会直接显示出来。 Angular 会把 <ng-template> 及其内容替换为*一个注释。如果没有使用结构型指令,而仅仅把一些别的元素包装进 <ng-template> 中,那些元素就是不可见的。
<p>Hip!</p>
<ng-template>
  <p>Hip!</p>
</ng-template>
<p>Hooray!</p>
<!--如果不使用指令ng-template的内容会变成一段注释-->
<ng-template>需要一个HTML元素作为宿主元素,比如<li>,有时候之想作用于文本,可以使用<span>或者<div>包裹内容。但是会引发接下来的问题:
1、可能会破坏了原有的html结构,使得css代码多渲染或者少渲染了部分元素。
2、另一个问题是:有些 HTML 元素需要所有的直属下级都具有特定的类型。 比如<select> 元素要求直属下级必须为 <option>,那就没办法把这些选项包装进 <div> 或 <span> 中。
解决方法:使用<ng-container>
ng-container
<ng-container> 是一个分组元素,但它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。
<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>
<ng-container>不会影响到dom结构,只是把一部分元素进行一个分组。
问:那我都用ng-container不就好了。
自定义指令
创建一个自定义指令
ng g d [directiveName]
生成代码
import { Directive } from '@angular/core';
@Directive({
  selector: '[directiveName]'
})
export class directiveName {
  constructor() { }
}
这是一个去除字符串两边空格的指令(输入多个用','隔开)
import { Directive, HostListener, ElementRef } from '@angular/core';
import { NgControl } from '@angular/forms';
/**
 * 自定义指令,表单数据字段去空格处理
 */
@Directive({
  selector: '[appTrim]'
})
export class TrimDirective {
  constructor(
   private elementRef: ElementRef,
   private ngControl?: NgControl//表单控制
  ) { }
  @HostListener('blur', ['$event.target'])//事件
  onBlur(event: any) {
    /*数据处理*/
    const result: string[] = [];
    event.value.split(',').map((v: string, i: number) => {
      result[i] = v.trim();
    });
    const res: string = result.join(',');
    if (event.value) {
      if (this.ngControl) {
        //是双向绑定表单值
        this.ngControl.control.patchValue(res);//更新双向绑定的值
        this.ngControl.control.updateValueAndValidity();//更新并且验证值
      } else {
        this.elementRef.nativeElement.value = res;
      }
    }
  }
}
关于更新双向绑定表单的值的方法有:ngControl.control.patchValue() + ngControl.control.patchValue()
依赖注入(DI)
依赖,是当类需要执行其功能时,所需要的服务或对象,其中的类会从外部源中请求获取依赖,而不是自己创建它们。
传统的方式不够健壮和灵活,不利于维护。如果想从服务器上获取数据,就要使用服务。
创建和注册可注入的服务类
创建:
ng generate service <name>
注册:
import { Injectable } from '@angular/core';
import { HEROES } from "./mock-heroes"
//@Injectable() 是每个 Angular 服务定义中的基本要素。
//@Injectable() 装饰器把它标记为可供注入的服务
@Injectable({
  providedIn: 'root'
})
export class HeroesService {
  getHeroes(){ 
    return HEROES 
  };
}
// 可以在三种位置设置元数据,以便在应用的不同层级使用提供者来配置注入器:
// 在服务本身的 @Injectable() 装饰器中。
// 在 NgModule 的 @NgModule() 装饰器中。
// 在组件的 @Component() 装饰器中。
Angular系统中通过在类上添加@Injectable装饰器来告诉系统这个类(服务)是可注入的。当然了这仅仅只是告诉Angular系统这个类(服务)类是可注入的。但是这个服务可以在哪里使用,由谁提供,就得靠注入器和提供商来一起确定了。
路由
最简单路由使用
1、ng new routing-app --appName生成一个带有应用路由模块(AppRoutingModule)的基本 Angular 应用,它是一个 NgModule,可用来配置路由。
2、请确保你的 index.html 文件的 <head> 中有 <base href="/"> 语句。这里假定 app 文件夹是应用的根目录,并使用 "/" 作为基础路径。
3、app-routing.module.ts文件:Routes数组就是路由表。
4、页面中使用<a routerLink="/second-component" routerLinkActive="active">Second Component</a>跳转路由,用<router-outlet></router-outlet>作为占位标签。当路由为当前路由时,routerLinkActive的类名active被激活。

一个路由表

四种路由传递数据的方式

接收参数:都需要引入ActivatedRoute模块,使用this.route.queryParams.subscribe((res)=>{consloe.log(res)})获取参数。
注意:第二种方式参数是params而不是queryParams。
网址组成部分
https://example.com:8042/over/there?name=ferret#nose
\___/  \_______________/\_________/ \_________/ \__/
 |           |            |            |        |
scheme    authority      path        query   fragment
路由的组成
| 路由器部件 | 含义 | 
|---|---|
| Routes | 用于配置路由表: route : Route = [{},{},...] | 
| Router | 路由用于跳转的模块: this.router.navigate() | 
| ActivatedRoute | 获取路由跳转后携带的信息: const route : ActivatedRoute;this.route.queryParams.subscribe((res)=>{...}); | 
路由的路径和参数可以通过注入名为 ActivatedRoute 的路由服务获得。它提供了大量有用的信息,包括:
| 属性 | 说明 | 
|---|---|
| url | 一个路由路径的 Observable,是一个由路由路径的各个部分组成的字符串数组。 | 
| data | 包含提供给当前路由的 data对象的Observable。 也包含任何由解析守卫解析出的值。 | 
| paramMap代替了params | 一个包含该路由的必要参数和可选参数 map的 Observable。 这个 map 支持从同一个参数中获得单个或多个值。 | 
| queryParamMap代替了queryParams | 一个包含适用于所有路由的查询参数 map 的 Observable。 这个 map 支持从同一个查询参数中获得单个或多个值。 | 
| fragment | 一个适用于所有路由的 URL 片段的 Observable。 | 
| outlet | 用来渲染该路由的 RouterOutlet的名字。 对于无名出口,这个出口的名字是primary。 | 
| routeConfig | 包含原始路径的那个路由的配置信息。 | 
| parent | 当该路由是子路由时,表示该路由的父级 ActivatedRoute。 | 
| firstChild | 包含该路由的子路由列表中的第一个 ActivatedRoute。 | 
| children | 包含当前路由下所有激活的子路由。 | 
==Observable是什么东西!?==
路由守卫
Angular 的 Route 路由参数中除了熟悉的 path、component 外,还包括四种是否允许路由激活与离开的属性。
用ng generate guard my-guard生成一个路由守卫文件
| 属性 | 含义 | 接口名 | 
|---|---|---|
| canActivate | 控制是否允许进入路由。 | CanActivate | 
| canActivateChild | 等同 canActivate,只不过针对是所有子路由。 | CanActivateChild | 
| canDeactivate | 控制是否允许离开路由。 | CanDeactivate<TComponent> | 
| canLoad | 控制是否允许延迟加载整个模块。 | CanLoad | 
使用实例:
export class MyGuardGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      let a=10;
    //这里进行逻辑判断
      if(a){
        console.log("允许进入");
        //返回true,则允许进入
        return true 
      }
      else{
        console.log("不允许进入");
        //返回false不允许进入
        return false
      }
  }
}
const routes: Routes = [
  { 
    path: 'first-component',
    component: FirstComponent,
    canActivate: [MyGuardGuard],
  },
  ...]
表单
Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单和模板驱动表单。两者都从视图中捕获用户输入事件、验证用户输入、创建表单模型、修改数据模型,并提供跟踪这些更改的途径。响应式表单和模板驱动表单以不同的方式处理和管理表单数据。每种方法都有各自的优点。
- 响应式表单提供对底层表单对象模型直接、显式的访问。它更加健壮:它们的可扩展性、可复用性和可测试性都更高。如果表单是你的应用程序的关键部分,或者你已经在使用响应式表单来构建应用,那就使用响应式表单。 
- 
模板驱动表单依赖模板中的指令来创建和操作底层的对象模型。它们对于向应用添加一个简单的表单非常有用,比如登录注册表单。它们很容易添加到应用中,但扩展性不如响应式表单。如果你有基本的表单需求和逻辑,那么模板驱动表单就很合适。 差异 
| 响应式表单 | 模板驱动表单 | |
|---|---|---|
| 建立表单模型 | 显式的,在组件类中创建 | 隐式的,由指令创建 | 
| 数据模型 | 结构化和不可变的 | 非结构化和可变的 | 
| 可预测性 | 同步 | 异步 | 
| 表单验证 | 函数 | 指令 | 
简单来说就是响应式适合大型表单,模板驱动(动态表单)适合小型、固定的表单如注册登录。
常用表单基础类
响应式表单和模板驱动表单都建立在下列基础类之上。
- 
FormControl实例用于追踪单个表单控件的值和验证状态。
- 
FormGroup用于追踪一个表单控件组的值和状态。
- 
FormArray用于追踪表单控件数组的值和状态。
- 
ControlValueAccessor用于在 Angular 的FormControl实例和原生 DOM 元素之间创建一个桥梁。
响应式表单
基本使用
使用表单控件有三个步骤。
- 在你的应用中注册响应式表单模块(从 @angular/forms包中导入ReactiveFormsModule,并把它添加到你的 NgModule 的imports数组中)。该模块声明了一些你要用在响应式表单中的指令。
- 生成一个新的 FormControl实例,并把它保存在组件中。
- 在模板中注册这个 FormControl。
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
  selector: 'app-reactive-favorite-color',
  template: 
  `
    Favorite Color: <input type="text" [formControl]="favoriteColorControl">
  `
})
export class FavoriteColorComponent {
  favoriteColorControl = new FormControl('');//这个字符串就是响应式表单的值
}
响应式表单的数据流

使用这种模板绑定语法,把该表单控件注册给了模板中名为
name的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化。或者使用
value属性获取表单值。在这个例子中,只使用单个控件,但是当调用 FormGroup 或 FormArray实例的
setValue()方法时,传入的值就必须匹配控件组或控件数组的结构才行。
FormGroup进行分组

[formGroup]为访问此表单的对象名,formGroupName为嵌套结构的对象名
使用 FormBuilder 服务生成控件
通过下列步骤可以利用这项服务。
- 导入 - FormBuilder类。- import { FormBuilder } from '@angular/forms';
- 注入这个 - FormBuilder服务。- constructor(private fb: FormBuilder) { }
- 
生成表单内容。 profileForm = this.fb.group({ firstName: [''], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }) });
验证表单输入
使用下列步骤添加表单验证。
- 在表单组件中导入一个验证器函数。 - import { Validators } from '@angular/forms';
- 
把这个验证器添加到表单中的相应字段。 profileForm = this.fb.group({ firstName: ['', [Validators.required]]; });<button type="submit" [disabled]="!student.valid">表单验证通过时允许提交</button>HTML5 有一组内置的属性,用来进行原生验证,包括 required、minlength、email、pattern等。是可选的,数组形式可以验证多个。
- 添加逻辑来处理验证状态。----->待掌握 
建立模板驱动表单
指令 NgModel 为指定的表单元素创建并管理一个 FormControl 实例。
import { Component } from '@angular/core';
@Component({
  selector: 'app-template-favorite-color',
  template: 
  `
    Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
  `
})
export class FavoriteColorComponent {
  favoriteColor = '';
}
模板驱动表单的数据流
- 最终用户在输入框元素中敲 "Blue"。 
- 该输入框元素会发出一个 "input" 事件,带着值 "Blue"。 
- 附着在该输入框上的控件值访问器会触发 - FormControl实例上的- setValue()方法。
- FormControl实例通过- valueChanges这个可观察对象发出新值。
- valueChanges的任何订阅者都会收到新值。
- 控件值访问器 - ControlValueAccessory还会调用- NgModel.viewToModelUpdate()方法,它会发出一个- ngModelChange事件。
- 由于该组件模板双向数据绑定到了 - favoriteColor,组件中的- favoriteColor属性就会修改为- ngModelChange事件所发出的值("Blue")。

Angular学习总结
创建项目
- 你使用 Angular CLI 创建了初始的应用结构。
- 你学会了使用 Angular 组件来显示数据。
- 你使用双花括号插值显示了应用标题。
编辑器
- 你使用 CLI 创建了第二个组件 HeroesComponent。
- 你把 HeroesComponent添加到了壳组件AppComponent中,以便显示它。
- 你使用 UppercasePipe来格式化英雄的名字。
- 你用 ngModel指令实现了双向数据绑定。
- 你知道了 AppModule。
- 你把 FormsModule导入了AppModule,以便 Angular 能识别并应用ngModel指令。
- 你知道了把组件声明到 AppModule是很重要的,并认识到 CLI 会自动帮你声明它。
显示列表
- 英雄之旅应用在一个主从视图中显示了英雄列表。
- 用户可以选择一个英雄,并查看该英雄的详情。
- 你使用 *ngFor显示了一个列表。
- 你使用 *ngIf来根据条件包含或排除了一段 HTML。
- 你可以用 class绑定来切换 CSS 的样式类。
特性组件
- 你创建了一个独立的、可复用的 HeroDetailComponent组件。
- 你用属性绑定语法来让父组件 HeroesComponent可以控制子组件HeroDetailComponent。
- 你用 @Input装饰器来让hero属性可以在外部的HeroesComponent中绑定。
添加服务
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。服务通俗理解就是把一一系列的方法组合在一起。
服务是在多个“互相不知道”的类之间共享信息的好办法。
- 你把数据访问逻辑重构到了 HeroService类中。
- 你在根注入器中把 HeroService注册为该服务的提供者,以便在别处可以注入它。
- 你使用 Angular 依赖注入机制把它注入到了组件中。
- 你给 HeroService中获取数据的方法提供了一个异步的函数签名。
- 你发现了 Observable以及 RxJS 库。
- 你使用 RxJS 的 of()方法返回了一个模拟英雄数据的可观察对象 (Observable<Hero[]>)。
- 在组件的 ngOnInit生命周期钩子中调用HeroService方法,而不是构造函数中。
- 你创建了一个 MessageService,以便在类之间实现松耦合通讯。
- 
HeroService连同注入到它的服务MessageService一起,注入到了组件中。
应用内导航
- 添加了 Angular 路由器在各个不同组件之间导航。
- 你使用一些 <a>链接和一个<router-outlet>把AppComponent转换成了一个导航用的壳组件。
- 你在 AppRoutingModule中配置了路由器。
- 你定义了一些简单路由、一个重定向路由和一个参数化路由。
- 你在 <a>元素中使用了routerLink指令。
- 你把一个紧耦合的主从视图重构成了带路由的详情视图。
- 你使用路由链接参数来导航到所选英雄的详情视图。
- 在多个组件之间共享了 HeroService服务。
从服务器上获取数据
- 你添加了在应用程序中使用 HTTP 的必备依赖:import { HttpClientModule } from '@angular/common/http';
- 你重构了 HeroService,以通过 web API 来加载英雄数据。
- 你扩展了 HeroService来支持post()、put()和delete()方法。
- 你修改了组件,以允许用户添加、编辑和删除英雄。
- 你配置了一个内存 Web API。
- 你学会了如何使用“可观察对象”。