深入了解Angular(新手入门指南)

广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买

深入了解Angular(新手入门指南)

本篇文章带大家深入了解Angular,分享最全的Angular新手入门指南,希望对大家有所帮助!

Angular概述

Angular 是谷歌开发的一款开源的 web 前端框架,基于 TypeScript 。【相关教程推荐:《angular教程》】

和 react 与 vue 相比, Angular 更适合中大型企业级项目。

Angular程序架构

Angular优势可伸缩性:基于RxJS 、immutable.js和其他推送模型,能适应海量数据需求跨平台:渐进式应用(高性能、离线使用、免安装),原生(Ionic),桌面端生产率:模版(通过简单而强大的模版语法,快速创建UI视图),CLI(快速进入构建环节、添加组件和测试,然后立即部署)测试:单元测试(支持Karma、Jasmine等工具进行单元测试),端到端测试(支持Protractor等工具进行端到端测试)@angular/cli脚手架

ng new 新建项目

——routing 配置路由——style=css|scss|less 配置css样式

ng serve 启动项目

——port 4200 端口号,默认4200——open 自动打开浏览器

ng build 打包项目

——aot 预编译——prod 压缩打包——base-href=/static/

ng generate 创建模块/组件/服务

module ——routing 创建模块component 创建组件service / 创建服务文件加载顺序

main.ts => app.module.ts => app.component.ts => index.html => app.component.html

项目目录结构
|-- project|-- .editorconfig // 用于在不同编辑器中统一代码风格|-- .gitignore // git中的忽略文件列表|-- README.md // markdown格式的说明文件|-- angular.json // angular的配置文件|-- browserslist // 用于配置浏览器兼容性的文件|-- karma.conf.js // 自动化测试框架Karma的配置文件|-- package-lock.json // 依赖包版本锁定文件|-- package.json // npm的包定义文件|-- tsconfig.app.json // 用于app项目的ts配置文件|-- tsconfig.json // 整个工作区的ts配置文件|-- tsconfig.spec.json // 用于测试的ts配置文件|-- tslint.json // ts的代码静态扫描配置|-- e2e // 自动化集成测试目录|-- src // 源代码目录 |-- src // 源代码目录|-- favicon.ico // 收藏图标|-- index.html // 单页应用到宿主HTML|-- main.ts // 入口 ts 文件|-- polyfills.ts // 用于不同浏览器的兼容脚本加载|-- styles.css // 整个项目的全局css|-- test.ts // 测试入口|-- app // 工程源码目录|-- assets // 资源目录|-- environments // 环境配置|-- environments.prod.ts // 生产环境|-- environments.ts // 开发环境
登录后复制Angular模块

在 app.module.ts 中定义 AppModule,这个根模块会告诉 Angular 如何组装应用。

@NgModule 装饰器

@NgModule 接受一个元数据对象,告诉 Angular 如何编译和启动应用

设计意图

静态的元数据(declarations)运行时的元数据(providers)组合与分组(imports 和 exports)

元数据

declarations 数组:模块拥有的组件、指令或管道,注意每个组件/指令/管道只能在一个模块中声明providers 数组: 模块中需要使用的服务imports 数组:导入本模块需要的依赖模块,注意是模块exports 数组: 暴露给其他模块使用的组件、指令或管道等bootstrap 数组:指定应用的主视图(称为根组件)通过引导根 AppModule 来启动应用,即项目刚加载时选择读哪个组件entryComponents 数组:一般用于动态组件内置模块

常用的有:核心模块、通用模块、表单模块、网络模块等

自定义模块

当项目比较小的时候可以不用自定义模块

但是当项目非常庞大的时候,把所有的组件都挂载到根模块里面就不太合适了

所以可以使用自定义模块来组织项目,并且通过自定义模块可以实现路由的懒加载

模块的tips

导入其他模块时,需要知道使用该模块的目的

如果是组件,那么需要在每一个需要的模块中都进行导入如果是服务,那么一般来说在根模块导入一次即可

需要在每个需要的模块中进行导入的

