Angular 学习笔记

Angular 学习笔记

  • 环境搭建
  • 项目搭建和目录分析
  • app.module.ts 和 自定义组件
    • app.moudule.ts
    • 自定义组件
      • 创建组件
      • 数据绑定 --- {{}}
      • 属性绑定 --- [属性名]
      • 数据循环 --- *ngFor
      • 条件判断 *ngIf
      • 条件判断 *ngSwitch
      • 绑定事件
      • 表单双向数据绑定
      • [ngClass]、[ngStyle]
      • 模板引用变量
      • 管道
        • 常用的内置管道
        • 自定义管道
        • 非纯管道
          • 问题复现
          • 解决办法
      • 组件安全导航
      • 组件内容投影
        • 单插槽内容投影
        • 多插槽内容投影
        • ContentChild
        • ContentChildren
      • 有条件的内容投影---ngTemplateOutlet
        • 基本用法
        • 上下文context
        • 结构型指令简写方式
        • 使用ng-template
        • 使用TemplateRef 和 ViewContainerRef实现 ngTemplateOutlet 功能
      • 组件/指令生命周期
        • ngOnChanges
        • ngOnInit
        • ngOnDestroy
        • 所有钩子函数的触发顺序
      • 变更检测 change detection
        • 触发变更检测的时机
        • 单向数据流
      • onPush策略下触发变更检测的时机
        • 手动触发变更检测
      • 组件样式
        • 宿主选择器
        • 祖先选择器
        • 样式模块化
        • 视图封装模式
      • 动态组件
      • 练习1
      • 练习2 --- 待办事项
    • 指令
      • 内置指令
      • 属性型指令
        • 建立属性型指令
        • 处理用户事件
        • 用户将值传递给指令属性
        • 用户动态输入设置属性值
      • 结构型指令
        • < ng-template> 和 < ng-container>
          • < ng-template>
          • < ng-container>
        • 创建结构型指令
        • 结构型指令简写形式
      • 获取组件模板中的元素
        • ViewChild
          • 获取普通DOM元素
          • 获取子组件/指令元素
        • ViewChildren
      • TemplateRef 和 ViewContainerRef
        • TemplateRef
        • ViewContainerRef
        • 视图上下文
    • 服务
    • 依赖注入
      • 什么是依赖注入
      • DI提供者
        • 类提供者
        • 提供不同于令牌(provide)的类
        • 别名提供者
        • 值提供者
        • 非类令牌
        • InjectionToken
        • 工厂提供者
      • 多级注入器
        • ElementInjector
        • 服务查找规则(令牌解析规则)
        • 解析修饰符
        • 指定NgModule的注入
        • viewProviders
        • 单例与多例
      • 使用 DI 访问父组件
    • Angular 中操作DOM、父组件调用子组件方法
    • 父子组件通信
      • 父组件给子组件传值-@input
      • 父组件通过@ViewChild 主动获取子组件的数据和方法
      • 子组件通过@Output 触发父组件的方法
      • 父子组件中的双向数据绑定
      • 非父子组件通讯
    • Angular 中的生命周期函数
    • Rxjs 异步数据流编程-Rxjs 快速入门教程
      • Rxjs
        • Rxjs unsubscribe 取消订阅
        • Rxjs 订阅后多次执行
        • Rxjs 的工具函数 map、filter
    • Angular中的数据交互
    • 路由
      • 路由配置
        • 配置路由激活状态样式 routerLinkActive
        • 路由跳转传值
          • 动态路由传值
          • 查询字符串传值
          • js 动态路由跳转
          • js 查询字符串跳转
        • 嵌套路由(父子路由)

环境搭建

  1. 安装npm,通过npm或cnpm安装angular。
  2. npm install -g @angualr/cli 或 npm install -g @angualr/cli

项目搭建和目录分析

  1. 创建项目:
	ng new demo(项目名称)

Angular 学习笔记_第1张图片

  1. 项目目录结构:
    参考链接
    Angular 学习笔记_第2张图片
  2. 运行本地服务器:
	cd demo
	ng serve --open

Angular 学习笔记_第3张图片

app.module.ts 和 自定义组件

app.moudule.ts

定义 AppModule,这个根模块会告诉 Angular 如何组装该应用。 目前,它只声明了 AppComponent。 稍后它还会声明更多组件。

Angular 学习笔记_第4张图片

自定义组件

参考链接

通过 ng help 命令查看ng支持的命令:
Angular 学习笔记_第5张图片

创建组件

	ng generate component components/news
	ng g c components/header  # 缩写形式

一般组件创建再comments目录下,所以创建自定义组件时,指定目录components
Angular 学习笔记_第6张图片
组件定义:
Angular 学习笔记_第7张图片

数据绑定 — {{}}

Angular 中使用{{}}绑定组件里面定义的数据:
Angular 学习笔记_第8张图片
Angular 学习笔记_第9张图片

属性绑定 — [属性名]

组件的属性通过 [属性名] 动态绑定:
在这里插入图片描述
Angular 学习笔记_第10张图片

数据循环 — *ngFor

官方文档

  1. 普通循环:
    在这里插入图片描述
  2. 带下标循环:
    在这里插入图片描述
  3. 指令自带的其他变量:
    Angular 学习笔记_第11张图片

条件判断 *ngIf

*ngIf 根据表达式是否成立,决定是否展示DOM标签。
在这里插入图片描述
*ngIf else指令
Angular 学习笔记_第12张图片

结构性指令都依赖于ng-template,*ngIf实际上就是ng-template指令的[ngIf]属性。
Angular 学习笔记_第13张图片

条件判断 *ngSwitch

Angular 学习笔记_第14张图片

绑定事件

Angular 学习笔记_第15张图片

表单双向数据绑定

  1. 在根模块中引入form模块:
    Angular 学习笔记_第16张图片
  2. 通过 [(ngModel)] 双向绑定数据:
    在这里插入图片描述

[ngClass]、[ngStyle]

官方文档
在这里插入图片描述
在这里插入图片描述

模板引用变量

官方文档

模板变量可以帮助你在模板的另一部分使用这个部分的数据。
使用模板变量,你可以执行某些任务,比如响应用户输入或微调应用的表单。

  1. 普通html元素引用:
    Angular 学习笔记_第17张图片
  2. 组件引用:
    Angular 学习笔记_第18张图片

管道

类似于AngularJS中的过滤器。
官方文档

常用的内置管道

AsyncPipe – 自动订阅模板中的Observable或Promise
DecimalPipe – 数字转字符串,并可以指定格式
JsonPipe – 将值转成json
TitleCasePipe – 把首字母大写,其它小写
SlicePipe – 截取Array或String
PercentPipe – 数字转百分比
LowerCasePipe和UpperCasePipe – 转化小写或大写
DatePipe – 格式化日期
在这里插入图片描述
KeyValuePipe – 使ngFor可以循环Object或Map对象
Angular 学习笔记_第19张图片
其他管道: http://bbs.itying.com/topic/5bf519657e9f5911d41f2a34

自定义管道

