Angular学习笔记

Posted by Shen Chaoran on October 1, 2017

架构

架构概览

模块简介

组件简介

服务与DI简介

单例模式

在 Angular 中有三种方式来生成单例服务:

  • 声明该服务应该在应用的根上提供,即放在 AppModule 中注入。
  • 把该服务包含在 AppModule 或某个只会被 AppModule 导入的模块中。
  • 在 @Injectable 装饰器的元数据中指明 provideIn: root

后续步骤

组件与模板

显示数据

模板语法

  • 插值表达式 ``
  • 模板表达式 [property]='expression':表达式上下文(本组件),表达式指南(执行迅速、没有副作用、简单、幂等)
  • 模板语句 (event)='statement':语句上下文(本组件),语句指南(简单,通常是函数调用或属性赋值)
  • 绑定语法:
    • 数据源 -> 视图: , [target]='expression', bind-target='expression'
    • 视图 -> 数据源: (target)='statement', on-target='statement'
    • 数据源 <-> 视图: [(target)]='expression', bindon-target='expression'
  • HTML attribute vs DOM property: HTML attribute init DOM property; HTML attribute can’t change, but DOM property can
  • 数据绑定:
    • 单向输入:不能通过属性绑定来调用方法(有副作用),因为可能会导致UI依赖的一些View Model改变,从而无法形成单向数据流
    • 一次性字符串初始化:routerLink='/posts' vs [routerLink]='"/posts"'
    • attribute 绑定: [attr.colspan]
    • CSS 样式绑定: [style.background]
    • CSS 类绑定: [class.myclass]
    • [ngClass]='{className1: true, className2: false}'
    • [ngStyle]='{color: red, background: #FFF}'
    • (click)='onSave($event)'
    • [(ngModel)]='item'
  • 模板引用变量:#varref-var,一般指向引用的 DOM 元素,也可以通过赋值引用 Angular 组件或指令,比如 <form #myForm='ngForm'></form>
  • 模板输入变量:<ng-template let-item='obj'></ng-template>
  • 输入和输出属性: @Input(), @Output(), @Input('alias')
  • 安全导航操作符: obj?.prop
  • 非空断言操作符: obj!.prop, 非空断言操作符不会防止出现 null 或 undefined。它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 “严格空值检测”。不使用这个操作符时类型检查器可能会报错。
  • 类型转换函数: $any(obj).prop

生命周期钩子

  • class 内的默认赋值语句先执行
  • constructor:在所有生命周期钩子前执行
    • 通常在构造函数中进行简单的赋值,不从远程请求数据
  • OnChanges:每次(重新)设置输入属性后
  • OnInit:第一次设置组件/指令的输入属性后调用,在第一次 onChanges 之后,只调用一次
    • 放置复杂初始化逻辑的好地方
    • 组件获取远程数据的好地方
  • DoChecked:每个变化检测周期中调用
  • AfterContentInit:内容投影进组件之后调用,只调用一次
  • AfterContentChecked:每当被投影组件变化检测之后调用
    • 无需担心单向数据流,为什么?
  • AfterViewInit:初始化完组件视图及其子视图后调用
    • 这里要注意必须是单向数据流,在要改变 View Model 的值时,需要把相关处理的语句放在 process.nextTick() 或者 setTimeout(fn, 0) 内。
  • AfterViewChecked:每当组件完成变化检测后调用
    • 和 AfterViewInit 相同,也要注意单向数据流
  • OnDestroy:销毁组件、指令时调用
    • 放置一些清理逻辑,释放 gc 不自动回收的资源
    • 取消 observable 和 DOM event 的订阅
    • 停止定时器
    • 注销该指令曾注册到全局服务或应用级服务中的各种回调函数

组件交互

  • @Input()
  • @Output()
  • setter
  • ngOnChanges()
  • 在父组件中使用模板引用变量指向子组件: <child-component #child></child-component>
  • 在父组件中使用 @ViewChild(ChildComponent)
  • 通过服务通讯: 服务的依赖注入会形成一棵树,因此在父组件中注入一个 Service,子组件不用重新注入,这时两者获取的是同一个服务实例。如果子组件也重新注入了,则不是同一个实例。

