下载APP

9-框架-Angular

       诞生于2009年,由Misko Hevery 等人创建的第一个版本AngularJS已经成为过去,这里我们将不再翻阅历史细数一二。本文所说Angular皆是指v10以后版本,截止目前写本文时间Angular最新版本v15已发布。 这里我们将与你一起深入Angular应用,了解在开发中常见的一些技巧。 Angular核心功能.png

1.核心知识

1.1 组件

1.1.1实现自定义组件双向绑定的两种方法

1.1.1.1.基于属性实现自定义双向绑定

自定义组件时的属性实现

private _value: number = 0;
@Input()
public get value(): number {
  return this._value;
}
public set value(value: number) {
  this._value = value;
  this.valueChange.emit(this._value);
}

@Output() readonly valueChange = new EventEmitter<number>();

调用时绑定value:

<banana [(value)]="value" />

1.1.1.2.实现自定义组件的ngModel指令

如果希望自定义组件能够具有与表单元素相同的 ngModel 效果,可以通过在组件内实现 ControlValueAccessor 接口达到目的。

  • ControlValueAccessor约束
interface ControlValueAccessor {
    
    writeValue(obj: any): void;
    
    registerOnChange(fn: any): void;
    
    registerOnTouched(fn: any): void;
    
    setDisabledState?(isDisabled: boolean): void;
}

最简实现示例参考如下


import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
    selector: 'banana',
    templateUrl: `./basic.template.html`,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => BananaComponent),
            multi: true
        }
    ]
})
export class BananaComponent implements ControlValueAccessor {
    private _innerValue: any = '';
    get innerValue(): any {
        return this._innerValue;
    }
    set innerValue(v: any) {
        if (v !== this._innerValue) {
            this._innerValue = v;
            this.onChangeCallback(v);
        }
    }
    private onChangeCallback: (_: any) => void = () => { };
    
    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }

    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    registerOnTouched(fn: any) {
    }
}
  • basic.template.html
<div class="row">
  <div class="col-md-12">
    innerValue:<input [(ngModel)]="innerValue" />
  </div>
</div>
  • 组件引用

<banana [(ngModel)]="innerValue" />

1.2 模板

实现一个自定义的树结构展示:ngTemplateOutlet与ngTemplateOutletContext

**ngTemplateOutlet **指令插入对应的 TemplateRef 内嵌视图 ngTemplateOutletContext设置 EmbeddedViewRef 的上下文对象,可通过 let语法来声明绑定上下文对象属性名,用来传递模板间变量。

// 模板
public list = [
  {
    id: 1,
    name: '商品',
    goods: [
      {
        id: 11,
        name: '冰箱',
        goods: [
          {
            id: 111,
            name: '海尔'
          },
          {
            id: 112,
            name: '创维'
          },
          {
            id: 113,
            name: '美的'
          }
        ]
      },
      {
        id: 12,
        name: '服饰',
        goods: [
          {
            id: 121,
            name: '安踏'
          }
        ]
      }]
  },
  {
    id: 2,
    name: '电影',
    goods: [
      {
        id: 21,
        name: '谢谢',
        goods: [
          {
            id: 211,
            name: '人海',
            goods: [
              {
                id: 2111,
                name: '葡萄'
              }
            ]
          }
        ]
      }
    ]
  }
];

重点:组件模板实现

<!-- 循环列表数据 -->
<ng-container *ngFor="let item of list">
    <ng-container [ngTemplateOutlet]="goodItemTpl" [ngTemplateOutletContext]="{item:item}"></ng-container>
    <ng-container [ngTemplateOutlet]="goodsTpl" [ngTemplateOutletContext]="{goods:item.goods}"></ng-container>
</ng-container>

<!-- 商品展示明细 -->
<ng-template let-gooditem="item" #goodItemTpl>
    <div>{{ gooditem.id }} - {{ gooditem.name }}</div>
</ng-template>

<!-- 递归模板 -->
<ng-template let-goods="goods" #goodsTpl>
    <ng-container *ngFor="let item of goods">
        <ng-container [ngTemplateOutlet]="goodItemTpl" [ngTemplateOutletContext]="{item:item}"></ng-container>
        <ng-container *ngIf="item.goods?.length" [ngTemplateOutlet]="goodsTpl" [ngTemplateOutletContext]="{goods:item.goods}"></ng-container>
    </ng-container>
</ng-template>

这样就将多层json数据以树结构展示出来了 image.png

1.3 指令

Angular指令作用在于影响Dom布局或者修改Dom属性,分为结构型指令与属性型指令。 这里我们讲下ngTemplateOutlet指令(结构型指令),使用ngTemplateOutlet指令需要引入CommonModule模块。在Angular框架里NgTemplateOutlet类定义了两个输入属性:ngTemplateOutlet、ngTemplateOutletContext。ngTemplateOutlet是TemplateRef类型的模板片段,ngTemplateOutletContext负责给 EmbeddedViewRef附加上下文对象.通过内置指令ngTemplateOutlet可以实现组件定制化功能,在开发通用组件的时候可提高组件的规范化与定制化能力。 例:实现一个组件的初始加载状态

<div>

  <div class="my-component" *ngIf="!loading else loadingTemp">
    content
  </div>

  <ng-template #loadingTemp>
    <ng-container [ngTemplateOutlet]="defaultTpl"></ng-container>
  </ng-template>

  <ng-template #defaultTpl>
    <p>spinner</p>
  </ng-template>

