广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买
本篇文章带大家深入了解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)
如何进行检测:检测两个状态值(当前状态和新状态)
何时触发脏值检测:浏览器事件(click
、mouseover
、keyup
等)、setTimeout()
或setInterval()
、HTTP请求
Angular 有两种变更检测策略:Default
和 OnPush
可以通过在@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
的指令的元素才能投影穿透过来
内置属性型指令指令可以理解为没有模版的组件,它需要一个宿主元素(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 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子
Angular路由constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化
ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges
ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建
ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后
ngAfterContentInit :内容投影ng-content完成时调用,只在第一次 ngDoCheck 之后调用
ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)
ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次
ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)
ngOnDestroy 当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏
路由基本配置路由(导航)本质上是切换视图的一种机制,路由的导航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 ()
的元数据中。
在组件中提供服务时,还可以使用 viewProdivers
,viewProviders
对子组件树不可见
依赖注入可以使用不同层级的提供者来配置注入器,也表示该服务的作用范围
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登录后复制
Angular 管道不管是在组件内还是在模块内,我们使用
providers
的时候,就是进行了一次依赖注入的注册和初始化其实模块类(
NgModule
)也和组件一样,在依赖注入中是一个注入器,作为容器提供依赖注入的接口
NgModule
使我们不需要在一个组件中注入另一个组件,通过模块类(NgModule
)可以进行获取和共享
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
装饰器定义 Pipe
的 metadata
信息,如 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
/removeClass
在 directive
的宿主元素添加或删除 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
不推荐。尽量采用@viewChild
和renderer2
组合,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
,只在根模块中导入,并且整个应用只需导入一次,不用在其他模块导入
在构造函数中注入HttpClient
,get/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登录后复制
Angular表单模版驱动表单如果有多个拦截器,请求顺序是按照配置顺序执行,响应拦截则是相反的顺序
如果提供拦截器的顺序是先 A再 B再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A
模板驱动表单在往应用中添加简单的表单时非常有用,但是不像响应式表单那么容易扩展
如果有非常基本的表单需求和简单到能用模板管理的逻辑,就使用模板驱动表单
响应式表单和模板驱动表单共享了一些底层构造块:
FormControl
实例用于追踪单个表单控件的值和验证状态
FormGroup
用于追踪一个表单控件组的值和状态
FormArray
用于追踪表单控件数组的值和状态,有长度属性,通常用来代表一个可以增长的字段集合
ControlValueAccessor
用于在 Angular 的FormControl
实例和原生 DOM 元素之间创建一个桥梁
FormControl
和 FormGroup
是 angular 中两个最基本的表单对象
FormControl
代表单一的输入字段,它是 Angular 表单中最小单员,它封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错误等
FormGroup
可以为一组 FormControl
提供总包接口(wrapper interface),来管理多个 FormControl
当我们试图从 FormGroup
中获取 value 时,会收到一个 “键值对” 结构的对象
它能让我们从表单中一次性获取全部的值而无需逐一遍历 FormControl
,使用起来相当顺手
FormGroup
和 FormControl
都继承自同一个祖先 AbstractControltractControl
(这是 FormControl
,FormGroup
和 FormArray
的基类)
首先加载 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
提供了两个重要的功能:
ngForm
的 FormGroup
对象一个输出事件 (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
构建 FormControl
和 FormGroup
很方便,但是无法提供定制化选项,因此引入响应式表单
响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化
使用响应式表单时,通过编写 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
生成的实例是一个数组,在这个数组中可以动态的放入 formGroup
和 formControl
,这样便形成了动态表单。
// 给子组件定义一个名称<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建站博客其它相关文章!
发表评论