创建管道: ng g p pipes/exponential-strength
Angular 学习笔记_第20张图片
Angular 学习笔记_第21张图片

非纯管道

通过默认情况下,管道会定义成纯的(pure),这样 Angular 只有在检测到输入值发生了纯变更时才会执行该管道。
纯变更是指原始输入值(比如 String、Number、Boolean 或 Symbol )的变更,或是引用对象的变更(比如 Date、Array、Function、Object)。
使用纯管道,Angular 会忽略复合对象中的变化,例如往现有数组中新增的元素,因为检查原始值或对象引用比对对象中的差异进行深度检查要快得多。Angular 可以快速判断是否可以跳过执行该管道并更新视图。

问题复现

如下示例:

  1. 组件类中定义heroes,初始时只有两个英雄,其中canFly分别为false和true;
  2. addHero 提供动态添加英雄的能力,新增的英雄都是 canFly: true;
    Angular 学习笔记_第22张图片
    可以正常新增英雄:
    Angular 学习笔记_第23张图片
    创建管道过滤会飞的英雄: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);
  }

}

调用管道过滤会飞的英雄:


<input type="text" #box (keyup.enter)="addHero(box.value)" placeholder="hero name" />

<div *ngFor="let hero of (heroes | flyingHeroes)">
  {{hero.name}}
div>

这时再出发添加英雄,由于heroes是列表,添加元素并不会引起引用对象的改变,因此管道检测不到变更,页面不会显示新增的会飞英雄:
在这里插入图片描述

解决办法
  1. addHero() 添加数据时重新赋值,不使用push方法:
    Angular 学习笔记_第24张图片
  2. 设置管道为非纯的:
    **注意:**虽然非纯管道很实用,但要小心使用。长时间运行非纯管道可能会大大降低你的应用速度。
    Angular 学习笔记_第25张图片

组件安全导航

组件初始加载时hero为undefined,如果在html中获取hero.name会报错。 这里使用hero?.name可以保证页面正常渲染而不报错,数据加载后会显示出正确内容。
Angular 学习笔记_第26张图片

组件内容投影

官方文档
内容投影是一种模式,你可以在其中插入或投影要在另一个组件中使用的内容。例如,你可能有一个 Card 组件,它可以接受另一个组件提供的内容。

单插槽内容投影

在组件模板中通过 ng-content 设置插槽,ng-content 元素只是一个占位符,不会创建真正的 DOM 元素。
Angular 学习笔记_第27张图片
Angular 学习笔记_第28张图片

多插槽内容投影

一个组件可以具有多个插槽。每个插槽可以指定一个选择器,该选择器会决定将哪些内容放入该插槽。
使用此模式,必须指定希望投影内容出现在的位置。可以通过使用 ng-content 的 select 属性来完成此任务。

将 select 属性添加到 ng-content 元素。 Angular 使用的选择器支持标签名、属性、CSS 类和 :not 伪类的任意组合。
Angular 学习笔记_第29张图片

ContentChild

官方文档
功能类似@ViewChild,用于获取外部投影到组件中的标签内容;

  1. 自定义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);
  }

}


<div>
    <h3>这是shadow组件自己的内容h3>
    <ng-content>ng-content>
div>



<app-shadow>
    <h3 #h3>这是投影到ng-content插槽中的内容h3>
app-shadow>

Angular 学习笔记_第30张图片

Angular 学习笔记_第31张图片

ContentChildren

官方文档
功能类似@ViewChildren,用于获取外部投影到组件中的获取元素或指令的 QueryList;
每当添加、删除或移动子元素时,此查询列表都会更新,并且其可观察对象 changes 都会发出新值。
Angular 学习笔记_第32张图片
Angular 学习笔记_第33张图片

有条件的内容投影—ngTemplateOutlet

如果组件需要有条件地渲染内容、多次渲染内容、默认内容,则应该使用 ngTemplateOutlet 指令。
在这种情况下,不建议使用 ng-content 元素,因为只要组件的使用者提供了内容,即使该组件从未定义 ng-content 元素或该 ng-content 元素位于 ngIf 语句的内部,该内容也总会被初始化。

官方文档

结构型指令,根据一个提前备好的 TemplateRef 插入一个内嵌视图。
用途:自定义组件,由外部传入一段模板用于显示。

基本用法

  1. 创建tpl-outlet组件,定义@Input() render参数用于接收外部传入的模板 templateRef;
  2. 组件html中使用< ng-container> 定义插入外部模板的位置,[ngTemplateOutlet]=“render || defaultTpl” 插入外部传入的模板或组件默认模板;
  3. 外部组件调用< 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 {
  }
}

<div>
    <h3>tpl-outlet works!h3>
    
    <div *ngIf="true">
        
        <ng-container 
            [ngTemplateOutlet]="render || defaultTpl">
        ng-container>
    div>
div>


<ng-template #defaultTpl>
    这是组件默认内容
ng-template>


<app-tpl-outlet [render]="parentTpl">app-tpl-outlet>


<ng-template #parentTpl>
    这是外部传入的内容
ng-template>

Angular 学习笔记_第34张图片
在这里插入图片描述

上下文context

  1. 自定义组件内部创建上下文对象myContext;赋值给[ngTemplateOutletContext];
  2. 外部调用组件 或 组件内部 可以使用let语法获取组件的上下文参数;
    Angular 学习笔记_第35张图片
    在这里插入图片描述

结构型指令简写方式

因为ngTemplateOutlet是结构型指令,所以也可以使用 * 语法简写:
Angular 学习笔记_第36张图片

使用ng-template

ngTemplateOutlet 指令也可以用在ng-template上,效果和ng-container一样,两者都是起到占位符承载 ngTemplateOutlet 指令的作用。
Angular 学习笔记_第37张图片

使用TemplateRef 和 ViewContainerRef实现 ngTemplateOutlet 功能

  1. 组件模板中定义 #test ng-container 作为视图容器;
  2. 组件类中通过ViewChild 获取 test 视图容器;
  3. 在ngOnInit中将外部传入的 templateRef视图 插入视图容器中;
    Angular 学习笔记_第38张图片

组件/指令生命周期

官方文档

当 Angular 实例化组件类并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。当 Angular 在执行过程中创建、更新和销毁实例时,指令就有了类似的生命周期。

ngOnChanges

输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges()。

  1. 创建组件life-cycle,设置输入属性title;分别在 constructor、ngOnChanges、ngOnInit 中打印 this.title;
  2. 外部调用时设置输入属性title = “input title”;
  3. 组件初始化时由于 类构造函数constructor时最早执行的,这时候还没有处理title属性,因此构造函数constructor中的this.title = undefined;
  4. ngOnChanges 在接收到输入属性title 由 undefined —> ‘input title’ 时触发,这里可以拿到 this.title 是一个 SimpleChanges对象;
  5. ngOnInit 钩子函数在ngOnChanges 之后执行,可以拿到this.title = “input title”;
  6. 组件加载完成后,通过点击button重新设置title属性,这属于组件内部修改title属性,不会触发ngOnChanges 钩子函数,也就是说ngOnChanges 只在外部输入属性变化时触发;
    Angular 学习笔记_第39张图片