</div>

1.4 依赖注入

2.配置项

如何实现@angular/cli项目的自定义webpack配置?

2.1.安装相关依赖

npm i @angular-builders/custom-webpack -D npm i @angular-builders/dev-server -D

2.2.创建webpack.config.js文件

module.exports = (angularWebpackConfig, options) => {


  return angularWebpackConfig;
};

2.3.更改angular.json的配置

"build": {
          "builder": "@angular-builders/custom-webpack:browser",
           ......
            "customWebpackConfig": {
              "path": "./extra-webpack.config.js",
              "libraryName": "ng-library",
              "libraryTarget": "umd"
            }
          }
"serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          ......
        },

3.AOT

在浏览器下载和运行代码之前的编译阶段,Angular 预先(AOT)编译器会先把 Angular HTML 和 TypeScript 代码转换成高效的 JavaScript 代码。 AOT 编译分为三个阶段:代码分析、代码生成、模板类型检查

小册Flows-Angular AOT.jpg Angular编译有两种:Ahead-of-time (AOT) 和 just-in-time (JIT)。但是实际上使用的是同一个编译器,AOT和JIT的区别只是编译的时机和编译所使用的工具库不同。.metadata.json文件是Angular编译器产生的,它用json的形式记录了.ts中decorator信息、依赖注入信息,这样Angular在二次编译时不再需要从.ts中提取metadata。NgFactories是浏览器可执行的代码,无论是AOT还是JIT,angular-complier都输出NgFactories,只不过AOT产生的输出到*.ngfactory.ts文件中,JIT产生的输出到客户端内存中。AOT模式下浏览器只做两件事:下载bundle、执行代码(创建组件实例)

4.服务懒加载

Angular6之后提供了provideIn勇于将服务注册到Angular依赖注入机制中,providedIn可选值包括‘root’、SomeModule,root代表APPModule即将该服务注册到全局,这里我们讨论如何实现providedIn:LazyLoadedModule方式,这种方式可以防止在所需模块之外调用当前服务。在开发大型应用程序时,能够确保良好的依赖关系,避免混乱的服务注入。 这里需要注意一个问题,如果直接在服务定义式使用providedIn:LazyModule会出现循环依赖的问题,所以我们需要借助一个LazyServiceModule来避免。 小册Flows-LazyServiceModule.jpg

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

@NgModule()
export class LazyServiceModule {}
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SLLComponent } from './index.component';
import { SLLRoutingModule } from './sll.routing';
import { LazyServiceModule } from './lazyservice.module';

@NgModule({
    declarations: [
        SLLComponent
    ],
    imports: [
        LazyServiceModule,
        CommonModule,
        SLLRoutingModule
    ]
})
export class LazyModule { }

通过借助LazyServiceModule可以完美避开循环依赖的问题,然后,LazyModule将以标准方式使用 Angular Router 为某些路由进行懒加载,LazyService也只负责服务于当前LazyModule模块。

5.自定义表单项

创建自定义表单项仍然需要借助ControlValueAccessor实现,该接口充当 Angular 表单 API 和 DOM 中的原生元素之间的桥梁

<form-address id="phone" type="text" formControlName="address" />

import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

const AUTHTREE_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => FormAddressComponent),
  multi: true
}
@Component({
  selector: 'form-address',
  template: `
        <textarea [(ngModel)]="addressValue"></textarea>
    `,
  providers: [AUTHTREE_VALUE_ACCESSOR]
})
export class FormAddressComponent implements ControlValueAccessor {
  // ControlValueAccessor处理
  private _addressValue: string = ``;
  public get addressValue(): string {
    return this._addressValue;
  }
  public set addressValue(value: string) {
    this.onChange(this._addressValue);
    this._addressValue = value;
  }
  private onChange = (_: any) => { };

  writeValue(value: any): void {
    if (value !== this._addressValue) {
      this._addressValue = value || [];
    }
  }
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void { }

}

6.RxJs使用

RxJS全称Reactive Extensions for JavaScript,是使用 Observables 的响应式编程的库,它使编写异步或基于回调的代码更加容易。RxJS的运行就是Observable和Observer之间的互动游戏。

基于RxJs实现Angular组件间通信

import { Injectable } from "@angular/core";
import { Subject } from "rxjs";

@Injectable({
  providedIn: 'root'
})
export class MessageService {
  private messageSource = new Subject<string>();

  public message$ = this.messageSource.asObservable();

  messageAction(name: string) {

    this.messageSource.next(name);
  }
}

发送消息

constructor(private msgService: MessageService) { }

public sendMessage() {

    this.msgService.messageAction('message content');
}

接收消息

this.msgService.message$.subscribe(msg=>{

    console.log(msg);// message content
})

7.Angular库开发

Angular 库是一个 Angular 项目,它与应用的不同之处在于它本身是不能运行的。必须在某个应用中导入库并使用它。在实际开发中我们可能会将一些表单验证、文件预览功能模块等抽离出来封装成通用库。 使用 Angular CLI中可以通过命令快速生成一个新库的骨架:

ng new workspace --no-create-application
cd workspace
ng generate library demo-lib

使用demo-lib库

import { DemoLibModule } from 'demo-lib';

@NgModule({
    declarations: [
    ],
    imports: [
        ......
        DemoLibModule
    ]
})
export class MyModule { }

8.最佳实践

项目源码下载(以后补充)

在线举报