前言

Angular在国内应用的人并不像国外那么多,根本都是外企在用,但其框架的思维却仍能够为咱们所借鉴,在某些问题没有思路的时候能够参考ng相干的解决,ng解决形式和思维的确比拟超前,但也因而而曲高和寡。本文旨在通过ng全家桶我的项目(前端Angular10 + 后端NestJS7)的实际来总结对于ng架构中一些亮点的关注与思考,Angular和Nest在前后端框架的解决上同出一脉,比照起来更有借鉴意义。

目录

  • 前端我的项目实际
  • 后端我的项目实际
  • 源码简析

案例

前端我的项目实际

[目录构造]

  • src

    • app

      • login

        • login.component.html
        • login.component.scss
        • login.component.spec.ts
        • login.component.ts
      • main

        • main.component.html
        • main.component.scss
        • main.component.spec.ts
        • main.component.ts
      • app.component.html
      • app.component.scss
      • app.component.spec.ts
      • app.component.ts
      • app.module.ts
      • app.service.ts
      • user.ts
    • main.ts
    • index.html
  • angular.json

[目录形容]

整个前端我的项目是基于angular脚手架生成的,其根本目录构造是在src的app下进行相干组件和页面的模块开发,main.ts和index.html是整个单页利用的主入口,根目录下angular.json用于配置相干的打包编译等环境配置参数

[实际分享]

  • 脚手架版本问题:对于ng10.1.x以上的版本其脚手架版本与库版本有出入,会导致引入HttpModule及其他局部模块时报错,须要将@angular/compiler这个库进行相干版本的降级 This likely means that the library (@angular/common/http) which declares HttpClientModule has not been processed correctly by ngcc, or is not compatible with Angular Ivy. Check if a newer version of the library is available, and update if so. Also consider checking with the library's authors to see if the library is expected to be compatible with Ivy.
  • 输入框双向数据绑定有效问题:对于forms表单的想应用[(ngModal)]指令,必须在module中引入FormsModule
import { FormsModule } from '@angular/forms';@NgModule({  imports: [    FormsModule  ]})
  • 组件库应用有问题:目前ng的组件库次要有 官网 MATERIAL 组件库、NG-ZORRO 组件库、NG-NEST 组件库,但这几个次要的组件库都在10以下或10.0版本,会有局部bug,因此本次未引入组件库,间接应用html和scss来优化页面
  • Animation引入问题:同样的跟版本有关系,想应用ng自带的animation动画库须要配合对应的版本,在做提示框隐没时本想应用ng的动画,起初改用了animation间接写
