Angular 学习笔记
- 环境搭建
- 项目搭建和目录分析
- app.module.ts 和 自定义组件
-
- app.moudule.ts
- 自定义组件
- 指令
- 服务
- 依赖注入
- Angular 中操作DOM、父组件调用子组件方法
- 父子组件通信
- Angular 中的生命周期函数
- Rxjs 异步数据流编程-Rxjs 快速入门教程
- Angular中的数据交互
- 路由
环境搭建
- 安装npm,通过npm或cnpm安装angular。
- npm install -g @angualr/cli 或 npm install -g @angualr/cli
项目搭建和目录分析
- 创建项目:
ng new demo(项目名称)
- 项目目录结构:
参考链接
- 运行本地服务器:
cd demo
ng serve --open
app.module.ts 和 自定义组件
app.moudule.ts
定义 AppModule,这个根模块会告诉 Angular 如何组装该应用。 目前,它只声明了 AppComponent。 稍后它还会声明更多组件。
自定义组件
通过 ng help
命令查看ng支持的命令:
创建组件
ng generate component components/news
ng g c components/header # 缩写形式
一般组件创建再comments目录下,所以创建自定义组件时,指定目录components
组件定义:
数据绑定 — { {}}
Angular 中使用{
{}}绑定组件里面定义的数据:
属性绑定 — [属性名]
组件的属性通过 [属性名] 动态绑定:
数据循环 — *ngFor
- 普通循环:
- 带下标循环:
- 指令自带的其他变量:
条件判断 *ngIf
*ngIf 根据表达式是否成立,决定是否展示DOM标签。
*ngIf else指令
结构性指令都依赖于ng-template,*ngIf实际上就是ng-template指令的[ngIf]属性。
条件判断 *ngSwitch
绑定事件
表单双向数据绑定
- 在根模块中引入form模块:
- 通过 [(ngModel)] 双向绑定数据:
[ngClass]、[ngStyle]
模板引用变量
模板变量可以帮助你在模板的另一部分使用这个部分的数据。
使用模板变量,你可以执行某些任务,比如响应用户输入或微调应用的表单。
- 普通html元素引用:
- 组件引用:
管道
类似于AngularJS中的过滤器。
官方文档
常用的内置管道
AsyncPipe – 自动订阅模板中的Observable或Promise
DecimalPipe – 数字转字符串,并可以指定格式
JsonPipe – 将值转成json
TitleCasePipe – 把首字母大写,其它小写
SlicePipe – 截取Array或String
PercentPipe – 数字转百分比
LowerCasePipe和UpperCasePipe – 转化小写或大写
DatePipe – 格式化日期
KeyValuePipe – 使ngFor可以循环Object或Map对象
其他管道: http://bbs.itying.com/topic/5bf519657e9f5911d41f2a34
自定义管道
创建管道: ng g p pipes/exponential-strength
非纯管道
通过默认情况下,管道会定义成纯的(pure),这样 Angular 只有在检测到输入值发生了纯变更时才会执行该管道。
纯变更是指原始输入值(比如 String、Number、Boolean 或 Symbol )的变更,或是引用对象的变更(比如 Date、Array、Function、Object)。
使用纯管道,Angular 会忽略复合对象中的变化,例如往现有数组中新增的元素,因为检查原始值或对象引用比对对象中的差异进行深度检查要快得多。Angular 可以快速判断是否可以跳过执行该管道并更新视图。
问题复现
如下示例:
- 组件类中定义heroes,初始时只有两个英雄,其中canFly分别为false和true;
- addHero 提供动态添加英雄的能力,新增的英雄都是 canFly: true;
可以正常新增英雄:
创建管道过滤会飞的英雄:ng g p pipes/flying-heroes
import {
Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'flyingHeroes'
})
export class FlyingHeroesPipe implements PipeTransform {
transform(value) {
return value.filter(item => item.canFly);
}
}
调用管道过滤会飞的英雄:
<!-- 监听回车键调用addHero方法添加英雄 -->
<input type="text" #box (keyup.enter)="addHero(box.value)" placeholder="hero name" />
<!-- 调用管道过滤会飞的英雄 -->
<div *ngFor="let hero of (heroes | flyingHeroes)">
{
{hero.name}}
</div>
这时再出发添加英雄,由于heroes是列表,添加元素并不会引起引用对象的改变,因此管道检测不到变更,页面不会显示新增的会飞英雄:
解决办法
- addHero() 添加数据时重新赋值,不使用push方法:
- 设置管道为非纯的:
**注意:**虽然非纯管道很实用,但要小心使用。长时间运行非纯管道可能会大大降低你的应用速度。
组件安全导航
组件初始加载时hero为undefined,如果在html中获取hero.name会报错。 这里使用hero?.name可以保证页面正常渲染而不报错,数据加载后会显示出正确内容。
组件内容投影
官方文档
内容投影是一种模式,你可以在其中插入或投影要在另一个组件中使用的内容。例如,你可能有一个 Card 组件,它可以接受另一个组件提供的内容。
单插槽内容投影
在组件模板中通过 ng-content 设置插槽,ng-content 元素只是一个占位符,不会创建真正的 DOM 元素。
多插槽内容投影
一个组件可以具有多个插槽。每个插槽可以指定一个选择器,该选择器会决定将哪些内容放入该插槽。
使用此模式,必须指定希望投影内容出现在的位置。可以通过使用 ng-content 的 select 属性来完成此任务。
将 select 属性添加到 ng-content 元素。 Angular 使用的选择器支持标签名、属性、CSS 类和 :not 伪类的任意组合。
ContentChild
官方文档
功能类似@ViewChild,用于获取外部投影到组件中的标签内容;
- 自定义shadow组件:
// shadow.component.ts
import {
AfterViewInit, Component, ContentChild, ElementRef, OnInit, ViewChild } from '@angular/core';
@Component({
selector: 'app-shadow',
templateUrl: './shadow.component.html',
styleUrls: ['./shadow.component.css']
})
export class ShadowComponent implements OnInit, AfterViewInit {
// 通过 ContentChild 获取外部投影到组件中的内容
@ContentChild('h3') h3El: ElementRef;
constructor() {
}
ngOnInit(): void {
}
ngAfterViewInit(): void {
console.log('h3El', this.h3El);
}
}
<!-- shadow.component.html -->
<div>
<h3>这是shadow组件自己的内容</h3>
<ng-content></ng-content>
</div>
<!-- app.component.html -->
<app-shadow>
<h3 #h3>这是投影到ng-content插槽中的内容</h3>
</app-shadow>
ContentChildren
官方文档
功能类似@ViewChildren,用于获取外部投影到组件中的获取元素或指令的 QueryList;
每当添加、删除或移动子元素时,此查询列表都会更新,并且其可观察对象 changes 都会发出新值。
有条件的内容投影—ngTemplateOutlet
如果组件需要有条件地渲染内容、多次渲染内容、默认内容,则应该使用 ngTemplateOutlet 指令。
在这种情况下,不建议使用 ng-content 元素,因为只要组件的使用者提供了内容,即使该组件从未定义 ng-content 元素或该 ng-content 元素位于 ngIf 语句的内部,该内容也总会被初始化。
结构型指令,根据一个提前备好的 TemplateRef 插入一个内嵌视图。
用途:自定义组件,由外部传入一段模板用于显示。
基本用法
- 创建tpl-outlet组件,定义@Input() render参数用于接收外部传入的模板 templateRef;
- 组件html中使用< ng-container> 定义插入外部模板的位置,[ngTemplateOutlet]=“render || defaultTpl” 插入外部传入的模板或组件默认模板;
- 外部组件调用< app-tpl-outlet>组件并传入自定义的模板;
// tpl-outlet.component.ts
import {
Component, Input, OnInit, TemplateRef } from '@angular/core';
@Component({
selector: 'app-tpl-outlet',
templateUrl: './tpl-outlet.component.html',
styleUrls: ['./tpl-outlet.component.css']
})
export class TplOutletComponent implements OnInit {
// 定义render参数用于接收外部传入的templateRef
@Input() render: TemplateRef<any>;
constructor() {
}
ngOnInit(): void {
}
}
<!-- tpl-outlet.component.html -->
<div>
<h3>tpl-outlet works!</h3>
<!-- 条件控制是否显示外部传入的内容 -->
<div *ngIf="true">
<!-- 使用外部传入的templateRef 或 默认templateRef -->
<ng-container
[ngTemplateOutlet]="render || defaultTpl">
</ng-container>
</div>
</div>
<!-- 自定义组件默认templateRef -->
<ng-template #defaultTpl>
这是组件默认内容
</ng-template>
<!-- app.component.html -->
<app-tpl-outlet [render]="parentTpl"></app-tpl-outlet>
<!-- 外部使用组件时需要传入定义的templateRef -->
<ng-template #parentTpl>
这是外部传入的内容
</ng-template>
上下文context
- 自定义组件内部创建上下文对象myContext;赋值给[ngTemplateOutletContext];
- 外部调用组件 或 组件内部 可以使用let语法获取组件的上下文参数;
结构型指令简写方式
因为ngTemplateOutlet是结构型指令,所以也可以使用 * 语法简写:
使用ng-template
ngTemplateOutlet 指令也可以用在ng-template上,效果和ng-container一样,两者都是起到占位符承载 ngTemplateOutlet 指令的作用。
使用TemplateRef 和 ViewContainerRef实现 ngTemplateOutlet 功能
- 组件模板中定义 #test ng-container 作为视图容器;
- 组件类中通过ViewChild 获取 test 视图容器;
- 在ngOnInit中将外部传入的 templateRef视图 插入视图容器中;
组件/指令生命周期
当 Angular 实例化组件类并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。当 Angular 在执行过程中创建、更新和销毁实例时,指令就有了类似的生命周期。
ngOnChanges
输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges()。
- 创建组件life-cycle,设置输入属性title;分别在 constructor、ngOnChanges、ngOnInit 中打印 this.title;
- 外部调用时设置输入属性title = “input title”;
- 组件初始化时由于 类构造函数constructor时最早执行的,这时候还没有处理title属性,因此构造函数constructor中的this.title = undefined;
- ngOnChanges 在接收到输入属性title 由 undefined —> ‘input title’ 时触发,这里可以拿到 this.title 是一个 SimpleChanges对象;
- ngOnInit 钩子函数在ngOnChanges 之后执行,可以拿到this.title = “input title”;
- 组件加载完成后,通过点击button重新设置title属性,这属于组件内部修改title属性,不会触发ngOnChanges 钩子函数,也就是说ngOnChanges 只在外部输入属性变化时触发;
ngOnInit
只在组件/指令初始化调用一次,在它之前调用的顺序是 constructor -> ngOnChanges -> ngOnInit 官方建议在这个钩子中获取组件初始化的数据,而构造函数中只适合写简单的逻辑,比如初始化局部变量。
ngOnDestroy
每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。
<button class="btn btn-primary" (click)="show = !show">toggle</button>
<app-life-cycle title="aaa" *ngIf="show"></app-life-cycle>
所有钩子函数的触发顺序
import {
Component, Input, OnInit, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-life-cycle',
templateUrl: './life-cycle.component.html',
styleUrls: ['./life-cycle.component.css']
})
export class LifeCycleComponent implements OnInit {
constructor() {
console.log('constructor');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges');
}
ngOnInit(): void {
console.log('ngOnInit');
}
ngDoCheck(): void {
console.log('ngDoCheck');
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit')