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。
- 你学会了如何使用“可观察对象”。