Lesson 21-Angular source code architecture

Angular Core Modules Source Code

NgModule Loading and Initialization

NgModule Loading Process:

  1. Bootstrap Phase: platformBrowserDynamic().bootstrapModule(AppModule)
  2. Compilation Phase: Compiler compiles templates into view factories
  3. Instantiation Phase: Creates NgModuleRef and initializes

Key Source Code Points:

// @angular/core/src/application_ref.ts
bootstrapModule<M>(
  moduleType: Type<M>,
  compilerOptions?: CompilerOptions
): Promise<NgModuleRef<M>> {
  // 1. Create compiler factory
  const compilerFactory = compiler.getCompilerFactory();
  const compiler = compilerFactory.createCompiler([/* compiler options */]);

  // 2. Compile NgModule
  return compiler.compileModuleAndAllComponentsAsync(moduleType).then(({ ngModuleFactory }) => {
    // 3. Create NgModuleRef
    const moduleRef = ngModuleFactory.create(injector);

    // 4. Initiate change detection
    moduleRef.injector.get(ApplicationInitStatus).runInitializers();
    moduleRef.injector.get(PlatformRef).tick();

    return moduleRef;
  });
}

NgModule Initialization Order:

  1. Providers registration
  2. Declarations processing
  3. Imports resolution
  4. Exports handling
  5. Bootstrap components initialization

Component and Template Compilation Process

Compiler Module Architecture:

Compiler
├── TemplateParser       // Parses templates into AST
├── ViewCompiler         // Generates view factories
├── StyleCompiler        // Compiles styles
└── DirectiveNormalizer  // Normalizes directives/pipes

Template Compilation Process:

// 1. Parse template to generate AST
const templateParser = new TemplateParser(
  /* dependency-injected services */
);
const ast = templateParser.parse(template, directives, pipes);

// 2. Generate view factory
const viewCompiler = new ViewCompiler(/* configuration */);
const viewFactory = viewCompiler.compileComponent(
  componentType,
  ast,
  /* other parameters */
);

// 3. Create component view
const componentFactory = new ComponentFactory(
  componentType,
  viewFactory
);

AST Node Types:

  • ElementAst: Element nodes
  • TextAst: Text nodes
  • BoundEventAst: Event bindings
  • BoundPropertyAst: Property bindings
  • ReferenceAst: Template reference variables

Dependency Injection Container Implementation

Injector Hierarchy:

NullInjector (root)
├── PlatformInjector
├── AppModuleInjector
└── ComponentInjector

Key Source Code Implementation:

// @angular/core/src/di/injector.ts
class Injector {
  get(token: any, notFoundValue?: any): any {
    // 1. Check if current Injector has provider
    const provider = this._providers.get(token);
    if (provider) {
      return this._instantiateProvider(provider);
    }

    // 2. Delegate to parent Injector
    if (this._parent) {
      return this._parent.get(token, notFoundValue);
    }

    // 3. Throw error if not found
    if (notFoundValue === Injector.THROW_IF_NOT_FOUND) {
      throw new Error(`No provider for ${token}`);
    }
    return notFoundValue;
  }

  private _instantiateProvider(provider: Provider): any {
    // Handle different provider types (ClassProvider, ValueProvider, etc.)
  }
}

Dependency Resolution Strategies:

  1. Immediate Resolution: Resolves all dependencies during component initialization
  2. Deferred Resolution: Uses @Optional() or @SkipSelf() decorators
  3. Circular Dependency Handling: Resolves via forwardRef()

Change Detection Mechanism

Change Detection Strategy Comparison:

StrategyTrigger ConditionsPerformance Characteristics
DefaultAny async eventFull check
OnPushInput changes or event triggersPartial check

Change Detection Process:

// @angular/core/src/change_detection/change_detection.ts
function detectChangesInternal(
  view: ViewData,
  CheckType: CheckType
): void {
  // 1. Check if skip is needed (OnPush strategy)
  if (view.def.flags & ViewFlags.OnPush && 
      !view.state & ViewState.ChecksEnabled) {
    return;
  }

  // 2. Perform change detection
  ViewAction.checkNoChangesView(view);
  ViewAction.detectChangesView(view);

  // 3. Recursively check child views
  if (view.child) {
    detectChangesInternal(view.child, CheckType);
  }

  // 4. Check projected views
  if (view.projection) {
    detectChangesInternal(view.projection, CheckType);
  }
}

Change Detection Optimizations:

  1. TrackBy Function: Reduces DOM operations
  2. Pure Pipes: Caches computation results
  3. OnPush Strategy: Narrows detection scope

Routing Module Implementation

Route Configuration Parsing:

// @angular/router/src/config/config_loader.ts
class RouterConfigLoader {
  load(parentInjector: Injector, route: Route): Promise<Route> {
    if (route.loadChildren) {
      // Dynamically load feature module
      return this.loadModuleFactory(route.loadChildren).then(factory => {
        route._loadedConfig = {
          module: factory.create(parentInjector).instance,
          routes: factory.create(parentInjector).instance.routes
        };
        return route;
      });
    }
    return Promise.resolve(route);
  }
}

Route Matching Process:

  1. URL Parsing: Splits URL into segments
  2. Path Matching: Matches route configurations in order
  3. Parameter Extraction: Extracts parameters from URL
  4. Redirect Handling: Processes redirect routes
  5. Component Resolution: Loads component factories

Route Guard Execution Order:

  1. CanActivateChild
  2. CanActivate
  3. Resolve
  4. CanDeactivate

Angular Compiler Source Code

Template Parsing and AST Generation

Template Parsing Process:

// @angular/compiler/src/template_parser/template_parser.ts
class TemplateParser {
  parse(
    template: string,
    directives: Directive[],
    pipes: Pipe[]
  ): TemplateAst[] {
    // 1. Lexical analysis
    const tokens = this._lexer.tokenize(template);

    // 2. Syntax analysis
    const parser = new TemplateParserVisitor(directives, pipes);
    return parser.visitAll(tokens);
  }
}

// AST node example
class BoundEventAst implements TemplateAst {
  name: string;       // Event name
  target: string;     // Event target (optional)
  handler: AST;       // Event handler expression
  phase: EventPhase;  // Event phase
}

AST Transformation Process:

  1. Element ASTView Node
  2. Binding ASTProperty/Event Listener
  3. Directive ASTDirective Instantiation Logic

View Factory Generation and Optimization

View Factory Generation Process:

// @angular/compiler/src/view_compiler/view_compiler.ts
class ViewCompiler {
  compileComponent(
    component: CompileComponent,
    template: TemplateAst[]
  ): ViewFactory {
    // 1. Create view nodes
    const viewNodes = this._createViewNodes(template);

    // 2. Generate event listeners
    const eventListeners = this._generateEventListeners(template);

    // 3. Generate binding update code
    const updateInstructions = this._generateUpdateInstructions(template);

    // 4. Generate view factory function
    return new ViewFactory(
      viewNodes,
      eventListeners,
      updateInstructions
    );
  }
}

View Factory Optimization Techniques:

  1. Static Content Hoisting: Extracts static nodes outside the view
  2. Binding Code Inlining: Reduces function call overhead
  3. Directive Merging: Consolidates multiple applications of the same directive

Dynamic Component Compilation and Loading

Dynamic Component Compilation Process:

// @angular/core/src/linker/dynamic_component_loader.ts
class DynamicComponentLoader {
  loadNextToLocation(
    componentType: Type<any>,
    location: ViewContainerRef
  ): Promise<ComponentRef<any>> {
    // 1. Get component factory
    const factory = this._compiler.resolveComponentFactory(componentType);

    // 2. Create component instance
    return location.createComponent(factory);
  }
}

// Runtime compilation (deprecated, recommend Ahead-of-Time compilation)
class JitCompiler {
  compileModuleAndAllComponentsSync(moduleType: Type<any>): ModuleWithComponentFactories<any> {
    // JIT compilation implementation
  }
}

Dynamic Loading Optimizations:

  1. Precompilation: Use AOT compilation to reduce runtime overhead
  2. Lazy Loading: Load feature modules on demand
  3. Caching: Cache compiled component factories

Directive and Pipe Compilation Process

Directive Compilation Process:

  1. Directive Normalization: Process @Directive decorator metadata
  2. Selector Parsing: Parse CSS selectors
  3. Lifecycle Hook Registration: Generate hook call code
  4. Input/Output Binding: Generate property access code

Pipe Compilation Process:

// @angular/compiler/src/expression_parser/ast.ts
class PipeAst implements AST {
  name: string;       // Pipe name
  arg: AST;           // Pipe arguments
  exp: AST;           // Input expression
}

// Pipe transformation code generation
function generatePipeCode(pipe: PipeAst): string {
  return `${pipe.exp}.pipe(${pipe.name}(${pipe.arg ? pipe.arg.toString() : ''}))`;
}

Directive/Pipe Optimizations:

  1. Pure Directive Marking: Skips unnecessary change detection
  2. Pipe Caching: Caches pipe computation results
  3. Compile-Time Optimization: Inlines simple directive logic

Compiler Performance Optimization