Angular 学习笔记_第40张图片
Angular 学习笔记_第41张图片

ngOnInit

只在组件/指令初始化调用一次,在它之前调用的顺序是 constructor -> ngOnChanges -> ngOnInit 官方建议在这个钩子中获取组件初始化的数据,而构造函数中只适合写简单的逻辑,比如初始化局部变量。

ngOnDestroy

每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。

<button class="btn btn-primary" (click)="show = !show">togglebutton>
<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');
  }

  ngAfterContentChecked(): void {
    console.log('ngAfterContentChecked');
  }


  ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
  }

  ngAfterViewChecked(): void {
    console.log('ngAfterViewChecked');
  }

  ngOnDestroy(): void {
    console.log('ngOnDestroy');
  }
}

注意:

  1. 指令没有content相关的钩子函数;
  2. 当组件或父组件发生变更检测后都会调用下面三个钩子 ngDoCheck、ngAfterContentChecked、ngAfterViewChecked。
    Angular 学习笔记_第42张图片

变更检测 change detection

Angular 框架会通过此机制将应用程序 UI 的状态与数据的状态同步。变更检测器在运行时会检查数据模型的当前状态,并在下一轮迭代时将其和先前保存的状态进行比较。

当应用逻辑更改组件数据时,绑定到视图中 DOM 属性上的值也要随之更改。变更检测器负责更新视图以反映当前的数据模型。类似地,用户也可以与 UI 进行交互,从而引发要更改数据模型状态的事件。这些事件可以触发变更检测。

使用默认的(“CheckAlways”)变更检测策略,变更检测器将遍历每个视图模型上的视图层次结构,以检查模板中的每个数据绑定属性。在第一阶段,它将所依赖的数据的当前状态与先前状态进行比较,并收集更改。在第二阶段,它将更新页面上的 DOM 以反映出所有新的数据值。

如果设置了 OnPush(“CheckOnce”)变更检测策略,则变更检测器仅在显式调用它或由 @Input 引用的变化或触发事件处理程序时运行。这通常可以提高性能。

触发变更检测的时机

事件:页面 click、submit、mouse down……
XHR:从后端服务器拿到数据
Timers:setTimeout()、setInterval()

单向数据流

一种数据流模型,它总是在一个方向(从父到子)上检查组件树是否有变化,以防止在变更检测中出现循环。

在实践中,这意味着 Angular 中的数据会在变更检测过程中向下流动。父组件可以很容易地改变子组件中的值,因为父组件是先检查的。但是,如果子组件在更改检测期间(反转预期的数据流)尝试更改其父组件中的值,则可能会导致错误,因为父组件已经渲染过了。在开发模式下,如果你的应用尝试这样做,Angular 会抛出 ExpressionChangedAfterItHasBeenCheckedError 错误,而不是沉默地渲染新值。

为了避免这个错误,进行此类更改的生命周期钩子方法中就要触发一次新的变更检测。这次新的变更检测与之前那次的方向一样,但可以成功获得新值。
Angular 学习笔记_第43张图片
如下:

  1. 父组件初始化时 title=“ABC”;
  2. 子组件初始化时发射信号修改父组件的title=“XYZ”,由于这时候子组件还在初始化阶段,变更检测还没有完成就去修改父组件的值,则Angular会报错,但不影响渲染;
    Angular 学习笔记_第44张图片
    Angular 学习笔记_第45张图片

onPush策略下触发变更检测的时机

从上面的图中可以看出,变更检测树中包含了所有的组件,因为某个组件的数据变化了,Angular并不清楚会不会影响到其他组件,因此就将所有的组件检查一遍,这样保证了所有的组件都能正常的渲染。但是这样带来的问题就是在大型的项目中,去检测所有组件性能上会有影响。

解决办法:设置 OnPush(“CheckOnce”)变更检测策略,这样变更检测只会在当前组件及其子组件中生效,但其他相邻兄弟组件不会被监测,变更检测器默认会跳过当前组件,仅在显式调用它 或 由 @Input 引用的变化 或 触发事件处理程序时运行,这通常可以提高性能。

设置OnPush策略后只有以下4种情况会触发变更检测(定时器已无法触发变更检测了)。

  1. 组件的@Input引用发生变化。
  2. 组件的 DOM 事件,包括它子组件的 DOM 事件,比如 click、submit、mouse down。
  3. Observable 订阅事件,同时设置 Async pipe。
  4. 手动使用ChangeDetectorRef.detectChanges()、ChangeDetectorRef.markForCheck()、ApplicationRef.tick()方法。
    Angular 学习笔记_第46张图片
    未配置OnPush时,3s后定时器触发变更检测刷新页面数据:
    Angular 学习笔记_第47张图片
    Angular 学习笔记_第48张图片

配置OnPush策略后,定时器已经不能触发变更检测,click事件、父组件中修改子组件的输入变量也可以触发
Angular 学习笔记_第49张图片
Angular 学习笔记_第50张图片
Angular 学习笔记_第51张图片

手动触发变更检测

设置了OnPush策略,又必须在定时器中去触发变更检测刷新页面,那么可以通过手动触发的方式:
Angular 学习笔记_第52张图片

Angular 学习笔记_第53张图片

组件样式

官方文档

宿主选择器

:host 选择是是把宿主元素作为目标的唯一方式。
它选中的是组件模板标签,比如,相当于在父组件的style中选择 app-user {}

当宿主标签上有 active class时生效

:host(.active) {
  border-width: 3px;
}

祖先选择器

当某个祖先元素有 CSS 类 theme-light 时,才会把 background-color 样式应用到组件内部的所有 .title 元素中
找到根元素(html标签)为止

:host-context(.theme-light) .title {
  background-color: #95f04c;
}

样式模块化

  1. 在 @Component 的元数据中指定的样式只会对该组件的模板生效
  2. 组件的样式不会影响到子组件中的模板
  3. 组件的样式不会影响到投影内容

视图封装模式

  1. ShadowDom – 不进不出,没有样式能进来,组件样式出不去, 就自己玩
  2. Emulated – 默认选项,只进不出,全局样式能进来,组件样式出不去
  3. None – 能进能出,此时组件的样式是全局生效的,注意与其他组件发生样式冲突

动态组件

官方文档

目前调用组件的方式都是在模板中通过< app-XXX>调用的,但是组件的模板不会永远是固定的,应用可能会需要在运行期间加载一些新的组件。

  1. 创建子组件 alerts:
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-alerts',
  templateUrl: './alerts.component.html',
  styles: [`
    .close {
      display: block;
      width: 20px;
      height: 20px;
      position: absolute;
      right: 10px;
      top: 50%;
      margin-top: -10px;
      cursor: pointer;
    }
  `],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AlertsComponent implements OnInit {
  options = {
    content: "",
    theme: "primary"
  }

  // 子组件中点击关闭弹框时通知父组件销毁子组件
  @Output() closed = new EventEmitter<void>();

  // 动态设置弹框主题颜色
  get wrapCls(): string {
    return 'alert alert-' + this.options.theme;
  }

  // 动态设置弹框内容
  setOptions(options) {
    this.options = { ...this.options, ...options };
  }

  constructor() { }

  ngOnInit(): void {
  }
}
<div style="margin-top: 100px;">
    <span [class]="wrapCls" role="alert">{{ options.content }}span>
    <button class="btn btn-warning" (click)="closed.emit()">关闭提示button>
div>
  1. 父组件:
import { ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injector } from '@angular/core';
import { AlertsComponent } from './components/alerts/alerts.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // 引入alerts组件类
  private container: AlertsComponent;
  // 提供对组件实例和相关对象的访问,并提供销毁实例的方法。
  private componentRef: ComponentRef<AlertsComponent>;

  constructor(
    // 一个简单的注册表,它将 Components 映射到生成的 ComponentFactory 类,该类可用于创建组件的实例。
    // 用于获取给定组件类型的工厂,然后使用工厂的 create() 方法创建该类型的组件。
    private cfr: ComponentFactoryResolver,
    // 依赖注入,子组件可能有使用依赖注入,创建子组件时需要传入依赖注入。
    private inject: Injector,
    // 对页面上运行的 Angular 应用程序的引用。
    private appRef: ApplicationRef
  ) {}

  showAlert() {
    // 如果没有创建子组件,则实例化子组件
    if (!this.container) {
      this.container = this.getContainer();
    }
  
    // 调用组件的某个方法执行逻辑,比如下面这个传参
    this.container.setOptions({ content: '一段提示', theme: 'warning' });
  }

  private getContainer(): AlertsComponent {
    // 创建指定类型的组件工厂(生产指定类型的组件)
    const factory = this.cfr.resolveComponentFactory<AlertsComponent>(AlertsComponent);

    // 根据指定的类型,创建组件的示例
    this.componentRef = factory.create(this.inject);

    // 将组件试图添加到试图树中,以激活变更检测
    this.appRef.attachView(this.componentRef.hostView);

    // 将组件到模版(包括app-alert标签),添加到body最后;也可以通过ng-template确定插入位置
    document.body.appendChild((this.componentRef.hostView as EmbeddedViewRef<{}>).rootNodes[0] as HTMLElement);
    
    // 监听组件销毁事件,在组件调用了destroy()后会调用onDestroy(),可以在这里做一些收尾工作
    this.componentRef.onDestroy(() => {
      console.log('componentRef destory');
    });

    // 获取子组件实例,相当于用@ViewChild获取子组件一样
    const { instance } = this.componentRef;

    // 监听子组件到output事件,子组件触发关闭时会发送信号,在父组件中调用destory()销毁
    instance.closed.subscribe(() => {
      this.componentRef.destroy();
      this.container = null;
    });

    // 返回组件实例
    return instance;
  }
}

<button class="btn btn-primary" (click)="showAlert()">创建子组件button>

练习1

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-forms',
  templateUrl: './forms.component.html',
  styleUrls: ['./forms.component.css']
})
export class FormsComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

  public cityList: string[] = ["北京", "上海", "深圳", "广州"];

  public username: string = "";
  public peopleInfo: any = {
    username: "",
    sex: "1",
    city: "上海",
    hobby: [
      {title: "吃饭", checked: false},
      {title: "睡觉", checked: false},
      {title: "敲代码", checked: true}
    ],
    remarks: ""
  }

  public getData() {
    console.log(this.peopleInfo);
  }
}

<h2>人员登记系统h2>

<div class="people_list">

    <ul>
        <li>姓名:<input type="text" [(ngModel)]="peopleInfo.username">li>
        <li>性别:
            <input type="radio" value="1" name="sex" id="sex1" [(ngModel)]="peopleInfo.sex"><label for="sex1">label>
            <input type="radio" value="2" name="sex" id="sex2" [(ngModel)]="peopleInfo.sex"><label for="sex2">label>
        li>

        <li>城市:
            <select name="city" id="city" [(ngModel)]="peopleInfo.city">
                <option [value]="item" *ngFor="let item of cityList">{{ item }}option>
            select>
        li>

        <li>爱好:
            <span *ngFor="let i=index; let item of peopleInfo.hobby">
                <input type="checkbox" [id]="'checked'+i" [(ngModel)]="item.checked"> <label [for]="'checked'+i">{{ item.title }}label>
                  
            span>
        li>

        <li>备注:
            <textarea name="remarks" id="remarks" cols="30" rows="10" [(ngModel)]="peopleInfo.remarks">textarea>
        li>
    ul>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    <button (click)="getData()">获取数据button>
    <br>
    <br>

    <div>{{ peopleInfo | json }}div>
div>
h2 {
    text-align: center;
}

ul,ol {
    list-style-type: none;
}

.people_list {
    width: 400px;
    margin: 20px auto;
    /* padding: 0px; */
    border: 1px solid #eee;
}

.people_list li {
    height: 20px;
    margin: 10px auto;
    
}

button {
    float: right;
}

Angular 学习笔记_第54张图片

练习2 — 待办事项

指令

指令是为 Angular 应用程序中的元素添加额外行为的类。

内置指令

官方文档

属性型指令

官方文档
使用属性型指令,可以更改 DOM 元素和 Angular 组件的外观或行为。

建立属性型指令

  1. 创建指令:ng generate directive directives/highlight
    Angular 学习笔记_第55张图片
    @Directive() 装饰器的配置属性会指定指令的 CSS 属性选择器 [appHighlight]。

  2. 通过 ElementRef 获取指令所在DOM元素,设置其背景颜色:
    Angular 学习笔记_第56张图片

处理用户事件

检测用户何时将鼠标移入或移出元素以及如何通过设置或清除突出显示颜色来进行响应。

  1. 从 ‘@angular/core’ 导入 HostListener 装饰器:
  2. 添加两个事件处理方法,分别在鼠标进入或离开时宿主DOM元素时做出响应,每个事件处理程序都带有 @HostListener() 装饰器。
    Angular 学习笔记_第57张图片
    Angular 学习笔记_第58张图片

用户将值传递给指令属性

指令中有些数据可能需要用户指定,比如设置的背景颜色。

  1. 通过@Input装饰器获取用户指定的color属性;
  2. 将用户设置的color属性设置到事件监听的方法中;
    Angular 学习笔记_第59张图片
  3. 更简单的方法:
    将输入属性名称和指令的属性选择器名称设置为一样的,这样就可以直接使用[appHighLight]设置默认颜色; 这种方法只适用于用户输入一个参数的场景。
    Angular 学习笔记_第60张图片

用户动态输入设置属性值

通过用户选择颜色展示不同的背景色,并且设置了默认颜色。
Angular 学习笔记_第61张图片
Angular 学习笔记_第62张图片

结构型指令

官方文档

< ng-template> 和 < ng-container>

ng-template 和 ng-container 都用于包裹元素,默认都不会渲染出来。
在TS中< ng-template>对应的是 templateRef 类型
在TS中< ng-container >对应的是 viewContainerRef 类型

< ng-template>

Angular 的 ng-template是视图元素, 它定义了一个默认情况下不渲染任何内容的模板。使用 < ng-template> 可以手动渲染内容,以完全控制内容的显示方式。

