ptimizedImage implements OnInit, OnChanges, OnDestroy {
  private imageLoader = inject(IMAGE_LOADER);
  private config: ImageConfig = processConfig(inject(IMAGE_CONFIG));
  private renderer = inject(Renderer2);
  private imgElement: HTMLImageElement = inject(ElementRef).nativeElement;
  private injector = inject(Injector);
  private readonly isServer = isPlatformServer(inject(PLATFORM_ID));
  private readonly preloadLinkCreator = inject(PreloadLinkCreator);

  // a LCP image observer - should be injected only in the dev mode
  private lcpObserver = ngDevMode ? this.injector.get(LCPImageObserver) : null;

  /**
   * Calculate the rewritten `src` once and store it.
   * This is needed to avoid repetitive calculations and make sure the directive cleanup in the
   * `ngOnDestroy` does not rely on the `IMAGE_LOADER` logic (which in turn can rely on some other
   * instance that might be already destroyed).
   */
  private _renderedSrc: string | null = null;

  /**
   * Name of the source image.
   * Image name will be processed by the image loader and the final URL will be applied as the `src`
   * property of the image.
   */
  @Input({required: true, transform: unwrapSafeUrl}) ngSrc!: string;

  /**
   * A comma separated list of width or density descriptors.
   * The image name will be taken from `ngSrc` and combined with the list of width or density
   * descriptors to generate the final `srcset` property of the image.
   *
   * Example:
   * ```
   * <img ngSrc="hello.jpg" ngSrcset="100w, 200w" />  =>
   * <img src="path/hello.jpg" srcset="path/hello.jpg?w=100 100w, path/hello.jpg?w=200 200w" />
   * ```
   */
  @Input() ngSrcset!: string;

  /**
   * The base `sizes` attribute passed through to the `<img>` element.
   * Providing sizes causes the image to create an automatic responsive srcset.
   */
  @Input() sizes?: string;

  /**
   * For responsive images: the intrinsic width of the image in pixels.
   * For fixed size images: the desired rendered width of the image in pixels.
   */
  @Input({transform: numberAttribute}) width: number | undefined;

  /**
   * For responsive images: the intrinsic height of the image in pixels.
   * For fixed size images: the desired rendered height of the image in pixels.
   */
  @Input({transform: numberAttribute}) height: number | undefined;

  /**
   * The desired loading behavior (lazy, eager, or auto). Defaults to `lazy`,
   * which is recommended for most images.
   *
   * Warning: Setting images as loading="eager" or loading="auto" marks them
   * as non-priority images and can hurt loading performance. For images which
   * may be the LCP element, use the `priority` attribute instead of `loading`.
   */
  @Input() loading?: 'lazy' | 'eager' | 'auto';

  /**
   * Indicates whether this image should have a high priority.
   */
  @Input({transform: booleanAttribute}) priority = false;

  /**
   * Data to pass through to custom loaders.
   */
  @Input() loaderParams?: {[key: string]: any};

  /**
   * Disables automatic srcset generation for this image.
   */
  @Input({transform: booleanAttribute}) disableOptimizedSrcset = false;

  /**
   * Sets the image to "fill mode", which eliminates the height/width requirement and adds
   * styles such that the image fills its containing element.
   */
  @Input({transform: booleanAttribute}) fill = false;

  /**
   * A URL or data URL for an image to be used as a placeholder while this image loads.
   */
  @Input({transform: booleanOrUrlAttribute}) placeholder?: string | boolean;

  /**
   * Configuration object for placeholder settings. Options:
   *   * blur: Setting this to false disables the automatic CSS blur.
   */
  @Input() placeholderConfig?: ImagePlaceholderConfig;

  /**
   * Value of the `src` attribute if set on the host `<img>` element.
   * This input is exclusively read to assert that `src` is not set in conflict
   * with `ngSrc` and that images don't start to load until a lazy loading strategy is set.
   * @internal
   */
  @Input() src?: string;

  /**
   * Value of the `srcset` attribute if set on the host `<img>` element.
   * This input is exclusively read to assert that `srcset` is not set in conflict
   * with `ngSrcset` and that images don't start to load until a lazy loading strategy is set.
   * @internal
   */
  @Input() srcset?: string;

  /** @nodoc */
  ngOnInit() {
    performanceMarkFeature('NgOptimizedImage');

    if (ngDevMode) {
      const ngZone = this.injector.get(NgZone);
      assertNonEmptyInput(this, 'ngSrc', this.ngSrc);
      assertValidNgSrcset(this, this.ngSrcset);
      assertNoConflictingSrc(this);
      if (this.ngSrcset) {
        assertNoConflictingSrcset(this);
      }
      assertNotBase64Image(this);
      assertNotBlobUrl(this);
      if (this.fill) {
        assertEmptyWidthAndHeight(this);
        // This leaves the Angular zone to avoid triggering unnecessary change detection cycles when
        // `load` tasks are invoked on images.
        ngZone.runOutsideAngular(() =>
          assertNonZeroRenderedHeight(this, this.imgElement, this.renderer),
        );
      } else {
        assertNonEmptyWidthAndHeight(this);
        if (this.height !== undefined) {
          assertGreaterThanZero(this, this.height, 'height');
        }
        if (this.width !== undefined) {
          assertGreaterThanZero(this, this.width, 'width');
        }
        // Only check for distorted images when not in fill mode, where
        // images may be intentionally stretched, cropped or letterboxed.
        ngZone.runOutsideAngular(() =>
          assertNoImageDistortion(this, this.imgElement, this.renderer),
        );
      }
      assertValidLoadingInput(this);
      if (!this.ngSrcset) {
        assertNoComplexSizes(this);
      }
      assertValidPlaceholder(this, this.imageLoader);
      assertNotMissingBuiltInLoader(this.ngSrc, this.imageLoader);
      assertNoNgSrcsetWithoutLoader(this, this.imageLoader);
      assertNoLoaderParamsWithoutLoader(this, this.imageLoader);

      if (this.lcpObserver !== null) {
        const ngZone = this.injector.get(NgZone);
        ngZone.runOutsideAngular(() => {
          this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.ngSrc, this.priority);
        });
      }

      if (this.priority) {
        const checker = this.injector.get(PreconnectLinkChecker);
        checker.assertPreconnect(this.getRewrittenSrc(), this.ngSrc);

        if (!this.isServer) {
          const applicationRef = this.injector.get(ApplicationRef);
          assetPriorityCountBelowThreshold(applicationRef);
        }
      }
    }
    if (this.placeholder) {
      this.removePlaceholderOnLoad(this.imgElement);
    }
    this.setHostAttributes();
  }

  private setHostAttributes() {
    // Must set width/height explicitly in case they are bound (in which case they will
    // only be reflected and not found by the browser)
    if (this.fill) {
      this.sizes ||= '100vw';
    } else {
      this.setHostAttribute('width', this.width!.toString());
      this.setHostAttribute('height', this.height!.toString());
    }

    this.setHostAttribute('loading', this.getLoadingBehavior());
    this.setHostAttribute('fetchpriority', this.getFetchPriority());

    // The `data-ng-img` attribute flags an image as using the directive, to allow
    // for analysis of the directive's performance.
    this.setHostAttribute('ng-img', 'true');

    // The `src` and `srcset` attributes should be set last since other attributes
    // could affect the image's loading behavior.
    const rewrittenSrcset = this.updateSrcAndSrcset();

    if (this.sizes) {
      if (this.getLoadingBehavior() === 'lazy') {
        this.setHostAttribute('sizes', 'auto, ' + this.sizes);
      } else {
        this.setHostAttribute('sizes', this.sizes);
      }
    } else {
      if (
        this.ngSrcset &&
        VALID_WIDTH_DESCRIPTOR_SRCSET.test(this.ngSrcset) &&
        this.getLoadingBehavior() === 'lazy'
      ) {
        this.setHostAttribute('sizes', 'auto, 100vw');
      }
    }

    if (this.isServer && this.priority) {
      this.preloadLinkCreator.createPreloadLinkTag(
        this.renderer,
        this.getRewrittenSrc(),
        rewrittenSrcset,
        this.sizes,
      );
    }
  }

  /** @nodoc */
  ngOnCh


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  ChangeDetectorRef,
  EventEmitter,
  OnDestroy,
  Pipe,
  PipeTransform,
  untracked,
  ɵisPromise,
  ɵisSubscribable,
} from '@angular/core';
import {Observable, Subscribable, Unsubscribable} from 'rxjs';

import {invalidPipeArgumentError} from './invalid_pipe_argument_error';

interface SubscriptionStrategy {
  createSubscription(
    async: Subscribable<any> | Promise<any>,
    updateLatestValue: any,
  ): Unsubscribable | Promise<any>;
  dispose(subscription: Unsubscribable | Promise<any>): void;
}

class SubscribableStrategy implements SubscriptionStrategy {
  createSubscription(async: Subscribable<any>, updateLatestValue: any): Unsubscribable {
    // Subscription can be side-effectful, and we don't want any signal reads which happen in the
    // side effect of the subscription to be tracked by a component's template when that
    // subscription is triggered via the async pipe. So we wrap the subscription in `untracked` to
    // decouple from the current reactive context.
    //
    // `untracked` also prevents signal _writes_ which happen in the subscription side effect from
    // being treated as signal writes during the template evaluation (which throws errors).
    return untracked(() =>
      async.subscribe({
        next: updateLatestValue,
        error: (e: any) => {
          throw e;
        },
      }),
    );
  }

  dispose(subscription: Unsubscribable): void {
    // See the comment in `createSubscription` above on the use of `untracked`.
    untracked(() => subscription.unsubscribe());
  }
}

class PromiseStrategy implements SubscriptionStrategy {
  createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): Promise<any> {
    return async.then(updateLatestValue, (e) => {
      throw e;
    });
  }

  dispose(subscription: Promise<any>): void {}
}

const _promiseStrategy = new PromiseStrategy();
const _subscribableStrategy = new SubscribableStrategy();

/**
 * @ngModule CommonModule
 * @description
 *
 * Unwraps a value from an asynchronous primitive.
 *
 * The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has
 * emitted. When a new value is emitted, the `async` pipe marks the component to be checked for
 * changes. When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
 * potential memory leaks. When the reference of the expression changes, the `async` pipe
 * automatically unsubscribes from the old `Observable` or `Promise` and subscribes to the new one.
 *
 * @usageNotes
 *
 * ### Examples
 *
 * This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
 * promise.
 *
 * {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
 *
 * It's also possible to use `async` with Observables. The example below binds the `time` Observable
 * to the view. The Observable continuously updates the view with the current time.
 *
 * {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
 *
 * @publicApi
 */
@Pipe({
  name: 'async',
  pure: false,
  standalone: true,
})
export class AsyncPipe implements OnDestroy, PipeTransform {
  private _ref: ChangeDetectorRef | null;
  private _latestValue: any = null;
  private markForCheckOnValueUpdate = true;

  private _subscription: Unsubscribable | Promise<any> | null = null;
  private _obj: Subscribable<any> | Promise<any> | EventEmitter<any> | null = null;
  private _strategy: SubscriptionStrategy | null = null;

  constructor(ref: ChangeDetectorRef) {
    // Assign `ref` into `this._ref` manually instead of declaring `_ref` in the constructor
    // parameter list, as the type of `this._ref` includes `null` unlike the type of `ref`.
    this._ref = ref;
  }

  ngOnDestroy(): void {
    if (this._subscription) {
      this._dispose();
    }
    // Clear the `ChangeDetectorRef` and its association with the view data, to mitigate
    // potential memory leaks in Observables that could otherwise cause the view data to
    // be retained.
    // https://github.com/angular/angular/issues/17624
    this._ref = null;
  }

  // NOTE(@benlesh): Because Observable has deprecated a few call patterns for `subscribe`,
  // TypeScript has a hard time matching Observable to Subscribable, for more information
  // see https://github.com/microsoft/TypeScript/issues/43643

  transform<T>(obj: Observable<T> | Subscribable<T> | Promise<T>): T | null;
  transform<T>(obj: null | undefined): null;
  transform<T>(obj: Observable<T> | Subscribable<T> | Promise<T> | null | undefined): T | null;
  transform<T>(obj: Observable<T> | Subscribable<T> | Promise<T> | null | undefined): T | null {
    if (!this._obj) {
      if (obj) {
        try {
          // Only call `markForCheck` if the value is updated asynchronously.
          // Synchronous updates _during_ subscription should not wastefully mark for check -
          // this value is already going to be returned from the transform function.
          this.markForCheckOnValueUpdate = false;
          this._subscribe(obj);
        } finally {
          this.markForCheckOnValueUpdate = true;
        }
      }
      return this._latestValue;
    }

    if (obj !== this._obj) {
      this._dispose();
      return this.transform(obj);
    }

    return this._latestValue;
  }

  private _subscribe(obj: Subscribable<any> | Promise<any> | EventEmitter<any>): void {
    this._obj = obj;
    this._strategy = this._selectStrategy(obj);
    this._subscription = this._strategy.createSubscription(obj, (value: Object) =>
      this._updateLatestValue(obj, value),
    );
  }

  private _selectStrategy(
    obj: Subscribable<any> | Promise<any> | EventEmitter<any>,
  ): SubscriptionStrategy {
    if (ɵisPromise(obj)) {
      return _promiseStrategy;
    }

    if (ɵisSubscribable(obj)) {
      return _subscribableStrategy;
    }

    throw invalidPipeArgumentError(AsyncPipe, obj);
  }

  private _dispose(): void {
    // Note: `dispose` is only called if a subscription has been initialized before, indicating
    // that `this._strategy` is also available.
    this._strategy!.dispose(this._subscription!);
    this._latestValue = null;
    this._subscription = null;
    this._obj = null;
  }

  private _updateLatestValue(async: any, value: Object): void {
    if (async === this._obj) {
      this._latestValue = value;
      if (this.markForCheckOnValueUpdate) {
        this._ref?.markForCheck();
      }
    }
  }
}


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Angular Examples</title>
    <base href="/" />

    <!-- Prevent the browser from requesting any favicon. This could throw off the console
      output checks. -->
    <link rel="icon" href="data:," />
  </head>
  <body>
    <example-app>Loading...</example-app>
    <script src="/app_bundle.js"></script>
  </body>
</html>


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

