Angular

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 即可正常执行。

image-20201215172749753.png

创建组件

ng generate compoent <cpn-name>

创建成功会生成一个文件夹里面有四个文件:

image-20201216105142240.png

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

image-20201216105256798.png

也可以用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() 允许子组件向父组件发送数据。

image-20201224102514538.png
@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、赋值
}

过程:

  1. 子组件使用 @Input() 装饰器装饰一属性item
  2. 在父组件中使用子模板<app-child>,并把子组件的item绑定到父组件的currentItem上:[item]="currentItem"
  3. 父组件为currentItem指定一个值。

最终结果是currentItem的值通过@Input传递给了子组件的item。

另外,要想监视 @Input() 属性的变化,你可以使用 Angular 的生命周期钩子OnChanges

具体代码:

image-20201218160816706.png
@Output

要使用 @Output() 装饰器,首先要导入 OutputEventEmitter,然后用 @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);
  }
}

过程:

  1. 子组件使用@Output() myEvent = new EventEmitter<string>()自定义一个事件myEvent。
  2. 通过已有的事件(click,focus等)触发myEvent.emit(value),把事件连同参数发送给父组件。
  3. 事件绑定 (myEvent)='fun($event)' 会把子组件中的 myEvent 事件连接到父组件的 fun() 方法,$event就是携带的参数。

最终结果是:子组件输入的内容会添加到父组件的ul上。

具体代码:

image-20201218171637537.png
总结

@Input是子组件用来接收父组件传来的数据的,@Output是子组件用来给父组件发送数据的,并且需要通过事件触发。

组件交互

通过输入型绑定把数据从父组件传到子组件

代码逻辑:

在子组件标签上使用数据绑定[h]=hero[m]="master"

子组件通过@Input() h:object@Input() m:string接收父组件传来的数据

使用h和m。

image-20201222151047078.png

效果:
image-20201222151115821.png
父组件监听子组件的事件

代码逻辑:

子组件通过@Output() 自定义事件 = new EventEmitter<boolean>()搞一个自定义事件voted

通过voted.emit(参数)把参数和事件发送出去

父组件通过触发自定义事件(voted)="onVoted($event)"获取参数,$event就是参数(可省略)

image-20201222165411441.png

效果:
image-20201222165904780.png

双向绑定

组件间数据的双向绑定

原理:

1、为了使双向数据绑定有效,@Output() 属性的名字必须遵循一个规则,如果 @Input() 属性为 size ,则 @Output() 属性必须为 sizeChange

2、子组件具有值属性 size 和自定义事件属性 sizeChangesize 属性是 @Input()可以接收父组件的数据 。 sizeChange 事件是一个 @Output()发送数据到父组件。

3、接下来,有两个方法, dec() 用于减小字体大小, inc() 用于增大字体大小。这两种方法使用 resize() 在最小/最大值的约束内更改 size 属性的值,并发出带有新 size 值的事件。

image-20201223174020146.png
<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 中有三种类型的指令:

  1. 组件 — 拥有模板的指令
  2. 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
  3. 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。

内置属性型指令

属性型指令会监听并修改其它 HTML 元素和组件的行为、Attribute 和 Property。 它们通常被应用在元素上,就好像它们是 HTML 属性一样,因此得名属性型指令。

许多 NgModule(例如 RouterModuleFormsModule都定义了自己的属性型指令。最常见的属性型指令如下:

  • 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 实际上是三个协作指令的集合: NgSwitchNgSwitchCaseNgSwitchDefault,如以下范例所示。

<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传值
image-20201228100446863.png
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,比如你已经看过的 NgIfngIf。 这里的原因在于,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被激活。

image-20210112120009052.png

一个路由表

image-20210113170210654.png

四种路由传递数据的方式

image-20210112181702433.png

接收参数:都需要引入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 路由参数中除了熟悉的 pathcomponent 外,还包括四种是否允许路由激活与离开的属性。

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 元素之间创建一个桥梁。

响应式表单

基本使用

使用表单控件有三个步骤。

  1. 在你的应用中注册响应式表单模块(从 @angular/forms 包中导入 ReactiveFormsModule,并把它添加到你的 NgModule 的 imports 数组中)。该模块声明了一些你要用在响应式表单中的指令。
  2. 生成一个新的 FormControl 实例,并把它保存在组件中。
  3. 在模板中注册这个 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('');//这个字符串就是响应式表单的值
}
响应式表单的数据流
image-20201231170422024.png

使用这种模板绑定语法,把该表单控件注册给了模板中名为 name 的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化。

或者使用 value 属性获取表单值。

在这个例子中,只使用单个控件,但是当调用 FormGroup 或 FormArray实例的 setValue() 方法时,传入的值就必须匹配控件组或控件数组的结构才行。

FormGroup进行分组
image-20210104102429955.png

[formGroup]为访问此表单的对象名,formGroupName为嵌套结构的对象名

使用 FormBuilder 服务生成控件

通过下列步骤可以利用这项服务。

  1. 导入 FormBuilder 类。import { FormBuilder } from '@angular/forms';

  2. 注入这个 FormBuilder 服务。constructor(private fb: FormBuilder) { }

  3. 生成表单内容。

    profileForm = this.fb.group({
      firstName: [''],
      lastName: [''],
      address: this.fb.group({
        street: [''],
        city: [''],
        state: [''],
        zip: ['']
      })
    });
    
验证表单输入

使用下列步骤添加表单验证。

  1. 在表单组件中导入一个验证器函数。import { Validators } from '@angular/forms';

  2. 把这个验证器添加到表单中的相应字段。

    profileForm = this.fb.group({
      firstName: ['', [Validators.required]];
    });
    
    <button type="submit" [disabled]="!student.valid">表单验证通过时允许提交</button>
    

    HTML5 有一组内置的属性,用来进行原生验证,包括 requiredminlengthemailpattern等。是可选的,数组形式可以验证多个。

  3. 添加逻辑来处理验证状态。----->待掌握

建立模板驱动表单

指令 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 = '';
}
模板驱动表单的数据流
  1. 最终用户在输入框元素中敲 "Blue"。

  2. 该输入框元素会发出一个 "input" 事件,带着值 "Blue"。

  3. 附着在该输入框上的控件值访问器会触发 FormControl 实例上的 setValue() 方法。

  4. FormControl 实例通过 valueChanges 这个可观察对象发出新值。

  5. valueChanges 的任何订阅者都会收到新值。

  6. 控件值访问器 ControlValueAccessory 还会调用 NgModel.viewToModelUpdate() 方法,它会发出一个 ngModelChange 事件。

  7. 由于该组件模板双向数据绑定到了 favoriteColor,组件中的 favoriteColor 属性就会修改为 ngModelChange 事件所发出的值("Blue")。

image-20210104095000480.png

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。
  • 你学会了如何使用“可观察对象”。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容