组件样式

  • 范围化的样式:不影响子组件和内容投影的组件
  • :host, :host(.active)
  • :host-context, :host-context(.className)
  • /deep/, »>, ::ng-deep (deprecated)
  • styles, styleUrls
  • template 中的 HTML 也可以引用外部文件 <link rel='stylesheet' href='/url'>
  • @imports
  • 全局样式文件 styles.css
  • 视图的封装模式:
    • Native: 使用浏览器原生的 Shadow DOM 的实现样式封装
    • Emulated: (默认),通过预处理CSS代码来模拟 Shadow DOM 的行为,以达到把 CSS 样式局限在组件视图中的目的
    • None: 不适用视图封装,CSS会添加到全局样式中,没有作用域隔离和保护

Angular 自定义元素

动态组件

  • 对于动态加载的组件,需要添加到 @NgModule 的装饰器的 entryComponents 元数据数组中
  • Angular 会对 entryComponents 数组中的组件生成一个 ComponentFactory 类

举个例子:

import { Component, Input, OnInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';

import { AdDirective } from './ad.directive';
import { AdItem }      from './ad-item';
import { AdComponent } from './ad.component';

@Component({
  selector: 'app-ad-banner',
  template: `
              <div class="ad-banner">
                <h3>Advertisements</h3>
                <ng-template ad-host></ng-template>
              </div>
            `
})
export class AdBannerComponent implements OnInit, OnDestroy {
  @Input() ads: AdItem[];
  currentAdIndex = -1;
  @ViewChild(AdDirective) adHost: AdDirective;
  interval: any;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

  ngOnInit() {
    this.loadComponent();
    this.getAds();
  }

  ngOnDestroy() {
    clearInterval(this.interval);
  }

  loadComponent() {
    this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
    let adItem = this.ads[this.currentAdIndex];

    // 参数是组件类名
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);

    let viewContainerRef = this.adHost.viewContainerRef;
    viewContainerRef.clear();

    let componentRef = viewContainerRef.createComponent(componentFactory);
    (<AdComponent>componentRef.instance).data = adItem.data;
  }

  getAds() {
    this.interval = setInterval(() => {
      this.loadComponent();
    }, 3000);
  }
}

属性型指令

属性型指令用于改变组件的外观和行为。

  • 使用 @HostListener(‘eventName’) 绑定事件
  • 使用 @Input(‘alias’) 绑定输入属性
  • 可以在构造函数中注入 ElementRef 改变 DOM 的样式

结构型指令

结构型指令用于改变组件的 DOM 结构

NgIf

语法糖翻译前后的代码:

<div *ngIf="hero" class="name"></div>

<ng-template [ngIf]="hero">
  <div class="name"></div>
</ng-template>

NgFor

ngFor里的 traceBy:对于重新从服务器获取数据时,可能只有部分数据时更新的,但是不加 TraceByFunction(index, item) 时,会将所有DOM重新渲染,而使用此函数时,会根据对象的属性跟踪对象,只渲染新增或变化的部分 DOM参考这里

语法糖翻译前后的代码:

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

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

NgSwitch

语法糖翻译前后的代码:

<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>

<div [ngSwitch]="hero?.emotion">
  <ng-template [ngSwitchCase]="'happy'">
    <app-happy-hero [hero]="hero"></app-happy-hero>
  </ng-template>
  <ng-template [ngSwitchCase]="'sad'">
    <app-sad-hero [hero]="hero"></app-sad-hero>
  </ng-template>
  <ng-template [ngSwitchCase]="'confused'">
    <app-confused-hero [hero]="hero"></app-confused-hero>
  </ng-template >
  <ng-template ngSwitchDefault>
    <app-unknown-hero [hero]="hero"></app-unknown-hero>
  </ng-template>
</div>

自定义结构型指令

接收的参数:constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { },在条件为真时会将内嵌的视图插入(TemplateRef)到视图容器(ViewContainerRef)中。

管道

动画

风格指南

单一规则

每个文件只定义一样东西,把文件大小限制在400行代码之内: 易阅读;维护;防止团队合作冲突;可以用路由按需加载;避免了代码中共享变量的bug

命名

feature.type.ts

踩过的坑

  • 使用jqWidget中的jqxMenu和jqxTree,在绑定属性时,视图没有随着数据模型更新。然后将数据模型深度拷贝后更新在赋值过去,就神奇的解决了。目前还是不清楚原因
  • 在angular中使用jquery等第三方库:
      npm install @types/jquery --save-dev
      npm install jquery --save
        
      import * as jQuery from 'jquery';
    
  • ngInit在页面第一次加载时会进行初始化,但是第二次不会走ngInit的方法。