/* tslint:disable:no-console  */
// #docregion Component
import {Component} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div formArrayName="cities">
        <div *ngFor="let city of cities.controls; index as i">
          <input [formControlName]="i" placeholder="City" />
        </div>
      </div>
      <button>Submit</button>
    </form>

    <button (click)="addCity()">Add City</button>
    <button (click)="setPreset()">Set preset</button>
  `,
  standalone: false,
})
export class NestedFormArray {
  form = new FormGroup({
    cities: new FormArray([new FormControl('SF'), new FormControl('NY')]),
  });

  get cities(): FormArray {
    return this.form.get('cities') as FormArray;
  }

  addCity() {
    this.cities.push(new FormControl());
  }

  onSubmit() {
    console.log(this.cities.value); // ['SF', 'NY']
    console.log(this.form.value); // { cities: ['SF', 'NY'] }
  }

  setPreset() {
    this.cities.patchValue(['LA', 'MTV']);
  }
}
// #enddocregion


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {SimpleFormComp} from './simple_form_example';

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [SimpleFormComp],
  bootstrap: [SimpleFormComp],
})
export class AppModule {}

export {SimpleFormComp as AppComponent};


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  inject,
  InjectFlags,
  InjectionToken,
  InjectOptions,
  Injector,
  ProviderToken,
  ɵInjectorProfilerContext,
  ɵsetCurrentInjector as setCurrentInjector,
  ɵsetInjectorProfilerContext,
} from '@angular/core';

class MockRootScopeInjector implements Injector {
  constructor(readonly parent: Injector) {}

  get<T>(
    token: ProviderToken<T>,
    defaultValue?: any,
    flags: InjectFlags | InjectOptions = InjectFlags.Default,
  ): T {
    if ((token as any).ɵprov && (token as any).ɵprov.providedIn === 'root') {
      const old = setCurrentInjector(this);
      const previousInjectorProfilerContext = ɵsetInjectorProfilerContext({
        injector: this,
        token: null,
      });
      try {
        return (token as any).ɵprov.factory();
      } finally {
        setCurrentInjector(old);
        ɵsetInjectorProfilerContext(previousInjectorProfilerContext);
      }
    }
    return this.parent.get(token, defaultValue, flags);
  }
}

{
  describe('injector metadata examples', () => {
    it('works', () => {
      // #docregion Injector
      const injector: Injector = Injector.create({
        providers: [{provide: 'validToken', useValue: 'Value'}],
      });
      expect(injector.get('validToken')).toEqual('Value');
      expect(() => injector.get('invalidToken')).toThrowError();
      expect(injector.get('invalidToken', 'notFound')).toEqual('notFound');
      // #enddocregion
    });

    it('injects injector', () => {
      // #docregion injectInjector
      const injector = Injector.create({providers: []});
      expect(injector.get(Injector)).toBe(injector);
      // #enddocregion
    });

    it('should infer type', () => {
      // #docregion InjectionToken
      const BASE_URL = new InjectionToken<string>('BaseUrl');
      const injector = Injector.create({
        providers: [{provide: BASE_URL, useValue: 'http://localhost'}],
      });
      const url = injector.get(BASE_URL);
      // Note: since `BASE_URL` is `InjectionToken<string>`
      // `url` is correctly inferred to be `string`
      expect(url).toBe('http://localhost');
      // #enddocregion
    });

    it('injects a tree-shakeable InjectionToken', () => {
      class MyDep {}
      const injector = new MockRootScopeInjector(
        Injector.create({providers: [{provide: MyDep, deps: []}]}),
      );

      // #docregion ShakableInjectionToken
      class MyService {
        constructor(readonly myDep: MyDep) {}
      }

      const MY_SERVICE_TOKEN = new InjectionToken<MyService>('Manually constructed MyService', {
        providedIn: 'root',
        factory: () => new MyService(inject(MyDep)),
      });

      const instance = injector.get(MY_SERVICE_TOKEN);
      expect(instance instanceof MyService).toBeTruthy();
      expect(instance.myDep instanceof MyDep).toBeTruthy();
      // #enddocregion
    });
  });
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  AfterContentChecked,
  AfterContentInit,
  AfterViewChecked,
  AfterViewInit,
  Component,
  DoCheck,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  SimpleChanges,
  Type,
} from '@angular/core';
import {TestBed} from '@angular/core/testing';

(function () {
  describe('lifecycle hooks examples', () => {
    it('should work with ngOnInit', () => {
      // #docregion OnInit
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements OnInit {
        ngOnInit() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngOnInit', []]]);
    });

    it('should work with ngDoCheck', () => {
      // #docregion DoCheck
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements DoCheck {
        ngDoCheck() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngDoCheck', []]]);
    });

    it('should work with ngAfterContentChecked', () => {
      // #docregion AfterContentChecked
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements AfterContentChecked {
        ngAfterContentChecked() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngAfterContentChecked', []]]);
    });

    it('should work with ngAfterContentInit', () => {
      // #docregion AfterContentInit
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements AfterContentInit {
        ngAfterContentInit() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngAfterContentInit', []]]);
    });

    it('should work with ngAfterViewChecked', () => {
      // #docregion AfterViewChecked
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements AfterViewChecked {
        ngAfterViewChecked() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngAfterViewChecked', []]]);
    });

    it('should work with ngAfterViewInit', () => {
      // #docregion AfterViewInit
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements AfterViewInit {
        ngAfterViewInit() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngAfterViewInit', []]]);
    });

    it('should work with ngOnDestroy', () => {
      // #docregion OnDestroy
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements OnDestroy {
        ngOnDestroy() {
          // ...
        }
      }
      // #enddocregion

      expect(createAndLogComponent(MyComponent)).toEqual([['ngOnDestroy', []]]);
    });

    it('should work with ngOnChanges', () => {
      // #docregion OnChanges
      @Component({
        selector: 'my-cmp',
        template: `...`,
        standalone: false,
      })
      class MyComponent implements OnChanges {
        @Input() prop: number = 0;

        ngOnChanges(changes: SimpleChanges) {
          // changes.prop contains the old and the new value...
        }
      }
      // #enddocregion

      const log = createAndLogComponent(MyComponent, ['prop']);
      expect(log.length).toBe(1);
      expect(log[0][0]).toBe('ngOnChanges');
      const changes: SimpleChanges = log[0][1][0];
      expect(changes['prop'].currentValue).toBe(true);
    });
  });

  function createAndLogComponent(clazz: Type<any>, inputs: string[] = []): any[] {
    const log: any[] = [];
    createLoggingSpiesFromProto(clazz, log);

    const inputBindings = inputs.map((input) => `[${input}] = true`).join(' ');

    @Component({
      template: `<my-cmp ${inputBindings}></my-cmp>`,
      standalone: false,
    })
    class ParentComponent {}

    const fixture = TestBed.configureTestingModule({
      declarations: [ParentComponent, clazz],
    }).createComponent(ParentComponent);
    fixture.detectChanges();
    fixture.destroy();
    return log;
  }

  function createLoggingSpiesFromProto(clazz: Type<any>, log: any[]) {
    const proto = clazz.prototype;
    // For ES2015+ classes, members are not enumerable in the prototype.
    Object.getOwnPropertyNames(proto).forEach((method) => {
      if (method === 'constructor') {
        return;
      }

      proto[method] = (...args: any[]) => {
        log.push([method, args]);
      };
    });
  }
})();


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  Injectable,
  Injector,
  Input,
  NgModule,
  OnInit,
  TemplateRef,
  ViewChild,
  ViewContainerRef,
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

// #docregion SimpleExample
@Component({
  selector: 'hello-world',
  template: 'Hello World!',
  standalone: false,
})
export class HelloWorld {}

@Component({
  selector: 'ng-component-outlet-simple-example',
  template: `<ng-container *ngComponentOutlet="HelloWorld"></ng-container>`,
  standalone: false,
})
export class NgComponentOutletSimpleExample {
  // This field is necessary to expose HelloWorld to the template.
  HelloWorld = HelloWorld;
}
// #enddocregion

// #docregion CompleteExample
@Injectable()
export class Greeter {
  suffix = '!';
}

@Component({
  selector: 'complete-component',
  template: `{{ label }}: <ng-content></ng-content> <ng-content></ng-content>{{ greeter.suffix }}`,
  standalone: false,
})
export class CompleteComponent {
  @Input() label!: string;

  constructor(public greeter: Greeter) {}
}

@Component({
  selector: 'ng-component-outlet-complete-example',
  template: ` <ng-template #ahoj>Ahoj</ng-template>
    <ng-template #svet>Svet</ng-template>
    <ng-container
      *ngComponentOutlet="
        CompleteComponent;
        inputs: myInputs;
        injector: myInjector;
        content: myContent
      "
    ></ng-container>`,
  standalone: false,
})
export class NgComponentOutletCompleteExample implements OnInit {
  // This field is necessary to expose CompleteComponent to the template.
  CompleteComponent = CompleteComponent;

  myInputs = {'label': 'Complete'};

  myInjector: Injector;
  @ViewChild('ahoj', {static: true}) ahojTemplateRef!: TemplateRef<any>;
  @ViewChild('svet', {static: true}) svetTemplateRef!: TemplateRef<any>;
  myContent?: any[][];

  constructor(
    injector: Injector,
    private vcr: ViewContainerRef,
  ) {
    this.myInjector = Injector.create({
      providers: [{provide: Greeter, deps: []}],
      parent: injector,
    });
  }

  ngOnInit() {
    // Create the projectable content from the templates
    this.myContent = [
      this.vcr.createEmbeddedView(this.ahojTemplateRef).rootNodes,
      this.vcr.createEmbeddedView(this.svetTemplateRef).rootNodes,
    ];
  }
}
// #enddocregion

@Component({
  selector: 'example-app',
  template: `<ng-component-outlet-simple-example></ng-component-outlet-simple-example>
    <hr />
    <ng-component-outlet-complete-example></ng-component-outlet-complete-example>`,
  standalone: false,
})
export class AppComponent {}

@NgModule({
  imports: [BrowserModule],
  declarations: [
    AppComponent,
    NgComponentOutletSimpleExample,
    NgComponentOutletCompleteExample,
    HelloWorld,
    CompleteComponent,
  ],
})
export class AppModule {}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
// #docregion activated-route
import {Component, NgModule} from '@angular/core';
// #enddocregion activated-route
import {BrowserModule} from '@angular/platform-browser';
// #docregion activated-route
import {ActivatedRoute, RouterModule} from '@angular/router';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
// #enddocregion activated-route

// #docregion activated-route

@Component({
  // #enddocregion activated-route
  selector: 'example-app',
  template: '...',
  standalone: false,
})
export class ActivatedRouteComponent {
  constructor(route: ActivatedRoute) {
    const id: Observable<string> = route.params.pipe(map((p) => p['id']));
    const url: Observable<string> = route.url.pipe(map((segments) => segments.join('')));
    // route.data includes both `data` and `resolve`
    const user = route.data.pipe(map((d) => d['user']));
  }
}
// #enddocregion activated-route

@NgModule({
  imports: [BrowserModule, RouterModule.forRoot([])],
  declarations: [ActivatedRouteComponent],
  bootstrap: [ActivatedRouteComponent],
})
export class AppModule {}


# DESIGN DOC (Ivy): Separate Compilation

AUTHOR: chuckj@

## Background

### Angular 5 (Renderer2)

In 5.0 and prior versions of Angular the compiler performs whole program
analysis and generates template and injector definitions that use
this global knowledge to flatten injector scope definitions, inline
directives into the component, pre-calculate queries, pre-calculate
content projection, etc. This global knowledge requires that module and
component factories are generated as the final global step when compiling
a module. If any of the transitive information changed, then all factories
need to be regenerated.

Separate component and module compilation is supported only at the module
definition level and only from the source. That is, npm packages must contain
the metadata necessary to generate the factories. They cannot contain,
themselves, the generated factories. This is because if any of their
dependencies change, their factories would be invalid, preventing them from
using version ranges in their dependencies. To support producing factories
from compiled source (already translated by TypeScript into JavaScript)
libraries include metadata that describe the content of the Angular
decorators.

This document refers to this style of code generation as Renderer2 (after the
name of the renderer class it uses at runtime).

### Angular Ivy

In Ivy, the runtime is crafted in a way that allows for separate compilation
by performing at runtime much of what was previously pre-calculated by
the compiler. This allows the definition of components to change without
requiring modules and components that depend on them to be recompiled.

The mental model of Ivy is that the decorator is the compiler. That is,
the decorator can be thought of as parameters to a class transformer that
transforms the class by generating definitions based on the decorator
parameters. A `@Component` decorator transforms the class by adding
an `ɵcmp` static property, `@Directive` adds `ɵdir`,
`@Pipe` adds `ɵpipe`, etc. In most cases the values supplied to the
decorator are sufficient to generate the definition. However, in the case of
interpreting the template, the compiler needs to know the selector defined for
each component, directive and pipe that are in the scope of the template. The
purpose of this document is to define the information that is needed by the
compiler, and how that information is serialized to be discovered and
used by subsequent calls to `ngc`.

This document refers to this style of code generation as Ivy (after the code
name of the project to create it). It would be more consistent to refer to it
as Renderer3, but that looks too similar to Renderer2.

## Information needed

The information available across compilations in Angular 5 is represented in
the  compiler by a summary description. For example, components and directives
are represented by the [`CompileDirectiveSummary`](https://github.com/angular/angular/blob/d3827a0017fd5ff5ac0f6de8a19692ce47bf91b4/packages/compiler/src/compile_metadata.ts#L257).
The following table shows where this information ends up in an ivy compiled
class:

### `CompileDirectiveSummary`

| field               | destination           |
|---------------------|-----------------------|
| `type`              | implicit              |
| `isComponent`       | `ɵcmp`                |
| `selector`          | `ngModuleScope`       |
| `exportAs`          | `ɵdir`                |
| `inputs`            | `ɵdir`                |
| `outputs`           | `ɵdir`                |
| `hostListeners`     | `ɵdir`                |
| `hostProperties`    | `ɵdir`                |
| `hostAttributes`    | `ɵdir`                |
| `providers`         | `ɵinj`                |
| `viewProviders`     | `ɵcmp`                |
| `queries`           | `ɵdir`                |
| `guards`            | not used              |
| `viewQueries`       | `ɵcmp`                |
| `changeDetection`   | `ɵcmp`                |
| `template`          | `ɵcmp`                |
| `componentViewType` | not used              |
| `renderType`        | not used              |
| `componentFactory`  | not used              |

Only one definition is generated per class. All components are directives so a
`ɵcmp` contains all the `ɵdir` information. All directives
are injectable so `ɵcmp` and `ɵdir` contain `ɵprov`
information.

For `CompilePipeSummary` the table looks like:

#### `CompilePipeSummary`

| field               | destination           |
|---------------------|-----------------------|
| `type`              | implicit              |
| `name`              | `ngModuleScope`       |
| `pure`              | `ɵpipe`               |

The only pieces of information that are not generated into the definition are
the directive selector and the pipe name as they go into the module scope.

The information needed to build an `ngModuleScope` needs to be communicated
from the directive and pipe to the module that declares them.

## Metadata

### Angul


# DESIGN DOC(Ivy): Compiler Architecture

AUTHOR: arick@, chuckj@
Status: Draft

## Overview

This document details the new architecture of the Angular compiler in a post-Ivy world, as well as the compatibility functionality needed for the ecosystem to gradually migrate to Ivy without breaking changes. This compatibility ensures Ivy and non-Ivy libraries can coexist during the migration period.

### The Ivy Compilation Model

Broadly speaking, The Ivy model is that Angular decorators (`@Injectable`, etc) are compiled to static properties on the classes (`ɵprov`). This operation must take place without global program knowledge, and in most cases only with knowledge of that single decorator.

The one exception is `@Component`, which requires knowledge of the metadata from the `@NgModule` which declares the component in order to properly generate the component def (`ɵcmp`). In particular, the selectors which are applicable during compilation of a component template are determined by the module that declares that component, and the transitive closure of the exports of that module's imports.

Going forward, this will be the model by which Angular code will be compiled, shipped to NPM, and eventually bundled into applications.

### Existing code on NPM

Existing Angular libraries exist on NPM today and are distributed in the Angular Package Format, which details the artifacts shipped. Today this includes compiled `.js` files in both ES2015 and ESM (ES5 + ES2015 modules) flavors, `.d.ts` files, and `.metadata.json` files. The `.js` files have the Angular decorator information removed, and the `.metadata.json` files preserve the decorator metadata in an alternate format.

### High Level Proposal

We will produce two compiler entry-points, `ngtsc` and `ngcc`.

`ngtsc` will be a Typescript-to-Javascript transpiler that reifies Angular decorators into static properties. It is a minimal wrapper around `tsc` which includes a set of Angular transforms. While Ivy is experimental, `ngc` operates as `ngtsc` when the `angularCompilerOption` `enableIvy` flag is set to `true` in the `tsconfig.json` file for the project.

`ngcc` (which stands for Angular compatibility compiler) is designed to process code coming from NPM and produce the equivalent Ivy version, as if the code was compiled with `ngtsc`. It will operate given a `node_modules` directory and a set of packages to compile, and will produce an equivalent directory from which the Ivy equivalents of those modules can be read. `ngcc` is a separate script entry point to `@angular/compiler-cli`.

`ngcc` can also be run as part of a code loader (e.g. for Webpack) to transpile packages being read from `node_modules` on-demand.

##


Detailed Design

### Ivy Compilation Model

The overall architecture of `ngtsc` it is a set of transformers that adjust what is emitted by TypeScript for a TypeScript program. Angular transforms both the `.js` files and the `.d.ts` files to reflect the content of Angular decorators that are then erased. This transformation is done file by file with no global knowledge except during the type-checking and for reference inversion discussed below.

For example, the following class declaration:

```ts
import {Component, Input} from '@angular/core';

