Appearance
angular 简结 
- angular 项目结构
 - angular 自带属性和指令与装饰器
 - angular 命令
 - angular 自定义属性指令 directive
 - angular 生命周期
 - angular 双向数据绑定原理
 - 请求后台资源
 - angular 路由
 - 父子组件通信
 - 组件间通信 service 依赖注入
 - 页面传参与获取
 - 使用 cookie
 - 宿主事件监听器
 - 请求响应拦截
 - rxjs
 - 引入外部 js
 - 结构指令和属性指令
 - 组件中的声明和提供与导入
 - 构建块
 - 组件和指令的区别
 - ngBootstrap
 - 路由守卫
 - 兼容IE
 - 同页面前进后退状态记录
 
angular 项目结构 
e2e 文件夹:end to end,测试目录,主要用于集成测试。
node_modules:项目的模块依赖目录。
src:项目的源代码。
- app:项目的主组件目录。 
- components:组件。
 - directives:自定义指令。
 - modules:自定义模块。
 - pages:页面。
 - services:服务。
 - app-routing.module.ts:组件路由配置文件。
 - app.component.css:组件私有 css 样式文件。
 - app.component.html:组件的模板文件。
 - app.component.spec.ts:组件的单元测试文件。
 - app.compenent.ts:组件 typescript 配置文件。
 - app.module.ts:组件模型配置文件。
 
 - assets:项目的资源目录。
 - environments:项目的环境配置目录
 - index.html:主页面。
 - karma.conf.js:karma 测试的配置文件。
 - main.ts:脚本入口文件。
 - polyfills.ts:兼容性检测配置文件。
 - style.css:全局 css 样式文件。
 - test.ts:单元测试入口文件。
 
- app:项目的主组件目录。 
 .editorconfig:编辑器配置文件。
.gitignore: git 版本控制时忽略的文件(此文件中配置的文件不纳入版本控制)。
.angular.json:angular 配置文件。
.package-lock.json:锁定项目依赖模块的版本号。
.package.json:配置项目依赖模块。
.README.md:项目说明文件
.tsconfig.json:typescript 配置文件。
.tslint.json:typescript 代码检测配置文件。
构建块:组件、模板、元数据、数据绑定、指令、服务、依赖注入
angular 自带属性和指令与装饰器 
- 条件判断 *ngIf="..."
 - 显示隐藏 [hidden]=""
 - 循环 *ngFor="let i of/in is" 
- for in 遍历的是数组的索引(即键名),而 for of 遍历的是数组元素值。
 - 带索引:
 
html<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div> - NgSwitch
 
html
<div [ngSwitch]="hero?.emotion">
  <app-happy-hero *ngSwitchCase="'happy'" [hero]="hero"></app-happy-hero>
  <app-sad-hero *ngSwitchCase="'sad'" [hero]="hero"></app-sad-hero>
  <app-confused-hero
          *ngSwitchCase="'app-confused'"
          [hero]="hero"
  ></app-confused-hero>
  <app-unknown-hero *ngSwitchDefault [hero]="hero"></app-unknown-hero>
</div>数据双向绑定 [(ngModel)]="..."
需导入 FormsModule,配合@Input()使用
点击事件 (click)="..."
属性绑定 [id]="id" [title]="msg"
a 路由跳转 routerLink="..."
模板引用变量 #var
管道 |
[ngClass]、[ngStyle]
html
public flag=false;
<div [ngClass]="{'red': flag, 'blue': !flag}">
  这是一个 div
