Angular知识整理(1.2W字),值得一看

684 阅读10分钟

基础篇

一、创建及具体文件配置

​ Angular 诞生于2009年,由Misko Hevery 等人创建,后为Google所收购。是一款优秀的前端框架。从09年到今天,发展了十二年,Angular的整个生态还是比较成熟的。Angular 是一个用 HTML 和 TypeScript 构建客户端应用的平台与框架。 Angular 本身就是用 TypeScript 写成的。它将核心功能和可选功能作为一组 TypeScript 库进行实现,你可以把它们导入你的应用中。

新建工程

  • 安装node.js、npm ----node -v //检查node版本 安装Angular CLI ---npm install -g @angular/cli

  • 新建angular工程 --ng new my-app

  • 启动工程 ---cd my-app //进入文件目录下 ng serve --open

    常用命令

  • 新建组件:ng g component {heros}--组件名 创建服务:ng g service my-new-service 创建自定义指令:ng g directive my-new-directive 新建类:ng g class my-new-class 创建管道类:ng g pipe my-new-pipe 创建接口:ng g interface my-new-interface 创建枚举类:ng g enum my-new-enum 创建特性模块:ng g module my-module

具体文件配置

e2e                 
	端到端的测试目录  用来做自动测试的
node_modules        
	第三方依赖包存放目录
src                 
	应用源代码目录,你的应用代码位于 src 文件夹中。 所有的 Angular 组件、模板、样式、图片以及你的应用所需的任何东西都在那里。  
editorconfig
    给你的编辑器看的一个简单配置文件,它用来确保参与你项目的每个人都具有基本的编辑器配置。
angular.json   
	Angular命令行工具的配置文件。后期可能会去修改它,引一些其他的第三方的包  比如jquery等
node_modules/
    Node.js 创建了这个文件夹,并且把 package.json 中列举的所有第三方模块都放在其中
package.json        
	这是一个标准的npm工具的配置文件,这个文件里面列出了该应用程序所使用的第三方依赖包。实际上我们在新建项目的时候,等了半天就是在下载第三方依赖包。下载完成后会放在node_modules这个目录中,后期我们可能会修改这个文件。
protractor.conf.js  
	自动化测试的配置文件,当运行 ng e2e 的时候会用到它
README.md           说明文件
tsconfig.json      TypeScript 编译器的配置,你的 IDE 会借助它来给你提供更好的帮助。 
tslint.json         是tslint的配置文件,用来定义TypeScript代码质量检查的规则,不用管它

Angular.json

Package.json

src

src
    app                      // 代码的主要文件夹
        app.component.css    // 根组件样式
        app.component.html   // 根组件模版
        app.component.spec.ts// 根组件测试
        app.component.ts     // 根组件脚本
        app.module.ts        // 根模块
    assets                   // 静态资源
        .gitkeep             // 保存空文件夹
    environments             // 环境配置
        environment.prod.ts	 //生产环境
        environment.ts		 //开发环境
    favicon.ico              // 图标
    index.html               // 页面主入口
    main.ts                  // 脚本主入口
    polyfills.ts             // 兼容浏览器
    styles.css               // 全局css样式
    test.ts                  // 单元测试主入口
    karma.conf.js       //单元测试的执行器的配置文件
   tsconfig.{app|spec}.json    
	//TypeScript 编译器的配置文件。tsconfig.app.json 是为 Angular 应用准备的,而 tsconfig.spec.json 是为单元测试准备的。 
   tslint.json     
	//额外的 Linting 配置。当运行 ng lint 时,它会供带有 Codelyzer 的 TSLint 使用。 Linting 可以帮你们保持代码风格的一致性。

二、架构概览

​ Angular 的基本构造块是 NgModule,它为组件提供了编译的上下文环境。组件定义视图,组件使用服务;组件和服务都是简单的类,这些类使用装饰器来标出它们的类型,并提供元数据以告知 Angular 该如何使用它们。 Router 服务来帮助你定义视图之间的导航路径。

1)模块

​ 一个模块,就是一个容器,存放一些内聚的代码块 包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义,NgModule 可以将其组件和一组相关代码(如服务)关联起来,形成功能单元。

模块主要做到以下几点: 声明某些组件、指令和管道属于这个模块。 公开其中的部分组件、指令和管道,以便其它模块中的组件模板中可以使用它们。 导入其它带有组件、指令和管道的模块,这些模块中的元件都是本模块所需的。 提供一些供应用中的其它组件使用的服务

惰性加载的特性模块,有三个主要步骤:

  1. 创建该特性模块。
  2. 创建该特性模块的路由模块。
  3. 配置相关路由。
//app.module.ts
import { BrowserModule } from '@angular/platform-browser';  //引入所需模块
import { NgModule } from '@angular/core';		//导入根模块
@NgModuel({
    declarations: [],   // 声明属于本模块的组件,指令,管道
    imports: [],        // 导入需要的模块
    exports: [],        // 导出的模块,跨模块交流
    entryComponents: [] // 需要编译的动态组件列表
    providers: [],      // 依赖注入服务 
    bootstrap: []       // 设置根组件,Angular 创建它并插入 index.html 宿主页面
})
export class AppModule { }

常用模块

NgModule导入自为何使用
BrowserModule@angular/platform-browser当你想要在浏览器中运行应用时
CommonModule@angular/common当你想要使用 NgIf 和 NgFor 时
FormsModule@angular/forms当要构建模板驱动表单时(它包含 NgModel )
ReactiveFormsModule@angular/forms当要构建响应式表单时
RouterModule@angular/router要使用路由功能,并且你要用到 RouterLink,.forRoot() 和 .forChild() 时
HttpClientModule@angular/common/http当你要和服务器对话时

2)组件

​ 创建组件:通过ng g c display创建一个名为display的组件

import { OnInit, Component } from '@angular/core'@Component({			// 装饰器表明紧随它的那个类是一个组件,并提供模板和该组件专属的元数据
  selector: 'app-display',		//通过css选择器( selector )来实例化该组件
  templateUrl: './display.component.html',		//外联模板文件路径
  styleUrls: ['./display.component.scss']		//外联样式文件路径
  //template(内联模板)、styles(内联样式)
})
export class DisplayComponent implements OnInit {
 constructor() {
  }
  ngOnInit() {
  }
}

1.组件交互

①父组件往子组件传值---Input方式
// box.component.html       父组件
<div class="box">
  <h3>box component</h3>
  <app-box-top [num]=100或者[object]="object"></app-box-top> //插入子组件**将父组件的数据给子组件
</div>

// box-top.component.html      //子组件html
<div class="box-top">
  <h4>box-top component</h4>
  <h4>{{num}}</h4>        	   //显示传过来的数据
</div>

//子组件的ts文件
import { Component, OnInit, Input, Output } from '@angular/core';   //引入input
@Component({
  selector: 'app-box-top',   选择子组件
  templateUrl: './box-top.component.html',
  styleUrls: ['./box-top.component.css']
})
export class BoxTopComponent implements OnInit {
  // 声明对外暴露的接口, 用于接受父组件的数据源输入, 必须是Number类型
  @Input() num: Number;       	//接受父组件传过来的值
  constructor() { }
  ngOnInit() {
  }

}
②子组件向父组件传值
///子组件.html
	<button (click)="transfer()">给父组件传值</button>  //传送按钮:传送方法transfer()