.app-message {    width: 300px;    height: 40px;    background: #fff;    transition: all 2s none;    border: 1px solid #ececec;    border-radius: 4px;    position: fixed;    left: 50%;    top: 10px;    margin-left: -150px;    text-align: center;        &-show {        animation: a 3s ease-out forwards;        animation-direction: alternate;        animation-iteration-count: 1;        @keyframes a {            0% {opacity: 1;}            100% {opacity: 0;}        }    }    &-hide {        opacity: 0;    }}

后端我的项目踩坑实际

[目录构造]

  • src

    • api

      • article

        • article.controller.ts
        • article.module.ts
      • audio

        • audio.controller.ts
        • audio.module.ts
        • audio.processor.ts
      • user

        • dto

          • create-user.dto.ts
          • get-user.dto.ts
          • update-user.dto.ts
        • user.controller.ts
        • user.entity.ts
        • user.interface.ts
        • user.module.ts
        • user.service.ts
    • auth

      • auth.controller.ts
      • auth.service.ts
      • constants.ts
      • jwt.strategy.ts
    • filters

      • exception

        • http-exception.filter.ts
    • guard

      • roles.guard.ts
    • interceptors

      • cache.interceptor.ts
      • exception.interceptor.ts
      • logger.interceptor.ts
      • timeout.interceptor.ts
      • transform.interceptor.ts
    • middlewares

      • logger

        • logger.middleware.ts
        • logger.ts
    • pipes

      • parse-int.pipe.ts
      • validate.pipe.ts
    • main.ts
    • app.module.ts
  • nest-cli.json

[目录形容]

后端我的项目是基于nestjs框架的大型后盾我的项目配置,api模块次要是对外输入的接口,auth、filters、guard、interceptors、middlewares、pipes等是对于须要的模块进行对立的收集解决,main.ts是主入口文件,用于启动及相干配置等,app.module.ts是用来收集所有模块的导入,ng基于模块的形式能够起到十分好的隔离成果

[实际分享]

  • typeorm数据库连贯:应用navicat连贯须要先创立数据库,否则无奈连贯
  • bull音讯队列:nestjs的音讯队列应用的是bull.js这个库,其实现了一个调度的音讯队列机制
  • 微服务:nestjs默认应用的根底的相似java的spring框架,并未采纳微服务,如需应用,须要齐全重构

源码简析

Angular

首先,对于没有用过ng的同学科普一下,angular其实分为两个大版本,一个是angular1.x的,也就是ng1,也就是当初还有的angularjs,另一个版本是ng2当前的版本,ng2之后被谷歌收买后,齐全重写了框架,惟一和1.x相通的预计也就剩那几个思维还在了:模块化、依赖注入、双向绑定、MVC,对于1.x感兴趣的同学能够去看Vue的1.x的版本,根本算是简化版的ng1.x,Vue2之后就和起初的ng各奔前程了,vue2次要是以公布订阅来代替依赖注入的思路,扯远了...(ps: 想看ng1版本的能够看这个地址,竟然还有更新... angularjs官网仓库),这里剖析的次要是Ng10,ng8之后除了引入Ivy(Ivy架构官网介绍)这个编译渲染器之外,其实改变不大,次要就是在优化以及破除和新建一些api等等。Ng的源码很宏大,goggle自研了一个bazel自动化构建工具,ng天然也是靠这个构建的,对bazel感兴趣的同学,能够看这个Google软件构建工具Bazel原理及应用办法介绍,我这里就不开展所有的源码,整体的外围大框架如下:

  • packages

    • complier (ps: 不开展了,这个编译局部做的很优良,本篇讲不完,回头写编译器局部专门说吧,尤其是Ivy那个,后续react的fiber以及vue3的最新的compiler局部都有其影响)

      • src

        • aot
        • css_parser
        • expression_parser
        • jit
        • ml_parser
        • template_parser
    • core

      • src

        • di

          • injector.ts
        • reflection

          • reflector.ts
        • view

          • provider.ts
        • render3 (ps: Ivy的渲染局部)

Nest

nestjs是nodejs的web利用的一个大的集成,它最后是基于express封装的一个后端框架,起初将服务端各种理念都应用js实现了一下,尽管不能和成熟的服务端语言框架如java等进行媲美,然而服务端所须要的货色根本都具备了,对于有需要想要应用js来开发后端的同学是个不错的抉择,集体认为简略的bff,比方想本人模仿的开发个后盾接管申请,抉择node间接写或者应用express、koa就能够,对于有肯定的中间层给前端解决,能够选用阿里的egg,对于如何基于egg构建中间层,能够看看这篇文章如何为团队定制本人的 Node.js 框架?(基于 EggJS),对于大型的服务端,尤其是前端是以ng为主栈的,能够优先思考应用nestjs;其次对于io较多而计算较少的(js自身的特质),或者服务端须要与c++配合的,大型服务端利用也能够应用nest。nest默认是不采纳微服务的模式的,nest将不同的平台封在了不同的platform下,这里只剖析一般的以express为platform的模式,对于喜爱微服务的同学,能够比照和java的springcloud的区别,这里就不做表述了,其整体的外围构造大抵如下:

  • packages

    • core

      • injector

        • injector.ts
        • module.ts
      • services

        • reflector.service.ts
    • platform-express

源码

这里次要在对依赖注入的实现做一个简略的了解分享,其思路是一脉相承的,对于了解后端理念的依赖注入有很好的了解,这也正是后端前端化的一个体现,也是最早的MVC框架向起初的MVVM框架适度的一个历史过程,依赖注入形式对于最早的前端框架还是有留念意义的,然而对于ng全家桶来说,这算是其根本哲学的一个基本面

Angular

先来看一下ng是如何实现injector的,这里重点在于应用了抽象类来重载不同函数的应用,对于provider循环依赖的解决,利用了一个Map数据结构来辨别不同的Provider

// 抽象类export abstract class Injector {    // get办法重载的应用    abstract get<T>(        token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags    ): T;    abstract get(        token: any,         notFoundValue?: any    ): any;      // create办法重载的应用    static create(        providers: StaticProvider[],         parent?: Injector    ): Injector;      static create(        options: {            providers: StaticProvider[],             parent?: Injector,             name?: string        }    ): Injector;        static create(        options: StaticProvider[]|{providers: StaticProvider[], parent?: Injector, name?: string},        parent?: Injector    ): Injector {      if (Array.isArray(options)) {        return INJECTOR_IMPL(options, parent, '');      } else {        return INJECTOR_IMPL(options.providers, options.parent, options.name || '');      }    }      static __NG_ELEMENT_ID__ = -1;  }  // 记录判断prodiver的数据结构,这里应用interface来承载  interface Record {    fn: Function;    useNew: boolean;    deps: DependencyRecord[];    value: any;  }    interface DependencyRecord {    token: any;    options: number;  }  // 实现抽象类  export class StaticInjector implements Injector {    readonly parent: Injector;    readonly source: string|null;    readonly scope: string|null;      private _records: Map<any, Record|null>;      constructor(        providers: StaticProvider[],         parent: Injector = Injector.NULL,         source: string|null = null    ) {      this.parent = parent;      this.source = source;      const records = this._records = new Map<any, Record>();      records.set(          Injector,           <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false}      );      records.set(          INJECTOR,           <Record>{token: INJECTOR, fn: IDENT, deps: EMPTY, value: this, useNew: false}      );      this.scope = recursivelyProcessProviders(records, providers);    }      get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;    get(token: any, notFoundValue?: any): any;    get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any {      const records = this._records;      // record的缓存队列      let record = records.get(token);      // 利用record防止循环提供的问题      if (record === undefined) {        // This means we have never seen this record, see if it is tree shakable provider.        const injectableDef = getInjectableDef(token);        if (injectableDef) {          const providedIn = injectableDef && injectableDef.providedIn;          if (providedIn === 'any' || providedIn != null && providedIn === this.scope) {            records.set(                token,                record = resolveProvider(                    {provide: token, useFactory: injectableDef.factory, deps: EMPTY}));          }        }        if (record === undefined) {          // Set record to null to make sure that we don't go through expensive lookup above again.          records.set(token, null);        }      }      let lastInjector = setCurrentInjector(this);      try {        return tryResolveToken(token, record, records, this.parent, notFoundValue, flags);      } catch (e) {        return catchInjectorError(e, token, 'StaticInjectorError', this.source);      } finally {        setCurrentInjector(lastInjector);      }    }      toString() {      const tokens = <string[]>[], records = this._records;      records.forEach((v, token) => tokens.push(stringify(token)));      return `StaticInjector[${tokens.join(', ')}]`;    }}  // 解析Provider的函数function resolveProvider(    provider: SupportedProvider): Record {    const deps = computeDeps(provider);    let fn: Function = IDENT;    let value: any = EMPTY;    let useNew: boolean = false;    let provide = resolveForwardRef(provider.provide);    // 一些错误处理    ...    return {deps, fn, useNew, value};}  // 解决循环依赖的问题function recursivelyProcessProviders(  records: Map<any, Record>,   provider: StaticProvider): string|null {    let scope: string|null = null;    // 依据不同状况解决一些谬误      ...    return scope;}// 解析Token的函数function resolveToken(    token: any,     record: Record|undefined|null,     records: Map<any, Record|null>, parent: Injector,    notFoundValue: any,     flags: InjectFlags): any {  let value;  ...  return value;}// 计算依赖函数function computeDeps(    provider: StaticProvider): DependencyRecord[] {  let deps: DependencyRecord[] = EMPTY;  const providerDeps: any[] =      (provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;  if (providerDeps && providerDeps.length) {    deps = [];    for (let i = 0; i < providerDeps.length; i++) {      let options = OptionFlags.Default;      let token = resolveForwardRef(providerDeps[i]);      if (Array.isArray(token)) {        for (let j = 0, annotations = token; j < annotations.length; j++) {          const annotation = annotations[j];          if (annotation instanceof Optional || annotation == Optional) {            options = options | OptionFlags.Optional;          } else if (annotation instanceof SkipSelf || annotation == SkipSelf) {            options = options & ~OptionFlags.CheckSelf;          } else if (annotation instanceof Self || annotation == Self) {            options = options & ~OptionFlags.CheckParent;          } else if (annotation instanceof Inject) {            token = (annotation as Inject).token;          } else {            token = resolveForwardRef(annotation);          }        }      }      deps.push({token, options});    }  }  ...  return deps;}

Nest

再来看一下,nest的实现,不同于ng的实现,nest是利用参数和继承父类参数来确定整个的循环依赖关系的,其没有应用重载来实现,但都对循环依赖做了解决,其基本思路是统一的。

export type InjectorDependency = Type<any> | Function | string | symbol;export interface PropertyDependency {  key: string;  name: InjectorDependency;  isOptional?: boolean;  instance?: any;}export interface InjectorDependencyContext {  key?: string | symbol;  name?: string | symbol;  index?: number;  dependencies?: InjectorDependency[];}export class Injector {  // 加载中间件 基于express的load形式  public async loadMiddleware(    wrapper: InstanceWrapper,    collection: Map<string, InstanceWrapper>,    moduleRef: Module,    contextId = STATIC_CONTEXT,    inquirer?: InstanceWrapper,  ) {    ...  }  // 记录控制器  public async loadController(    wrapper: InstanceWrapper<Controller>,    moduleRef: Module,    contextId = STATIC_CONTEXT,  ) {    ...  }  public async loadInjectable<T = any>(    wrapper: InstanceWrapper<T>,    moduleRef: Module,    contextId = STATIC_CONTEXT,    inquirer?: InstanceWrapper,  ) {    const injectables = moduleRef.injectables;    await this.loadInstance<T>(      wrapper,      injectables,      moduleRef,      contextId,      inquirer,    );  }  // 加载Provider  public async loadProvider(    wrapper: InstanceWrapper<Injectable>,    moduleRef: Module,    contextId = STATIC_CONTEXT,    inquirer?: InstanceWrapper,  ) {    const providers = moduleRef.providers;    await this.loadInstance<Injectable>(      wrapper,      providers,      moduleRef,      contextId,      inquirer,    );    await this.loadEnhancersPerContext(wrapper, contextId, wrapper);  }  public loadPrototype<T>(    { name }: InstanceWrapper<T>,    collection: Map<string, InstanceWrapper<T>>,    contextId = STATIC_CONTEXT,  ) {   ...  }  // 解析继承父类的参数  public async resolveConstructorParams<T>(    wrapper: InstanceWrapper<T>,    moduleRef: Module,    inject: InjectorDependency[],    callback: (args: unknown[]) => void,    contextId = STATIC_CONTEXT,    inquirer?: InstanceWrapper,    parentInquirer?: InstanceWrapper,  ) {    ...  }  // 反射继承父类的参数  public reflectConstructorParams<T>(      type: Type<T>    ): any[]   {    ...  }  // 反射性能参数  public reflectOptionalParams<T>(      type: Type<T>    ): any[]   {    ...  }  // 反射本人的参数  public reflectSelfParams<T>(      type: Type<T>  ): any[]   {    ...  }  // 解析单个参数  public async resolveSingleParam<T>  (    wrapper: InstanceWrapper<T>,    param: Type<any> | string | symbol | any,    dependencyContext: InjectorDependencyContext,    moduleRef: Module,    contextId = STATIC_CONTEXT,    inquirer?: InstanceWrapper,    keyOrIndex?: string | number,  ) {    if (isUndefined(param)) {      throw new UndefinedDependencyException(        wrapper.name,        dependencyContext,        moduleRef,      );    }    const token = this.resolveParamToken(wrapper, param);    return this.resolveComponentInstance<T>(      moduleRef,      isFunction(token) ? (token as Type<any>).name : token,      dependencyContext,      wrapper,      contextId,      inquirer,      keyOrIndex,    );  }  // 解析参数的token  public resolveParamToken<T>(    wrapper: InstanceWrapper<T>,    param: Type<any> | string | symbol | any,  ) {    if (!param.forwardRef) {      return param;    }    wrapper.forwardRef = true;    return param.forwardRef();  }}

总结:从nest和ng对injector的实现能够看出,尽管都是注射器的实现,然而因为出现形式的不同,因此在实现形式上也会有所不同,对于ts而言,选用interface还是抽象类,的确能够借鉴java的模式思路,对于习惯js的咱们来说,对于整个数据类型的扩大(如:抽象类、接口)等是须要向后端借鉴的。整体来说,对于依赖注入的实现最要害的就是在于解决provider的整个依赖问题,这两者都是采纳token的形式来辨别看待到底是属于哪一个provider,而后对于非凡的相干依赖循环的问题做对应的解决

总结

ng整个生态体系在国内利用的并不广,但并不障碍其作为前端理念的扩大先行者的这样一个角色,集体认为其在隔离性以及系统性方面都是要优于vue和react的,因此对于目前比拟风行的微前端框架(ps: 对于ng的微前端利用,能够参考这篇文章【第1789期】应用 Angular 打造微前端架构的 ToB 企业级利用),集体感觉在沙箱隔离等零碎交融方面的确能够借鉴一下ng的某些思路,或者正是因为这个起因,它才是三大框架中最先上ts的,也有可能整个ng的开发者更像是传统的软件工程师,对于整个开发要做到定义数据、定义模型、零碎设计等等,对于大型项目而言,这样的确会缩小很多因bug而须要反复批改的工夫,然而对于小型我的项目,集体认为还是vue更适合。尽管对于国内,ng根本曾经属于明日黄花了,然而它的一些理念及设计思路的确还是值得借鉴的,在这个内卷的时代,各大利用都在向着高级化、大型化倒退,说不定哪天ng又在国内重回巅峰了呢,尽管很难~~哈哈哈,各位加油!

参考

  • Angular源码
  • Angular中文文档
  • Nest源码
  • Nest中文文档
  • AngularJs和angular的比照
  • Angular 9.1.0在我的项目门路蕴含软链接时编译某些包时报错,9.1.1已修改
  • angularjs源码剖析
  • 如何为团队定制本人的 Node.js 框架?(基于 EggJS)
  • 【第1789期】应用 Angular 打造微前端架构的 ToB 企业级利用