ng-template本身不会渲染在视图中,在视图渲染之前Angular 会将ng-template替换为注释< !–container–> 。其中包裹的元素默认不会渲染出来,必须搭配结构型指令,ng-template中包裹的元素才能渲染出来

在下面的示例中,Angular 不会渲染中间的 “Mid!”,因为它被 < ng-template> 包裹着。

<p>Hip!p>
<ng-template>
  <p>Mid!p>
ng-template>
<p>Hooray!p>

Angular 学习笔记_第63张图片

下面的例子中,在ng-template 上添加结构型指令ngIf后可以显示出来:

<p>Hip!p>
<ng-template [ngIf]="true">
  <p>Mid!p>
ng-template>
<p>Hooray!p>

Angular 学习笔记_第64张图片

< ng-container>

ng-container 元素是一个逻辑结构,可用于对其他 DOM 元素进行分组,ng-container 本身不会在 DOM 树中渲染。
使用结构型指令时,通常都需要一个根元素作为结构型指令的宿主。例如:< li>元素作为ngFor指令的宿主。

<ul>
    <li *ngFor="let item of list">li>
ul>

当原始的html没有一个宿主元素时,可以引入一个div或span标签包裹原始html,使其成为宿主元素,并将结构型附加到这个div上。但是这种方式引入了一个容器元素div可能会存在问题。
问题1:比如css中设置了div标签中的字体都是red,那么引入div作为宿主元素就会导致div所包裹的元素中字体变为red。
问题2:select标签默认下级标签时option,那么就不能把option标签再包裹进div或span中。

解决办法:ng-container是视图容器,和ng-template一样Angular不会渲染它,它不需要搭配结构型指令也可以显示出包裹的元素。
ng-container是由Angular解释器负责识别处理的语法元素,类似于js中的条件语法 if 块中的花括号,将一组元素包裹起来;

  1. 不需要搭配结构型指令,可以渲染内部元素:
<ng-container >
    <p>bbbp>
ng-container>
  1. 不改变dom结构,控制页面渲染:

<p>
    aaaaaaaaaaaaaaaaaa
    bbbbbbbbbbbbbbbbbb
    cccccccccccccccccc
    dddddddddddddddddd
p>

<p>
    aaaaaaaaaaaaaaaaaa
    <span *ngIf="false">bbbbbbbbbbbbbbbbbbspan>
    cccccccccccccccccc
    dddddddddddddddddd
p>

<p>
    aaaaaaaaaaaaaaaaaa
    <ng-container *ngIf="false">bbbbbbbbbbbbbbbbbbng-container>
    cccccccccccccccccc
    dddddddddddddddddd
p>

创建结构型指令


<p *ngIf="true">aaap>

<ng-container>
    <ng-template [ngIf]="true">
        <p>bbbp>
    ng-template>
ng-container>

在指令类中要获取ng-template需要使用TemplateRef类;要获取视图容器需要使用ViewContainerRef类,创建视图时需要调用ViewContainerRef的createEmbeddedView()方法。
TemplateRef类文档
ViewContainerRef类文档

创建指令:ng generate directive directives/unless

下面创建一个和ngIf功能相反的指令,ture时隐藏,false时显示:

import { Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appUnless]'
})

// 1. UnlessDirective指令会通过 Angular 生成  创建一个嵌入的视图,
// 然后将该视图插入到该指令的宿主元素 

紧后面的视图容器中(不是父子元素,是兄弟元素); export class UnlessDirective implements OnChanges{ private hasView = false; @Input() appUnless: boolean = false; constructor( // 2. TemplateRef 可以获取视图 的内容; private templateRef: TemplateRef<any>, // 3. ViewContainerRef 可以获取视图容器 private viewContainer: ViewContainerRef ) { } // 4. 监听输入属性变更 // 每当condition的值更改时,Angular 都会设置 appUnless 属性。 // 如果condition是false,并且 Angular 以前尚未创建视图,则此 setter 会导致视图容器从模板创建出嵌入式视图。 // 如果condition为true,并且当前正显示着视图,则此 setter 会清除容器,这会导致销毁该视图。 // 监听输入属性变更有两种方式: // (1) 生命周期函数:ngOnChanges ngOnChanges(changes: SimpleChanges) { console.log(changes); if (!changes.appUnless.currentValue && !this.hasView) { // 实例化一个内嵌视图,并把它插入到该容器中。 this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } else if (changes.appUnless.currentValue && this.hasView) { // 销毁本容器中的所有视图。 this.viewContainer.clear(); this.hasView = false; } } // (2)添加一个带 setter 的 @Input() 属性 appUnless // @Input() set appUnless(condition: boolean) { // if (!condition && !this.hasView) { // this.viewContainer.createEmbeddedView(this.templateRef); // this.hasView = true; // } else if (condition && this.hasView) { // this.viewContainer.clear(); // this.hasView = false; // } // } }

Angular 学习笔记_第65张图片
在这里插入图片描述
在这里插入图片描述

结构型指令简写形式

结构型指令(例如 *ngIf)上的星号 * 语法是简写形式。 Angular 将结构型指令前面的星号转换为围绕宿主元素及其后代的 < ng-template >标签。

  1. 下面是一个 *ngIf 的示例,如果 hero 存在,则显示英雄的名称:
<div *ngIf="hero" class="name">{{hero.name}}div>

完整的写法是:*ngIf 指令移到了 < ng-template> 上,它成为绑定在方括号 [ngIf] 中的属性。 < div> 的其余部分(包括其 class 属性)移到了 < ng-template> 内部。

<ng-template [ngIf]="hero">
  <div class="name">{{hero.name}}div>
ng-template>

Angular 不会创建真正的 < ng-template> 元素,只会将 < div> 和注释节点占位符渲染到 DOM 中。

  1. 下面*ngFor 中的星号的简写形式与非简写的 < ng-template> 形式进行比较:

简写形式:


<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
div>


<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
  <div [class.odd]="odd">({{i}}) {{hero.name}}div>
ng-template>

这里,ngFor 结构型指令相关的所有内容都应用到了 < ng-template> 中。而元素上的所有其他绑定和属性应用到了宿主元素 < div> 上。在这个例子中,[class.odd]=“odd” 就留在了 < div> 中。

let 关键字会声明一个模板输入变量,可以在模板中引用该变量。在这个例子中,解析器将 let hero、let i 和 let odd 转换为名为 let-hero、let-i 和 let-odd 的变量。 let-i 和 let-odd 变量变为 let i=index 和 let odd=odd 。 Angular 会将 i 和 odd 设置为上下文中 index 和 odd 属性值

获取组件模板中的元素

变更检测器会在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。 如果视图的 DOM 发生了变化,出现了匹配该选择器的新的子节点,该属性就会被更新。

用于获取模板中的指令、子组件、普通的HTML DOM元素、templateRef。

ViewChild

官方文档

获取普通DOM元素
  1. 创建view-child组件,并在父组件中引用。
  2. 在view-child组件中定义 #box 标签,在组件中通过@ViewChild(‘box’)装饰器定位到boxEl元素,这是ElementRef类型。 这里就类似于css选择器。
  3. 分别在构造函数、OnInit、AfterViewInit生命周期函数中打印boxEl元素,只有AfterViewInit中可以拿到boxEl元素。
    Angular 学习笔记_第66张图片
    Angular 学习笔记_第67张图片
    如果已知DOM中要获取的元素是静态的,例如,没有使用ngIf等结构性指令包裹;并且想在ngOnInit中拿到元素(变更检测运行之前解析查询结果),那么可以设置static参数为true,即:@ViewChild(‘box’, {static: true}) private boxEl: ElementRef 。
    Angular 学习笔记_第68张图片
获取子组件/指令元素
  1. 创建子组件panel,并在view-child中引用;
  2. 通过 @ViewChild(PannelComponent, {static: true}) private pannelInstance: PannelComponent; 直接获取子组件实例,可以调用子组件中的方法、属性;
    Angular 学习笔记_第69张图片
  3. 也可以通过在html中引用子组件时设置 标签pannel,然后在view-child中通过’pannel’获取子组件实例:
    Angular 学习笔记_第70张图片

ViewChildren

官方文档
QueryList API文档

ViewChildren获取模板元素的方法和ViewChild基本一致,只是返回结果为QueryList类型,将匹配到的多个元素放在了QueryList中。

下面两种获取pannel子组件的方式结果不一样,通过 #pannel只能获取到2个Pannel组件实例l;通过PannelComponent可以获取到3个实例;
Angular 学习笔记_第71张图片
Angular 学习笔记_第72张图片

TemplateRef 和 ViewContainerRef

前面已经学习了通过ng-template和ng-container在html包裹其他DOM元素,手动控制视图渲染。
那么如何在ts中控制视图渲染呢?

ng-template 对应到ts类中就是 templateRef类型。
ng-container 对应到ts类中就是 viewContainerRef类型。viewContainerRef官方文档

TemplateRef

官方文档

表示一个内嵌模板,它可用于实例化内嵌的视图。 要想根据模板实例化内嵌的视图,请使用 ViewContainerRef 的 createEmbeddedView() 方法。

如下,通过ViewChild获取ng-template元素,其类型为TemplateRef。
在生命周期函数中打印元素是注释类型,即html中渲染成注释 < !-- container -->。
Angular 学习笔记_第73张图片
在这里插入图片描述

ViewContainerRef

官方文档
可以将一个或多个视图附着到组件中的容器。

import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-tpl-container',
  templateUrl: './tpl-container.component.html',
  styleUrls: ['./tpl-container.component.css']
})
export class TplContainerComponent implements OnInit, AfterViewInit {
  // 通过ViewChild获取html中的ng-template元素,类型为TemplateRef
  @ViewChild('firstTemplate', {read: TemplateRef}) firstTemplate: TemplateRef<any>;
  // 通过ViewChild获取html中的ng-container元素,类型为ViewContainerRef
  @ViewChild('firstContain', {read: ViewContainerRef}) firstContain: ViewContainerRef;
  constructor() { }