CommonModule : 提供绑定、*ngIf 和 *ngFor 等基础指令,基本上每个模块都需要导入它FormsModule / ReactiveFormsModule : 表单模块需要在每个需要的模块导入提供组件、指令或管道的模块

只在根模块导入一次的

HttpClientModule / BrowerAnimationsModule NoopAnimationsModule只提供服务的模块Angular组件

组件是 Angular 的核心,是 Angular 应用中最基本的 UI 构造块,控制屏幕上被称为视图的一小片区域组件必须从属于某个 NgModule 才能被其他组件或应用使用组件在 @NgModule 元数据的 declarations 字段中引用@Component 元数据selector :选择器,选择相匹配的HTML里的指令模版templateUrl :将选择器中匹配的指令同级替换成值的模版template :内嵌模版,直接可以在里面写HTML模版styleUrls :对应模版的样式,为一个数组,可以引入多个css样式控制组件encapsulation:组件样式封装策略
@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})
登录后复制数据绑定

数据绑定 {{data}}

属性绑定 [id]="id",其中[class.样式类名]=“判断表达式”是在应用单个class样式时的常用技巧

事件绑定 (keyup)="keyUpFn($event)"

样式绑定可以用 :host 这样一个伪类选择器,绑定的样式作用于组件本身

双向数据绑定 [(ngModel)]

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"
登录后复制脏值检测

脏值检测:当数据改变时更新视图(DOM)

如何进行检测:检测两个状态值(当前状态和新状态)

何时触发脏值检测:浏览器事件(clickmouseoverkeyup等)、setTimeout()setInterval()、HTTP请求

Angular 有两种变更检测策略:DefaultOnPush

可以通过在@Component元数据中设置changeDetection: ChangeDetectionStrategy.OnPush进行切换

Default

优点:每一次有异步事件发生,Angular 都会触发变更检测,从根组件开始遍历其子组件,对每一个组件都进行变更检测,对dom进行更新。

缺点:有很多组件状态没有发生变化,无需进行变更检测。如果应用程序中组件越多,性能问题会越来越明显。

OnPush

优点:组件的变更检测完全依赖于组件的输入(@Input),只要输入值不变就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升。

缺点:必须保证输入(@Input)是不可变的(可以用Immutable.js解决),每一次输入变化都必须是新的引用。

父子组件通讯

父组件给子组件传值 @input

父组件不仅可以给子组件传递简单的数据,还可把自己的方法以及整个父组件传给子组件。

// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>
登录后复制

**子组件触发父组件的方法 @Output **

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}
登录后复制

父组件通过 ViewChild 主动调用子组件DOM和方法

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}
登录后复制投影组件

由于组件过度嵌套会导致数据冗余和事件传递,因此引入投影组件的概念

投影组件 ng-content 作为一个容器组件使用

主要用于组件动态内容的渲染,而这些内容没有复杂的业务逻辑,也不需要重用,只是一小部分 HTML 片段

使用 ng-content 指令将父组件模板中的任意片段投影到它的子组件上

组件里面的 ng-content 部分可以被组件外部包裹的元素替代

// 表现形式: <ng-content select="样式类/HTML标签/指令"></ng-content><ng-content select="[appGridItem]"></ng-content>
登录后复制

select 表明包含 appGridItem 的指令的元素才能投影穿透过来

Angular指令

指令可以理解为没有模版的组件,它需要一个宿主元素(Host)

推荐使用方括号 [] 指定 Selector,使它变成一个属性

@Directive({selector: '[appGridItem]'})
登录后复制
内置属性型指令

NgClass

ngClass 是自由度和拓展性最强的样式绑定方式

<div [ngClass]="{'red': true, 'blue': false}">  这是一个 div</div>
登录后复制

NgStyle

ngStyle由于是嵌入式样式,因此可能会覆盖掉其他样式,需谨慎

<div [ngStyle]="{'background-color':'green'}">你好 ngStyle</div>
登录后复制

NgModel

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})0
登录后复制内置结构型指令

ngIf

ngIf 根据表达式是否成立,决定是否展示 DOM 标签

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})1
登录后复制

ngIf else

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})2
登录后复制

