Look directly at the summary of people’s words

Start by introducing yourself

Angular source code reading

The project address

The article project address

Angular version: 8.0.0-RC.4

Welcome to my angular class framework

The article lists

Read Chapter 1 of angular source code: Opening and platformBrowserDynamic

Read Chapter 2 of the Angular source code: The bootstrapModule

Read Chapter 3: Initializing a Zone

Chapter 4: Angular Modules and JIT-compiled Modules

Why write such a project

Statement: only for personal reading of the understanding of the source code, not necessarily completely correct, but also need to guide the big guy.

There are a lot of source code readings for Vue and React, but there are almost no systematic source code readings for Angular.

And most people instinctively shun Angular when they hear about it.

But it’s not. In my opinion, Angular just uses a lot of existing back-end concepts, such as DI, AOT, etc.

I wrote an Angular like framework earlierInDiv, basically implements most of ng’s decorators.

And I learned a lot from Angular while writing this project.

This time, we hope to learn more about Google’s use of design patterns, code optimization and structure by reading Angular source code.

Also have a little selfish, hope more people say ng dafa good, ha ha.

The premise

I hope you understand the basics of typescripy and Angular before reading this article, because DI and service providers are used a lot

  1. typescript
  2. Presents the document

The project structure

There are only three folders under the project: Angular Docs and My-Demo

-Angular: annotated version of Angular ts source code -docs: document location -my-demo: a demo project to launchCopy the code

Set the Angular alias to the Angular folder via tsconfig to read the ts source.

Start the app

On the browser side, every Angular app starts at main.ts.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err= > console.error(err));
Copy the code

As for the startup project, it all starts on this line platformBrowserDynamic().bootstrapModule(AppModule).

In the Angular world, all apps are launched with the bootstrapModule method provided by platformBrowserDynamic() to boot the root or main module.

Platform platform

Angular abstracts platform to implement cross-platform.

The method to instantiate the Angular root module’s bootstrapModule on the browser side comes from @angular/platform-browser-dynamic.

In addition to @angular/platform-browser-dynamic, there is @angular/platform-browser.

The main difference between the two modules is the compilation style. Platform-browser-dynamic provides JIT compilation, which means compilation is done in the browser, while platform-Browser provides AOT compilation, which is done locally.

As for the difference between

platformBrowserDynamic

angular/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts

/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
Copy the code

The platformBrowserDynamic method is simply a function that calls createPlatformFactory, the factory that created the platform. One of the returns is the PlatformRef platform instance.

createPlatformFactory

angular/packages/core/src/application_ref.ts