  ngOnInit(): void {
  }

  ngAfterViewInit(): void {
    // 将TemplateRef插入视图容器ViewContainer有两种办法:
    // 方法一:
    // 1. 通过TemplateRef生成 ViewRef视图
    // const viewRef = this.firstTemplate.createEmbeddedView(null);
    // 2. 调用ViewContainer的insert方法,将ViewRef 视图插入容器中
    // this.firstContain.insert(viewRef);

    // 方法二:
    // 直接调用ViewContainer的createEmbeddedView方法传入TemplateRef
    this.firstContain.createEmbeddedView(this.firstTemplate, {index: 0});
  }
}

<ng-template #firstTemplate>
    <p>aaaap>
ng-template>


<ng-container #firstContain>
    <p>bbbp>
ng-container>

视图上下文

在ngFor等指令中可以使用 let创建局部变量,实际上就是使用了视图的上下文中的参数:

import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'app-tpl-container',
  templateUrl: './tpl-container.component.html',
  styleUrls: ['./tpl-container.component.css']
})
export class TplContainerComponent implements OnInit {
  @ViewChild('firstTemplate', {read: TemplateRef}) firstTemplate: TemplateRef<any>;
  @ViewChild('firstContain', {read: ViewContainerRef}) firstContain: ViewContainerRef;
  constructor() { }

  ngOnInit(): void {
  }

  insert() {
    // $implicit: "defaultVal" ,test: "test111" 设置上下文参数
    this.firstContain.createEmbeddedView(this.firstTemplate, {$implicit: "defaultVal" ,test: "test111", index: 0});
  }
}


<ng-template #firstTemplate let-te="test" let-def>
    <p>aaaap>
    <p>te--- {{ te }}p>
    <p>def--- {{ def }}p>
ng-template>


<button class="btn btn-primary" (click)="insert()">插入视图button>

<ng-container #firstContain>
    <p>bbbp>
ng-container>

服务

官方文档

组件不应该直接获取或保存数据,它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
Angular 学习笔记_第74张图片

  1. 创建服务:
ng g service my-new-service 
# 创建到指定目录下面 
ng g service services/storage

在这里插入图片描述
2. 在跟模块中声明服务:
Angular 学习笔记_第75张图片

  1. 在要使用服务的组件中引入服务,并在构造函数中初始化:
    Angular 学习笔记_第76张图片

依赖注入

官方文档

什么是依赖注入

依赖注入(DI)是一种设计模式, 也有相应的框架,比如InversifyJS。
Angular 有自己的 DI 框架, DI 框架会在实例化该类时向其提供这个类所声明的依赖项。

  1. 创建服务: ng g s services/hero
import { Injectable } from '@angular/core';
// 引入默认英雄数据
import Heros from 'src/app/configs/heros';

// 在Angular中,使用@Injectable 修饰的类,可以被依赖注入
@Injectable({
  // 在根模块中提供服务
  providedIn: 'root'
})
export class HeroService {

  constructor() { }

  // 服务提供获取英雄数据的方法
  getHero() {
    return Heros
  }
}
  1. 原来heros组件是直接获取的默认英雄数据,现在修改heros组件,从服务中获取数据:
import { Component, OnInit } from '@angular/core';
import { Hero, HeroArg } from 'src/app/configs/types';
import { HeroService } from 'src/app/services/hero.service';

@Component({
  selector: 'app-heros',
  templateUrl: './heros.component.html',
  styleUrls: ['./heros.component.css']
})
export class HerosComponent implements OnInit {

  searchParams: HeroArg = {
    name: '',
    job: '',
    sort: 'desc'
  };

  heros: Hero[];

  // 1. 不使用依赖注入时,要使用服务就得先声明服务 并在构造函数中 new HeroService();
  // heroService: HeroService;
  // constructor() {
  //   this.heroService = new HeroService();
  // }

  // 2. 在构造函数中依赖注入服务,框架会实例化服务并绑定在属性上
  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.heros = this.heroService.getHero();
  }

  search() {
    console.log(this.searchParams);
  }

}