ngFor

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})3
登录后复制

ngSwitch

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})4
登录后复制指令事件样式绑定

@HostBinding 绑定宿主的属性或者样式

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})5
登录后复制

@HostListener 绑定宿主的事件

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})6
登录后复制Angular生命周期

生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法

当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子

constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化

ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges

ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建

ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后

ngAfterContentInit :内容投影ng-content完成时调用,只在第一次 ngDoCheck 之后调用

ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)

ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次

ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)

ngOnDestroy 当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏

Angular路由

路由(导航)本质上是切换视图的一种机制,路由的导航URL并不真实存在

Angular 的路由借鉴了浏览器URL变化导致页面切换的机制

Angular 是单页程序,路由显示的路径不过是一种保存路由状态的机制,这个路径在 web 服务器上不存在

路由基本配置
@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})7
登录后复制激活路由

找到 app.component.html 根组件模板,配置 router-outlet

通过模版属性访问路由,即路由链接 routerLink

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})8
登录后复制

控制路由激活状态的样式 routerLinkActive

@Component({  selector: 'app-xxx',  templateUrl: 'XXX',  styleUrls: ['XXX'],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})9
登录后复制路由参数

路径参数读取

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"0
登录后复制

查询参数读取

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"1
登录后复制

路由传递一个参数及其接收方法:

传递参数:path:’info/:id’

接收参数:

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"2
登录后复制

路由传递多个参数及其接收方法:

传递:[queryParams]=‘{id:1,name:‘crm’}’

接收参数:

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"3
登录后复制路由懒加载

懒加载子模块,子模块需要配置路由设置启动子模块 loadChildren

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"4
登录后复制Angular服务

组件不应该直接获取或保存数据,应该聚焦于展示数据,而把数据访问的职责委托给某个服务

获取数据和视图展示应该相分离,获取数据的方法应该放在服务中

类似 VueX,全局的共享数据(通用数据)及非父子组件传值、共享数据放在服务中

组件之间相互调用各组件里定义的方法

多个组件都用的方法(例如数据缓存的方法)放在服务(service)里

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"5
登录后复制@Injectable()装饰器

在 Angular 中,要把一个类定义为服务,就要用 @Injectable() 装饰器来提供元数据,以便让 Angular 把它作为依赖注入到组件中。

同样,也要使用 @Injectable () 装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。

@Injectable () 装饰器把这个服务类标记为依赖注入系统的参与者之一,它是每个 Angular 服务定义中的基本要素。

在未配置好 Angular 的依赖注入器时,Angular 实际上无法将它注入到任何位置。

@Injectable () 装饰器具有一个名叫 providedIn 的元数据选项,providedIn 设置为 'root',即根组件中,那么该服务就可以在整个应用程序中使用了。

providedIn 提供这些值:‘root''platform''any'null

对于要用到的任何服务,必须至少注册一个提供者。

服务可以在自己的元数据中把自己注册为提供者,可以让自己随处可用,也可以为特定的模块或组件注册提供者。

要注册提供者,就要在服务的 @Injectable () 装饰器中提供它的元数据,或者在 @NgModule ()@Component () 的元数据中。

在组件中提供服务时,还可以使用 viewProdiversviewProviders 对子组件树不可见

可以使用不同层级的提供者来配置注入器,也表示该服务的作用范围

Angular 创建服务默认采用的方式:在服务本身的 @Injectable () 装饰器中

该服务只在某服务中使用:在 NgModule 的 @NgModule () 装饰器中

该服务在某组件中使用:在组件的 @Component () 装饰器中

依赖注入

在项目中,有人提供服务,有人消耗服务,而依赖注入的机制提供了中间的接口,并替消费者创建并初始化处理

消费者只需要知道拿到的是完整可用的服务就好,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。

Angular 通过 service共享状态,而这些管理状态和数据的服务便是通过依赖注入的方式进行处理的

Angular 的 service 的本质就是依赖注入,将service作为一个Injector注入到component

归根到底,很多时候我们创建服务,是为了维护公用的状态和数据,通过依赖注入的方式来规定哪些组件可共享