//子组件.ts
	import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";		//引入
	@Output() event = new EventEmitter<any>();		  //output
	public list = [9, 8, 7, 6];   					//待传送的数据
	transfer() {
    	this.event.emit(this.list);					//执行传送方法,emit(待传送数据)
  	}
//父组件.html
	<p *ngFor="let item of falist">{{item}}</p>    //父组件页面显示传过来的数据
	<app-headerchild (event)="getlist($event)"></app-headerchild>   //子组件--event接收
///父组件.ts
	public falist = [];     		 ///声明一个数组,用于接收传送过来的数据
	getlist(event) {         		////父组件中的接收方法,
    	this.falist = event;		///将传过来的event赋给falist,接收过来
    	console.log(this.falist);	//[9, 8, 7, 6]
  	}

③父组件调用子组件方法---ViewChild方式
//父级.html
<div class="box">
  <div (click)="runBoxTopClick()">调用子组件box-top的testClick方法</div>  //调用**子组件方法
  <box-top></box-top>
  或者 <box-top #boxTop></box-top>
</div>

//父级.ts文件
import { Component, OnInit, ViewChild } from '@angular/core';   //引入ViewChild***
import { BoxTopComponent } from '../box-top/box-top.component'   //引入子组件***
@Component({
  selector: 'app-box',
  templateUrl: './box.component.html',
  styleUrls: ['./box.component.css']
})
export class BoxComponent implements OnInit {
  @ViewChild(BoxTopComponent) boxTopFn: BoxTopComponent;     //引入声明子组件
  @ViewChild(引入的子组件名字) 自身的属性: 引入的子组件名字 
  或者	@ViewChild('boxTop')  boxTopFn: BoxTopComponent;
  constructor() { }
  runBoxTopClick() {   			//调用方法
    console.log('box调用子组件box-top里的testClick方法')
    this.boxTopFn.testClick()    //在调用方法中,this.自身属性.子组件方法()
  }
  ngOnInit() {
  }
}
//子组件.ts
import { Component, OnInit, Input, Output } from '@angular/core';
export class BoxTopComponent implements OnInit {
  constructor() { }
  testClick() {                 //子组件的方法
    console.log('box-top')
  }
  ngOnInit() {
  }
}

④子组件调用父组件方法
//父组件.html
	<app-headerchild (childEvent)="getchildevent()"></app-headerchild>
	--将父组件中的deta给予子组件 的value;将父组件的方法赋给子组件的childEvent
//父组件.ts
	deta = [1,2,3,4];     //声明一个数组数据
	getchildevent() {       	//父组件的方法,对deta执行操作
    	this.deta.splice(1, 1);
  	}
//子组件.html
	<button (click)="firechildevent()">触发父级方法</button>
//子组件.ts
	import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core"; //引入input,output,EventEmitter;
	@Output() childEvent = new EventEmitter<any>();
//Output相当于指令的方法绑定,子作用域触发事件执行响应函数,而响应函数方法体则位于父作用域中,相当于将事件“输出到”父作用域中,在父作用域中处理。
	firechildevent() {
    	this.childEvent.emit();     //响应父组件方法
  	}

2.组件内容嵌入

//父组件
<div class="box">
  <h3>box</h3>
  <app-box-child select="[name=middle]">  //子组件
    <div class="middle">
      <h4>嵌入内容到子组件, 此h4会嵌入到子组件中</h4>
    </div>
    <div class="bottom">
      <h4>嵌入内容到子组件, 此内容不会到子组件中, 不会显示在页面中</h4>
    </div>
    <div name="top">
      <h5>嵌入内容到子组件, 此h5会嵌入到子组件中</h5>
    </div>
  </app-box-child>
</div>


//子组件
<div class="box-child">
  <ng-content select="[name=top]"></ng-content>
  <ng-content select=".middle"></ng-content>
</div>

3.构建动态组件

动态加载组件的流程:

1.获取装载动态组件的容器。

2.在组件类的构造函数中,注入 ComponentFactoryResolver 对象。

3.调用 ComponentFactoryResolver 对象的 resolveComponentFactory() 方法创建 ComponentFactory 对象。

4.调用组件容器对象的 createComponent() 方法创建组件并自动添加动态组件到组件容器中。

5.基于返回的 ComponentRef 组件实例,配置组件相关属性(可选)。

6.在模块 Metadata 对象的 entryComponents 属性中添加动态组件:

declarations —— 用于指定属于该模块的指令和管道列表。
entryComponents —— 用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular 将会创建对应的一个 ComponentFactory 对象,并将其存储在 ComponentFactoryResolver 对象中。

4.组件加载顺序

​ 父组件constructor ==> 子组件constructor ==> 父组件OnChanges ==> 父组件OnInit ==> 子组件OnChanges ==> 子组件OnInit ==> 子组件AfterViewInit ==> 父组件AfterViewInit

3)服务

​ 组件不应该直接获取或保存数据, 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。主要来写视图有关的功能,诸如从服务器获取数据、验证用户输入或直接往控制台中写日志等工作. ​ 服务是在多个“互相不知道”的类之间共享信息的好办法,采用依赖注入机制

创建服务:ng g service services/storage

//storage.service.ts -- storage服务
import { Injectable } from '@angular/core';
@Injectable({					//注入 --服务表示依赖注入系统    
  providedIn: 'root'
})
export class StorageService {
	function(){}    	 	 	//写一些公用方法
}

//app.module.ts  里面引入创建的服务
import { StorageService } from './services/storage.service';
**NgModule 里面 里面的 的 providers 
@NgModule({
    providers: [StorageService],		//提供商
})

//使用的页面引入服务,注册服务
import { StorageService } from '../../services/storage.service';      //引入服务
constructor(private storage: StorageService) {       //声明服务
}
OnInit(){
    this.storage.function();   	//可调用服务中的方法
}         		 	 	

​ 该服务是在根模块中注入,所以该模块中的所有组件使用这个服务时,使用的都是同一个实例。 如果HomeComponent(子组件)也使用了这个服务,那它使用的将是同一个实例。这个可以从Service中的数据变化来看出。

分层依赖注入

​ Angular还有个分层依赖注入的概念,也就是说,你可以为任一组件创建自己独立的服务。如果想要HomeComponent(子组件)不和它的父组件同使用一个服务实例的话,只要在该组件中重新注入即可:

//HomeComponent.component.ts
@Component({
    selector: 'home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.css'],
    providers: [ Service ],  // 重新注入服务
})

4)路由

1.路由配置

1、创建一个配置好路由的项目

​ 创建命令:ng new project --routing

​ 生成文件:app-routing.module.ts

2、创建路由模块

​ 创建命令:ng generate module app-routing --flat --module=app

​ 含义:

​ ng: 代表使用Angular CLI执行本条命令

​ generate: 英文含义"生成",顾名思义,可用于创建组件、指令、模块、服务等

​ module: 代表使用generate命令创建一个模块

​ app-routing: 创建的模块名字叫做app-routing

​ –flat :额外参数,加上该参数不会单独一个创建一个文件夹。若不加该参数则会额外创建一个文件夹,带上该额外参数,创建的模块将放入 src/app 中,而不是单独的目录中。