@Component({
  selector: 'greet',
  template: '<div> Hello, {{name}}! </div>'
})
export class GreetComponent {
  @Input() name: string;
}
```

will normally be translated into something like this:

```js
const tslib_1 = require("tslib");
const core_1 = require("@angular/core");
let GreetComponent = class GreetComponent {
};
tslib_1.__decorate([
    core_1.Input(),
    tslib_1.__metadata("design:type", String)
], GreetComponent.prototype, "name", void 0);
GreetComponent = tslib_1.__decorate([
    core_1.Component({
        selector: 'greet',
        template: '<div> Hello, {{name}}! </div>'
    })
], GreetComponent);
```

which translates the decorator into a form that is executed at runtime. A `.d.ts` file is also emitted that might look something like

```ts
export class GreetComponent {
  name: string;
}
```

In `ngtsc` this is instead emitted as,

```js
const i0 = require("@angular/core");
class GreetComponent {}
GreetComponent.ɵcmp = i0.ɵɵdefineComponent({
    type: GreetComponent,
    tag: 'greet',
    factory: () => new GreetComponent(),
    template: function (rf, ctx) {
        if (rf & RenderFlags.Create) {
            i0.ɵɵelementStart(0, 'div');
            i0.ɵɵtext(1);
            i0.ɵɵelementEnd();
        }
        if (rf & RenderFlags.Update) {
            i0.ɵɵadvance();
            i0.ɵɵtextInterpolate1('Hello ', ctx.name, '!');
        }
    }
});
```

and the `.d.ts` contains:

```ts
import * as i0 from '@angular/core';
export class GreetComponent {
  static ɵcmp: i0.NgComponentDef<
    GreetComponent,
    'greet',
    {input: 'input'}
  >;
}
```

The information needed by reference inversion and type-checking is included in
the type declaration of the `ɵcmp` in the `.d.ts`.

#### TypeScript architecture

The overall architecture of TypeScript is:

                                                                    |------------|
                               |----------------------------------> | TypeScript |
                               |                                    |   .d.ts    |
                               |                                    |------------|
                               |
    |------------|          |-----|               |-----|           |------------|
    | TypeScript | -parse-> | AST | ->transform-> | AST | ->print-> | JavaScript |
    |   source   |    |     |-----|       |       |-----|           |   source   |
    |------------|    |        |          |                         |------------|
                      |    type-check     |
                      |        |          |
                      |        v          |
                      |    |--------|     |
                      |--> | errors | <---|
                           |--------|

The parse step is a traditional recursive descent parser, augmented to support incremental parsing, that emits an abstract syntax tree (AST).

The type-checker construct a symbol table and then performs type analysis of every expression in the file, reporting errors it finds. This process is not extended or modified by `ngtsc`.

The transform step is a set of AST to AST transformations that perform various tasks such as, removing type declarations, lowering module and class declarations to ES5, converting `async` methods to state-machines, etc.

#### Extension points

TypeScript supports the following extension points to alter its output. You can,

1. Modify the TypeScript source it sees (`CompilerHost.getSourceFile`)
2. Alter the list of transforms (`CustomTransformers`)
3. Intercept the output before it is written (`WriteFileCallback`)

It is not recommended to alter the source code as this complicates the managing of source maps, makes it difficult to support incremental parsing, and is not supported by TypeScript's language service plug-in model.

#### Angular Extensions

Angular transforms the `.js` output by adding Angular specific transforms to the list of transforms executed by TypeScript.

As of TypeScript 2.7, there is no similar transformer pipe-line for `.d.ts` files so the .d.ts files will be altered during the `WriteFileCallback`.

#### Decorator Reification

Angular supports the following class decorators:

- `@Component`
- `@Directive`
- `@Injectable`
- `@NgModule`
- `@Pipe`

There are also a list of helper decorators that make the `@Component` and `@Directive` easier to use such as `@Input`, `@Output`, etc.; as well as a set of decorators that help `@Injectable` classes customize the injector such as `@Inject` and `@SkipSelf`.

Each of the class decorators can be thought of as class transformers that take the declared class and transform it, possibly using information from the helper decorators, to produce an Angular class. The JIT compiler performs this transformation at runtime. The AOT compiler performs this transformation at compile time.

Each of the class decorators' class transformer creates a corresponding static member on the class that describes to the runtime how to use the class. For example, the `@Component` decorator creates a `ɵcmp` static member, `@Directive` create a `ɵdir`, etc. Internally, these class transformers are called a "Compiler". Most of the compilers are straight forward translations of the metadata specified in the decorator to the information provided in the corresponding definition and, therefore, do not require anything outside the source file to perform the conversion. However, the component, during production builds and for type checking a template require the module scope of the component which requires information from other files in the program.

#### Compiler design

Each "Compiler" which transforms a single decorator into a static field will operate as a "pure function". Given input metadata about a particular type and decorator, it will produce an object describing the field to be added to the type, as well as the initializer value for that field (in Output AST format).

A Compiler must not depend on any inputs not directly passed to it (for example, it must not scan sources or metadata for other symbols). This restriction is important for two reasons:

1. It helps to enforce the Ivy locality principle, since all inputs to the Compiler will be visible.
2. It protects against incorrect builds during `--watch` mode, since the dependencies between files will be easily traceable.

Compilers will also not take Typescript nodes directly as input, but will operate against information extracted from TS sources by the transformer. In addition to helping enforce the rules above, this restriction also enables Compilers to run at runtime during JIT mode.

For example, the input to the `@Component` compiler will be:

* A reference to the class of the component.
* The template and style resources of the component.
* The selector of the component.
* A selector map for the module to which the component belongs.

#### Need for static value resolution

During some parts of compilation, the compiler will need to statically interpret particular values in the AST, especially values from the decorator metadata. This is a complex problem. For example, while this form of a component is common:

```javascript
@Component({
  selector: 'foo-cmp',
  templateUrl: 'templates/foo.html',
})
export class Foo {}
```

The following is also permitted:

```javascript
export const TEMPLATE_BASE = 'templates/';

export function getTemplateUrl(cmp: string): string {
  return TEMPLATE_BASE + cmp + '.html';
}

export const FOO_SELECTOR = 'foo-cmp';
export const FOO_TEMPLATE_URL = getTemplateUrl('foo');

@Component({
  selector: FOO_SELECTOR,
  templateUrl: FOO_TEMPLATE_URL,
})
export class Foo {}
```

`ngc` has a metadata system which attempts to statically understand the "value side" of a program. This allowed it to follow the references and evaluate the expressions required to understand that `FOO_TEMPLATE_URL` evaluates statically to `templates/foo.html`. `ngtsc` will need a similar capability, though the design will be different.

The `ngtsc` metadata evaluator will be built as a partial Typescript interpreter, which visits Typescript nodes and evaluates expressions statically. This allows metadata evaluation to happen on demand. It will have some restrictions that aren't present in the `ngc` model - in particular, evaluation will not cross `node_module` boundaries.

#### Compiling a te


ty compiler

#### The compatibility problem

Not all Angular code is compiled at the same time. Applications have dependencies on shared libraries, and those libraries are published on NPM in their compiled form and not as Typescript source code. Even if an application is built using `ngtsc`, its dependencies may not have been.

If a particular library was not compiled with `ngtsc`, it does not have reified decorator properties in its `.js` distribution as described above. Linking it against a dependency that was not compiled in the same way will fail at runtime.

#### Converting pre-Ivy code

Since Ivy code can only be linked against other Ivy code, to build the application all pre-Ivy dependencies from NPM must be converted to Ivy dependencies. This transformation must happen as a precursor to running `ngtsc` on the application, and future compilation and linking operations need to be made against this transformed version of the dependencies.

It is possible to transpile non-Ivy code in the Angular Package Format (v6) into Ivy code, even though the `.js` files no longer contain the decorator information. This works because the Angular Package Format includes `.metadata.json` files for each `.js` file. These metadata files contain information that was present in the Typescript source but was removed during transpilation to Javascript, and this information is sufficient to generate patched `.js` files which add the Ivy static properties to decorated classes.

#### Metadata from APF

The `.metadata.json` files currently being shipped to NPM includes, among other information, the arguments to the Angular decorators which `ngtsc` downlevels to static properties. For example, the `.metadata.json` file for `CommonModule` contains the information for its `NgModule` decorator which was originally present in the Typescript source:

```json
"CommonModule": {
  "__symbolic": "class",
  "decorators": [{
    "__symbolic": "call",
    "expression": {
      "__symbolic": "reference",
      "module": "@angular/core",
      "name": "NgModule",
      "line": 22,
      "character": 1
    },
    "arguments": [{
      "declarations": [...],
      "exports": [...],
      "providers": [...]
    }]
  }]
}
```

#### ngcc operation

`ngcc` will by default scan `node_modules` and produce Ivy-compatible versions of every package it discovers built using Angular Package Format (APF). It detects the APF by looking for the presence of a `.metadata.json` file alongside the package's `module` entrypoint.

Alternatively, `ngcc` can be initiated by passing the name of a single NPM package. It will begin converting that package, and recurse into any dependencies of that package that it discovers which have not yet been converted.

The output of `ngcc` is a directory called `ngcc_node_modules` by default, but can be renamed based on an option. Its structure mirrors that of `node_modules`, and the packages that are converted have the non-transpiled files copied verbatim - `package.json`, etc are all preserved in the output. Only the `.js` and `.d.ts` files are changed, and the `.metadata.json` files are removed.

An example directory layout would be:

```
# input
node_modules/
  ng-dep/
    package.json
    index.js (pre-ivy)
    index.d.ts (pre-ivy)
    index.metadata.json
    other.js

# output
ngcc_node_modules
  ng-dep/
    package.json
    index.js (ivy compatible)
    index.d.ts (ivy-compatible)
    other.js (copied verbatim)
```

#### Operation as a loader

`ngcc` can be called as a standalone entrypoint, but it can also be integrated into the dependency loading operation of a bundler such as Rollup or Webpack. In this mode, the `ngcc` API can be used to read a file originally in `node_modules`. If the file is from a package which has not yet been converted, `ngcc` will convert the package and its dependencies before returning the file's contents.

In this mode, the on-disk `ngcc_node_modules` directory functions as a cache. If the file being requested has previously been converted, its contents will be read from `ngcc_node_modules`.

#### Compilation Model

`ngtsc` operates using a pipeline of different transformations, each one processing a different Angular decorator and converting it into a static property on the type being decorated. `ngcc` is architected to reuse as much of that process as possible.

Compiling a package in `ngcc` involves the following steps:

1. Parse the JS files of the package with the Typescript parser.
2. Invoke the `StaticReflector` system from the legacy `@angular/compiler` to parse the `.metadata.json` files.
3. Run through each Angular decorator in the Ivy system and compile:
    1. Use the JS AST plus the information from the `StaticReflector` to construct the input to the annotation's Compiler.
    2. Run the annotation's Compiler which will produce a partial class and its type declaration.
    3. Extract the static property definition from the partial class.
4. Combine the compiler outputs with the JS AST to produce the resulting `.js` and `.d.ts` files, and write them to disk.
5. Copy over all other files.

#### Merging with JS output

At first glance it is desirable for each Compiler's output to be patched into the AST for the modules being compiled, and then to generate the resulting JS code and sourcemaps using Typescript's emit on the AST. This is undesirable for several reasons:

* The round-trip through the Typescript parser and emitter might subtly change the input JS code - dropping comments, reformatting code, etc. This is not ideal, as users expect the input code to remain as unchanged as possible.
* It isn't possible in Typescript to directly emit without going through any of Typescript's own transformations. This may cause expressions to be reformatted, code to be downleveled, and requires configuration of an output module system into which the code will be transformed.

For these reasons, `ngcc` will not use the TS emitter to produce the final patched `.js` files. Instead, the JS text will be manipulated directly, with the help of the `magic-string` or similar library to ensure the changes are reflected in the output sourcemaps. The AST which is parsed from the JS files contains position information of all the types in the JS source, and this information can be used to determine the correct insertion points for the Ivy static fields.

Similarly, the `.d.ts` files will be parsed by the TS parser, and the information used to determine the insertion points of typing information that needs to be added to individual types (as well as associated imports).

##### Module systems

The Angular Package Format includes more than one copy of a package's code. At minimum, it includes one ESM5 (ES5 code in ES Modules) entrypoint, one ES2015 entrypoint, and one UMD entrypoint. Some libraries _not_ following the package format may still work in the Angular CLI, if they export code that can be loaded by Webpack.

Thus, `ngcc` will have two approaches for dealing with packages on NPM.

1. APF Path: libraries following the Angular package format will have their source code updated to contain Ivy definitions. This ensures tree-shaking will work properly.
2. Compatibility Path: libraries where `ngcc` cannot determine how to safely modify the existing code will have a patching operation applied. This patching operation produces a "wrapper" file for each file containing an Angular entity, which re-exports patched versions of the Angular entities. This is not compatible with tree-shaking, but will work for libraries which `ngcc` cannot otherwise understand. A warning will be printed to notify the user they should update the version of the library if possible.

For example, if a library ships with commonjs-only code or a UMD bundle that `ngcc` isn't able to patch directly, it can generate patching wrappers instead of modifying the input code.

### Language Service

The `@angular/language-service` is mostly out of scope for this document, and will be treated in a separate design document. However, it's worth a consideration here as the architecture of the compiler impacts the language service's design.

A Language Service is an analysis engine that integrates into an IDE such as Visual Studio Code. It processes code and provides static analysis information regarding that code, as well as enables specific IDE operations such as code completion, tracing of references, and refactoring. The `@angular/language-service` is a wrapper around the Typescript language service (much as `ngtsc` wraps `tsc`) and extends the analysis of Typescript with a specific understanding of Angular concepts. In particular, it also understands the Angular Template Syntax and can bridge between the component class in Typescript and expressions in the templates.

To provide code completion and other intelligence around template contents, the Angular Language Service must have a similar understanding of the template contents as the `ngtsc` compiler - it must know the selector map associated with the component, and the metadata of each directive or pipe used in the template. Whether the language service consumes the output of `ngcc` or reuses its metadata transformation logic, the data it needs will be available.


describe('for loop blocks', () => {
    it('should parse a for loop block', () => {
      expectFromHtml(`
        @for (item of items.foo.bar; track item.id) {
          {{ item }}
        } @empty {
          There were no items in the list.
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
        ['ForLoopBlockEmpty'],
        ['Text', ' There were no items in the list. '],
      ]);
    });

    it('should parse a for loop block with optional parentheses', () => {
      expectFromHtml(`
        @for ((item of items.foo.bar); track item.id){
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
      ]);

      expectFromHtml(`
        @for ((item of items.foo.bar()); track item.id) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar()', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
      ]);

      expectFromHtml(`
        @for ((   ( (item of items.foo.bar()) )   ); track item.id) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar()', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
      ]);
    });

    it('should parse a for loop block with let parameters', () => {
      expectFromHtml(`
        @for (item of items.foo.bar; track item.id; let idx = $index, f = $first, c = $count; let l = $last, ev = $even, od = $odd) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['Variable', 'idx', '$index'],
        ['Variable', 'f', '$first'],
        ['Variable', 'c', '$count'],
        ['Variable', 'l', '$last'],
        ['Variable', 'ev', '$even'],
        ['Variable', 'od', '$odd'],
        ['BoundText', ' {{ item }} '],
      ]);
    });

    it('should parse a for loop block with newlines in its let parameters', () => {
      expectFromHtml(`
        @for (item of items.foo.bar; track item.id; let\nidx = $index,\nf = $first,\nc = $count,\nl = $last,\nev = $even,\nod = $odd) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['Variable', 'idx', '$index'],
        ['Variable', 'f', '$first'],
        ['Variable', 'c', '$count'],
        ['Variable', 'l', '$last'],
        ['Variable', 'ev', '$even'],
        ['Variable', 'od', '$odd'],
        ['BoundText', ' {{ item }} '],
      ]);
    });

    it('should parse nested for loop blocks', () => {
      expectFromHtml(`
        @for (item of items.foo.bar; track item.id) {
          {{ item }}

            @for (subitem of item.items; track subitem.id) {
{{subitem}}
}

        } @empty {
          There were no items in the list.
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
        ['Element', 'div'],
        ['ForLoopBlock', 'item.items', 'subitem.id'],
        ['Variable', 'subitem', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['Element', 'h1'],
        ['BoundText', '{{ subitem }}'],
        ['ForLoopBlockEmpty'],
        ['Text', ' There were no items in the list. '],
      ]);
    });

    it('should parse a for loop block with a function call in the `track` expression', () => {
      expectFromHtml(`
        @for (item of items.foo.bar; track trackBy(item.id, 123)) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', 'items.foo.bar', 'trackBy(item.id, 123)'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
      ]);
    });

    it('should parse a for loop block with newlines in its expression', () => {
      const expectedResult = [
        ['ForLoopBlock', 'items.foo.bar', 'item.id + foo'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', '{{ item }}'],
      ];

      expectFromHtml(`
        @for (item\nof\nitems.foo.bar; track item.id +\nfoo) {{{ item }}}
      `).toEqual(expectedResult);
      expectFromHtml(`
        @for ((item\nof\nitems.foo.bar); track (item.id +\nfoo)) {{{ item }}}
      `).toEqual(expectedResult);
    });

    it('should parse for loop block expression containing new lines', () => {
      expectFromHtml(`
        @for (item of [
          { id: 1 },
          { id: 2 }
        ]; track item.id) {
          {{ item }}
        }
      `).toEqual([
        ['ForLoopBlock', '[{id: 1}, {id: 2}]', 'item.id'],
        ['Variable', 'item', '$implicit'],
        ['Variable', '$index', '$index'],
        ['Variable', '$first', '$first'],
        ['Variable', '$last', '$last'],
        ['Variable', '$even', '$even'],
        ['Variable', '$odd', '$odd'],
        ['Variable', '$count', '$count'],
        ['BoundText', ' {{ item }} '],
      ]);
    });


describe('validations', () => {
      it('should report if for loop does not have an expression', () => {
        expect(() => parse(`@for {hello}`)).toThrowError(/@for loop does not have an expression/);
      });

      it('should report if for loop does not have a tracking expression', () => {
        expect(() => parse(`@for (a of b) {hello}`)).toThrowError(
          /@for loop must have a "track" expression/,
        );
        expect(() => parse(`@for (a of b; track      ) {hello}`)).toThrowError(
          /@for loop must have a "track" expression/,
        );
      });

      it('should report mismatching optional parentheses around for loop expression', () => {
        expect(() => parse(`@for ((a of b; track c) {hello}`)).toThrowError(
          /Unclosed parentheses in expression/,
        );
        expect(() => parse(`@for ((a of b(); track c) {hello}`)).toThrowError(
          /Unexpected end of expression: b\(/,
        );
        expect(() => parse(`@for (a of b); track c) {hello}`)).toThrowError(
          /Unexpected character "EOF"/,
        );
      });

      it('should report unrecognized for loop parameters', () => {
        expect(() => parse(`@for (a of b; foo bar) {hello}`)).toThrowError(
          /Unrecognized @for loop paramater "foo bar"/,
        );
      });

      it('should report multiple `track` parameters', () => {
        expect(() => parse(`@for (a of b; track c; track d) {hello}`)).toThrowError(
          /@for loop can only have one "track" expression/,
        );
      });

      it('should report invalid for loop expression', () => {
        const errorPattern =
          /Cannot parse expression\. @for loop expression must match the pattern "<identifier> of <expression>"/;

        expect(() => parse(`@for (//invalid of items) {hello}`)).toThrowError(errorPattern);
        expect(() => parse(`@for (item) {hello}`)).toThrowError(errorPattern);
        expect(() => parse(`@for (item in items) {hello}`)).toThrowError(errorPattern);
        expect(() => parse(`@for (item of    ) {hello}`)).toThrowError(errorPattern);
      });

      it('should report syntax error in for loop expression', () => {
        expect(() => parse(`@for (item of items..foo) {hello}`)).toThrowError(
          /Unexpected token \./,
        );
      });

      it('should report for loop with multiple `empty` blocks', () => {
        expect(() =>
          parse(`
          @for (a of b; track a) {
            Main
          } @empty {
            Empty one
          } @empty {
            Empty two
          }
        `),
        ).toThrowError(/@for loop can only have one @empty block/);
      });

      it('should report empty block with parameters', () => {
        expect(() =>
          parse(`
          @for (a of b; track a) {
            main
          } @empty (foo) {
            empty
          }
        `),
        ).toThrowError(/@empty block cannot have parameters/);
      });

      it('should content between @for and @empty blocks', () => {
        expect(() =>
          parse(`
          @for (a of b; track a) {
            main
          }

 @empty {
            empty
          }
        `),
        ).toThrowError(/@empty block can only be used after an @for block/);
      });

      it('should report an empty block used without a @for loop block', () => {
        expect(() => parse(`@empty {hello}`)).toThrowError(
          /@empty block can only be used after an @for block/,
        );
      });

      it('should report an empty `let` parameter', () => {
        expect(() => parse(`@for (item of items.foo.bar; track item.id; let ) {}`)).toThrowError(
          /Invalid @for loop "let" parameter. Parameter should match the pattern "<name> = <variable name>"/,
        );
      });

      it('should report an invalid `let` parameter', () => {
        expect(() =>
          parse(`@for (item of items.foo.bar; track item.id; let i = $index, $odd) {}`),
        ).toThrowError(
          /Invalid @for loop "let" parameter\. Parameter should match the pattern "<name> = <variable name>"/,
        );
      });

      it('should an unknown variable in a `let` parameter', () => {
        expect(() =>
          parse(`@for (item of items.foo.bar; track item.id; let foo = $foo) {}`),
        ).toThrowError(/Unknown "let" parameter variable "\$foo"\. The allowed variables are:/);
      });

      it('should report duplicate `let` parameter variables', () => {
        expect(() =>
          parse(
            `@for (item of items.foo.bar; track item.id; let i = $index, f = $first, i = $index) {}`,
          ),
        ).toThrowError(/Duplicate "let" parameter variable "\$index"/);
        expect(() =>
          parse(`@for (item of items.foo.bar; track item.id; let $index = $index) {}`),
        ).toThrowError(/Duplicate "let" parameter variable "\$index"/);
      });

      it('should report an item name that conflicts with the implicit context variables', () => {
        ['$index', '$count', '$first', '$last', '$even', '$odd'].forEach((varName) => {
          expect(() => parse(`@for (${varName} of items; track $index) {}`)).toThrowError(
            /@for loop item name cannot be one of \$index, \$first, \$last, \$even, \$odd, \$count/,
          );
        });
      });

      it('should report a context variable alias that is the same as the variable name', () => {
        expect(() =>
          parse(`@for (item of items; let item = $index; track $index) {}`),
        ).toThrowError(/Invalid @for loop "let" parameter. Variable cannot be called "item"/);
      });
    });
  });


export function createSwitchBlock(
  ast: html.Block,
  visitor: html.Visitor,
  bindingParser: BindingParser,
): {node: t.SwitchBlock | null; errors: ParseError[]} {
  const errors = validateSwitchBlock(ast);
  const primaryExpression =
    ast.parameters.length > 0
      ? parseBlockParameterToBinding(ast.parameters[0], bindingParser)
      : bindingParser.parseBinding('', false, ast.sourceSpan, 0);
  const cases: t.SwitchBlockCase[] = [];
  const unknownBlocks: t.UnknownBlock[] = [];
  let defaultCase: t.SwitchBlockCase | null = null;

  // Here we assume that all the blocks are valid given that we validated them above.
  for (const node of ast.children) {
    if (!(node instanceof html.Block)) {
      continue;
    }

    if ((node.name !== 'case' || node.parameters.length === 0) && node.name !== 'default') {
      unknownBlocks.push(new t.UnknownBlock(node.name, node.sourceSpan, node.nameSpan));
      continue;
    }

    const expression =
      node.name === 'case' ? parseBlockParameterToBinding(node.parameters[0], bindingParser) : null;
    const ast = new t.SwitchBlockCase(
      expression,
      html.visitAll(visitor, node.children, node.children),
      node.sourceSpan,
      node.startSourceSpan,
      node.endSourceSpan,
      node.nameSpan,
      node.i18n,
    );

    if (expression === null) {
      defaultCase = ast;
    } else {
      cases.push(ast);
    }
  }

  // Ensure that the default case is last in the array.
  if (defaultCase !== null) {
    cases.push(defaultCase);
  }

  return {
    node: new t.SwitchBlock(
      primaryExpression,
      cases,
      unknownBlocks,
      ast.sourceSpan,
      ast.startSourceSpan,
      ast.endSourceSpan,
      ast.nameSpan,
    ),
    errors,
  };
}

/** Parses the parameters of a `for` loop block. */
function parseForLoopParameters(
  block: html.Block,
  errors: ParseError[],
  bindingParser: BindingParser,
) {
  if (block.parameters.length === 0) {
    errors.push(new ParseError(block.startSourceSpan, '@for loop does not have an expression'));
    return null;
  }

  const [expressionParam, ...secondaryParams] = block.parameters;
  const match = stripOptionalParentheses(expressionParam, errors)?.match(
    FOR_LOOP_EXPRESSION_PATTERN,
  );

  if (!match || match[2].trim().length === 0) {
    errors.push(
      new ParseError(
        expressionParam.sourceSpan,
        'Cannot parse expression. @for loop expression must match the pattern "<identifier> of <expression>"',
      ),
    );
    return null;
  }

  const [, itemName, rawExpression] = match;
  if (ALLOWED_FOR_LOOP_LET_VARIABLES.has(itemName)) {
    errors.push(
      new ParseError(
        expressionParam.sourceSpan,
        `@for loop item name cannot be one of ${Array.from(ALLOWED_FOR_LOOP_LET_VARIABLES).join(
          ', ',
        )}.`,
      ),
    );
  }

  // `expressionParam.expression` contains the variable declaration and the expression of the
  // for...of statement, i.e. 'user of users' The variable of a ForOfStatement is _only_ the "const
  // user" part and does not include "of x".
  const variableName = expressionParam.expression.split(' ')[0];
  const variableSpan = new ParseSourceSpan(
    expressionParam.sourceSpan.start,
    expressionParam.sourceSpan.start.moveBy(variableName.length),
  );
  const result = {
    itemName: new t.Variable(itemName, '$implicit', variableSpan, variableSpan),
    trackBy: null as {expression: ASTWithSource; keywordSpan: ParseSourceSpan} | null,
    expression: parseBlockParameterToBinding(expressionParam, bindingParser, rawExpression),
    context: Array.from(ALLOWED_FOR_LOOP_LET_VARIABLES, (variableName) => {
      // Give ambiently-available context variables empty spans at the end of
      // the start of the `for` block, since they are not explicitly defined.
      const emptySpanAfterForBlockStart = new ParseSourceSpan(
        block.startSourceSpan.end,
        block.startSourceSpan.end,
      );
      return new t.Variable(
        variableName,
        variableName,
        emptySpanAfterForBlockStart,
        emptySpanAfterForBlockStart,
      );
    }),
  };

  for (const param of secondaryParams) {
    const letMatch = param.expression.match(FOR_LOOP_LET_PATTERN);

    if (letMatch !== null) {
      const variablesSpan = new ParseSourceSpan(
        param.sourceSpan.start.moveBy(letMatch[0].length - letMatch[1].length),
        param.sourceSpan.end,
      );
      parseLetParameter(
        param.sourceSpan,
        letMatch[1],
        variablesSpan,
        itemName,
        result.context,
        errors,
      );
      continue;
    }

    const trackMatch = param.expression.match(FOR_LOOP_TRACK_PATTERN);

    if (trackMatch !== null) {
      if (result.trackBy !== null) {
        errors.push(
          new ParseError(param.sourceSpan, '@for loop can only have one "track" expression'),
        );
      } else {
        const expression = parseBlockParameterToBinding(param, bindingParser, trackMatch[1]);
        if (expression.ast instanceof EmptyExpr) {
          errors.push(
            new ParseError(block.startSourceSpan, '@for loop must have a "track" expression'),
          );
        }
        const keywordSpan = new ParseSourceSpan(
          param.sourceSpan.start,
          param.sourceSpan.start.moveBy('track'.length),
        );
        result.trackBy = {expression, keywordSpan};
      }
      continue;
    }

    errors.push(
      new ParseError(param.sourceSpan, `Unrecognized @for loop paramater "${param.expression}"`),
    );
  }

  return result;
}

/** Parses the `let` parameter of a `for` loop block. */
function parseLetParameter(
  sourceSpan: ParseSourceSpan,
  expression: string,
  span: ParseSourceSpan,
  loopItemName: string,
  context: t.Variable[],
  errors: ParseError[],
): void {
  const parts = expression.split(',');
  let startSpan = span.start;
  for (const part of parts) {
    const expressionParts = part.split('=');
    const name = expressionParts.length === 2 ? expressionParts[0].trim() : '';
    const variableName = expressionParts.length === 2 ? expressionParts[1].trim() : '';

    if (name.length === 0 || variableName.length === 0) {
      errors.push(
        new ParseError(
          sourceSpan,
          `Invalid @for loop "let" parameter. Parameter should match the pattern "<name> = <variable name>"`,
        ),
      );
    } else if (!ALLOWED_FOR_LOOP_LET_VARIABLES.has(variableName)) {
      errors.push(
        new ParseError(
          sourceSpan,
          `Unknown "let" parameter variable "${variableName}". The allowed variables are: ${Array.from(
            ALLOWED_FOR_LOOP_LET_VARIABLES,
          ).join(', ')}`,
        ),
      );
    } else if (name === loopItemName) {
      errors.push(
        new ParseError(
          sourceSpan,
          `Invalid @for loop "let" parameter. Variable cannot be called "${loopItemName}"`,
        ),
      );
    } else if (context.some((v) => v.name === name)) {
      errors.push(
        new ParseError(sourceSpan, `Duplicate "let" parameter variable "${variableName}"`),
      );
    } else {
      const [, keyLeadingWhitespace, keyName] =
        expressionParts[0].match(CHARACTERS_IN_SURROUNDING_WHITESPACE_PATTERN) ?? [];
      const keySpan =
        keyLeadingWhitespace !== undefined && expressionParts.length === 2
          ? new ParseSourceSpan(
              /* strip leading spaces */
              startSpan.moveBy(keyLeadingWhitespace.length),
              /* advance to end of the variable name */
              startSpan.moveBy(keyLeadingWhitespace.length + keyName.length),
            )
          : span;

      let valueSpan: ParseSourceSpan | undefined = undefined;
      if (expressionParts.length === 2) {
        const [, valueLeadingWhitespace, implicit] =
          expressionParts[1].match(CHARACTERS_IN_SURROUNDING_WHITESPACE_PATTERN) ?? [];
        valueSpan =
          valueLeadingWhitespace !== undefined
            ? new ParseSourceSpan(
                startSpan.moveBy(expressionParts[0].length + 1 + valueLeadingWhitespace.length),
                startSpan.moveBy(
                  expressionParts[0].length + 1 + valueLeadingWhitespace.length + implicit.length,
                ),
              )
            : undefined;
      }
      const sourceSpan = new ParseSourceSpan(keySpan.start, valueSpan?.end ?? keySpan.end);
      context.push(new t.Variable(name, variableName, sourceSpan, keySpan, valueSpan));
    }
    startSpan = startSpan.moveBy(part.length + 1 /* add 1 to move past the comma */);
  }
}


Implements a domain-specific language (DSL) for defining web animation sequences for HTML elements as
multiple transformations over time.

Use this API to define how an HTML element can move, change color, grow or shrink, fade, or slide off
the page. These changes can occur simultaneously or sequentially. You can control the timing of each
of these transformations. The function calls generate the data structures and metadata that enable Angular
to integrate animations into templates and run them based on application states.

Animation definitions are linked to components through the `{@link Component.animations animations}`
property in the `@Component` metadata, typically in the component file of the HTML element to be animated.
The `trigger()` function encapsulates a named animation, with all other function calls nested within. Use
the trigger name to bind the named animation to a specific triggering element in the HTML template.

Angular animations are based on CSS web transition functionality, so anything that can be styled or
transformed in CSS can be animated the same way in Angular. Angular animations allow you to:

* Set animation timings, styles, keyframes, and transitions.
* Animate HTML elements in complex sequences and choreographies.
* Animate HTML elements as they are inserted and removed from the DOM, including responsive real-time
  filtering.
* Create reusable animations.
* Animate parent and child elements.

Additional animation functionality is provided in other Angular modules for animation testing, for
route-based animations, and for programmatic animation controls that allow an end user to fast forward
and reverse an animation sequence.

@see Find out more in the [animations guide](guide/animations).
@see See what polyfills you might need in the [browser support guide](reference/versions#browser-support).


/*!
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {isPlatformBrowser} from '@angular/common';
import {
  APP_INITIALIZER,
  ApplicationRef,
  EnvironmentProviders,
  InjectionToken,
  Injector,
  makeEnvironmentProviders,
  NgZone,
  PLATFORM_ID,
} from '@angular/core';
import {merge, from, Observable, of} from 'rxjs';
import {delay, take} from 'rxjs/operators';

import {NgswCommChannel} from './low_level';
import {SwPush} from './push';
import {SwUpdate} from './update';

export const SCRIPT = new InjectionToken<string>(ngDevMode ? 'NGSW_REGISTER_SCRIPT' : '');

export function ngswAppInitializer(
  injector: Injector,
  script: string,
  options: SwRegistrationOptions,
  platformId: string,
): Function {
  return () => {
    if (
      !(isPlatformBrowser(platformId) && 'serviceWorker' in navigator && options.enabled !== false)
    ) {
      return;
    }

    const ngZone = injector.get(NgZone);
    const appRef = injector.get(ApplicationRef);

    // Set up the `controllerchange` event listener outside of
    // the Angular zone to avoid unnecessary change detections,
    // as this event has no impact on view updates.
    ngZone.runOutsideAngular(() => {
      // Wait for service worker controller changes, and fire an INITIALIZE action when a new SW
      // becomes active. This allows the SW to initialize itself even if there is no application
      // traffic.
      const sw = navigator.serviceWorker;
      const onControllerChange = () => sw.controller?.postMessage({action: 'INITIALIZE'});

      sw.addEventListener('controllerchange', onControllerChange);

      appRef.onDestroy(() => {
        sw.removeEventListener('controllerchange', onControllerChange);
      });
    });

    let readyToRegister$: Observable<unknown>;

    if (typeof options.registrationStrategy === 'function') {
      readyToRegister$ = options.registrationStrategy();
    } else {
      const [strategy, ...args] = (
        options.registrationStrategy || 'registerWhenStable:30000'
      ).split(':');

      switch (strategy) {
        case 'registerImmediately':
          readyToRegister$ = of(null);
          break;
        case 'registerWithDelay':
          readyToRegister$ = delayWithTimeout(+args[0] || 0);
          break;
        case 'registerWhenStable':
          const whenStable$ = from(injector.get(ApplicationRef).whenStable());
          readyToRegister$ = !args[0]
            ? whenStable$
            : merge(whenStable$, delayWithTimeout(+args[0]));
          break;
        default:
          // Unknown strategy.
          throw new Error(
            `Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`,
          );
      }
    }

    // Don't return anything to avoid blocking the application until the SW is registered.
    // Also, run outside the Angular zone to avoid preventing the app from stabilizing (especially
    // given that some registration strategies wait for the app to stabilize).
    // Catch and log the error if SW registration fails to avoid uncaught rejection warning.
    ngZone.runOutsideAngular(() =>
      readyToRegister$
        .pipe(take(1))
        .subscribe(() =>
          navigator.serviceWorker
            .register(script, {scope: options.scope})
            .catch((err) => console.error('Service worker registration failed with:', err)),
        ),
    );
  };
}

function delayWithTimeout(timeout: number): Observable<unknown> {
  return of(null).pipe(delay(timeout));
}

export function ngswCommChannelFactory(
  opts: SwRegistrationOptions,
  platformId: string,
): NgswCommChannel {
  return new NgswCommChannel(
    isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker : undefined,
  );
}

/**
 * Token that can be used to provide options for `ServiceWorkerModule` outside of
 * `ServiceWorkerModule.register()`.
 *
 * You can use this token to define a provider that generates the registration options at runtime,
 * for example via a function call:
 *
 * {@example service-worker/registration-options/module.ts region="registration-options"
 *     header="app.module.ts"}
 *
 * @publicApi
 */
export abstract class SwRegistrationOptions {
  /**
   * Whether the ServiceWorker will be registered and the related services (such as `SwPush` and
   * `SwUpdate`) will attempt to communicate and interact with it.
   *
   * Default: true
   */
  enabled?: boolean;

  /**
   * A URL that defines the ServiceWorker's registration scope; that is, what range of URLs it can
   * control. It will be used when calling
   * [ServiceWorkerContainer#register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).
   */
  scope?: string;

  /**
   * Defines the ServiceWorker registration strategy, which determines when it will be registered
   * with the browser.
   *
   * The default behavior of registering once the application stabilizes (i.e. as soon as there are
   * no pending micro- and macro-tasks) is designed to register the ServiceWorker as soon as
   * possible but without affecting the application's first time load.
   *
   * Still, there might be cases where you want more control over when the ServiceWorker is
   * registered (for example, there might be a long-running timeout or polling interval, preventing
   * the app from stabilizing). The available option are:
   *
   * - `registerWhenStable:<timeout>`: Register as soon as the application stabilizes (no pending
   *     micro-/macro-tasks) but no later than `<timeout>` milliseconds. If the app hasn't
   *     stabilized after `<timeout>` milliseconds (for example, due to a recurrent asynchronous
   *     task), the ServiceWorker will be registered anyway.
   *     If `<timeout>` is omitted, the ServiceWorker will only be registered once the app
   *     stabilizes.
   * - `registerImmediately`: Register immediately.
   * - `registerWithDelay:<timeout>`: Register with a delay of `<timeout>` milliseconds. For
   *     example, use `registerWithDelay:5000` to register the ServiceWorker after 5 seconds. If
   *     `<timeout>` is omitted, is defaults to `0`, which will register the ServiceWorker as soon
   *     as possible but still asynchronously, once all pending micro-tasks are completed.
   * - An Observable factory function: A function that returns an `Observable`.
   *     The function will be used at runtime to obtain and subscribe to the `Observable` and the
   *     ServiceWorker will be registered as soon as the first value is emitted.
   *
   * Default: 'registerWhenStable:30000'
   */
  registrationStrategy?: string | (() => Observable<unknown>);
}

/**
 * @publicApi
 *
 * Sets up providers to register the given Angular Service Worker script.
 *
 * If `enabled` is set to `false` in the given options, the module will behave as if service
 * workers are not supported by the browser, and the service worker will not be registered.
 *
 * Example usage:
 * ```ts
 * bootstrapApplication(AppComponent, {
 *   providers: [
 *     provideServiceWorker('ngsw-worker.js')
 *   ],
 * });
 * ```
 */
export function provideServiceWorker(
  script: string,
  options: SwRegistrationOptions = {},
): EnvironmentProviders {
  return makeEnvironmentProviders([
    SwPush,
    SwUpdate,
    {provide: SCRIPT, useValue: script},
    {provide: SwRegistrationOptions, useValue: options},
    {
      provide: NgswCommChannel,
      useFactory: ngswCommChannelFactory,
      deps: [SwRegistrationOptions, PLATFORM_ID],
    },
    {
      provide: APP_INITIALIZER,
      useFactory: ngswAppInitializer,
      deps: [Injector, SCRIPT, SwRegistrationOptions, PLATFORM_ID],
      multi: true,
    },
  ]);
}


### Error

By default, `zone.js/plugins/zone-error` will not be loaded for performance reasons.
This package provides the following functionality:

1. **Error Inheritance:** Handle the `extend Error` issue:

   ```ts
     class MyError extends Error {}
     const myError = new MyError();
     console.log('is MyError instanceof Error', (myError instanceof Error));
   ```

   Without the `zone-error` patch, the example above will output `false`. With the patch, the result will be `true`.

2. **ZoneJsInternalStackFrames:** Remove the zone.js stack from `stackTrace` and add `zone` information. Without this patch, many `zone.js` invocation stacks will be displayed in the stack frames.

   ```
     at zone.run (polyfill.bundle.js: 3424)
     at zoneDelegate.invokeTask (polyfill.bundle.js: 3424)
     at zoneDelegate.runTask (polyfill.bundle.js: 3424)
     at zone.drainMicroTaskQueue (polyfill.bundle.js: 3424)
     at a.b.c (vendor.bundle.js: 12345 <angular>)
     at d.e.f (main.bundle.js: 23456)
   ```

   With this patch, those zone frames will be removed, and the zone information `<angular>/<root>` will be added.

   ```
     at a.b.c (vendor.bundle.js: 12345 <angular>)
     at d.e.f (main.bundle.js: 23456 <root>)
   ```

The second feature may slow down `Error` performance, so `zone.js` provides a flag that allows you to control this behavior.
The flag is `__Zone_Error_ZoneJsInternalStackFrames_policy`. The available options are:

1. **default:** This is the default setting. If you load `zone.js/plugins/zone-error` without setting the flag, `default` will be used. In this case, `ZoneJsInternalStackFrames` will be available when using `new Error()`, allowing you to obtain an `error.stack` that is zone-stack-free. However, this may slightly slow down the performance of new `Error()`.

2. **disable:** This option will disable the `ZoneJsInternalStackFrames` feature. If you load `zone.js/plugins/zone-error`, you will only receive a wrapped `Error`, which can handle the `Error` inheritance issue.

3. **lazy:** This feature allows you to access `ZoneJsInternalStackFrames` without impacting performance. However, as a trade-off, you won't be able to obtain the zone-free stack frames via `error.stack`. You can only access them through `error.zoneAwareStack`.

### Angular

Angular uses zone.js to manage asynchronous operations and determine when to perform change detection. Therefore, in Angular, the following APIs should be patched; otherwise, Angular may not work as expected:

1. ZoneAwarePromise
2. timer
3. on_property
4. EventTarget
5. XHR


Supports execution of Angular apps on different supported browsers.

The `BrowserModule` is included by default in any app created through the CLI,
and it re-exports the `CommonModule` and `ApplicationModule` exports,
making basic Angular functionality available to the app.

For more information, see [Browser Support](reference/versions#browser-support).


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {DOCUMENT} from '@angular/common';
import {HttpClient, HttpTransferCacheOptions, provideHttpClient} from '@angular/common/http';
import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';
import {
  ApplicationRef,
  Component,
  Injectable,
  PLATFORM_ID,
  ɵSSR_CONTENT_INTEGRITY_MARKER as SSR_CONTENT_INTEGRITY_MARKER,
} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {withBody} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';

import {provideClientHydration, withNoHttpTransferCache} from '../public_api';
import {withHttpTransferCacheOptions} from '../src/hydration';

describe('provideClientHydration', () => {
  @Component({
    selector: 'test-hydrate-app',
    template: '',
    standalone: false,
  })
  class SomeComponent {}

  function makeRequestAndExpectOne(
    url: string,
    body: string,
    options: HttpTransferCacheOptions | boolean = true,
  ): void {
    TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe();
    TestBed.inject(HttpTestingController).expectOne(url).flush(body);
  }

  function makeRequestAndExpectNone(
    url: string,
    options: HttpTransferCacheOptions | boolean = true,
  ): void {
    TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe();
    TestBed.inject(HttpTestingController).expectNone(url);
  }

  @Injectable()
  class ApplicationRefPatched extends ApplicationRef {
    override isStable = new BehaviorSubject<boolean>(false);
  }

  describe('default', () => {
    beforeEach(
      withBody(
        `<!--${SSR_CONTENT_INTEGRITY_MARKER}--><test-hydrate-app></test-hydrate-app>`,
        () => {
          TestBed.resetTestingModule();

          TestBed.configureTestingModule({
            declarations: [SomeComponent],
            providers: [
              {provide: PLATFORM_ID, useValue: 'server'},
              {provide: DOCUMENT, useFactory: () => document},
              {provide: ApplicationRef, useClass: ApplicationRefPatched},
              provideClientHydration(),
              provideHttpClient(),
              provideHttpClientTesting(),
            ],
          });

          const appRef = TestBed.inject(ApplicationRef);
          appRef.bootstrap(SomeComponent);
        },
      ),
    );

    it(`should use cached HTTP calls`, () => {
      makeRequestAndExpectOne('/test-1', 'foo');
      // Do the same call, this time it should served from cache.
      makeRequestAndExpectNone('/test-1');
    });
  });

  describe('withNoHttpTransferCache', () => {
    beforeEach(
      withBody(
        `<!--${SSR_CONTENT_INTEGRITY_MARKER}--><test-hydrate-app></test-hydrate-app>`,
        () => {
          TestBed.resetTestingModule();

          TestBed.configureTestingModule({
            declarations: [SomeComponent],
            providers: [
              {provide: PLATFORM_ID, useValue: 'server'},
              {provide: DOCUMENT, useFactory: () => document},
              {provide: ApplicationRef, useClass: ApplicationRefPatched},
              provideClientHydration(withNoHttpTransferCache()),
              provideHttpClient(),
              provideHttpClientTesting(),
            ],
          });

          const appRef = TestBed.inject(ApplicationRef);
          appRef.bootstrap(SomeComponent);
        },
      ),
    );

    it(`should not cache HTTP calls`, () => {
      makeRequestAndExpectOne('/test-1', 'foo', false);
      // Do the same call, this time should pass through as cache is disabled.
      makeRequestAndExpectOne('/test-1', 'foo');
    });
  });

  describe('withHttpTransferCacheOptions', () => {
    beforeEach(
      withBody(
        `<!--${SSR_CONTENT_INTEGRITY_MARKER}--><test-hydrate-app></test-hydrate-app>`,
        () => {
          TestBed.resetTestingModule();

          TestBed.configureTestingModule({
            declarations: [SomeComponent],
            providers: [
              {provide: PLATFORM_ID, useValue: 'server'},
              {provide: DOCUMENT, useFactory: () => document},
              {provide: ApplicationRef, useClass: ApplicationRefPatched},
              provideClientHydration(
                withHttpTransferCacheOptions({includePostRequests: true, includeHeaders: ['foo']}),
              ),
              provideHttpClient(),
              provideHttpClientTesting(),
            ],
          });

          const appRef = TestBed.inject(ApplicationRef);
          appRef.bootstrap(SomeComponent);
        },
      ),
    );

    it(`should cache HTTP POST calls`, () => {
      const url = '/test-1';
      const body = 'foo';
      TestBed.inject(HttpClient).post(url, body).subscribe();
      TestBed.inject(HttpTestingController).expectOne(url).flush(body);

      TestBed.inject(HttpClient).post(url, body).subscribe();
      TestBed.inject(HttpTestingController).expectNone(url);
    });
  });
});