正是因为 Angular 提供的这种依赖注入机制,才能在构造函数中直接声明实例化

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"6
登录后复制

先看一下 Angular 中 TS 单文件的注入

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"7
登录后复制

再看一下Angular 中 module 模块的注入

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"8
登录后复制

不管是在组件内还是在模块内,我们使用 providers 的时候,就是进行了一次依赖注入的注册和初始化

其实模块类(NgModule)也和组件一样,在依赖注入中是一个注入器,作为容器提供依赖注入的接口

NgModule 使我们不需要在一个组件中注入另一个组件,通过模块类(NgModule)可以进行获取和共享

Angular 管道

Angular 管道是编写可以在 HTML 组件中声明的显示值转换的方法

管道将数据作为输入并将其转换为所需的输出

管道其实就是过滤器,用来转换数据然后显示给用户

管道将整数、字符串、数组和日期作为输入,用 | 分隔,然后根据需要转换格式,并在浏览器中显示出来

在插值表达式中,可以定义管道并根据情况使用

Angular 应用程序中可以使用许多类型的管道

内置管道String -> String UpperCasePipe 转换成大写字符LowerCasePipe 转换成小写字符TitleCasePipe 转换成标题形式,第一个字母大写,其余小写Number -> String DecimalPipe 根据数字选项和区域设置规则格式化值PercentPipe 将数字转换为百分比字符串CurrencyPipe 改变人名币格式Object -> String JsonPipe 对象序列化DatePipe 日期格式转换Tools SlicePipe 字符串截取AsyncPipe 从异步回执中解出一个值I18nPluralPipe 复数化I18nSelectPipe 显示与当前值匹配的字符串

使用方法

// 注意引入:FormsModuleimport { FormsModule } from '@angular/forms';<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"9
登录后复制自定义管道

管道本质上就是个类,在这个类里面去实现 PipeTransfrom 接口的 transform 这个方法

使用 @Pipe 装饰器定义 Pipemetadata 信息,如 Pipe 的名称 - 即 name 属性实现 PipeTransform 接口中定义的 transform 方法
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>0
登录后复制Angular操作DOM原生JS操作
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>1
登录后复制ElementRef

ElementRef 是对视图中某个原生元素的包装类

因为 DOM 元素不是 Angular 中的类,所以需要一个包装类以便在 Angular 中使用和标识其类型

ElementRef 的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素

// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>2
登录后复制

当需要直接访问 DOM 时,请把本 API 作为最后选择 。优先使用 Angular 提供的模板和数据绑定机制

如果依赖直接访问 DOM 的方式,就可能在应用和渲染层之间产生紧耦合。这将导致无法分开两者,也就无法将应用发布到 Web Worker 中

ViewChild

使用模板和数据绑定机制,使用 @viewChild

// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>3
登录后复制

父组件中可以通过 ViewChild 调用子组件的方法

// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>4
登录后复制

引用多个模版元素,可以用@ViewChildren,在ViewChildren中可以使用引用名

或者使用 Angular 组件/指令的类型,声明类型为 QueryList<?>

// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>5
登录后复制Renderer2

Renderer2 是 Angular 提供的操作 element 的抽象类,使用该类提供的方法,能够实现在不直接接触 DOM 的情况下操作页面上的元素。

Renderer2 的常用方法:

addClass /removeClassdirective 的宿主元素添加或删除 class
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>6
登录后复制createElement /appendChild/createText 创建 DIV 元素,插入文本内容,并将其挂载到宿主元素上
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>7
登录后复制setAttribute /removeAttribute 在宿主元素上添加或删除 attribute
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>8
登录后复制setStyle /removeStyle 在宿主元素上添加 inline-style
// 父组件调用子组件的时候传入数据<app-header [msg]="msg"></app-header>// 子组件引入 Input 模块import { Component, OnInit ,Input } from '@angular/core';// 子组件中 @Input 装饰器接收父组件传过来的数据export class HeaderComponent implements OnInit {  @Input() msg:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据<h2>这是头部组件--{{msg}}</h2>9
登录后复制