DI提供者

类提供者

  1. 创建服务:
import { Injectable } from '@angular/core';

@Injectable()
export class LoggerService {
  logs: string[] = [];
  constructor() { }
  log(message: string) {
    this.logs.push(message);
    console.log(message);
  }
}
  1. 在app.moudle.ts 中 providers 注册该服务:
app.moudle.ts
类提供者,使用useClass在NgModule中提供该服务

providers: [
    LoggerService,
    // { provide: LoggerService, useClass: LoggerService } 与上面写法相同
  ]

提供不同于令牌(provide)的类

providers: [
  // provide 参数是令牌, userClass可以指定会实例化的类,如果两者相同则可以简写为 LoggerService
  { provide: LoggerService, useClass: BetterLogger }
]

别名提供者

useExisting指向的服务一定是已经注册过的,这是和useClass的区别之一

providers: [
  UserLoggerService,
  // 如果用useClass, 则会得到两份UserLoggerService实例,使用useExisting则可以复用上面已经实例化的UserLoggerService 
  { provide: LoggerService, useExisting: UserLoggerService }
]

值提供者

对于很简单的值,没必要把它做成一个类,可用useValue提供简单的值

providers: [{ provide: Logger, useValue: 'simpleValue' }]

非类令牌

上面每个provide都是一个类,那么也可以用其它数据类型作为令牌
providers: [{ provide: 'httpApi', useValue: '123.com' }]

注入方式
class AppComponent {
  constructor(@Inject('httpApi') readonly api){}
}

InjectionToken

官方文档

一般情况下无法使用ts接口作为令牌
interface AppConfig {
  apiEndpoint: string;
  title: string;
}
无法用AppConfig作为令牌:
[
  {
      provide: AppConfig,
      useValue: {
        apiEndpoint: 'api.heroes.com',
        title: 'Dependency Injection'
      }
}]
但又想要限制值的类型,可以借助InjectionToken

import { InjectionToken } from '@angular/core';
// 参数是该令牌的一个描述,可选
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

[
  {
      provide: APP_CONFIG,
      useValue: {
        apiEndpoint: 'api.heroes.com',
        title: 'Dependency Injection'
      }
}]
注入方式

class AppComponent {
  constructor(@Inject(APP_CONFIG) config: AppConfig) {
    this.title = config.title;
  }
}

工厂提供者

直接注册的服务没办法在实例化时传入参数,可以通过useFactory() 自定义实例化服务 user-logger.service

import { Injectable } from '@angular/core';
import {UserService} from './user.service';
import {LoggerService} from './logger.service';

@Injectable()
export class UserLoggerService extends LoggerService {
  constructor(private userService: UserService, extra: string) {
    super();
    console.log('UserLoggerService', extra);
  }
  log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

{
  provide: UserLoggerService,
  useFactory(userServe: UserService) {
    return new UserLoggerService(userServe, 'factory msg');
  },
  deps: [UserService] // 依赖其它服务的话,要列在这里
}

多级注入器

官方文档

ElementInjector

在@NgModule() 或 @Injectable() 提供的服务称为 ModuleInjector。
在 @Directive() 或 @Component()里提供的服务称为 ElementInjector。

服务查找规则(令牌解析规则)

  1. 优先查找ElementInjector,如果当前组件找不到提供者,将会去父组件中的ElementInjector;
  2. 当所有的ElementInjector都找不到,就会去ModuleInjector中找;
  3. 如果ModuleInjector也找不到,就会抛出一个错误;
  4. 对于同名的令牌,只会解析遇到的第一个依赖。

解析修饰符

默认情况下,Angular 始终从当前的 Injector 开始,并一直向上搜索。修饰符使你可以更改开始(默认是自己)或结束位置。

  1. 如果 Angular 找不到你要的服务怎么办,用 @Optional()阻止报错;
  2. 用 @SkipSelf()跳过自身,从父组件(指令)开始找;
  3. 用@Self()只在当前组件(指令)找;
  4. 用@Host()只在当前组件(指令)宿主上找。禁止在宿主组件以上的搜索,宿主组件通常就是请求该依赖的那个组件。 不过,当该组件投影进某个父组件时,那个父组件就会变成宿主。

指定NgModule的注入

之前providedIn: ‘root’ 指定从根模块中提供该服务 也可以像下面这样指定其它模块提供该服务

@Injectable({
  providedIn: ComponentsModule
})

如果这么做,意味着这个服务无法该模块自身内部使用,会报一个循环依赖的错误只能在其它引入了ComponentsModule的NgModule中,才能使用该服务。

viewProviders

上面说了,组件会从自身开始寻找服务,然后遍历父组件的ElementInjector,直到找到为止。
viewProviders也有这个特性,区别是,对投影内容不可见

 <app-mobile-list>
    <app-mobile-content>app-mobile-content>
  app-mobile-list>

MobileContent无法找到MobileList里用viewProviders提供的服务。

单例与多例

  1. 对于ModuleInjector来讲,不论服务在哪个NgModule提供,所有注入的地方获得的都是同一份实例,也就是说服务只会实例化一次。
  2. 对于ElementInjector来讲,每个组件元数据中注入的服务都是一份单独的实例,多个组件中注入服务,会实例化多次。

使用 DI 访问父组件

官方文档

组件 A 是 组件B 父组件,通过Angular的API Query、QueryList、ViewChildren 和 ContentChildren等,可以在父组件中拿到子组件的实例。但是不存在用于获取父组件实例的公共 API。
不过,由于每个组件的实例都会添加到注入器的容器中,因此可以通过 Angular 的依赖注入来访问父组件。

父组件
@Component({
  selector: 'alex',
  template: `
    

{{name}}

`
, }) export class AlexComponent extends Base { name = 'Alex'; }
子组件
@Component({
  selector: 'cathy',
  template: `
  

Cathy

{{alex ? 'Found' : 'Did not find'}} Alex via the component class.
`
}) export class CathyComponent { // 通过将父组件注入到构造函数中,来获取父组件的属性 constructor( @Optional() public alex?: AlexComponent ) { } }

还有其他方式见官方文档。

Angular 中操作DOM、父组件调用子组件方法

  1. 在html标签中定义 #名称;
  2. 在父组件中通过core模块引入ViewChild;
  3. 在组件类中通过ViewChild装饰器获取news组件并赋值给news属性;
  4. 在ngAfterViewInit 生命周期函数中中,DOM已经加载完成,可以在这里操作DOM;或者在其他地方通过this.news.方法名 操作子组件的方法。
    Angular 学习笔记_第77张图片
    Angular 学习笔记_第78张图片

父子组件通信

官方文档

父组件给子组件传值-@input

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

  1. 父组件调用子组件的时候传入数据:
    Angular 学习笔记_第79张图片
  2. 子组件引入 Input 模块:
    在这里插入图片描述
  3. 通过装饰器@Input 接收父组件传过来的数据:
    Angular 学习笔记_第80张图片
  4. 子组件中使用父组件的数据:
    在这里插入图片描述

父组件通过@ViewChild 主动获取子组件的数据和方法

参考 Angular 中操作DOM、父组件调用子组件方法。

子组件通过@Output 触发父组件的方法

  1. 子组件引入 Output 和 EventEmitter:
    在这里插入图片描述
  2. 子组件中实例化 EventEmitter:
    在这里插入图片描述
  3. 子组件通过 EventEmitter 对象 outer 实例广播数据:
    在这里插入图片描述
  4. 父组件调用子组件的时候,定义接收事件 , outer 就是子组件的 EventEmitter 对象 outer:
    在这里插入图片描述
  5. 父组件接收到数据会调用自己的 run 方法,这个时候就能拿到子组件的数据:
    在这里插入图片描述
    Angular 学习笔记_第81张图片

父子组件中的双向数据绑定

  1. 自定义子组件:
    @Input()属性名称为size,通过父组件可以设置size属性值;
    @Output()属性对应名称为sizeChange,可以将属性变化返回给父组件。
    Angular 学习笔记_第82张图片
  2. 父组件:
    Angular 学习笔记_第83张图片
    Angular 学习笔记_第84张图片

非父子组件通讯

  1. 公共的服务
  2. Localstorage (推荐)
  3. Cookie

Angular 中的生命周期函数

官方文档

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

Rxjs 异步数据流编程-Rxjs 快速入门教程

参考手册
中文手册

RxJS 是 ReactiveX 编程理念的 JavaScript 版本。ReactiveX 来自微软,它是一种针对异步数据 流的编程。简单来说,它将一切数据,包括 HTTP 请求,DOM 事件或者普通数据等包装成流 的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据, 并组合不同的操作符来轻松优雅的实现你所需要的功能。
RxJS 是一种针对异步数据流编程工具,或者叫响应式扩展编程;可不管如何解释 RxJS 其目 标就是异步编程,Angular 引入 RxJS 为了就是让异步可控、更简单。
RxJS 里面提供了很多模块。这里我们主要给大家讲 RxJS 里面最常用的 Observable 和 fromEvent。

常见的异步编程的几种方法:

  1. 回调函数:将回调函数传入获取数据的方法,等数据获取到后调用回调函数。
    Angular 学习笔记_第85张图片
  2. 事件监听/发布订阅
  3. Promise:获取异步数据时返回promise对象,通过.then方法处理数据。
    Angular 学习笔记_第86张图片

Rxjs

从下面例子可以看到 RxJS 和 Promise 的基本用法非常类似,除了一些关键词不同。Promise 里面用的是 then() 和 resolve(),而 RxJS 里面用的是 next() 和 subscribe()。
Angular 学习笔记_第87张图片

Rxjs unsubscribe 取消订阅

Rxjs 可以通过 unsubscribe() 可以撤回 subscribe 的动作,异步数据返回后,不作处理:
Angular 学习笔记_第88张图片

Rxjs 订阅后多次执行

如果想让异步里面的方法多次执行,比如下面代码。 这一点 Promise 是做不到的,对于 Promise 来说,最终结果要么 resole(兑现)、要么 reject (拒绝),而且都只能触发一次。如果在同一个 Promise 对象上多次调用 resolve 方法, 则会抛异常。而 Observable 不一样,它可以不断地触发下一个值,就像 next() 这个方法的名字所暗示的那样。

Angular 学习笔记_第89张图片

Rxjs 的工具函数 map、filter

Angular 学习笔记_第90张图片

Angular中的数据交互

Angular5.x 以后 get、post 和和服务器交互使用的是 HttpClientModule 模块。
Angular 学习笔记_第91张图片
异常处理:
Angular 学习笔记_第92张图片

路由

命令创建项目 ng new angualrdemo08 时选择带路由:
Angular 学习笔记_第93张图片
会自动生成如下配置:
Angular 学习笔记_第94张图片

路由配置



<header class="header">
    <a routerLink="/home">首页a>
    
    <a [routerLink]="[ '/news' ]">新闻a>
    <a routerLink="/product">商品a>
header>

<router-outlet>router-outlet>
/* app.component.css:根组件样式文件 */
.header {
    height: 44px;
    line-height: 44px;
    background: #000;
    color: #fff;
}

.header a {
    color: #fff;
    padding: 10px;
    cursor: pointer;
    /* 取消a标签下划线 */
    text-decoration: none;
}
// app-routing.module.ts :配置路由
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { HomeComponent } from './components/home/home.component';
import { NewsComponent } from './components/news/news.component';
import { ProductComponent } from './components/product/product.component';

// 配置路由
const routes: Routes = [
  {path: "home", component: HomeComponent},
  {path: "news", component: NewsComponent},
  {path: "product", component: ProductComponent},
  // 默认路由两种方式
  // 匹配不到路由的时候加载默认的组件 或者 路由跳转 
  { path: '**', /* 匹配任意的路由 */
  // component:HomeComponent,   可以指定组件,也可以重定向到其他路由,一般使用重定向
  redirectTo:'home' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Angular 学习笔记_第95张图片

配置路由激活状态样式 routerLinkActive

Angular 学习笔记_第96张图片

路由跳转传值

动态路由传值
  1. 配置动态路由: 参数id是最终接收的key
    Angular 学习笔记_第97张图片
  2. 配置组件路由跳转: routerLink 第二个参数是传递的参数。
    Angular 学习笔记_第98张图片
  3. 被传值组件中获取传值:
    Angular 学习笔记_第99张图片
    Angular 学习笔记_第100张图片
    获取的值是对象类型,key是在app-routing.moudule.ts中定义的传值的key。
    Angular 学习笔记_第101张图片
查询字符串传值
  1. 配置路由:
    Angular 学习笔记_第102张图片

  2. **配置查询字符串跳转传值:**通过queryParams参数传值
    Angular 学习笔记_第103张图片

  3. 在被传值组件中接收传入的值:
    Angular 学习笔记_第104张图片

js 动态路由跳转
  1. 配置动态路由:
    Angular 学习笔记_第105张图片
  2. 通过click事件配置跳转:
    Angular 学习笔记_第106张图片
    Angular 学习笔记_第107张图片
  3. 获取传入的值:
    Angular 学习笔记_第108张图片

Angular 学习笔记_第109张图片
Angular 学习笔记_第110张图片

js 查询字符串跳转
  1. 配置路由:
    Angular 学习笔记_第111张图片
  2. 配置路由跳转传值:
    Angular 学习笔记_第112张图片
  3. 获取传入的值:
    Angular 学习笔记_第113张图片

Angular 学习笔记_第114张图片
Angular 学习笔记_第115张图片

嵌套路由(父子路由)

  1. 注册组件:
    Angular 学习笔记_第116张图片
  2. 在home路由中children字段配置子路由和默认路由:
    Angular 学习笔记_第117张图片
  3. app根组件中配置路由跳转:
    Angular 学习笔记_第118张图片
  4. home组件中配置子路由跳转:
    Angular 学习笔记_第119张图片
  5. 效果:
    Angular 学习笔记_第120张图片
    Angular 学习笔记_第121张图片

你可能感兴趣的:(前端,node.js,vue.js,react.js)