Home  >  Article  >  Web Front-end  >  Let’s talk about metadata and decorators in Angular

Let’s talk about metadata and decorators in Angular

青灯夜游
青灯夜游forward
2022-02-28 11:10:442191browse

This article continues the learning of Angular, takes you to understand the metadata and decorators in Angular, and briefly understands their usage. I hope it will be helpful to everyone!

Let’s talk about metadata and decorators in Angular

As a front-end framework designed “for large-scale front-end projects”, Angular actually has many designs worthy of reference and learning. This series is mainly used to study these designs and functions. Realization principle. This article mainly introduces metadata that can be found everywhere in Angular. [Related tutorial recommendations: "angular tutorial"]

Decorators are the core concept when developing using Angular. In Angular, decorators are used to attach metadata to classes or properties to let you know what those classes or properties mean and how to deal with them.

Decorators and metadata

Neither decorators nor metadata are concepts proposed by Angular. So let’s take a quick look at it first.

Metadata

In a general concept, metadata is data that describes user data. It summarizes basic information about the data and can make it easier to find and use specific data instances. For example, author, creation date, modification date, and file size are examples of very basic document metadata.

In the scenario used for classes, metadata is used to decorate the class to describe the definition and behavior of the class so that the expected behavior of the class can be configured.

Decorator

Decorator is a language feature of JavaScript, which is located in stage 2. Test characteristics.

Decorators are functions called on a class, class element, or other JavaScript syntax form during definition.

Decorators have three main functions:

  • You can replace the value being decorated with a matching value with the same semantics. (For example, a decorator can replace a method with another method, one field with another field, one class with another class, and so on).

  • Metadata can be associated with the value being decorated; this metadata can be read externally and used for metaprogramming and self-inspection.

  • Access to the value being modified can be provided through metadata. For public values, they do so via the value name; for private values, they receive accessor functions, which can then optionally be shared.

Essentially, decorators can be used to metaprogram a value and add functionality to it without fundamentally changing its external behavior.

For more information, please refer to tc39/proposal-decorators proposal.

Decorators and metadata in Angular

When we develop Angular applications, whether they are components, instructions, services, modules, etc., they all need to be defined and developed through decorators. Decorators appear immediately before a class definition and are used to declare that the class has a specified type and provide metadata suitable for that type.

For example, we can use the following decorators to declare Angular classes: @Component(), @Directive(), @Pipe(), @Injectable(), @NgModule().

Use decorators and metadata to change the behavior of a class

Take @Component() as an example, the The functions of the decorator include:

  • Mark the class as an Angular component.

  • Provides configurable metadata that determines how the component should be processed, instantiated, and used at runtime.

For how to use @Component(), you can refer to , I won’t introduce it here. Let’s take a look at the definition of this decorator:

// 提供 Angular 组件的配置元数据接口定义
// Angular 中,组件是指令的子集,始终与模板相关联
export interface Component extends Directive {
  // changeDetection 用于此组件的变更检测策略
  // 实例化组件时,Angular 将创建一个更改检测器,该更改检测器负责传播组件的绑定。
  changeDetection?: ChangeDetectionStrategy;
  // 定义对其视图 DOM 子对象可见的可注入对象的集合
  viewProviders?: Provider[];
  // 包含组件的模块的模块ID,该组件必须能够解析模板和样式的相对 URL
  moduleId?: string;
  ...
  // 模板和 CSS 样式的封装策略
  encapsulation?: ViewEncapsulation;
  // 覆盖默认的插值起始和终止定界符(`{{`和`}}`)
  interpolation?: [string, string];
}