移除 inline-style :

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}0
登录后复制setProperty 设置宿主元素的 property 的值
// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}1
登录后复制

直接操作DOM,Angular不推荐。尽量采用 @viewChildrenderer2 组合,Angular推荐使用 constructor(private rd2: Renderer2) {} 依赖注入,

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}2
登录后复制Angular网络请求HttpClient

需导入 HttpClientModule ,只在根模块中导入,并且整个应用只需导入一次,不用在其他模块导入

在构造函数中注入HttpClientget/post方法对应HTTP方法,这些方法是泛型的,可以直接把返回的JSON转换成对应类型。若是不规范的请求,使用request方法

返回的值是 Observable,必须订阅才会发送请求,否则不会发送

get 请求数据

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}3
登录后复制

post 提交数据

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}4
登录后复制

Jsonp请求数据

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}5
登录后复制拦截器

Angular 拦截器是 Angular 应用中全局捕获和修改 HTTP 请求和响应的方式,例如携带 Token 和捕获 Error

前提是只能拦截使用 HttpClientModule 发出的请求,如果使用 axios 则拦截不到

创建拦截器

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}6
登录后复制

注入拦截器

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}7
登录后复制

请求头拦截

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}8
登录后复制

响应捕获

// 子组件引入 Output 和 EventEmitterimport { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';// 子组件中实例化 EventEmitter// 用 EventEmitter 和 @Output 装饰器配合使用 <string> 指定类型变量@Output() private outer=new EventEmitter<string>();// 子组件通过 EventEmitter 对象 outer 实例广播数据sendParent(){  this.outer.emit('msg from child')}// 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer<app-header (outer)="runParent($event)"></app-header>// 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据// 接收子组件传递过来的数据  runParent(msg:string){   alert(msg);}8
登录后复制

如果有多个拦截器,请求顺序是按照配置顺序执行,响应拦截则是相反的顺序

如果提供拦截器的顺序是先 A再 B再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A

Angular表单模版驱动表单

模板驱动表单在往应用中添加简单的表单时非常有用,但是不像响应式表单那么容易扩展

如果有非常基本的表单需求和简单到能用模板管理的逻辑,就使用模板驱动表单

响应式表单和模板驱动表单共享了一些底层构造块:

FormControl 实例用于追踪单个表单控件的值和验证状态

FormGroup 用于追踪一个表单控件组的值和状态

FormArray 用于追踪表单控件数组的值和状态,有长度属性,通常用来代表一个可以增长的字段集合

ControlValueAccessor 用于在 Angular 的 FormControl 实例和原生 DOM 元素之间创建一个桥梁

FormControlFormGroup 是 angular 中两个最基本的表单对象

FormControl 代表单一的输入字段,它是 Angular 表单中最小单员,它封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错误等

FormGroup 可以为一组 FormControl 提供总包接口(wrapper interface),来管理多个 FormControl

当我们试图从 FormGroup 中获取 value 时,会收到一个 “键值对” 结构的对象

它能让我们从表单中一次性获取全部的值而无需逐一遍历 FormControl,使用起来相当顺手

FormGroupFormControl 都继承自同一个祖先 AbstractControltractControl(这是 FormControlFormGroupFormArray 的基类)

首先加载 FormsModule

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}0
登录后复制

接下来创建一个模版表单

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}1
登录后复制

我们导入了 FormsModule,因此可以在视图中使用 NgForm

当这些指令在视图中可用时,它就会被附加到任何能匹配其 selector 的节点上

NgForm 做了一件便利但隐晦的工作:它的选择器包含 form 标签(而不用显式添加 ngForm 属性)

这意味着当导入 FormsModule 时候,NgForm 就会被自动附加到视图中所有的标签上

NgForm 提供了两个重要的功能:

一个 ngFormFormGroup 对象一个输出事件 (ngSubmit)
// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}2
登录后复制

NgModel 会创建一个新的 FormControl 对象,把它自动添加到父 FormGroup 上(这里也就是 form 表单对象)

并把这个 FormControl 对象绑定到一个 DOM 上

也就是说,它会在视图中的 input 标签和 FormControl 对象之间建立关联

这种关联是通过 name 属性建立的,在本例中是 "name"

响应式表单

使用 ngForm 构建 FormControlFormGroup 很方便,但是无法提供定制化选项,因此引入响应式表单

响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化

使用响应式表单时,通过编写 TypeScript 代码而不是 HTML 代码来创建一个底层的数据模型

在这个模型定义好以后,使用一些特定的指令将模板上的 HTML 元素与底层的数据模型连接在一起

FormBuilder 是一个名副其实的表单构建助手(可以把他看作一个 “工厂” 对象)

在先前的例子中添加一个 FormBuilder,然后在组件定义类中使用 FormGroup

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}3
登录后复制

在视图表单中使用自定义的 FormGroup

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}4
登录后复制

记住以下两点:

如果想隐式创建新的 FormGroup 和 FormControl,使用:ngForm、ngModel如果要绑定一个现有的 FormGroup 和 FormControl,使用:formGroup、formControl
表单验证

用户输入的数据格式并不总是正确的,如果有人输入错误的数据格式,我们希望给他反馈并阻止他提交表单

因此,我们要用到验证器,由 validators 模块提供

Validators.required 是最简单的验证,表明指定的字段是必填项,否则就认为 FormControl 是无效的

如果 FormGroup 中有一个 FormControl 是无效的, 那整个 FormGroup 都是无效的

要为 FormControl 对象分配一个验证器 ,可以直接把它作为第二个参数传给 FormControl 的构造函数

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}5
登录后复制

在视图中检查验证器的状态,并据此采取行动

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}6
登录后复制

内置校验器

Angular 提供了几个内置校验器,下面是比较常用的校验器:

Validators.required - 表单控件值非空Validators.email - 表单控件值的格式是 emailValidators.minLength() - 表单控件值的最小长度Validators.maxLength() - 表单控件值的最大长度Validators.pattern() - 表单控件的值需匹配 pattern 对应的模式(正则表达式)

自定义验证器

假设我们的 name 有特殊的验证需求,比如 name 必须以 123 作为开始

当输入值(控件的值 control.value)不是以 123 作为开始时,验证器会返回错误代码 invalidSku

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}7
登录后复制

FormControl 分配验证器,但是 name 已经有一个验证器了,如何在同一个字段上添加多个验证器

Validators.compose 来实现

Validators.compose 把两个验证器包装在一起,我们可以将其赋值给 FormControl

只有当两个验证器都合法时,FormControl 才是合法的

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}8
登录后复制动态表单

要实现 Angular 动态表单,主要使用 formArray 方法,formArray 生成的实例是一个数组,在这个数组中可以动态的放入 formGroupformControl,这样便形成了动态表单。

// 给子组件定义一个名称<app-footer #footerChild></app-footer>// 引入 ViewChildimport { Component, OnInit ,ViewChild} from '@angular/core';// ViewChild 和子组件关联起来@ViewChild('footerChild') footer;// 调用子组件run(){   this.footer.footerRun();}9
登录后复制
// 表现形式: <ng-content select="样式类/HTML标签/指令"></ng-content><ng-content select="[appGridItem]"></ng-content>0
登录后复制Angular CDK

CDK 是 Component Dev kit 的简称,是 Angular Material 团队在开发 Library 时发现组件有很多相似的地方,最后进行了抽取,提炼出了公共的逻辑,这部分即是 CDK

官方用了一个很形象的比喻:如果组件库是火箭飞船,那么 CDK 就是发动机零件盒

更多编程相关知识,请访问:编程入门!!

以上就是深入了解Angular(新手入门指南)的详细内容,更多请关注9543建站博客其它相关文章!

广告:SSL证书一年128.66元起,点击购买~~~

9543建站博客
一个专注于网站开发、微信开发的技术类纯净博客。

作者头像
admin创始人

肥猫,知名SEO博客站长,14年SEO经验。

上一篇:如何使用HTML创建表单的发送
下一篇:解决uni-app入坑集合的一种方案(分享)

发表评论

关闭广告
关闭广告