Compilation Optimization Strategies:

  1. Incremental Compilation: Recompile only changed parts
  2. Parallel Compilation: Utilize multi-core CPUs
  3. Caching Mechanism: Cache compilation results
  4. Lazy Compilation: Defer non-critical compilation tasks

AOT Compilation Advantages:

  1. Faster Rendering: Reduces runtime compilation overhead
  2. Smaller Bundle Size: Removes compiler code
  3. Earlier Error Detection: Catches template errors at compile time
  4. Better Security: Reduces runtime code generation

Angular Change Detection Source Code

Change Detection Strategy Implementation

OnPush Strategy Implementation:

// @angular/core/src/change_detection/change_detection.ts
class ChangeDetectorRef {
  markForCheck(): void {
    // Mark current view and ancestors for checking
    let current: ViewData|null = this._view;
    while (current) {
      current.state |= ViewState.CheckOnce;
      current = current.parent;
    }
  }

  checkNoChanges(): void {
    // Strictly check for no changes
    ViewAction.checkNoChangesView(this._view);
  }
}

Default Strategy Implementation:

class DefaultChangeDetector extends ChangeDetectorRef {
  detectChanges(): void {
    // Full check of all views
    ViewAction.detectChangesView(this._view);
  }
}

Strategy Selection Recommendations:

  1. OnPush: Suitable for components with stable input properties
  2. Default: Suitable for frequently changing components

Change Detection Trigger Mechanism

Trigger Source Categories:

  1. Event Bindings: (click)="handler()"
  2. Timers: setTimeout/setInterval
  3. Promises: then/catch/finally
  4. RxJS: Observable subscriptions

Trigger Process:

// @angular/core/src/util/async.ts
class ApplicationRef {
  tick(): void {
    // 1. Perform change detection
    this._zone.run(() => {
      this._changeDetectorRefs.forEach(ref => ref.detectChanges());
    });

    // 2. Process microtask queue
    this._zone.runOutsideAngular(() => {
      this._microtaskQueue.forEach(task => task());
    });
  }
}

Trigger Optimizations:

  1. Debouncing: Merge multiple triggers in a short period
  2. Batch Updates: Use NgZone to batch process async events
  3. Manual Control: Use detach()/reattach()

Change Detection Optimization Strategies

TrackBy Function Implementation:

// @angular/core/src/change_detection/differs/iterable_differs.ts
class DefaultIterableDiffer implements IterableDiffer<any> {
  diff(collection: any[]): any {
    if (this._trackByFn) {
      // Compare items using trackBy function
      return this._diffWithTrackBy(collection);
    } else {
      // Default full comparison
      return this._diffWithoutTrackBy(collection);
    }
  }
}

// Usage in component
@Component({
  template: `
    <div *ngFor="let item of items; trackBy: trackById">
      {{ item.name }}
    </div>
  `
})
export class MyComponent {
  trackById(index: number, item: any): number {
    return item.id;
  }
}

Pure Pipe Optimization:

// @angular/core/src/pipes/pure_pipe.ts
class PurePipeTransform implements PipeTransform {
  private _lastInput: any;
  private _lastValue: any;

  transform(value: any): any {
    if (this._lastInput === value) {
      return this._lastValue;
    }
    this._lastInput = value;
    this._lastValue = this._doTransform(value);
    return this._lastValue;
  }
}

Performance Analysis and Tuning

Performance Analysis Tools:

  1. Angular DevTools: Visualizes change detection cycles
  2. Chrome Performance Panel: Records performance timelines
  3. Custom Performance Markers: console.time('changeDetection'); this.changeDetectorRef.detectChanges(); console.timeEnd('changeDetection');

Tuning Recommendations:

  1. Reduce Detection Scope: Use OnPush strategy
  2. Optimize Binding Count: Minimize unnecessary bindings
  3. Avoid Complex Expressions: Simplify template expressions
  4. Use trackBy: Optimize ngFor performance

Security and Compatibility

Security Measures:

  1. XSS Protection: Automatically escapes interpolation expressions
  2. DOM Sanitization: Safely handles innerHTML
  3. Style Isolation: Encapsulates component styles

Compatibility Handling:

  1. Legacy Browser Support: Polyfill services
  2. IE Compatibility: classlist/promise polyfills
  3. Mobile Adaptation: Touch event handling

Change Detection Security Mechanisms:

  1. Immutable Data: Recommend using immutable data structures
  2. Pure Functions: Ensure pipes and pure directives have no side effects
  3. Error Boundaries: Use ErrorBoundary component to catch exceptions

By deeply understanding Angular’s source architecture, developers can build high-performance, maintainable applications more efficiently and perform precise optimizations when encountering performance bottlenecks.

Share your love