</div>
public attr='red';
<div [ngStyle]="{'background-color':attr}">你好 ngStyle</div>装饰器负责把元数据附加到类上
常用装饰器:@Component,@Input,@Output,@Directive,@viewChildren,@HostListener,@Injectable
angular 命令 
创建新项目:ng new {项目名} 本地浏览:ng serve ,后面加上--open 可自动打开浏览器
生成服务器文件:ng build
生成组件:ng generate component {(路径/)组件名} ,后面加上--inline-style,CLI 就会定义一个空的 styles 数组
生成服务:ng generate service {(路径/)服务名} ,后面加上--module=app 让 CLI 自动把它提供给 AppModule
生成模块:ng generate module {(路径/)模块名}, 后面加上--flat 会把这个文件放进了 src/app 中,而不是单独的目录中, 加上--module=app 告诉 CLI 把它注册到 AppModule 的 imports 数组中。
生成类:ng generate class
生成指令:ng generate directive
测试:ng test
其中 generate 可省略为 g
angular 自定义属性指令 directive 
directive 可以对 DOM 元素添加行为,且可以添加多个,但它没有 view
typescript
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) { }
  @Input('appHighlight') highlightColor: string;
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'red');
  }
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }angular 生命周期 
- ngOnInit() 用于初始化属性,ajax
 - ngOnChanges() 用于监听组件传值变化、绑定数据变化,!!注意:首次调用一定会发生在 ngOnInit()之前,整个输入对象改变才能触发,只改变对象属性值不能触发
 - ngDoCheck() 发生 Angular 无法或不愿意自己检测的变化时作出反应。脏检查触发,触发场景多不建议用。
 - ngAfterContentInit() 当父组件向子组件投影内容的时.在子组件内会初始化父组件的投影内容,此时会调用
 - ngAfterContentChecked() 当父组件向子组件的投影内容发生改变时会调用
 - ngAfterViewInit() 可 dom 操作
 - ngAfterViewChecked() 初始化完组件视图及其子视图之后调用
 - ngOnDestroy() 每次销毁指令/组件之前调用
 
angular 双向数据绑定原理 
Angular 通过脏检测来进行双向数据绑定,在$digest cycle 流程里面,会从 rootscope 开始遍历,检查每个元素绑定的 watcher。
Angular 只有指定事件触发,才会进入$digest cycle:
- DOM 事件,比如用户输入文本,点击按钮等.
 - ajax 事件
 - 浏览器 location 变更事件
 - Timer 事件($timeout,$interval)
 - 执行$scope.$digest();或$scope.$apply()
 
请求后台资源 rxjs 与 axios 
rxjs 是一种针对异步数据流的编程,它将一切数据,包括 HTTP 请求,DOM 事件或者普通数据等包装成流的形式,使你能以同步编程的方式处理异步数据。
- 异步处理数据
 
typescriptlet stream = new Observable < any > (observer = >{ let count = 0; setInterval(() = >{ observer.next(count++); }, 1000); }); stream.filter(val = >val % 2 == 0).subscribe(value => console.log("filter>" + value)); stream.map(value = >{ return value * value }).subscribe(value = >console.log("map>" + value));- 请求数据
 
javascriptimport {HttpClient} from "@angular/common/http"; constructor(public http:HttpClient) { } //get var api = "http://a.itying.com/api/productlist"; this.http.get(api).subscribe(response => {console.log(response);}); //post doLogin() { // 手动设置请求类型 const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; var api = "http://127.0.0.1:3000/doLogin"; this.http.post(api, { username: '张三', age: '20' },httpOptions).subscribe(response = >{ console.log(response); }); } //jsonp this.http.jsonp(api,'callback').subscribe(response => {console.log(response); });axios
typescript
axios
        .get("/user?ID=12345")
        .then(function (response) {
          // handle success
          console.log(response);
        })
        .catch(function (error) {
          // handle error console.log(error);
        })
        .then(function () {
          // always executed
        });angular 路由与鉴权 
- 路由与子路由
 
typescript
const routes: Routes = [
  { path: "", redirectTo: "home", pathMatch: "full" }, //路径为空
  { path: "home", component: HomeComponent },
  {
    path: "product/:id",
    component: ProductComponent,
    children: [
      { path: "", component: ProductDescComponent },
      { path: "seller/:id", component: SellerInfoComponent },
    ],
  },
  { path: "**", component: Code404Component },
];- 路由守卫
 
typescript
const routes: Routes = [
  {
    path: '**', component: ***,
    canActivate: [LoginGuard],//进入守卫
    canDeactivate: [UnsaveGuard]//离开守卫
  }
]
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [LoginGuard,UnsaveGuard]
})
export class AppRoutingModule { }typescript
import { CanActivate } from "@angular/router";
export class LoginGuard implements CanActivate {
  canActivate() {
    let loggedIn: boolean = Math.random() < 0.5;
    if (!loggedIn) {
      console.log("用户未登录");
    }
    return loggedIn;
  }
}javascript
import { CanDeactivate } from "@angular/router";
import { ProductComponent } from "../product/product.component";
export class UnsaveGuard implements CanDeactivate<ProductComponent> {
  //第一个参数 范型类型的组件
  //根据当前要保护组件 的状态 判断当前用户是否能够离开
  canDeactivate(component: ProductComponent) {
    return window.confirm("你还没有保存,确定要离开吗?");
  }
}父子组件通信 
子组件
html
<h1>{{childTitle}}</h1>typescript
import { Component, OnInit, Input } from "@angular/core";
@Component({
  selector: "app-child",
  templateUrl: "./child.component.html",
  styleUrls: ["./child.component.sass"],
})
export class ChildComponent implements OnInit {
  private _childTitle: string = "子组件标题";
  //用于供父组件读写
  @Input()
  set childTitle(childTitle: string) {
    this._childTitle = childTitle;
  }
  get childTitle(): string {
    return this._childTitle;
  }
  //向父组件发送事件
  @Output()
  initEmit = new EventEmitter<string>();
  constructor() {}
  ngOnInit() {
    this.initEmit.emit("子组件初始化成功");
  }
  childPrint() {
    alert("来自子组件的打印");
  }
}父组件
html
<p>
  parent-and-child works!
</p>
<!--父组件调用子组件的方法,接收事件-->
<app-child
        childTitle="可设置子组件标题"
        (initEmit)="accept($event)"
        #child
></app-child>
<button (click)="child.childPrint()"></button>typescript
import { Component, OnInit } from "@angular/core";
@Component({
  selector: "app-parent",
  templateUrl: "./parent-and-child.component.html",
  styleUrls: ["./parent-and-child.component.sass"],
})
export class ParentAndChildComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
  accept(msg: string) {
    console.log(msg);
  }
}组件间通信 service 依赖注入 
- 服务
 
typescript
import { Component, Injectable, EventEmitter } from "@angular/core";
@Injectable()
export class myService {
  public info: string = "";
  constructor() {}
}- 组件 1
 