​ –module=app: 额外参数,将创建的模块注册到app模块中,告诉 CLI 把它注册到 AppModule 的 imports 数组中,这样便不用手动去注册

​ 生成文件:app-routing.module.ts

//app-routing.module.ts    根路由
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RoutesRouterModule } from '@angular/router'   //引入根路由   
import { HomeComponent} from './home/home.component';     //引入根页面组件
export const rootRouterConfig: Routes = [
  {path: 'home', component: HomeComponent},                //根页面路径,引入根页面组件
  {path: '',   redirectTo: '/home', pathMatch: 'full' },   //输入空路径跳转到home页,pathMatch路由重定向;pathMatch:为字符串默认为前缀匹配 "prefix"; "full" 为完全匹配
  {path: '**', component: PageNotFoundComponent },		  	 //输入未知路径,引入PageNotFound页
  {path: 'settings', loadChildren: './settings/settings.module#SettingsModule'},
    // 没有将 SettingsModule 导入到我们的 AppModule 中,而是通过 loadChildren 属性,告诉 Angular 路由依据 loadChildren 属性配置的路径去加载 SettingsModule 模块。这就是模块懒加载功能的具体应用,当用户访问 /settings/** 路径的时候,才会加载对应的 SettingsModule 模块,这减少了应用启动时加载资源的大小。
   {path: 'newscontent/:id', component: NewscontentComponent},     //动态路由
]
@NgModule({
  imports: [
    CommonModule,
    RouterModule.forRoot(router,{ useHash: true })  //使用“hash URL”风格
  ],
  declarations: [],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule { }
//ModuleWithProviders:对 NgModule 及其相关 providers 的包装。
let rootRouteModule:ModuleWithProviders = RouterModule.forRoot(rootRouterConfig)  //配置路由
imports: [
    rootRouteModule      //引入根组件
  ],
	
//app.module.ts
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
    BrowserModule,
    AppRoutingModule,
    ]
})
export class AppModule { }
//app.component.ts
import { Router, ActivatedRoute, Params } from '@angular/router';
constructor( private route: ActivatedRoute) {}
 ngOnInit() {
	console.log(this.route.params);//
	this.route.params.subscribe(data=>this.id=data.id);
 }
//App.component.html
<router-outlet></router-outlet>   //插入路由

//是否使用hash;
export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { useHash: true });
//使用PathLocationStrategy策略时需在服务器将所有路由重定向到首页, 因为应用会将请求路径原封不动发往后台

  
路由的两种策略
1、PathLocationStrategy - 默认的策略,支持“HTML 5 pushState”风格。例:/concent
2HashLocationStrategy - 支持“hash URL”风格。例:#/concent
	几乎所有的 Angular 项目都会使用默认的 HTML 5 风格。它生成的 URL 更易于被用户理解,它也为将来做服务端渲染预留了空间。在服务器端渲染指定的页面,是一项可以在该应用首次加载时大幅提升响应速度的技术。那些原本需要十秒甚至更长时间加载的应用,可以预先在服务端渲染好,并在少于一秒的时间内完整呈现在用户的设备上。
	只有当应用的 URL 看起来像是标准的 Web URL,中间没有 hash(#)时,这个选项才能生效。除非你有强烈的理由不得不使用 hash 路由,否则就应该坚决使用默认的 HTML 5 路由风格。
3、Component-less routes将路由组合在一起

使用 component-less 路由允许我们将路由组合在一起,并让它们共享路由配置信息和 outlet。

可以定义 setttings 路由而不需要使用 SettingsComponent 组件:

import { ProfileSettingsComponent } from './settings/profile/profile.component';
import { PasswordSettingsComponent } from './settings/password/password.component';

export const ROUTES: Routes = [
  {
    path: 'settings',
    children: [
      { path: 'profile', component: ProfileSettingsComponent },
      { path: 'password', component: PasswordSettingsComponent }
    ]
  }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
})
export class AppModule {}

2.激活路由样式

<div routerLinkActive="atv">     //被激活的路由自动赋予atv类
  <a [routerLink]="['/list']">goto-list</a>
</div>
<div routerLinkActive="atv">     //被激活的路由自动赋予atv类
  <a [routerLink]="['/detail']">goto-detail</a>
</div>

.atv {
  background: red;
}

3.默认跳转路由

///app.component.ts
import { Router } from '@angular/router'
export class AppComponent {
  constructor( 
    private router: Router
  ) {
    this.router.navigateByUrl('/list')   //默认路由
  }
}

4.js跳转路由

//app.component.html
<div class="route-fn" (click)="jsGoToList()">js-route</div>
//app.component.ts
import { Router } from '@angular/router'
export class AppComponent {
  constructor( 
    private router: Router
  ) {}
  jsGoToList(): void {
    this.router.navigateByUrl('/list?num=8')    
    // localhost:4200/#/list?num=8
    // 或者this.router.navigate(['/list'], {queryParams: {num: 8}})  localhost:4200/#/list?num=8
  }
}

属性说明
url路由路径的 Observable 对象,是一个由路由路径中的各个部分组成的字符串数组。
data一个 Observable,其中包含提供给路由的 data 对象。也包含由解析守卫(resolve guard)解析而来的值。
paramMap一个 Observable,其中包含一个由当前路由的必要参数和可选参数组成的map对象。用这个 map 可以获取来自同名参数的单一值或多重值。
queryParamMap一个 Observable,其中包含一个对所有路由都有效的查询参数组成的map对象。 用这个 map 可以获取来自查询参数的单一值或多重值。
fragment一个适用于所有路由的 URL 的 fragment(片段)Observable
outlet要把该路由渲染到的 RouterOutlet 的名字。对于无名路由,它的路由名是 primary,而不是空串。
routeConfig用于该路由的路由配置信息,其中包含原始路径。
parent当该路由是一个子路由时,表示该路由的父级 ActivatedRoute
firstChild包含该路由的子路由列表中的第一个 ActivatedRoute
children包含当前路由下所有已激活的子路由

5.forRoot()和forChild()

CLI 会把 RouterModule.forRoot(routes) 添加到 app-routing.module.tsimports 数组中。 这会让 Angular 知道 AppRoutingModule 是一个路由模块,而 forRoot() 表示这是一个根路由模块。 它会配置你传入的所有路由、让你能访问路由器指令并注册 RouterService。 在 AppRoutingModule 中使用 forRoot(),在本应用中这只会在顶层模块中写一次。

CLI 还会把 RouterModule.forChild(routes) 添加到各个特性模块中。这种方式下 Angular 就会知道这个路由列表只负责提供额外的路由并且其设计意图是作为特性模块使用。你可以在多个模块中使用 forChild()

forRoot() 包含的注入器配置是全局性的,比如对路由器的配置。forChild() 中没有注入器配置,只有像 RouterOutletRouterLink 这样的指令。

6.路由参数

//组件.html
<a [routerLink]="['/detail']" [queryParams] = "{num: 10, from: 'a', addr: 'usa'}">to-detail</a>
//子组件.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'   //引入ActivatedRoute
import { Router } from "@angular/router";//路由传参用到
import{ActivatedRoute,Params} from  '@angular/router';//获取路由传参用到

export class DetailComponent implements OnInit {
  constructor(
    private activatedRoute: ActivatedRoute    //声明
  ) { }
  ngOnInit() {
    this.activatedRoute.queryParams.subscribe(params => {
      console.log(params) 		 // {num: "10", from: "a", addr: "usa"}  
    })
  }
  goto(){
       this.router.navigate(['/url'], {    ///设置路由参数
        queryParams: {
        	id: res.data,
        	type: 'clone'
        }
    });
  }
}
/////路由跳转的内容
1.this.router.navigate(['user', 1]); 
以根路由为起点跳转

2.this.router.navigate(['user', 1],{relativeTo: route}); 
默认值为根路由,设置后相对当前路由跳转,route是ActivatedRoute的实例,使用需要导入ActivatedRoute

3.this.router.navigate(['user', 1],{ queryParams: { id: 1 } }); 
路由中传参数 /user/1?id=1

4.this.router.navigate(['view', 1], { preserveQueryParams: true }); 
默认值为false,设为true,保留之前路由中的查询参数/user?id=1 to /view?id=1

5.this.router.navigate(['user', 1],{ fragment: 'top' }); 
路由中锚点跳转 /user/1#top

6.this.router.navigate(['/view'], { preserveFragment: true }); 
默认值为false,设为true,保留之前路由中的锚点/user/1#top to /view#top

7.this.router.navigate(['/user',1], { skipLocationChange: true }); 
默认值为false,设为true路由跳转时浏览器中的url是跳转前的路径,但是传入的参数依然有效

8.this.router.navigate(['/user',1], { replaceUrl: true }); 
未设置时默认为true,设置为false路由不会进行跳转

7.路由守卫

路由守卫:也就是,拦截器,在进入或离开路由前执行一些逻辑

一、在module中添加providers

二、在routing.module中添加需要守卫的路由的canActivate 、 canDeactivate 或者 Resolve,前两个是数组形式,Resolve是对象形式。

CanActivate :

处理导航到某路由之前的情况,控制是否允许进入路由,如:付费用户

canActivateChild

等同 canActivate,只不过针对是所有子路由。是否允许进入子路由

CanDeactivate :

处理从当前路由离开的情况,控制是否允许离开路由。 如:是否未保存就离开

canLoad

控制是否允许延迟加载整个模块。

Resolve :

在路由激活之前获取路由数据,提高用户体验

Resolve服务
import { Injectable } from '@angular/core';
import { Router, Resolve,ActivatedRouteSnapshot } from '@angular/router';
@Injectable()
export class GuardService implements Resolve<Data>{
    resolve(route:ActivatedRouteSnapshot): Promise<Data>|boolean{
        //获取数据,并返回。数据挂在路由上,并非挂在视图组件上。
        //获取不到,返回false。
    }
}

//路由模块
{
    path:'',    
    component:   HomeComponent,
    resolve:{resultData: GuardService}
}
由于我们已经挂了Resolve守卫,在组件中想要获取数据时,不需要再重新通过服务获取,而可以直接从Resolve守卫中拿到数据。

//目标路由组件
ngOnInit() {
  this.route.data
    .subscribe((data: { resultData }) => {
      this.name = data.resultData.name;
      this.value = data.resultData.value;
    });
}
路由重加载
onSameUrlNavigation:
			定义当路由器收到一个导航到当前 URL 的请求时应该怎么做。 默认情况下,路由器将会忽略这次导航。但这样会阻止类似于 "刷新" 按钮的特性。 使用该选项可以配置导航到当前 URL 时的行为。
@NgModule({
  imports: [RouterModule.forRoot(
    routes,
    { onSameUrlNavigation: 'reload' } //有两个值'reload''ignore'。默认为'ignore'
  )],
  exports: [RouterModule]
})
    
runGuardsAndResolvers:
      {
            path : "about",
            component : AboutComponent,
            runGuardsAndResolvers : "always", 
            //"always" || "paramsChange" || "paramsOrQueryParamsChange" 选择不同的检测时机
            canActivate : [GuardService]
        },
        // always :始终触发
        // paramsChange: 仅在路由参数更改时触发。如/reports/:idid更改
				// paramsOrQueryParamsChange: 当路由参数更改或参训参数更改时触发。如/reports/:id/list?page=23中的id或page属性更改

8.辅助路由

  1. 在页面中设置路由插座:<router-outlet name="aux"></router-outlet>
  2. 单独开发一个新组件,只显示在新定义的插座上。
  3. 通过设置路由参数,控制辅助路由的插座是否显示组件内容。

具体设置:{ path: 'consult', component: ConsultComponent, outlet: 'aux'}

9.第二路由:

<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
<router-outlet name="popup"></router-outlet>
////当用户点击此链接时,在路由出口 popup 中显示与 compose 路由相关联的组件。
outlet:null  时清除第二路由
closePopup() {
  this.router.navigate([{ outlets: { popup: null }}]);  设置为null清除路由
}

10.路由监测

  • NavigationStart:导航开始
  • NavigationEnd:导航结束
  • NavigationCancel:取消导航
  • NavigationError:导航出错
  • RoutesRecoginzed:路由已认证
import { Router, NavigationStartNavigationEnd, NavigationCancel, NavigationError, RoutesRecognized} from '@angular/router'constructor(router:Router) {
  router.events.subscribe(event => {
    if(event instanceof NavigationStart) {
      //
    } else if(event instanceof NavigationEnd) {
      //
    } else if(event instanceof NavigationCancel) {
      //
    } else if(event instanceof NavigationError) {
      //
    } else if(event instanceof RoutesRecognized) {
      //
    }
})
}

5)装饰器

Angular 有很多装饰器,它们负责把元数据附加到类上,以了解那些类的设计意图以及它们应如何工作。

类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

@Component--标记类作为组件并收集组件配置元数据(继承Directive) 
@Directive--标记类作为指令并收集组件配置元数据 声明当前类是一个指令,并提供关于该指令的元数据 
@Pipe --声明当前类是一个管道,并且提供关于该管道的元数据
@Injectable--标记元数据并可以使用Injector注入器注入 声明当前类有一些依赖,当依赖注入器创建该类的实例时,这些依赖应该被注入到构造函数中。
@NgModule--NgModule是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。其中最重要的属性是: declarations -声明本模块中拥有的视图类。(Angular有三种视图类:组件、指令和管道?。) exports - declarations的子集,可用于其它模块的组件模板。Ps:模块导出声明 imports -本模块声明的组件模板需要的类所在的其它**模块。Ps:模块导入声明*

ViewChild与ContentChild的联系和区别

​ ViewChild和ContentChild其实都是从子组件中获取内容的装饰器,它们本质的区别其实就只是在于方法调用的时机以及获取内容的地方:

​ 1、时机:ViewChild在ngAfterViewInit的回调中调用,ContentChild在ngAfterContentInit的回调用调用;

​ 2、获取内容的地方:ViewChild从模板中获取内容,ContentChild需要从ng-content 中投影的内容中获取内容,也就是没有使用ng-content投影就无法获取内容

三、模板语法

1)插值表达式({{···}})

把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。

{{obj.name}} {{value}} {{1+2}} <img src="{{heroImageUrl}}" style="height:30px"> JavaScript 中那些具有或可能引发副作用的表达式是被禁止的,包括: 赋值 (=, +=, -=, ...)、 new 运算符、 使用 ; 或 , 的链式表达式、 自增和自减运算符:++ 和 --。

和 JavaScript 语法的其它显著不同包括: 不支持位运算 | 和 &、 具有新的模板表达式运算符,比如 |、?. 和 !。

2)表达式上下文

​ 表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。例如:<div *ngFor="let hero of heroes">{{hero.name}}

​ <input #hero> {{hero.value}} ​ 组件具有一个名叫 hero 的属性,而 *ngFor 声明了一个也叫 hero 的模板变量。 在 {{hero.name}} 表达式中的 hero 实际引用的是模板变量,而不是组件的属性。

3)模板语句

​ 来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件

<button (click)="deleteHero()">Delete hero</button>  		
//deleteHero 就是这个数据绑定组件上的一个方法。
<button (click)="onSave($event)">Save</button>  		
//传递$event 对象
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>  
/deleteHero(hero) 中,hero 是一个模板输入变量,而不是组件中的 hero 属性
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>  //表单提交

模板表达式操作符 //过滤显示

管道操作符 ( | )  {{title | uppercase}}   

安全导航操作符 ( ?. ) 和空属性路径 //保护出现在属性路径中 null 和 undefined 值。

{{currentHero?.name}}

非空断言操作符(!) //不允许 null 或 undefined 值

{{hero!.name}}

类型转换函数 anyany (any( <表达式> )) //把表达式转换成 any 类型

{{$any(hero).marker}}

4)绑定语法

数据方向语法绑定类型
单向 从数据源 到视图{{expression}}[target]="expression"bind-target="expression"插值表达式 属性 Attribute CSS 类 样式
从视图到数据源的单向绑定(target)="statement"on-target="statement"事件
双向[(target)]="expression"bindon-target="expression"双向

HTML attribute 与 DOM property 的对比 时,它的 value这个attribute为Bob,这个 property 被初始化为 “Bob”。当浏览器中输入值时,property会发生改变,attribute还是Bob。

5)绑定目标

绑定类型目标范例
属性元素的 property 组件的 property 指令的 property<img [src]="heroImageUrl"><app-hero-detail [hero]="currentHero"><div [ngClass]="{'special': isSpecial}">
事件元素的事件 组件的事件 指令的事件<button (click)="onSave()">Save<app-hero-detail (deleteRequest)="deleteHero()"><div (myClick)="clicked=$event" clickable>click me
双向事件与 property<input [(ngModel)]="name">
Attributeattribute(例外情况)<button [attr.aria-label]="help">help
CSS 类class property<div [class.special]="isSpecial">Special
样式style property<button [style.color]="isSpecial ? 'red' : 'green'">

四、指令

属性型指令改变一个元素的外观或行为。例如,内置的 NgStyle 指令可以同时修改元素的多个样式。

创建指令: ng generate directive highlight

1)属性型指令

1.自定义指令
///highlight.directive.ts
import { Directive, ElementRef } from '@angular/core';
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
    constructor(el: ElementRef) {
       el.nativeElement.style.backgroundColor = 'yellow';
    }
}

//html
<p appHighlight>Highlight me!</p>
///highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {

  constructor(private el: ElementRef) { }
  //输入型属性
  @Input() defaultColor: string;
  @Input('appHighlight') highlightColor: string;  

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || this.defaultColor || 'red');
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
//html
<p [appHighlight]="'red'" defaultColor="violet">Highlight me!</p>
2.ngStyle
//HTML
<div class="box-top">
  <div [ngStyle]="setStyle()">ngStyle</div>    //设置行内样式方法
  <div [ngStyle]="currentStyles">hello</div>	//设置行内样式
</div>
//ts
export class BoxTopComponent implements OnInit {
  constructor() { }
  setStyle() {     //内部定义行内样式
    return {
      'font-size': '22px',
      'color': 'blue'
    }
  }
//
currentStyles: {};
setCurrentStyles() {
  this.currentStyles = {
    'font-style':  this.canSave      ? 'italic' : 'normal',
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal', //可进行判断取值
    'font-size':   this.isSpecial    ? '24px'   : '12px'
  };
}
  ngOnInit() {
  }
}
3.ngClass
<li *ngFor="let val of arr,index as key,first as f" [ngClass]="{'aaa': key==0?true:false}">{{key}}>>{{val}}</li>       //aaa为class名,第一个为布尔值(判断加不加)
  <li *ngFor="let val of arr,index as key,first as f" [ngClass]="['aaa','bbb']">{{key}}>>{{val}}</li>      //数组形式添加,覆盖添加元素的class
  <li *ngFor="let val of arr,index as key,first as f" [ngClass]="'aaa bbb'">{{key}}>>{{val}}</li>      //字符串类型添加
  
  [ng-class] = "'red':key==0"   //class:判断
  [ng-class] = "'red':key== 'key'"   //class:判断

2)结构性指令

结构型指令的职责是 HTML 布局。 它们塑造或重塑 DOM 的结构,比如添加、移除或维护这些元素。

ngIf

NgIf - 根据条件把一个元素添加到 DOM 中或从 DOM 移除

<app-hero-detail *ngIf="isActive"></app-hero-detail>   //isActive为真时,添加,反之移除

ngFor

//遍历数组

<li class="her" *ngFor="let hero of heros;trackBy:trackByHeros //trackByHeros方法更新时如果相同时不需更新" (click)="onSelect(hero)">
trackByHeros(id:number ,hero):number{      //该方法检测如果更新时,前后值一样,则无需刷新
return hero.id
}

//遍历对象

objectKeys = Object.keys;
items = { keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3' };
<div *ngFor="let key of objectKeys(items)">{{key + ' : ' + items[key]}}</div>

//遍历块

<ng-template ngFor let-item [ngForOf]="userAllMsgArr" let-i="index" [ngForTrackBy]="trackByAllMsg" >
    <div *ngIf="item.isTrue">
    	<li>{{i}}---{{item.name}}</li>
    </div>
</ng-template>

NgSwitch

​ 一组指令,用来在多个可选视图之间切换。可以从多个可能的元素中根据switch 条件来显示某一个。 Angular 只会把选中的元素放进 DOM 中

NgSwitch 是一个属性型指令,而不是结构型指令。 它要修改的是所在元素的行为,而不会直接接触 DOM 结构。

NgSwitchCaseNgSwitchDefault 都是结构型指令。

NgSwitchCase 会在它绑定到的值等于候选值时,把它所在的元素加入到 DOM 中。

NgSwitchDefault 会在没有任何一个 NgSwitchCase 被选中时把它所在的元素加入 DOM 中。

<input type="text" [(ngModel)]="num">
<p [ngSwitch]="num">
	<span *ngSwitchCase="'1'">值为1</span>
	<span *ngSwitchCase="'2'">值为2</span>
	<span *ngSwitchCase="'3'">值为3</span>
	<span *ngSwitchDefault>默认值10</span>
</p>

ng-content

ng-content是内容映射指令(也叫内容嵌入),内容映射指的是在组件中嵌入模板代码,方便定制可复用的组件,很好地扩充组件的功能,方便代码的复用。ng-content相当于一个占位符(留了个位置),类似于路由出口router-outlet一样。之后会把相应的内容放到这个位置上来。

<ng-content select="div"></ng-content>
// select="xx"选择,xx对应html里面标签或者组件的名字。比如select="div"表示ng-content位置只会放div标签。

<ng-content select=".select-class"></ng-content>
// select=".xx"选择,xx对应html标签或者组件的class名字。比如select=".select-class"表示ng-content位置只会放设有class="select-class"的html标签或者组件。

<ng-content select="[name=test]"></ng-content>
//
select="[key=value]"选择,key-value的形式。选择设置了属性key=“value“的html标签或者组件。比如select=”[name=test]"表示ng-content位置只会放设置了属性name=”test“的html标签或者组件。
select="[key]" 也是类型,ng-content会选择设置有key的属性的html标签或者组件。

<div name="test">我是第一号位置 div[name="test"]</div>
强调一点select的值不能设置为动态的

ngProjectAs

// 我们先自定义一个组件app-content-section 里面会使用ng-content

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

@Component({
    selector: 'app-content-section',
    template: `
        <div>
            <h1>ng content</h1>
            <div style="background-color: #039be5">
                <ng-content select="app-content-child"></ng-content>
            </div>
        </div>
    `,
    styleUrls: ['./content-section.component.less']
})
export class ContentSectionComponent {
}

// 下面中情况下 ng-content没有投射到对应的内容

<app-content-section>
    <ng-container>
        <app-content-child [title]="'测试下'"></app-content-child>
    </ng-container>
</app-content-section>

// 通过使用 ngProjectAs 让ng-content的内容能正确的投射过来。

<app-content-section>
    <ng-container ngProjectAs="app-content-child">
        <app-content-child [title]="'测试下'"></app-content-child>
    </ng-container>
</app-content-section>

ng-conent提供了@ContentChild和@ContentChildren来获取ng-conent里面包含的组件(类似@ViewChild和@ViewChildren)。获取到ng-conent里面的组件之后你就可以为所欲为了。

​ 我觉得如果有需要获取ng-content包含组件的情况,前提条件是咱得对放在ng-content位置的是啥类型的组件心里有数。有一点要注意@ContentChild和@ContentChildren所在的ts文件一定是有ng-content对应的哪个ts文件。可千万别搞错了。

// 定义一个app-content-section组件

import {AfterContentInit, Component, ContentChild, ContentChildren, QueryList} from '@angular/core';
import {ContentChildComponent} from '../child/content-child.component';

/**
 * 想获取ng-content里面的组件的使用@ContentChild或者@ContentChildren
 */
@Component({
    selector: 'app-content-section',
    template: `
        <div>

            <h1>ng content</h1>
            <!--这里我们确定我们这里会放ContentChildComponent组件,才好使用@ContentChild和@ContentChildren-->
            <ng-content></ng-content>

        </div>
    `,
    styleUrls: ['./content-section.component.less']
})
export class ContentSectionComponent implements AfterContentInit {
    
    // 通过 #section_child_0 获取组件
    @ContentChild('section_child_0')
    childOne: ContentChildComponent;
    // 通过 ContentChildComponent 组件名获取组件
    @ContentChildren(ContentChildComponent)
    childrenList: QueryList<ContentChildComponent>;

    ngAfterContentInit(): void {
        console.log(this.childOne);
        this.childrenList.forEach((item) => {
            console.log(item);
        });
    }

}

// 使用app-content-section

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

@Component({
    selector: 'app-ng-content',
    template: `
        <app-content-section>
            <app-content-child #section_child_0 [title]="title_0"></app-content-child>
            <app-content-child #section_child_1 [title]="title_1"></app-content-child>
        </app-content-section>
    `,
    styleUrls: ['./ng-content.component.less']
})
export class NgContentComponent {

    title_0 = 'child_0';
    title_1 = 'child_1';

}


ng-template

ng-template是Angular 结构型指令中的一种,用于定义模板渲染HTML(模板加载)。定义的模板不会直接显示出来,需要通过其他结构型指令(如 ng-if)或 template-ref 将模块内容渲染到页面中。

​ 上面既然说了ng-template的内容默认是不会在页面上显示出来的。这肯定不行呀,咱们使用ng-template不管中间的原因是啥,反正最后肯定是要把这些内容显示在界面上的。那么咱们有哪些办法来显示ng-template的内容呢。

<!-- 通过ngIf结构型指令显示ng-template的内容 -->
<div class="lessons-list" *ngIf="condition else elseTemplate">
    判断条件为真
</div>
<ng-template #elseTemplate>
    <div>判断条件为假</div>
</ng-template>

ng-container

ng-container既不是一个Component,也不是一个Directive,只是单纯的一个特殊tag。ng-container可以直接包裹任何元素,包括文本,但本身不会生成元素标签,也不会影响页面样式和布局。包裹的内容,如果不通过其他指令控制,会直接渲染到页面中。咱们可以把ng-container简单理解为一个逻辑容器。用来做一些逻辑处理的。

​ ng-container一个重要的作用就是和ng-template一起使用。ng-container还有一个用处就是配合ngFor和×ngIf使用。我们知道ngFor和×ngIf不能同时处在一个元素上。所以咱们想要在不添加额外的html标签的情况下达到同样的效果就得请出ng-container。具体参见如下的代码。

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

@Component({
    selector: 'app-ng-container',
    template: `
        <h1>ng-container</h1>
        <ul>
            <ng-container *ngFor="let item of list;let index=index">
                <li *ngIf="index%2 === 0">
                    {{"index is " + index + " value is " + item}}
                </li>
            </ng-container>
        </ul>
    `,
    styleUrls: ['./ng-container.component.less']
})
export class NgContainerComponent {

    list = ['1号位置', '2号位置', '3号位置', '4号位置', '5号位置', '6号位置', '7号位置', '8号位置', '9号位置'];
}


五、管道

<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
AsyncPipe从一个异步回执中解出一个值。
CurrencyPipe把数字转换成金额字符串, 根据本地化规则进行格式化,这些规则会决定分组大小和分组分隔符、小数点字符以及其它与本地化环境有关的配置项。
DatePipe根据区域设置规则格式化日期值。
DecimalPipe把数字转换成字符串, 根据本地化规则进行格式化,这些规则会决定分组大小和分组分隔符、小数点字符以及其它与本地化环境有关的配置项。
I18nPluralPipe将值映射到字符串,该字符串根据本地环境中的规则对值进行多元化处理。
I18nSelectPipe显示与当前值匹配的字符串的通用选择器。
JsonPipe把一个值转换成 JSON 字符串格式。在调试时很有用。
KeyValuePipe将对象或映射转换为键值对数组。
LowerCasePipe把文本转换成全小写形式。
PercentPipe把数字转换成百分比字符串, 根据本地化规则进行格式化,这些规则会决定分组大小和分组分隔符、小数点字符以及其它与本地化环境有关的配置项。
SlicePipe从一个 ArrayString 中创建其元素一个新子集(slice)。
TitleCasePipe把文本转换成标题形式。 把每个单词的第一个字母转成大写形式,并把单词的其余部分转成小写形式。 单词之间用任意空白字符进行分隔,比如空格、Tab 或换行符。
UpperCasePipe把文本转换成全大写形式。

六、生命周期

ngOnChanges()当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 SimpleChanges 对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在 ngOnInit() 之前。 父组件在初始化或者在修改子组件的输入属性时被调用。引用类型改变,不触发,初始化输入属性
ngOnInit()在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮 ngOnChanges() 完成之后调用,只调用一次
ngDoCheck()检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。在每个 Angular 变更检测周期中调用,ngOnChanges()ngOnInit() 之后。 数变更检测(zone.js) 发生的时候被调用,很频繁的被调用,只要视图有变化都会触发
ngAfterContentInit()当把内容投影进组件之后调用。第一次 ngDoCheck() 之后调用,只调用一次。 插槽值初始化时调用(一次)
ngAfterContentChecked()每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit() 和每次 ngDoCheck() 之后调用 对插槽内容的初始化和变更检测,在视图初始化之前调用。此时可以更改值
ngAfterViewInit()初始化完组件视图及其子视图之后调用。第一次 ngAfterContentChecked() 之后调用,只调用一次。 模板初始化时调用(一次)
ngAfterViewChecked()每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。 模板变化检测
ngOnDestroy()当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在 Angular 销毁指令/组件之前调用。 路由切换的时候,前一个组件销毁,后一个组件创建

七、响应式表单

import { FormsModule } from @angular/forms;   //在根模块中导入FormsModule
// 在所有模块中都能使用
@NgModule({
  FormsModule
})

响应式表单https://blog.csdn.net/kuangshp128/article/details/71467207
//app.module.ts 
import { ReactiveFormsModule } from '@angular/forms';   //引入ReactiveFormsModule
@NgModule({
  imports: [
    // other imports ...
    ReactiveFormsModule   		//导入ReactiveFormsModule
  ],
})
export class AppModule { }

//ProfileEditor.compontent.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';  //引入FormGroup,FormControl
@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

//html文件
<form [formGroup]="profileForm">
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>
  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>
</form>

anguar的响应式表单简化了管理数据时响应式风格的编码实现,使用了在无视图数据模型(从服务器获取)以及以视图为导向的模型用于保持屏幕上HTML控件显示的值与状态。响应式表单提供了响应式模式测试以及验证上的便利。
FormControl类是angular响应式表单最基本的构造快,要注册单个的表单控件,请在组件中导入FormControl类,并创建一个FormControl的新实例,把它保存在某个属性里面。

把表单控件分组
FormControl的实例能控制单个输入框所对应的控件,FormGroup可以控制一组FormControl实例的表单状态,当创建FormGroup时,其中的每一个控件都会根据名字进行跟踪

响应式表单是同步的。模板驱动表单是异步的,这是其区别的根源。

setValue方法会在赋值给任何表单控件之前先检查数据对象的值。
它不会接受一个与FormGroup结构不同或缺少表单组中任何一个控件的数据对象。 这种方式下,如果我们有什么拼写错误或控件嵌套的不正确,它就能返回一些有用的错误信息。 patchValue会默默地失败。而setValue会捕获错误,并清晰的报告它。

八、变化检测机制

  • 可自定义的变化检测策略: DefaultonPush

  • 可自定义的变化检测操作:markForcheck()detectChanges()detach()reattach()checkNoChanges()

    @Component({
      template: `
        <h2>{{vData.name}}</h2>
        <span>{{vData.email}}</span>
      `,
      // 设置该组件的变化检测策略为onPush
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    class VCardCmp {
      @Input() vData;
    }
    
    当vData的属性值发生变化的时候,这个组件不会发生变化检测,只有当vData重新赋值的时候才会。一般,只接受输入的木偶子组件(dumb components)比较适合采用onPush策略。
    
  • markForCheck():将检查组件的所有父组件所有子组件,即使设置了变化检测策略为onPush

  • detach():将变化检测对象脱离检测对象树,不再进行变化检查;结合detectChanges可实现局部变化检测

  • detectChanges():将检测该组件及其子组件,结合detach可实现局部检测

  • checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成

  • reattach():将脱离的变化检测对象重新链接到变化检测树上

@Component({
  template: '{{counter}}',
  changeDetection: ChangeDetectionStrategy.OnPush   //使用onPush
})
class CartBadgeCmp {
  @Input() addItemStream:Observable<any>;
  counter = 0;
  constructor(private cd: ChangeDetectorRef) {}
  ngOnInit() {
    this.addItemStream.subscribe(() => {
      this.counter++;        // 数据模型发生变化
      this.cd.markForCheck(); // 手动触发检测
    })
  }
}
@Component({
  template: `{{counter}}
  <input type="check" (click)="toggle()">`, 
})
class CartBadgeCmp { 
  counter = 0;
  detectEnabled = false;
  constructor(private cd: ChangeDetectorRef) {}
  ngOnInit() {
    // 每10毫秒增加1
    setInterval(()=>{this.counter++}, 10);
  }
  toggle(){
      if( this.detectEnabled ){
          this.cd.reattach();  // 链接上变化检测树
      }
      else{
          this.cd.detach(); // 脱离变化检测树
      }
  }
}

  • CheckOnce:表示只检查一次,调用detectChanges之后状态将会变为Checked
  • Checked:表示在状态变为CheckOnce之前会跳过所有检测
  • CheckAlways:表示总是接受变化检测,每次调用detectChanges后状态还是CheckAlways
  • Detached:代表变化检测对象脱离了变化检测对象树,不再进行变化检测
  • Errored:表述变化测试对象发生错误,变化检测实效
  • Destroyed:表示变化检测对象已经被销毁

进阶篇

一、NgZone

Angular为我们提供了NgZone服务,对于一些频繁的操作,可以不去触发变更检测。

import { NgZone } from '@angular/core';
  constructor(
  private zone: NgZone
  ) { }
  //使得Angular不跟踪变化,调用runOutsideAngular方法,Angular不会对里面的变化进行跟踪。
  this.zone.runOutsideAngular(() => {
      for (let i = 0; i < 100; i++) {
        setInterval(() => this.counter++, 10);
      }
    });
//重新跟踪变化, 如果此时又想让Angular跟踪foo的变化,用其提供的run方法。
this.zone.run(() => {
  setTimeout(() => this.foo = this.foo, 1000);
});

二、RxJs

RxJS是ReactiveX编程理念的JavaScript版本。ReactiveX是一种针对异步数据流的编程。简单来说,它将一切数据,包括HTTP请求,DOM事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能

Observable可观察序列--一系列值的生产者

数据就在Observable中流动,你可以使用各种operator对流进行处理

创建Observable

  • Observable.from(), 把数组或iterable对象转换成Observable
  • Observable.create(), 返回一个可以在Observer上调用方法的Observable.
  • Observable.fromEvent(),Observable.fromEvent($input, 'click'), 从DOM事件创建序列,把event转换成Observable.
  • Observable.fromPromise(), Observable.fromPromise(promise),把Promise转换成Observable.
  • Observable.range(), 在指定范围内返回一串数.
  • Observable.of(...args),Observable.of()可以将普通JavaScript数据转为可观察序列
  • Observable.ajax(), 从ajax创建一个observable
//observable_from.ts
import { Observable } from "rxjs/Observable"; // 这里没有使用Rx对象而是直接使用其下面的Observable对象, 因为Rx里面很多的功能都用不上.
import 'rxjs/add/observable/from'; // 这里我需要使用from 操纵符(operator)
let persons = [
    { name: 'Dave', age: 34, salary: 2000 },
    { name: 'Nick', age: 37, salary: 32000 },
    { name: 'Howie', age: 40, salary: 26000 },
    { name: 'Brian', age: 40, salary: 30000 },
    { name: 'Kevin', age: 47, salary: 24000 },
];
let index = 1;
Observable.from(persons)
    .subscribe(
        person => {
            console.log(index++, person.name);
        },
        err => console.log(err),
        () => console.log("Streaming is over.")
    );
    
    //输出
    0 Dave
    1 Nick
    2 Howie
    3 Brian
    4 Kevin
    Streaming is over
    
//observable_creates.ts:
import { Observable } from "rxjs/Observable";
function getData() {
    let persons = [
        { name: 'Dave', age: 34, salary: 2000 },
        { name: 'Nick', age: 37, salary: 32000 },
        { name: 'Howie', age: 40, salary: 26000 },
        { name: 'Brian', age: 40, salary: 30000 },
        { name: 'Kevin', age: 47, salary: 24000 },
    ];
    return Observable.create(
        observer => { // 这部分就是subscribe function
            persons.forEach(p => observer.next(p));
            observer.complete();
        }
    );
}
getData()
    .subscribe(
        person => console.log(person.name),
        err => console.error(err),
        () => console.log("Streaming is over.")
    );
     //输出
     Dave
    Nick
     Howie
     Brian
     Kevin
    Streaming is over

Subject---即是Observable又是Observer

//subject.ts
import { Subject } from "rxjs/Subject";
const subject = new Subject();
const subscriber1 = subject.subscribe({
    next: (v) => console.log(`observer1: ${v}`)
});
const subscriber2 = subject.subscribe({
    next: (v) => console.log(`observer2: ${v}`)
});
subject.next(1);
subscriber2.unsubscribe();
subject.next(2);
const subscriber3 = subject.subscribe({
    next: (v) => console.log(`observer3: ${v}`)
});
subject.next(3);
//输出
observer1:1
observer2:1
observer1:2
observer1:3
observer3:3
// 订阅者1,2从开始就订阅了subject. 然后subject推送值1的时候, 它们都收到了. 
// 然后订阅者2, 取消了订阅, 随后subject推送值2, 只有订阅者1收到了.
// 后来订阅者3也订阅了subject, 然后subject推送了3, 订阅者1,3都收到了这个值.

BehaviorSubject

BehaviorSubject 是Subject的一个变种, 它有一个当前值的概念, 它会把它上一次发送给订阅者值保存起来, 一旦有新的Observer进行了订阅, 那这个Observer马上就会从BehaviorSubject收到这个当前值.

特点:

  • 它代表一个随时间变化的值, 例如, 生日的流就是Subject, 而一个人的年龄流就是BehaviorSubject.
  • 每个订阅者都会从BehaviorSubject那里得到它推送出来的初始值和最新的值.
  • 用例: 共享app状态.
//behavior-subject.ts
import { BehaviorSubject } from "rxjs/BehaviorSubject";
const subject = new BehaviorSubject(0);
subject.subscribe({
    next: v => console.log(`Observer1: ${v}`)
});
subject.next(1);
subject.next(2);
subject.subscribe({
    next: v => console.log(`Observer2: ${v}`)
});
subject.next(3);

//输出:
Observer1:0
Observer1:1
Observer1:2
Observer2:2
Observer1:3
Observer2:3

常用Operators:

filter(),map()

import { Observable,of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
const nums = of(12345);
const squareOddVals = pipe(  
filter((n: number) => n % 2 !== 0),  
map(n => n * n));
const squareOdd = squareOddVals(nums);
squareOdd.subscribe(x => console.log(x));
// 1// 9// 25

share() 操作符允许多个订阅者共享同一个Observable. 也就是把Cold变成Hot.

Hot 和 Cold Observable
Cold: Observable可以为每个Subscriber创建新的数据生产者
Hot: 每个Subscriber从订阅的时候开始在同一个数据生产者那里共享其余的数据.
从原理来说是这样的: Cold内部会创建一个新的数据生产者, 而Hot则会一直使用外部的数据生产者.
举个例子:
Cold: 就相当于我在腾讯视频买体育视频会员, 可以从头看里面的足球比赛.
Hot: 就相当于看足球比赛的现场直播, 如果来晚了, 那么前面就看不到了.

concat: 按顺序合并observables. 只会在前一个observable结束之后才会订阅下一个observable.

merge:把多个输入的observable交错的混合成一个observable, 不按顺序.

forkjoin并联请求

多个请求,无所谓先后顺序,等到全部请求完成后执行一定的操作时,需要使用并联请求;

能够实现多异步请求后再执行某方法。

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';

Observable.forkJoin([postA(), postB()]).subscribe(results => {
    this.storage.set('categories', results[0].data);
    this.storage.set('regions', results[1].data);
});

range操作符

range 操作符顺序发出一个区间范围内的连续整数, 你可以决定区间的开始和长度。

import { Component, OnInit } from '@angular/core';
import { range } from 'rxjs/observable/range';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-create',
  templateUrl: './create.component.html',
  styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
  constructor() { }
  ngOnInit() {
    range(600, 10).subscribe((val: number) => {
      console.log(val);
      //600 601 602 603 ~~~~609
    });
  }
}

三、解决angular-cli打包时内存溢出问题的方法**

CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory JavaScript堆内存不足,这里说的 JavaScript 其实就是 Node,我们都知道 Node 是基于V8引擎,在一般的后端开发语言中,在基本的内存使用上没有什么限制,但在 Node 中通过 JavaScript 使用内存时只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB),这就是我们编译项目时为什么会出现内存泄露了,因为前端项目如果非常的庞大,webpack 编译时就会占用很多的系统资源,如果超出了V8对 Node 默认的内存限制大小就会出现刚刚我截图的那个错误了,那怎么解决呢?V8依然提供了选项让我们使用更多的内存。Node 在启动时可以传递 --max-old-space-size 或 --max-new-space-size 来调整内存大小的使用限制,示例如下

//package.json

"scripts": {
    "ng": "ng",
    "start": "ng serve --host 0.0.0.0 --port 8889 -o ",
    "build": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --prod --aot",  ///最大内存容量8G
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },

node --max-old-space-size=1700 test.js // 单位为MB
// 或者
node --max-new-space-size=1024 test.js // 单位为KB

//以此放宽V8默认的内存限制,避免在执行过程中稍微多用了一些内存就轻易崩溃

四、angular 全局监听键盘事件

根模块中不要引用,组件模块中引用 import { EventManager } from '@angular/platform-browser';

ngOnInit中注册全局监听

 import { Component, OnInit } from '@angular/core';
 import { FormsModule } from '@angular/forms';
 import { EventManager } from '@angular/platform-browser';
       
@Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css']
 })
 export class AppComponent implements OnInit {
  constructor(
     private eventManager:EventManager
   ){}
   
   ngOnInit(): void {
     this.eventManager.addGlobalEventListener('window','keyup.esc',()=>{
       alert('你点击了ESC');
     });
   }
 }

结语

Angular的知识远不止于此,这里只是简单的整理了一些,我会继续学习积累,做的更加精确细致。

本人首次发表文章,在编写过程中难免有不准确的地方,如若发现,还望大家能够指正,避免错误引导热爱前端的小伙伴们,谢谢!