/** * Creates a factory for a platform * * 1. Check if there is a parent 'Factory' *. If there is a parent Factory, merge the Provider passed in by calling Factory with the Provider passed in by calling createPlatformFactory, and then call the parent Factory * 4. If you don't have a parent 'Factory', create an 'Injector' first, / export function createPlatformFactory(parentPlatformFactory: ((extraProviders? : StaticProvider[]) => PlatformRef) | null, name: string, providers: StaticProvider[] = []): (extraProviders? : StaticProvider[]) => PlatformRef { const desc = `Platform: ${name}`; const marker = new InjectionToken(desc); return (extraProviders: StaticProvider[] = []) => { let platform = getPlatform(); // comment: check whether platform instance if (! Platform | | platform. The injector. The get (ALLOW_MULTIPLE_PLATFORMS, false)) {if (parentPlatformFactory) {/ / comment: Call the parent platform method parentPlatformFactory(provider.concat (extraProviders).concat({provide: marker, useValue: true})); } else { const injectedProviders: StaticProvider[] = providers.concat(extraProviders).concat({provide: marker, useValue: true}); CreatePlatform (Injector. Create ({providers: injectedProviders, name: desc})); injectedProviders ({providers: injectedProviders, name: desc})); } } return assertPlatform(marker); }; }Copy the code

The method takes three arguments:

  1. parentPlatformFactory: ((extraProviders? : StaticProvider []) = > PlatformRef) | null returns the parent platform factory instance methods

  2. Name: string Platform name

  3. Providers: StaticProvider[] = [] Indicates the DI service provider

  4. First create a Platform: ${name} value provider through InjectionToken

  5. Then return a method that accepts the extraProviders? : StaticProvider[] returns a platform instance PlatformRef

Method returned by createPlatformFactory

  1. Gets the current platform instance
  2. If the current platform instance does not exist and does not existAllowMultipleTokenThis service provider allows multiple tokens
    1. Parent platform factory methodparentPlatformFactoryThere is,Merging service providersAnd call it recursivelyparentPlatformFactory
    2. Parent platform factory methodparentPlatformFactoryIf no, thenCreate instance methods using an injectorInjector.createCreate instance platform instances and use themcreatePlatformSet to a global platform instance
  3. callassertPlatformVerify that this exists in the IOC containermarkerPlatform instance and return

So in order to create platform instances, it should beMerge browserDynamic's provider => merge coreDynamic's provider => merge provider and create core

In human terms:

  1. Check whether it has been created
  2. Determine if there is a fatherFactory
  3. If there is a fatherFactoryThe callFactoryWhen the incomingProviderAnd callcreatePlatformFactoryThe incomingProviderMerge, and then call the parentFactory
  4. If you don’t have a fatherFactoryFirst create oneInjector“, and then createPlatformRefThe instance

createPlatform

angular/packages/core/src/application_ref.ts


let _platform: PlatformRef;

/** * Creates a platform. * Platforms have to be eagerly created via this function. * * @publicApi */
export function createPlatform(injector: Injector) :PlatformRef {
  if(_platform && ! _platform.destroyed && ! _platform.injector.get(ALLOW_MULTIPLE_PLATFORMS,false)) {
    throw new Error(
        'There can be only one platform. Destroy the previous one to create a new one.');
  }
  _platform = injector.get(PlatformRef);
  // Note: The function to be executed when the platform is initialized, provided by platform browserDynamic
  const inits = injector.get(PLATFORM_INITIALIZER, null);
  if (inits) inits.forEach((init: any) = > init());
  return _platform;
}
Copy the code

_platformIs a globally unique platform instance.

The key method for creating a platform instance is to pass in the service injector instance injector and return the platform instance:

  1. Verify that the global platform instance exists, that the state is not destroyed, and that there are no multiple platform instances
  2. Get the platform instance from the injector
  3. injector.get(PLATFORM_INITIALIZER, null)To obtainThe functions that need to be executed when the platform is initialized and executed

Back to platformBrowserDynamic:

angular/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts

/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
Copy the code

INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS

What services does this provider provide?

angular/packages/platform-browser-dynamic/src/platform_providers.ts

/** * @publicApi */
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: StaticProvider[] = [
  INTERNAL_BROWSER_PLATFORM_PROVIDERS, // Note: several initialization methods are injected here
  {
    provide: COMPILER_OPTIONS,
    useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
    multi: true
  },
  {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
];
Copy the code

INTERNAL_BROWSER_PLATFORM_PROVIDERS: COMPILER_OPTIONS and PLATFORM_ID

INTERNAL_BROWSER_PLATFORM_PROVIDERS comes from @angular/platform-browser:

angular/packages/platform-browser/src/browser.ts

export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
  {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
  {provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true}, // comment: initialization method
  {provide: PlatformLocation, useClass: BrowserPlatformLocation, deps: [DOCUMENT]},
  {provide: DOCUMENT, useFactory: _document, deps: []},
];
Copy the code

@angular/platform-browser provides some browser-side ng implementations:

  1. PLATFORM_INITIALIZERIs to initialize the set of methods that need to be executedThis is important
  2. DOCUMENTBrowser sidedocument_documentFactory method returndocument

Const inits = Injector. get(PLATFORM_INITIALIZER, null); if (inits) inits.forEach((init: any) => init()); Execute the factory methods injected by PLATFORM_INITIALIZER in turn.

So look at initDomAdapter:

angular/packages/platform-browser/src/browser.ts

export function initDomAdapter() {
  BrowserDomAdapter.makeCurrent();
  BrowserGetTestability.init();
}
Copy the code
  1. BrowserDomAdapter.makeCurrent();throughBrowserDomAdapterInstantiate a static method ofBrowserDomAdapterThe global DOM adapter, specificallyImplement and encapsulate some methods on the browser sideYou can see the detailsangular/packages/platform-browser/src/browser/browser_adapter.tsIn theclass BrowserDomAdapter extends GenericBrowserDomAdapter
  2. BrowserGetTestability.init();Initialize angular tests

Back to that, when we created platformBrowserDynamic, we passed in a method called platformCoreDynamic that returns the parent platform instance

platformCoreDynamic

angular/packages/platform-browser-dynamic/src/platform_core_dynamic.ts

import {COMPILER_OPTIONS, CompilerFactory, PlatformRef, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
import {JitCompilerFactory} from './compiler_factory';

/** * A platform that included corePlatform and the compiler. * * @publicApi */
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
  {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
  {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
]);
Copy the code

PlatformCoreDynamic is passed in again

  1. from@angular/corePlatform coreplatformCore
  2. The platform ofcoreDynamic
  3. Two static service providers: compilation optionCOMPILER_OPTIONSplatformDynamic 的JITCompiler factoryJitCompilerFactory

JitCompilerFactory

The key to the

Take a look at JitCompilerFactory:

angular/packages/platform-browser-dynamic/src/compiler_factory.ts

/** * @publicApi */
export class JitCompilerFactory implements CompilerFactory {
  private _defaultOptions: CompilerOptions[];

  /* @internal */
  constructor(defaultOptions: CompilerOptions[]) {
    const compilerOptions: CompilerOptions = {
      useJit: true,
      defaultEncapsulation: ViewEncapsulation.Emulated,
      missingTranslation: MissingTranslationStrategy.Warning,
    };

    this._defaultOptions = [compilerOptions, ...defaultOptions];
  }
  createCompiler(options: CompilerOptions[] = []): Compiler {
    const opts = _mergeOptions(this._defaultOptions.concat(options));
    const injector = Injector.create([
      // Note: Compiler is replaced here with CompilerImpl
      COMPILER_PROVIDERS, {
        provide: CompilerConfig,
        useFactory: (a)= > {
          return new CompilerConfig({
            // let explicit values from the compiler options overwrite options
            // from the app providers
            useJit: opts.useJit,
            jitDevMode: isDevMode(),
            // let explicit values from the compiler options overwrite options
            // from the app providersdefaultEncapsulation: opts.defaultEncapsulation, missingTranslation: opts.missingTranslation, preserveWhitespaces: opts.preserveWhitespaces, }); }, deps: [] }, opts.providers ! ] );returninjector.get(Compiler); }}Copy the code

COMPILER_PROVIDERS are provided to syringes as service PROVIDERS. This is important because later compilers use it a lot:

angular/packages/platform-browser-dynamic/src/compiler_factory.ts

/** * A set of providers that provide `JitCompiler` and its dependencies to use for * template compilation. */
export const COMPILER_PROVIDERS = <StaticProvider[]>[
  // Note: This is also a core point - compiler reflector
  {provide: CompileReflector, useValue: new JitReflector()},
  // comment: ResourceLoader- ResourceLoader
  {provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
  // Comment: JIT summary parser
  {provide: JitSummaryResolver, deps: []},
  // Comments: digest parser
  {provide: SummaryResolver, useExisting: JitSummaryResolver},
  {provide: Console, deps: []},
  // Comments: parsers
  {provide: Lexer, deps: []},
  // Comment: parser
  {provide: Parser, deps: [Lexer]},
  // Comments: basic HTML parser
  {
    provide: baseHtmlParser,
    useClass: HtmlParser,
    deps: [],
  },
  // Comments: internationalized HTML parser
  {
    provide: I18NHtmlParser,
    useFactory: (parser: HtmlParser, translations: string | null, format: string,
                 config: CompilerConfig, console: Console) => {
      translations = translations || ' ';
      const missingTranslation =
          translations ? config.missingTranslation ! : MissingTranslationStrategy.Ignore;
      // Comments: new internationalized HTML parser
      return new I18NHtmlParser(parser, translations, format, missingTranslation, console);
    },
    deps: [
      baseHtmlParser,
      [new Optional(), new Inject(TRANSLATIONS)],
      [new Optional(), new Inject(TRANSLATIONS_FORMAT)],
      [CompilerConfig],
      [Console],
    ]
  },
  {
    provide: HtmlParser,
    useExisting: I18NHtmlParser,
  },
  // Comment: template parser
  {
    provide: TemplateParser, deps: [CompilerConfig, CompileReflector,
    Parser, ElementSchemaRegistry,
    I18NHtmlParser, Console]
  },
  { provide: JitEvaluator, useClass: JitEvaluator, deps: [] },
  // Comments: directive normalizer
  { provide: DirectiveNormalizer, deps: [ResourceLoader, UrlResolver, HtmlParser, CompilerConfig]},
  // Comment: metadata parser
  { provide: CompileMetadataResolver, deps: [CompilerConfig, HtmlParser, NgModuleResolver,
                      DirectiveResolver, PipeResolver,
                      SummaryResolver,
                      ElementSchemaRegistry,
                      DirectiveNormalizer, Console,
                      [Optional, StaticSymbolCache],
                      CompileReflector,
                      [Optional, ERROR_COLLECTOR_TOKEN]]},
  DEFAULT_PACKAGE_URL_PROVIDER,
  // Comments: style compiler
  { provide: StyleCompiler, deps: [UrlResolver]},
  // Comments: view compiler
  { provide: ViewCompiler, deps: [CompileReflector]},
  // Comments: NgModule compiler
  { provide: NgModuleCompiler, deps: [CompileReflector] },
  // Comment: compiler configuration project
  { provide: CompilerConfig, useValue: new CompilerConfig()},
  // Note: JIT, Compiler's service provider CompilerImpl
  { provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
                                TemplateParser, StyleCompiler,
                                ViewCompiler, NgModuleCompiler,
                                SummaryResolver, CompileReflector, JitEvaluator, CompilerConfig,
                                Console]},
  // comment: DOM schema
  { provide: DomElementSchemaRegistry, deps: []},
  // Comment: Element schema
  { provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
  // Comment: URL resolver
  { provide: UrlResolver, deps: [PACKAGE_ROOT_URL]},
  // Comments: directive resolver
  { provide: DirectiveResolver, deps: [CompileReflector]},
  // Comment: pipe resolver
  { provide: PipeResolver, deps: [CompileReflector]},
  // Comment: module parser
  { provide: NgModuleResolver, deps: [CompileReflector]},
];
Copy the code

Finally, an injector was created and the Compiler instance was obtained.

This is probably why @angular/ platform-browser-Dynamic provides JIT compilation.

platformCore

angular/packages/core/src/platform_core_providers.ts

import {PlatformRef, createPlatformFactory} from './application_ref';
import {PLATFORM_ID} from './application_tokens';
import {Console} from './console';
import {Injector, StaticProvider} from './di';
import {TestabilityRegistry} from './testability/testability';

const _CORE_PLATFORM_PROVIDERS: StaticProvider[] = [
  // Set a default platform name for platforms that don't set it explicitly.
  {provide: PLATFORM_ID, useValue: 'unknown'},
  // Comment: Here PlatformRef has been added to the Injector and instantiated in createPlatformFactory
  {provide: PlatformRef, deps: [Injector]},
  {provide: TestabilityRegistry, deps: []},
  {provide: Console, deps: []},
];

/** * This platform has to be included in any other platform * * @publicApi */
export const platformCore = createPlatformFactory(null.'core', _CORE_PLATFORM_PROVIDERS);
Copy the code

PlatformCore creates a method that returns an instance of the root platform factory and sets up four basic DI service providers

  1. PLATFORM_IDPlatform id
  2. PlatformRefHere,PlatformRefIs added to theinjectorAnd in theThe subsequentcreatePlatformFactoryThrough thecreatePlatform(Injector.create({providers: injectedProviders, name: desc}));Platform instances are instantiated
  3. TestabilityRegistryTestability registryRelevant test
  4. Console It’s interesting that Angular injects Console into DI as a service, but Console only implements log and WARN methods

PlatformRef

angular/packages/core/src/application_ref.ts

@Injectable(a)export class PlatformRef {
  private _modules: NgModuleRef<any= > [] [];private _destroyListeners: Function[] = [];
  private _destroyed: boolean = false;

  /** @internal */
  constructor(private _injector: Injector) {} bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options? : BootstrapOptions):Promise<NgModuleRef<M>> {
        ...
  }

  bootstrapModule<M>(
      moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
      Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
    // Comments: bootstrapModule 'first passes the optionsReducer recursive reduce to the compiler option' compilerOptions' as an object
    const options = optionsReducer({}, compilerOptions);
    // Note: This gets the compiled module factory and returns it to bootstrapModuleFactory to create the module
    return compileNgModuleFactory(this.injector, options, moduleType)
        .then(moduleFactory= > this.bootstrapModuleFactory(moduleFactory, options));
  }

  private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>) :void{... } onDestroy(callback:(a)= > void) :void { this._destroyListeners.push(callback); }

  get injector(): Injector { return this._injector; }

  destroy() {
    if (this._destroyed) {
      throw new Error('The platform has already been destroyed! ');
    }
    this._modules.slice().forEach(module= > module.destroy());
    this._destroyListeners.forEach(listener= > listener());
    this._destroyed = true;
  }

  get destroyed() { return this._destroyed; }}Copy the code

PlatformRef is a platform instance class with some methods and attributes, such as a few key methods

  1. bootstrapModuleMethod to boot the root module
  2. bootstrapModuleFactoryThe factory method of the instance module willRun zone.js and listen for events
  3. destroyMethods to destroy platform instances

We’ll talk about that later

conclusion

Calling platformBrowserDynamic() and generating a platform instance PlatformRef goes something like this:

  1. callcreatePlatformFactoryMerge platformbrowserDynamicprovidersAnd triggers the parent platformcoreDynamicPlatform factory function ofplatformbrowserDynamicprovidesPLATFORM_INITIALIZERPlatform initialization functions andBrowserDomAdapterThe global DOM adapter is the service provider
  2. callcreatePlatformFactoryMerge platformcoreDynamicprovidersAnd triggers the parent platformcorePlatform factory function ofplatformcoreDynamicprovidesJitCompilerFactoryRuntime compiler,JitCompilerFactoryAnd by creatingCOMPILER_PROVIDERSThe compiler instance is createdso@angular/platform-browser-dynamicProvides JIT runtime compilation
  3. platformcoreprovidesPlatformRefPlatform instance this service provider
  4. Due to the platformcoreNo parent platform,callInjector.createcreatePlatformRefThe instanceAnd,Assign to a globally unique platform instance_platform
  5. increatePlatformcreatePlatformRefInstantiate aBrowserDomAdapterThe global DOM adapter, specificallyImplement and encapsulate some methods on the browser side
  6. Finally assert, confirm existencePlatformRefInstance and returnPlatformRefThe instance

So, roughly, @angular/ platform-browser-Dynamic provides runtime compilation that implements and encapsulates browser methods