typescript
<p>child1 work</p>
<button (click)="showInfo()"></button>
import { Component, OnInit} from '@angular/core';
import { myService } from '../../../service/myService..service';
@Component({
  selector: 'app-child',
  templateUrl: './child1.component.html',
  styleUrls: [[./child1.component.sass']
})
export class Child1Component implements OnInit {
  constructor(
          public service: myService
  ) { }
  ngOnInit() {
  }
  showInfo() {
    alert(this.service.info);
  }
}- 组件 2
 
typescript
<p>child2 works!</p>
<button (click)="changeInfo()"></button>
import { Component, OnInit} from '@angular/core';
import { myService } from '../../service/myService..service';
@Component({
  selector: 'app-child2',
  templateUrl: './child2.component.html',
  styleUrls: [[./child2.component.sass']
})
export class Child2Component implements OnInit {
  constructor(
          public service: myService
  ) { }
  ngOnInit() {
  }
  changeInfo() {
    this.service.info = this.service.info + "1234";
  }
}页面传参与获取 
- 使用 routerLink 跳转
 
html
<a routerLink=["/exampledetail",id]></a>url为/exampledetail/id,使用snapshot.params获取参数
<a routerLink=["/exampledetail",{queryParams:object}] ></a>url为/exampledetail?...=...&...,使用snapshot.queryParams获取参数- 使用 navigate 跳转
 
typescript
this.router.navigate([[user', 1]);//使用snapshot.params
以根路由为起点跳转
this.router.navigate([[user', 1],{relativeTo: route});
默认值为根路由,设置后相对当前路由跳转,route是ActivatedRoute的实例,使用需要导入ActivatedRoute
this.router.navigate([[user', 1],{ queryParams: {id: '1',status: true});//使用snapshot.queryParams
路由中传参数 /user/1?id=1
        this.router.navigate([[user', 1],{ queryParams: {id: '1',status: true},skipLocationChange: true });
路由中传参数同上,但url不会变化
this.router.navigate([[user', 1],{ fragment: 'top' });
路由中锚点跳转 /user/1#top获取参数
- snapshot
 
typescriptimport { ActivatedRoute } from '@angular/router'; constructor( public activatedRoute: ActivatedRoute ) { }; ngOnInit(){ this.id= this.activatedRoute.snapshot.params[[id']; };- queryParams
 
typescriptid: number = 0; status: boolean = false; ngOnInit() { this.route.queryParams .subscribe((params: Params) => { this.id = params[[id']; this.status = params[[status']; }) }注意
跟 vue 不同无法通过 router.push 做到既传参数,url 又不显示参数
使用 cookie 
- npm install ngx-cookie-service
 - 根模块引入
 
typescript
import { CookieService } from "ngx-cookie-service";
providers: [CookieService];- 子模块使用
 
typescript
import { CookieService } from 'ngx-cookie-service';
constructor( private cookieService: CookieService ) { }
ngOnInit() {
  this.cookieService.set( name, value, time );
  this.cookieService.get( name );
}宿主事件监听器 
当宿主元素发出特定的事件时,Angular 就会执行所提供的处理器方法,并使用其结果更新所绑定到的元素。 如果该事件处理器返回 false,则在所绑定的元素上执行 preventDefault。
屏幕滚动监听
typescript
public isScollDown : boolean = false;
@HostListener('window:scroll', [[$event'])
public onScroll = (e) => {
  if(document.documentElement.scrollTop > 252){
    this.isScollDown = true;
  }
}请求响应拦截 
- http 拦截器
 
实现 HttpInterceptor 接口
typescript
import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpResponse,
} from "@angular/common/http";
import { Observable, of, throwError } from "rxjs";
import { catchError } from "rxjs/internal/operators";
import { Router } from "@angular/router";
import { Injectable } from "@angular/core";
import { AuthService } from "./../app.service";
import { environment } from "src/environments/environment";
/** Pass untouched request through to the next request handler. */
@Injectable()
export class MyInterceptor implements HttpInterceptor {
  constructor(private router: Router, private authService: AppService) {}
  intercept(
          req: HttpRequest<any>,
          next: HttpHandler
  ): Observable<HttpEvent<any>> {
    // 获取本地存储的token值,
    const authToken = this.authService.getAuthorizationToken();
    // 若token存在,则对请求添加请求头
    // 并格式化处理url地址,简化service中接口地址的编辑
    if (authToken) {
      const authReq = req.clone({
        headers: req.headers.set("Authorization", "bearer" + authToken),
        url: environment.api_endpoint + req.url,
      });
      return (
              next
                      // 返回处理后的请求
                      .handle(authReq)
                      // 返回结果错误处理
                      .pipe(catchError((error) => this.auth.handleError(error)))
      );
    }
    // 若token不存在,则不对请求进行处理
    return next
            .handle(req)
            .pipe(catchError((err: HttpErrorResponse) => this.handleData(err)));
  }
  private handleData(
          res: HttpResponse<any> | HttpErrorResponse
  ): Observable<any> {
    // 业务处理:一些通用操作
    switch (res.status) {
      case 401:
        console.log("not login");
        this.router.navigate(["/"]);
        return of(res);
        break;
      default:
    }
    return throwError(res);
  }
}在 app.module.ts 中 provide
typescript
providers: [
  {provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi:true}
],- axios 拦截器
 
在 angular-cli 项目的 src/ 文件夹下新建一个文件夹为 plugins,然后在 plugins/ 下新建 config.js 文件,写入如下代码
typescript
import axios from 'axios';//引入axios依赖
axios.defaults.timeout = 5000;
axios.defaults.baseURL ='';
// 定义加载动画
let loading = null
let loadingShow = false
//http request 封装请求头拦截器
axios.interceptors.request.use(
        config => {
          if (!loadingShow) {
            loadingShow = true
            loading = message.loading('数据加载中...', 0)
          }
          // 设置 token 判断是否存在token,如果存在的话,则每个http header都加上token
          if (sessionStorage.getItem('auth')) {
            conf.headers[[Authorize'] = sessionStorage.getItem('auth')
            return config;
          },
          error => {
            return Promise.reject(err);
          }
        );
//http response 封装后台返回拦截器
          axios.interceptors.response.use(
                  response => {
                    //当返回信息为未登录或者登录失效的时候重定向为登录页面
                    if(response.data.code == 'W_100004' || response.data.message == '用户未登录或登录超时,请登录!'){
                      // todo
                    }
                    return response;
                  },
                  error => {
                    return Promise.reject(error)
                  }
          )
// 移除拦截器
// var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
// axios.interceptors.request.eject(myInterceptor);
          /**
           * 封装get方法
           * @param url
           * @param data
           * @returns {Promise}
           */
          export function fetch(url,params={}){
            return new Promise((resolve,reject) => {
              axios.get(url,{
                params:params
              })
                      .then(response => {
                        resolve(response.data);
                      })
                      .catch(err => {
                        reject(err)
                      })
            })
          }
          /**
           * 封装post请求
           * @param url
           * @param data
           * @returns {Promise}
           */
          export function post(url,data = {}){
            return new Promise((resolve,reject) => {
              axios.post(url,data)
                      .then(response => {
                        resolve(response.data);
                      },err => {
                        reject(err)
                      })
            })
          }
          /**
           * 封装导出Excal文件请求
           * @param url
           * @param data
           * @returns {Promise}
           */
          export function exportExcel(url,data = {}){
            return new Promise((resolve,reject) => {
              axios({
                method: 'post',
                url: url, // 请求地址
                data: data, // 参数
                responseType: 'blob' // 表明返回服务器返回的数据类型
              })
                      .then(response => {
                        resolve(response.data);
                        let blob = new Blob([response.data], {type: "application/vnd.ms-excel"});
                        let fileName = "订单列表_"+Date.parse(new Date())+".xls" ;
                        if (window.navigator.msSaveOrOpenBlob) {
                          navigator.msSaveBlob(blob, fileName);
                        } else {
                          var link = document.createElement('a');
                          link.href = window.URL.createObjectURL(blob);
                          link.download = fileName;
                          link.click();
                          window.URL.revokeObjectURL(link.href);
                        }
                      },err => {
                        reject(err)
                      })
            })
          }
          /**
           * 封装patch请求
           * @param url
           * @param data
           * @returns {Promise}
           */
          export function patch(url,data = {}){
            return new Promise((resolve,reject) => {
              axios.patch(url,data)
                      .then(response => {
                        resolve(response.data);
                      },err => {
                        reject(err)
                      })
            })
          }
          /**
           * 封装put请求
           * @param url
           * @param data
           * @returns {Promise}
           */
          export function put(url,data = {}){
            return new Promise((resolve,reject) => {
              axios.put(url,data)
                      .then(response => {
                        resolve(response.data);
                      },err => {
                        reject(err)
                      })
            })
          }在组件中 import 以上文件并使用 axios
typescript
import axios from '../../Plugins/axios';
getSomething() {
  axios.get(...)
}rxjs 
- 引入
 
typescript
import { Observable, fromEvent, from, of, interval, throwError } from "rxjs";
import {
  mapTo,
  map,
  scan,
  mergeMap,
  concatMap,
  bufferTime,
  take,
  reduce,
  filter,
  throttleTime,
  throttle,
  distinctUntilChanged,
  debounce,
  debounceTime,
} from "rxjs/operators";- 使用
 
从 rxjs6 版本往后,所有的操作符需要再 pipe 中执行
typescript
ObservableObject.pipe(funcA,funcB,...).subscribe(...)- 创建操作符
 
- fromEvent: 创建一个 Observable,该 Observable 发出来自给定事件对象的指定类型事件
 
typescript
// 获取html元素
const btnElem = document.querySelector("button#rxjsBtn");
// 创建按钮的点击事件为可观察对象
fromEvent(btnElem, "click")
        .pipe(scan((count) => count + 1, 0)) // count为定义的变量;逗号后面的0为count的初始值;箭头后面的语句值为scan返回的值;
        .subscribe((count) => {
          console.log("fromEvent" + count);
        });
// 第一次点击输出: fromEvent1;第二次点击输出fromEvent2;依次同理- from: 将各种其他对象和数据类型转化为 Observables
 
typescript
const arrayData = [5, 6];
from(arrayData)
        .pipe(
                scan((scanData, item) => (scanData += item), 10),
                map((item) => item * 2)
        )
        .subscribe((data: any) => {
          console.log("from:" + data); //from:30 from:42
        });- of: 创建一个 Observable,它会依次发出由你提供的参数,最后发出完成通知。
 
typescript
of("value1", "value2").subscribe((data: any) => {
  console.log("of:" + data); //of:value1 of: value2
});- interval: 返回一个无线自增的序列整数
 
typescript
const numbers = interval(1000);
numbers.subscribe((x) => console.log("interval:" + x));
// 浏览器输出: interval:1 interval2 依次增加- create: 创建 Observable 对象, 当观察者( Observer )订阅该 Observable 时,它会执行指定的函数
 
typescript
new Observable((obsever) => {
  obsever.next("add");
  obsever.next("upt");
  obsever.complete();
  obsever.next("del");
})
        .pipe(map((data) => data + "Map"))
        .subscribe((data: any) => {
          console.log(data);
        });
// 浏览器输出: addMap uptMap- 转换操作符
 
- Map: 把每个源值传递给转化函数以获得相应的输出值
 
typescript
from([1, 2]).pipe(.map((item) => item * 2))
.subscribe((data: any) => { console.log('map:' + data);});
// 浏览器输出: map: 2 map: 4- MergeMap: 将每个源值投射成 Observable ,该 Observable 会合并到输出 Observable 中,可用于串联请求
 
typescript
const mergeA = of(1, 2, 3);
const mergeB = mergeA.pipe(
        map((r) => of(r)),
        mergeMap((r) => r)
);
mergeB.subscribe((c) => console.log("mergeMap:" + c));
// 浏览器输出: mergeMap1 mergeMap2 mergeMap3- MapTo: 类似于 map,但它每一次都把源值映射成同一个输出值。
 
typescript
of(1, 2, 3)
        .pipe(mapTo(33))
        .subscribe((data) => {
          console.log(data);
        });
// 浏览器输出: 3个33- scan: 对源 Observable 使用累加器函数,返回生成的中间值,可选的初始值
 
typescript
from([1, 2])
        .pipe(scan((acc, item) => (acc += item), 10)) // acc为一个新变量,item为[1,2]中的每一项, 10为新变量acc的默认初始值;返回新生成的中间值acc reduce同理
        .subscribe((v) => console.log(v));
//  浏览器输出 11  13- reduce: 和 scan 同理;只不过中间变量的值不会清 0,会保留上一次源操作之后的得到的中间值;并且只会输出最后一个值;
 
typescript
from([1, 2])
        .pipe(reduce((acc, item) => (acc += item), 10))
        .subscribe((v) => console.log(v));
// 输出13- 过滤操作符
 
- filter: 数据进行过滤返回你想要的数据
 
typescript
from([2, 3, 4])
        .pipe(filter((item) => item <= 3))
        .subscribe((v) => console.log(v));
// 浏览器输出: 2 3- throttleTime: 在一定时间范围内不管产生了多少事件,它只放第一个过去,剩下的都将舍弃
 
typescript
@ViewChild('child') child;
ngAfterViewInit() {
  this.do()
}
do() {
  // 一秒内只触发一次点击事件
  fromEvent(this.child.nativeElement, 'click').pipe(
        throttleTime(1000),
        scan(count => count + 1, 0))
        .subscribe(data => {
          console.log('点击了' + data + '次');
        });
}- distinctUntilChanged: 把相同的元素过滤掉,如果提供了比较器功能,则将为每个项目调用它以测试是否应该发出该值。如果未提供比较器功能,则默认使用相等性检查
 
typescript
of(1, 1, 2, 2, 2, 1, 1, 2, 3, 3, 4)
        .pipe(distinctUntilChanged())
        .subscribe((val) => {
          console.log(val);
        });
//1 2 1 2 3 4- throttle: 以某个时间间隔为阈值,在 durationSelector 完成前将抑制新值的发出
 
typescript
const source = interval(1000);
// 节流2秒后才发出最新值
const example = source.pipe(throttle((val) => interval(2000)));
// 输出: 0...3...6...9
const subscribe = example.subscribe((val) => console.log(val));- debounce: 根据一个选择器函数,舍弃掉在两次输出之间小于指定时间的发出值。
 
typescript
// 每1秒发出值, 示例: 0...1...2
const interval$ = interval(1000);
// 每1秒都将 debounce 的时间增加200毫秒
const debouncedInterval = interval$.pipe(
        debounce((val) => interval(val * 200))
);
/*
  5秒后,debounce 的时间会大于 interval 的时间,之后所有的值都将被丢弃
  输出: 0...1...2...3...4......(debounce 的时间大于1秒后则不再发出值)
*/
const subscribe = debouncedInterval.subscribe((val) =>
        console.log(`Example Two: ${val}`)
);- debounceTime: 舍弃掉在两次输出之间小于指定时间的发出值。
 
typescript
@ViewChild('child') child;
ngAfterViewInit() {
  this.do()
}
do() {
  // 一秒内只触发一次点击事件
  fromEvent(this.child.nativeElement, 'click').pipe(
        debounceTime(1000),
        scan(count => count + 1, 0))
        .subscribe(data => {
          console.log('点击了' + data + '次');
        });
}引入外部 js 
参考链接:Angular 引入外部 js 文件的方法(引入 jQuery)
tsconfig.json
在 compilerOptions 下新增:"allowJs":true
外部 js 文件放置
例如放在:assets/lib/jquery-3.4.1.js
angular.json
找到 architect 下的 scripts,加入"src/assets/lib/jquery-3.4.1.js"
调用
import * as ejs from 'src/assets/lib/jquery-3.4.1.js';
console.log(ejs("body").height());
结构指令和属性指令 
结构指令用于通过删除和添加 DOM 元素来更改 DOM 布局。示例是 NgFor 和 Nglf。
属性指令用作元素的特征,如 NgStyle。
组件中的声明和提供与导入 
- 声明:selector 使当前组件可用于其他组件
 - 提供:provider 用于依赖注入,例如向构造函数注入服务用来通信
 - 导入:import 用于导入其它模块,使当前模块可用
 
构建块 
- 组件
 - template
 - 元数据:@Component/@ViewChild/@ViewChildren/@HostListener/@Input/@Output/@Pipe/@Directive
 - 数据绑定
 - 指令
 - 服务
 - 依赖注入
 
组件和指令的区别 
- 组件使用@Component 声明,指令用@Directive 声明
 - 指令用于向现有 DOM 元素添加行为,如判断是否点击元素外部
 - DOM 元素可使用多个指令
 - 指令没有 view
 
ng Bootstrap 
ng Do Bootstrap 是 Angular 7 中添加的新生命周期挂钩, Do Bootstrap 是接口。
javascript
//ng Do Bootstrap - Life-Cycle Hook Interface
classApp Module implements Do Bootstrap {
  ng Do Bootstrap(appRef: ApplicationRef) {
    appRef.bootstrap(AppComponent);
  }
}路由守卫 
auth.guard.ts
typescript
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { StorageService } from '../services/storage.service';
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router, private storage: StorageService) {}
  // 路由守卫
  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    //console.log(next,state)
    if (navigator.userAgent.indexOf('Mobile') >-1){
      window.location.href = 'https://www.baidu.com';
    }
    this.storage.sendMessage({message: 'sendMessage', goToURL: state.url});
    return true;
  }
}app-routing.module.ts
typescript
const routes: Routes = [
  {
    path: '',
    component: IndexComponent,
    canActivate: [AuthGuard]
  }
]storage.service.ts
typescript
import { Injectable } from '@angular/core';
import { Subject, Subscription, Observable } from 'rxjs';
@Injectable({
  providedIn: 'root'
})
export class StorageService {
  private subject = new Subject<any>();
  sendMessage(message){
    this.subject.next(message);
  }
  clearMessage(){
    this.subject.next();
  }
  getMessage():Observable<any> {
    return this.subject.asObservable();
  }
  constructor(){}
}某页面
typescript
ngAfterViewInit() {
  this.subscription = this.storage.getMessage().subscribe(
          msg => {
            console.log(msg);
          }
  )兼容IE 
polyfill.ts 并打开注释,缺失的包需要npm install
typescript
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */
/***************************************************************************************************
 * BROWSER POLYFILLS
 */
// 兼容ie10
(function() {
  Object.setPrototypeOf = Object.setPrototypeOf || ({__proto__: []} instanceof Array ? setProtoOf : mixinProperties);
  function setProtoOf(obj, proto) {
    obj.__proto__ = proto;
    return obj;
  }
  function mixinProperties(obj, proto) {
    for (const prop in proto) {
      if (!obj.hasOwnProperty(prop)) {
        obj[prop] = proto[prop];
      }
    }
    return obj;
  }
})();
/** IE9, IE10 and IE11 requires all of the following polyfills. **/<br>// 打开这里的注释
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 **/
// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
/**
 * By default, zone.js will patch all possible macroTask and DomEvents
 * user can disable parts of macroTask/DomEvents patch by setting following flags
 */
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = [[scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js/dist/zone';  // Included with Angular CLI.
import 'babel-polyfill';
/***************************************************************************************************
 * APPLICATION IMPORTS
 */同页面前进后退状态记录 
typescript
import { PlatformLocation } from '@angular/common';
import { Router, ActivatedRoute } from '@angular/router';
constructor(private router: Router, public activatedRoute: ActivatedRoute,location: PlatformLocation) {
  //前进后退触发
  location.onPopState((e) => {
    //console.log(this.activatedRoute.snapshot.queryParams)
    //需要setTimeout才能正确获取this.activatedRoute.snapshot.queryParams,使其与url参数一致
    setTimeout(()=>{
      this.loadURL();
      this.selectInfo();
    },0);
  });
}
getJobList(...): void {
  let config = {
    headers: {
      'Content-Type': 'application/json; charset=utf-8'
    }
  };
  axios.post('url',{
    参数
  },config)
          .then(response => {
            //console.log(response)
          })
          .catch(response => {
            //console.log(response)
          ...
          });
}
selectInfo(): void {
  this.getJobList(
          参数
  );
}
loadURL(): void {
  //获取路由参数并设置入data
  let jobId = this.activatedRoute.snapshot.queryParams[[jobId'];
  // console.log(jobId,window.location.search)
  if(jobId){
    this.jobTypeActiveId = parseInt(jobId);
  }
  else{
    this.jobTypeActiveId = 0;
  }
}
saveURL(参数): void {
  //相当于pushState
  this.router.navigate([[/本页面url'],{
    queryParams: {
      key: value参数
    }
  });
}