// 组件装饰器和元数据
export const Component: ComponentDecorator = makeDecorator(
    'Component',
    // 使用默认的 CheckAlways 策略,在该策略中,更改检测是自动进行的,直到明确停用为止。
    (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
    Directive, undefined,
    (type: Type<any>, meta: Component) => SWITCH_COMPILE_COMPONENT(type, meta));

The above is the definition of component decoration and component metadata. Let’s take a look at the creation process of the decorator.

The creation process of the decorator

We can find it from the source code. The decorators of components and instructions will pass makeDecorator() To generate:

export function makeDecorator<T>(
    name: string, props?: (...args: any[]) => any, parentClass?: any, // 装饰器名字和属性
    additionalProcessing?: (type: Type<T>) => void,
    typeFn?: (type: Type<T>, ...args: any[]) => void):
    {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
  // noSideEffects 用于确认闭包编译器包装的函数没有副作用
  return noSideEffects(() => { 
    const metaCtor = makeMetadataCtor(props);
    // 装饰器工厂
    function DecoratorFactory(
        this: unknown|typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
      if (this instanceof DecoratorFactory) {
        // 赋值元数据
        metaCtor.call(this, ...args);
        return this as typeof DecoratorFactory;
      }
      // 创建装饰器工厂
      const annotationInstance = new (DecoratorFactory as any)(...args);
      return function TypeDecorator(cls: Type<T>) {
        // 编译类
        if (typeFn) typeFn(cls, ...args);
        // 使用 Object.defineProperty 很重要,因为它会创建不可枚举的属性,从而防止该属性在子类化过程中被复制。
        const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
            (cls as any)[ANNOTATIONS] :
            Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
        annotations.push(annotationInstance);
        // 特定逻辑的执行
        if (additionalProcessing) additionalProcessing(cls);

        return cls;
      };
    }
    if (parentClass) {
      // 继承父类
      DecoratorFactory.prototype = Object.create(parentClass.prototype);
    }
    DecoratorFactory.prototype.ngMetadataName = name;
    (DecoratorFactory as any).annotationCls = DecoratorFactory;
    return DecoratorFactory as any;
  });
}

In the above example, we use makeDecorator() to generate a Component decorator factory for defining components. When you create a component using @Component(), Angular compiles the component based on the metadata.

Compile the component based on the decorator metadata

Angular will compile the Angular component based on the decorator metadata, and then convert the generated component Definition (ɵcmp) is patched onto the component type:

export function compileComponent(type: Type<any>, metadata: Component): void {
  // 初始化 ngDevMode
  (typeof ngDevMode === &#39;undefined&#39; || ngDevMode) && initNgDevMode();
  let ngComponentDef: any = null;
  // 元数据可能具有需要解析的资源
  maybeQueueResolutionOfComponentResources(type, metadata);
  // 这里使用的功能与指令相同,因为这只是创建 ngFactoryDef 所需的元数据的子集
  addDirectiveFactoryDef(type, metadata);
  Object.defineProperty(type, NG_COMP_DEF, {
    get: () => {
      if (ngComponentDef === null) {
        const compiler = getCompilerFacade();
        // 根据元数据解析组件
        if (componentNeedsResolution(metadata)) {
          ...
          // 异常处理
        }
        ...
        // 创建编译组件需要的完整元数据
        const templateUrl = metadata.templateUrl || `ng:///${type.name}/template.html`;
        const meta: R3ComponentMetadataFacade = {
          ...directiveMetadata(type, metadata),
          typeSourceSpan: compiler.createParseSourceSpan(&#39;Component&#39;, type.name, templateUrl),
          template: metadata.template || &#39;&#39;,
          preserveWhitespaces,
          styles: metadata.styles || EMPTY_ARRAY,
          animations: metadata.animations,
          directives: [],
          changeDetection: metadata.changeDetection,
          pipes: new Map(),
          encapsulation,
          interpolation: metadata.interpolation,
          viewProviders: metadata.viewProviders || null,
        };
        // 编译过程需要计算深度,以便确认编译是否最终完成
        compilationDepth++;
        try {
          if (meta.usesInheritance) {
            addDirectiveDefToUndecoratedParents(type);
          }
          // 根据模板、环境和组件需要的元数据,来编译组件
          ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta);
        } finally {
          // 即使编译失败,也请确保减少编译深度
          compilationDepth--;
        }
        if (compilationDepth === 0) {
          // 当执行 NgModule 装饰器时,我们将模块定义加入队列,以便仅在所有声明都已解析的情况下才将队列出队,并将其自身作为模块作用域添加到其所有声明中
          // 此调用运行检查以查看队列中的任何模块是否可以出队,并将范围添加到它们的声明中
          flushModuleScopingQueueAsMuchAsPossible();
        }
        // 如果组件编译是异步的,则声明该组件的 @NgModule 批注可以执行并在组件类型上设置 ngSelectorScope 属性
        // 这允许组件在完成编译后,使用模块中的 directiveDefs 对其自身进行修补
        if (hasSelectorScope(type)) {
          const scopes = transitiveScopesFor(type.ngSelectorScope);
          patchComponentDefWithScope(ngComponentDef, scopes);
        }
      }
      return ngComponentDef;
    },
    ...
  });
}

编译组件的过程可能是异步的(比如需要解析组件模板或其他资源的 URL)。如果编译不是立即进行的,compileComponent会将资源解析加入到全局队列中,并且将无法返回ɵcmp,直到通过调用resolveComponentResources解决了全局队列为止。

编译过程中的元数据

元数据是有关类的信息,但它不是类的属性。因此,用于配置类的定义和行为的这些数据,不应该存储在该类的实例中,我们还需要在其他地方保存此数据。

在 Angular 中,编译过程产生的元数据,会使用CompileMetadataResolver来进行管理和维护,这里我们主要看指令(组件)相关的逻辑:

export class CompileMetadataResolver {
  private _nonNormalizedDirectiveCache =
      new Map<Type, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>();
  // 使用 Map 的方式来保存
  private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>(); 
  private _summaryCache = new Map<Type, cpl.CompileTypeSummary|null>();
  private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
  private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
  private _ngModuleOfTypes = new Map<Type, Type>();
  private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();

  constructor(
      private _config: CompilerConfig, private _htmlParser: HtmlParser,
      private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
      private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
      private _schemaRegistry: ElementSchemaRegistry,
      private _directiveNormalizer: DirectiveNormalizer, private _console: Console,
      private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector,
      private _errorCollector?: ErrorCollector) {}
  // 清除特定某个指令的元数据
  clearCacheFor(type: Type) {
    const dirMeta = this._directiveCache.get(type);
    this._directiveCache.delete(type);
    ...
  }
  // 清除所有元数据
  clearCache(): void {
    this._directiveCache.clear();
    ...
  }
  /**
   * 加载 NgModule 中,已声明的指令和的管道
   */
  loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
      Promise<any> {
    const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound);
    const loading: Promise<any>[] = [];
    if (ngModule) {
      ngModule.declaredDirectives.forEach((id) => {
        const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync);
        if (promise) {
          loading.push(promise);
        }
      });
      ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference));
    }
    return Promise.all(loading);
  }
  // 加载指令(组件)元数据
  loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
    // 若已加载,则直接返回
    if (this._directiveCache.has(directiveType)) {
      return null;
    }
    directiveType = resolveForwardRef(directiveType);
    const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType)!;
    // 创建指令(组件)元数据
    const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata|null) => {
      const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
        isHost: false,
        type: metadata.type,
        isComponent: metadata.isComponent,
        selector: metadata.selector,
        exportAs: metadata.exportAs,
        changeDetection: metadata.changeDetection,
        inputs: metadata.inputs,
        outputs: metadata.outputs,
        hostListeners: metadata.hostListeners,
        hostProperties: metadata.hostProperties,
        hostAttributes: metadata.hostAttributes,
        providers: metadata.providers,
        viewProviders: metadata.viewProviders,
        queries: metadata.queries,
        guards: metadata.guards,
        viewQueries: metadata.viewQueries,
        entryComponents: metadata.entryComponents,
        componentViewType: metadata.componentViewType,
        rendererType: metadata.rendererType,
        componentFactory: metadata.componentFactory,
        template: templateMetadata
      });
      if (templateMetadata) {
        this.initComponentFactory(metadata.componentFactory!, templateMetadata.ngContentSelectors);
      }
      // 存储完整的元数据信息,以及元数据摘要信息
      this._directiveCache.set(directiveType, normalizedDirMeta);
      this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
      return null;
    };

    if (metadata.isComponent) {
      // 如果是组件,该过程可能为异步过程,则需要等待异步过程结束后的模板返回
      const template = metadata.template !;
      const templateMeta = this._directiveNormalizer.normalizeTemplate({
        ngModuleType,
        componentType: directiveType,
        moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation),
        encapsulation: template.encapsulation,
        template: template.template,
        templateUrl: template.templateUrl,
        styles: template.styles,
        styleUrls: template.styleUrls,
        animations: template.animations,
        interpolation: template.interpolation,
        preserveWhitespaces: template.preserveWhitespaces
      });
      if (isPromise(templateMeta) && isSync) {
        this._reportError(componentStillLoadingError(directiveType), directiveType);
        return null;
      }
      // 并将元数据进行存储
      return SyncAsync.then(templateMeta, createDirectiveMetadata);
    } else {
      // 指令,直接存储元数据
      createDirectiveMetadata(null);
      return null;
    }
  }
  // 获取给定指令(组件)的元数据信息
  getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
    const dirMeta = this._directiveCache.get(directiveType)!;
    ...
    return dirMeta;
  }
  // 获取给定指令(组件)的元数据摘要信息
  getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
    const dirSummary =
        <cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
    ...
    return dirSummary;
  }
}

可以看到,在编译过程中,不管是组件、指令、管道,还是模块,这些类在编译过程中的元数据,都使用Map来存储。

总结

本节我们介绍了 Angular 中的装饰器和元数据,其中元数据用于描述类的定义和行为。

在 Angular 编译过程中,会使用Map的数据结构来维护和存储装饰器的元数据,并根据这些元数据信息来编译组件、指令、管道和模块等。

更多编程相关知识,请访问:编程教学!!

The above is the detailed content of Let’s talk about metadata and decorators in Angular. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete