preface

Angular is not as widely used in China as it is in foreign countries. However, the framework of Angular can still be used for reference. In some cases, we can refer to the processing of NG. The purpose of this article is to summarize some of the highlights of the NG architecture through the implementation of the Ng Family bucket project (front-end Angular10 + back-end NestJS7). Angular and Nest have the same approach to front-end and back-end frameworks, which is more useful in comparison.

directory

  • Front-end project practice
  • Back-end project practices
  • Source analysis

case

Front-end project practice

[Directory structure]

  • 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

[Directory Description]

The entire front-end project is generated based on Angular scaffolding. The basic directory structure is used for module development of related components and pages under the SRC app. Main. ts and index.html are the main entry of the entire single-page app

[Practice sharing]

  • Scaffolding version problems: For ng10.1.x or higher, the scaffolding version may be inconsistent with the library version, causing an error when importing HttpModule and some other modules. This is likely means that the library (@angular/common/ HTTP) which declares a greater demand for upgrading the @angular/ Compiler library 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.

  • Invalid input box bidirectional data binding problem: To use the [(ngModal)] directive for forms forms, you must introduce FormsModule in the Module

import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    FormsModule
  ]
})
Copy the code
  • Component library usage problem: At present, ng component library mainly includes official MATERIAL component library, NG-Zorro component library and NG-NEST component library. However, these major component libraries are under 10 or 10.0 version, which will have some bugs. Therefore, THIS time, WE do not introduce component library, and directly use HTML and SCSS to optimize the page

  • Animation introduction problem: It also depends on the version. If you want to use ng’s built-in Animation library, you need to match the corresponding version. When the prompt box disappears, I originally intended to use NG’s Animation, but later I switched to Animation to write directly

.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; }}Copy the code

Backend project trampling practice

[Directory structure]

  • 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

[Directory Description]

The back-end project is a large background project configuration based on the NestJS framework. The API module is mainly the interface for external output. Auth, filters, Guard, Interceptors, Middlewares, Pipes and other modules are collected and processed uniformly. Ts is the main entry file, used for startup and related configuration, etc. App.module. ts is used to collect all module imports, ng module-based approach can play a very good isolation effect

[Practice sharing]

  • Typeorm Database Connection: Navicat connection requires the creation of a database, otherwise the connection cannot be made

  • Bull message queue: NestJS message queue uses the bull.js library, which implements a scheduled message queue mechanism

  • Microservices: The basic Java-like Spring framework that NestJS uses by default does not use microservices and requires complete refactoring if needed

Source analysis

Angular

First, for those of you who haven’t used NG, There are actually two major versions of Angular. One is angular1.x (ng1), which is still angularJS, and the other is later ng2, which was acquired by Google and completely rewritten. The only ideas that seem to connect with 1.x are these: The 1.x version of Vue is basically a simplified version of NG1. x. After Vue2, it has parted ways with the later NG version. Vue2 mainly uses publish and subscribe to replace the idea of dependency injection. Ps: If you want to see ng1, you can see this address. Ng10 has changed little since ng8, except for the introduction of The Ivy compiler and renderer. The main changes are optimizations, scrapping and creating new apis, etc. The source code of Ng is very large. Goggle developed an automatic bazel building tool, and Ng was also built by this tool. If you are interested in Bazel, you can see the principle and usage of Google Software building tool Bazel.

  • packages
    • Complier (ps: not to extend, this compilation part is very good, I will not finish this article, I will write the compiler part specifically, especially Ivy, the subsequent React fiber and the latest vue3 compiler part are affected)
      • 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 render part)

Nest

Nestjs is a large integration of NodeJS Web applications. It was originally a back-end framework based on Express encapsulation, and then it implemented various concepts of the server using JS. Although it is not comparable to mature server language frameworks such as Java, it basically has all the things needed by the server. It is a good choice for students who want to use JS to develop the backend. Personally, I think simple BFF, such as want to simulate the development of a background to receive requests, choose Node to write directly or use Express and KOA. For certain middle layer to front-end processing, you can choose Ali’s Egg. For an example of how to build an intermediate layer based on an Egg, see this article how to customize your Node.js framework for your team. (Based on EggJS), nestJS can be used preferentially for large server, especially for front-end ng main stack; Nest can also be used for large server applications with more IO and less computation (the nature of js), or where the server needs to work with c++. Nest does not use microservice by default. Nest seals different platforms under different platforms. Here we only analyze the common form of Express platform. Its overall core structure is roughly as follows:

  • packages
    • core
      • injector
        • injector.ts
        • module.ts
      • services
        • reflector.service.ts
    • platform-express

The source code

Here, I mainly share a simple understanding of the implementation of dependency injection. The idea is consistent, and I have a good understanding of the dependency injection concept of the back end, which is also a reflection of the front-end of the back end, and also a historical process of the transition from the earliest MVC framework to the later MVVM framework. The dependency injection approach is memorable for the earliest front-end frameworks, but for the Ng family, it’s a fundamental part of their basic philosophy

Angular

Ng has implemented an abstract class for injector that overloads the use of different functions. For provider loop dependencies, it uses a Map data structure to distinguish different providers

Abstract class export Abstract Class Injector {abstract get<T>(token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue? : T, flags? : InjectFlags ): T; abstract get( token: any, notFoundValue? : any ): any; // 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; } // Diver data structure, where interface is used to accommodate 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; // Let record = records.get(token); 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(', ')}]`; 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); // Some error handling... return {deps, fn, useNew, value}; } / / deal with the problem of circular dependencies function recursivelyProcessProviders (records: the Map < any Record >, the provider: StaticProvider) : string|null { let scope: string|null = null; // Handle some errors depending on the situation... return scope; } / / analytic function is the function of Token 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; }Copy the code

Nest

Different from NG, NEST uses parameters and inherited parent class parameters to determine the entire loop dependency. It does not use overloading to implement nest, but it does deal with the loop dependency. The basic idea is the same.

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 {// Loading middleware express based 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, ); } 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, ); } public resolveParamToken<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol | any, ) { if (! param.forwardRef) { return param; } wrapper.forwardRef = true; return param.forwardRef(); }}Copy the code

Conclusion: As can be seen from the implementation of Nest and NG on Injector, although both of them are injector implementations, they have different implementation methods due to different presentation methods. For TS, interface or abstract class can be used for reference. For those of us who are used to JS, Extensions to entire data types (e.g., abstract classes, interfaces) are borrowed from the back end. On the whole, the most critical thing for the implementation of dependency injection is to deal with the whole dependency problem of the provider. Both of them use the way of token to distinguish which provider belongs to, and then deal with the problem of special dependency cycle

conclusion

The whole ng ecosystem is not widely used in China, but it does not hinder its role as a pioneer of front-end concept expansion. Personally, I think it is superior to VUE and React in isolation and systemality. Therefore, for the currently popular micro front-end framework (PS: For ng microfront-end applications, see this article [# 1789]. I think we can learn some ideas from NG in terms of system integration such as sandbox isolation. Perhaps because of this, IT is the first of the three frameworks to introduce TS. It is also possible that the whole NG developer is more like a traditional software engineer. For the whole development, they need to define data, define models, and design systems, etc. For large projects, this will indeed reduce the time for repeated modifications due to bugs, but for small projects, I think VUE is more suitable. Although to domestic, ng is basically belongs to the thing of the past, but some of its concept and design thinking is still worthy of reference, in The Times of volume within the application are to high-grade, large-scale development, perhaps which day ng again at home to return to the top of it, though it’s hard to ~ ~ ha ha ha, you come on!

Warehouse address, welcome STAR: front-end warehouse address back-end warehouse address

reference

  • Presents the source code
  • Angular Chinese documentation
  • Nest source
  • Chinese Version of Nest
  • AngularJs vs. Angular
  • Angular 9.1.0 reported an error when compiling some packages with soft links in the project path. 9.1.1 has been fixed
  • Angularjs source code analysis
  • How to customize your Node.js framework for your team? (Based on EggJS)
  • # 1789. ToB enterprise applications using Angular to build a micro front-end architecture