/**
 * @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.io/license
 */

import {signalAsReadonlyFn, WritableSignal} from './signal';
import {Signal, ValueEqualityFn} from './api';
import {
  producerMarkClean,
  ReactiveNode,
  SIGNAL,
  signalSetFn,
  signalUpdateFn,
  producerUpdateValueVersion,
  REACTIVE_NODE,
  defaultEquals,
  consumerBeforeComputation,
  consumerAfterComputation,
  producerAccessed,
} from '@angular/core/primitives/signals';
import {performanceMarkFeature} from '../../util/performance';

type ComputationFn<S, D> = (source: S, previous?: {source: S; value: D}) => D;

interface LinkedSignalNode<S, D> extends ReactiveNode {
  /**
   * Value of the source signal that was used to derive the computed value.
   */
  sourceValue: S;

  /**
   * Current state value, or one of the sentinel values (`UNSET`, `COMPUTING`,
   * `ERROR`).
   */
  value: D;

  /**
   * If `value` is `ERRORED`, the error caught from the last computation attempt which will
   * be re-thrown.
   */
  error: unknown;

  /**
   * The source function represents reactive dependency based on which the linked state is reset.
   */
  source: () => S;

  /**
   * The computation function which will produce a new value based on the source and, optionally - previous values.
   */
  computation: ComputationFn<S, D>;

  equal: ValueEqualityFn<D>;
}

export type LinkedSignalGetter<S, D> = (() => D) & {
  [SIGNAL]: LinkedSignalNode<S, D>;
};

const identityFn = <T>(v: T) => v;

/**
 * Create a linked signal which represents state that is (re)set from a linked reactive expression.
 */
function createLinkedSignal<S, D>(node: LinkedSignalNode<S, D>): WritableSignal<D> {
  const linkedSignalGetter = () => {
    // Check if the value needs updating before returning it.
    producerUpdateValueVersion(node);

    // Record that someone looked at this signal.
    producerAccessed(node);

    if (node.value === ERRORED) {
      throw node.error;
    }

    return node.value;
  };

  const getter = linkedSignalGetter as LinkedSignalGetter<S, D> & WritableSignal<D>;
  getter[SIGNAL] = node;

  if (ngDevMode) {
    getter.toString = () => `[LinkedSignal: ${getter()}]`;
  }

  getter.set = (newValue: D) => {
    producerUpdateValueVersion(node);
    signalSetFn(node, newValue);
    producerMarkClean(node);
  };

  getter.update = (updateFn: (value: D) => D) => {
    producerUpdateValueVersion(node);
    signalUpdateFn(node, updateFn);
    producerMarkClean(node);
  };

  getter.asReadonly = signalAsReadonlyFn.bind(getter as any) as () => Signal<D>;

  return getter;
}

/**
 * @experimental
 */
export function linkedSignal<D>(
  computation: () => D,
  options?: {equal?: ValueEqualityFn<NoInfer<D>>},
): WritableSignal<D>;
export function linkedSignal<S, D>(options: {
  source: () => S;
  computation: (source: NoInfer<S>, previous?: {source: NoInfer<S>; value: NoInfer<D>}) => D;
  equal?: ValueEqualityFn<NoInfer<D>>;
}): WritableSignal<D>;
export function linkedSignal<S, D>(
  optionsOrComputation:
    | {
        source: () => S;
        computation: ComputationFn<S, D>;
        equal?: ValueEqualityFn<D>;
      }
    | (() => D),
  options?: {equal?: ValueEqualityFn<D>},
): WritableSignal<D> {
  performanceMarkFeature('NgSignals');

  const isShorthand = typeof optionsOrComputation === 'function';
  const node: LinkedSignalNode<unknown, unknown> = Object.create(LINKED_SIGNAL_NODE);
  node.source = isShorthand ? optionsOrComputation : optionsOrComputation.source;
  if (!isShorthand) {
    node.computation = optionsOrComputation.computation as ComputationFn<unknown, unknown>;
  }
  const equal = isShorthand ? options?.equal : optionsOrComputation.equal;
  if (equal) {
    node.equal = equal as ValueEqualityFn<unknown>;
  }
  return createLinkedSignal(node as LinkedSignalNode<S, D>);
}

/**
 * A dedicated symbol used before a state value has been set / calculated for the first time.
 * Explicitly typed as `any` so we can use it as signal's value.
 */
const UNSET: any = /* @__PURE__ */ Symbol('UNSET');

/**
 * A dedicated symbol used in place of a linked signal value to indicate that a given computation
 * is in progress. Used to detect cycles in computation chains.
 * Explicitly typed as `any` so we can use it as signal's value.
 */
const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING');

/**
 * A dedicated symbol used in place of a linked signal value to indicate that a given computation
 * failed. The thrown error is cached until the computation gets dirty again or the state is set.
 * Explicitly typed as `any` so we can use it as signal's value.
 */
const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED');

// Note: Using an IIFE here to ensure that the spread assignment is not considered
// a side-effect, ending up preserving `LINKED_SIGNAL_NODE` and `REACTIVE_NODE`.
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
const LINKED_SIGNAL_NODE = /* @__PURE__ */ (() => {
  return {
    ...REACTIVE_NODE,
    value: UNSET,
    dirty: true,
    error: null,
    equal: defaultEquals,
    computation: identityFn,

    producerMustRecompute(node: LinkedSignalNode<unknown, unknown>): boolean {
      // Force a recomputation if there's no current value, or if the current value is in the
      // process of being calculated (which should throw an error).
      return node.value === UNSET || node.value === COMPUTING;
    },

    producerRecomputeValue(node: LinkedSignalNode<unknown, unknown>): void {
      if (node.value === COMPUTING) {
        // Our computation somehow led to a cyclic read of itself.
        throw new Error('Detected cycle in computations.');
      }

      const oldValue = node.value;
      node.value = COMPUTING;

      const prevConsumer = consumerBeforeComputation(node);
      let newValue: unknown;
      try {
        const newSourceValue = node.source();
        const prev =
          oldValue === UNSET || oldValue === ERRORED
            ? undefined
            : {
                source: node.sourceValue,
                value: oldValue,
              };
        newValue = node.computation(newSourceValue, prev);
        node.sourceValue = newSourceValue;
      } catch (err) {
        newValue = ERRORED;
        node.error = err;
      } finally {
        consumerAfterComputation(node, prevConsumer);
      }

      if (oldValue !== UNSET && newValue !== ERRORED && node.equal(oldValue, newValue)) {
        // No change to `valueVersion` - old and new values are
        // semantically equivalent.
        node.value = oldValue;
        return;
      }

      node.value = newValue;
      node.version++;
    },
  };
})();


/**
 * @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 {createWatch, Watch, WatchCleanupRegisterFn} from '@angular/core/primitives/signals';

import {ChangeDetectorRef} from '../../change_detection/change_detector_ref';
import {Injector} from '../../di/injector';
import {inject} from '../../di/injector_compatibility';
import {ɵɵdefineInjectable} from '../../di/interface/defs';
import {ErrorHandler} from '../../error_handler';
import type {ViewRef} from '../view_ref';
import {DestroyRef} from '../../linker/destroy_ref';
import {FLAGS, LViewFlags, EFFECTS_TO_SCHEDULE} from '../interfaces/view';

import type {CreateEffectOptions, EffectCleanupRegisterFn, EffectRef} from './effect';
import {type SchedulableEffect, ZoneAwareEffectScheduler} from './root_effect_scheduler';
import {performanceMarkFeature} from '../../util/performance';
import {assertNotInReactiveContext} from './asserts';
import {assertInInjectionContext} from '../../di';
import {PendingTasksInternal} from '../../pending_tasks';

export class MicrotaskEffectScheduler extends ZoneAwareEffectScheduler {
  private readonly pendingTasks = inject(PendingTasksInternal);
  private taskId: number | null = null;

  override schedule(effect: SchedulableEffect): void {
    // Check whether there are any pending effects _before_ queueing in the base class.
    super.schedule(effect);
    if (this.taskId === null) {
      this.taskId = this.pendingTasks.add();
      queueMicrotask(() => this.flush());
    }
  }

  override flush(): void {
    try {
      super.flush();
    } finally {
      if (this.taskId !== null) {
        this.pendingTasks.remove(this.taskId);
        this.taskId = null;
      }
    }
  }

  /** @nocollapse */
  static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
    token: MicrotaskEffectScheduler,
    providedIn: 'root',
    factory: () => new MicrotaskEffectScheduler(),
  });
}

/**
 * Core reactive node for an Angular effect.
 *
 * `EffectHandle` combines the reactive graph's `Watch` base node for effects with the framework's
 * scheduling abstraction (`MicrotaskEffectScheduler`) as well as automatic cleanup via `DestroyRef`
 * if available/requested.
 */
class EffectHandle implements EffectRef, SchedulableEffect {
  unregisterOnDestroy: (() => void) | undefined;
  readonly watcher: Watch;

  constructor(
    private scheduler: MicrotaskEffectScheduler,
    private effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
    public zone: Zone | null,
    destroyRef: DestroyRef | null,
    private injector: Injector,
    allowSignalWrites: boolean,
  ) {
    this.watcher = createWatch(
      (onCleanup) => this.runEffect(onCleanup),
      () => this.schedule(),
      allowSignalWrites,
    );
    this.unregisterOnDestroy = destroyRef?.onDestroy(() => this.destroy());
  }

  private runEffect(onCleanup: WatchCleanupRegisterFn): void {
    try {
      this.effectFn(onCleanup);
    } catch (err) {
      // Inject the `ErrorHandler` here in order to avoid circular DI error
      // if the effect is used inside of a custom `ErrorHandler`.
      const errorHandler = this.injector.get(ErrorHandler, null, {optional: true});
      errorHandler?.handleError(err);
    }
  }

  run(): void {
    this.watcher.run();
  }

  private schedule(): void {
    this.scheduler.schedule(this);
  }

  destroy(): void {
    this.watcher.destroy();
    this.unregisterOnDestroy?.();

    // Note: if the effect is currently scheduled, it's not un-scheduled, and so the scheduler will
    // retain a reference to it. Attempting to execute it will be a no-op.
  }
}

// Just used for the name for the debug error below.
function effect() {}

/**
 * Create a global `Effect` for the given reactive function.
 */
export function microtaskEffect(
  effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
  options?: CreateEffectOptions,
): EffectRef {
  performanceMarkFeature('NgSignals');
  ngDevMode &&
    assertNotInReactiveContext(
      effect,
      'Call `effect` outside of a reactive context. For example, schedule the ' +
        'effect inside the component constructor.',
    );

  !options?.injector && assertInInjectionContext(effect);

  const injector = options?.injector ?? inject(Injector);
  const destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;

  const handle = new EffectHandle(
    injector.get(MicrotaskEffectScheduler),
    effectFn,
    typeof Zone === 'undefined' ? null : Zone.current,
    destroyRef,
    injector,
    options?.allowSignalWrites ?? false,
  );

  // Effects need to be marked dirty manually to trigger their initial run. The timing of this
  // marking matters, because the effects may read signals that track component inputs, which are
  // only available after those components have had their first update pass.
  //
  // We inject `ChangeDetectorRef` optionally, to determine whether this effect is being created in
  // the context of a component or not. If it is, then we check whether the component has already
  // run its update pass, and defer the effect's initial scheduling until the update pass if it
  // hasn't already run.
  const cdr = injector.get(ChangeDetectorRef, null, {optional: true}) as ViewRef<unknown> | null;
  if (!cdr || !(cdr._lView[FLAGS] & LViewFlags.FirstLViewPass)) {
    // This effect is either not running in a view injector, or the view has already
    // undergone its first change detection pass, which is necessary for any required inputs to be
    // set.
    handle.watcher.notify();
  } else {
    // Delay the initialization of the effect until the view is fully initialized.
    (cdr._lView[EFFECTS_TO_SCHEDULE] ??= []).push(handle.watcher.notify);
  }

  return handle;
}


/**
 * @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 {SIGNAL} from '@angular/core/primitives/signals';

/**
 * A reactive value which notifies consumers of any changes.
 *
 * Signals are functions which returns their current value. To access the current value of a signal,
 * call it.
 *
 * Ordinary values can be turned into `Signal`s with the `signal` function.
 */
export type Signal<T> = (() => T) & {
  [SIGNAL]: unknown;
};

/**
 * Checks if the given `value` is a reactive `Signal`.
 */
export function isSignal(value: unknown): value is Signal<unknown> {
  return typeof value === 'function' && (value as Signal<unknown>)[SIGNAL] !== undefined;
}

/**
 * A comparison function which can determine if two values are equal.
 */
export type ValueEqualityFn<T> = (a: T, b: T) => boolean;


/**
 * @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 {
  createSignal,
  SIGNAL,
  SignalGetter,
  SignalNode,
  signalSetFn,
  signalUpdateFn,
} from '@angular/core/primitives/signals';

import {performanceMarkFeature} from '../../util/performance';

import {isSignal, Signal, ValueEqualityFn} from './api';

/** Symbol used distinguish `WritableSignal` from other non-writable signals and functions. */
export const ɵWRITABLE_SIGNAL = /* @__PURE__ */ Symbol('WRITABLE_SIGNAL');

/**
 * A `Signal` with a value that can be mutated via a setter interface.
 */
export interface WritableSignal<T> extends Signal<T> {
  [ɵWRITABLE_SIGNAL]: T;

  /**
   * Directly set the signal to a new value, and notify any dependents.
   */
  set(value: T): void;

  /**
   * Update the value of the signal based on its current value, and
   * notify any dependents.
   */
  update(updateFn: (value: T) => T): void;

  /**
   * Returns a readonly version of this signal. Readonly signals can be accessed to read their value
   * but can't be changed using set or update methods. The readonly signals do _not_ have
   * any built-in mechanism that would prevent deep-mutation of their value.
   */
  asReadonly(): Signal<T>;
}

/**
 * Utility function used during template type checking to extract the value from a `WritableSignal`.
 * @codeGenApi
 */
export function ɵunwrapWritableSignal<T>(value: T | {[ɵWRITABLE_SIGNAL]: T}): T {
  // Note: the function uses `WRITABLE_SIGNAL` as a brand instead of `WritableSignal<T>`,
  // because the latter incorrectly unwraps non-signal getter functions.
  return null!;
}

/**
 * Options passed to the `signal` creation function.
 */
export interface CreateSignalOptions<T> {
  /**
   * A comparison function which defines equality for signal values.
   */
  equal?: ValueEqualityFn<T>;

  /**
   * A debug name for the signal. Used in Angular DevTools to identify the signal.
   */
  debugName?: string;
}

/**
 * Create a `Signal` that can be set or updated directly.
 */
export function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): WritableSignal<T> {
  performanceMarkFeature('NgSignals');
  const signalFn = createSignal(initialValue) as SignalGetter<T> & WritableSignal<T>;
  const node = signalFn[SIGNAL];
  if (options?.equal) {
    node.equal = options.equal;
  }

  signalFn.set = (newValue: T) => signalSetFn(node, newValue);
  signalFn.update = (updateFn: (value: T) => T) => signalUpdateFn(node, updateFn);
  signalFn.asReadonly = signalAsReadonlyFn.bind(signalFn as any) as () => Signal<T>;

  if (ngDevMode) {
    signalFn.toString = () => `[Signal: ${signalFn()}]`;
    node.debugName = options?.debugName;
  }

  return signalFn as WritableSignal<T>;
}

export function signalAsReadonlyFn<T>(this: SignalGetter<T>): Signal<T> {
  const node = this[SIGNAL] as SignalNode<T> & {readonlyFn?: Signal<T>};
  if (node.readonlyFn === undefined) {
    const readonlyFn = () => this();
    (readonlyFn as any)[SIGNAL] = node;
    node.readonlyFn = readonlyFn as Signal<T>;
  }
  return node.readonlyFn;
}

/**
 * Checks if the given `value` is a writeable signal.
 */
export function isWritableSignal(value: unknown): value is WritableSignal<unknown> {
  return isSignal(value) && typeof (value as any).set === 'function';
}


/**
 * @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 {createComputed, SIGNAL} from '@angular/core/primitives/signals';

import {performanceMarkFeature} from '../../util/performance';

import {Signal, ValueEqualityFn} from './api';

/**
 * Options passed to the `computed` creation function.
 */
export interface CreateComputedOptions<T> {
  /**
   * A comparison function which defines equality for computed values.
   */
  equal?: ValueEqualityFn<T>;

  /**
   * A debug name for the computed signal. Used in Angular DevTools to identify the signal.
   */
  debugName?: string;
}

/**
 * Create a computed `Signal` which derives a reactive value from an expression.
 */
export function computed<T>(computation: () => T, options?: CreateComputedOptions<T>): Signal<T> {
  performanceMarkFeature('NgSignals');
  const getter = createComputed(computation);
  if (options?.equal) {
    getter[SIGNAL].equal = options.equal;
  }

  if (ngDevMode) {
    getter.toString = () => `[Computed: ${getter()}]`;
    getter[SIGNAL].debugName = options?.debugName;
  }

  return getter;
}


* Register callbacks to be invoked the next time the application finishes rendering, during the
 * specified phases. The available phases are:
 * - `earlyRead`
 *   Use this phase to **read** from the DOM before a subsequent `write` callback, for example to
 *   perform custom layout that the browser doesn't natively support. Prefer the `read` phase if
 *   reading can wait until after the write phase. **Never** write to the DOM in this phase.
 * - `write`
 *    Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
 * - `mixedReadWrite`
 *    Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if
 *    it is possible to divide the work among the other phases instead.
 * - `read`
 *    Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.
 *
 *

 *
 * You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`
 * phases when possible, to avoid performance degradation.
 *
 *

 *
 * Note that:
 * - Callbacks run in the following phase order *once, after the next render*:
 *   1. `earlyRead`
 *   2. `write`
 *   3. `mixedReadWrite`
 *   4. `read`
 * - Callbacks in the same phase run in the order they are registered.
 * - Callbacks run on browser platforms only, they will not run on the server.
 *
 * The first phase callback to run as part of this spec will receive no parameters. Each
 * subsequent phase callback in this spec will receive the return value of the previously run
 * phase callback as a parameter. This can be used to coordinate work across multiple phases.
 *
 * Angular is unable to verify or enforce that phases are used correctly, and instead
 * relies on each developer to follow the guidelines documented for each value and
 * carefully choose the appropriate one, refactoring their code if necessary. By doing
 * so, Angular is better able to minimize the performance degradation associated with
 * manual DOM access, ensuring the best experience for the end users of your application
 * or library.
 *
 *

 *
 * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.
 * You must use caution when directly reading or writing the DOM and layout.
 *
 *

 *
 * @param spec The callback functions to register
 * @param options Options to control the behavior of the callback
 *
 * @usageNotes
 *
 * Use `afterNextRender` to read or write the DOM once,
 * for example to initialize a non-Angular library.
 *
 * ### Example
 * ```ts
 * @Component({
 *   selector: 'my-chart-cmp',
 *   template: `<div #chart>{{ ... }}</div>`,
 * })
 * export class MyChartCmp {
 *   @ViewChild('chart') chartRef: ElementRef;
 *   chart: MyChart|null;
 *
 *   constructor() {
 *     afterNextRender({
 *       write: () => {
 *         this.chart = new MyChart(this.chartRef.nativeElement);
 *       }
 *     });
 *   }
 * }
 * ```
 *
 * @developerPreview
 */
export function afterNextRender<E = never, W = never, M = never>(
  spec: {
    earlyRead?: () => E;
    write?: (...args: ɵFirstAvailable<[E]>) => W;
    mixedReadWrite?: (...args: ɵFirstAvailable<[W, E]>) => M;
    read?: (...args: ɵFirstAvailable<[M, W, E]>) => void;
  },
  options?: Omit<AfterRenderOptions, 'phase'>,
): AfterRenderRef;

/**
 * Register a callback to be invoked the next time the application finishes rendering, during the
 * `mixedReadWrite` phase.
 *
 *

 *
 * You should prefer specifying an explicit phase for the callback instead, or you risk significant
 * performance degradation.
 *
 *

 *
 * Note that the callback will run
 * - in the order it was registered
 * - on browser platforms only
 * - during the `mixedReadWrite` phase
 *
 *

 *
 * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.
 * You must use caution when directly reading or writing the DOM and layout.
 *
 *

 *
 * @param callback A callback function to register
 * @param options Options to control the behavior of the callback
 *
 * @usageNotes
 *
 * Use `afterNextRender` to read or write the DOM once,
 * for example to initialize a non-Angular library.
 *
 * ### Example
 * ```ts
 * @Component({
 *   selector: 'my-chart-cmp',
 *   template: `<div #chart>{{ ... }}</div>`,
 * })
 * export class MyChartCmp {
 *   @ViewChild('chart') chartRef: ElementRef;
 *   chart: MyChart|null;
 *
 *   constructor() {
 *     afterNextRender({
 *       write: () => {
 *         this.chart = new MyChart(this.chartRef.nativeElement);
 *       }
 *     });
 *   }
 * }
 * ```
 *
 * @developerPreview
 */
export function afterNextRender(
  callback: VoidFunction,
  options?: AfterRenderOptions,
): AfterRenderRef;

export function afterNextRender(
  callbackOrSpec:
    | VoidFunction
    | {
        earlyRead?: () => unknown;
        write?: (r?: unknown) => unknown;
        mixedReadWrite?: (r?: unknown) => unknown;
        read?: (r?: unknown) => void;
      },
  options?: AfterRenderOptions,
): AfterRenderRef {
  !options?.injector && assertInInjectionContext(afterNextRender);
  const injector = options?.injector ?? inject(Injector);

  if (!isPlatformBrowser(injector)) {
    return NOOP_AFTER_RENDER_REF;
  }

  performanceMarkFeature('NgAfterNextRender');

  return afterRenderImpl(callbackOrSpec, injector, options, /* once */ true);
}

function getHooks(
  callbackOrSpec:
    | VoidFunction
    | {
        earlyRead?: () => unknown;
        write?: (r?: unknown) => unknown;
        mixedReadWrite?: (r?: unknown) => unknown;
        read?: (r?: unknown) => void;
      },
  phase: AfterRenderPhase,
): AfterRenderHooks {
  if (callbackOrSpec instanceof Function) {
    const hooks: AfterRenderHooks = [undefined, undefined, undefined, undefined];
    hooks[phase] = callbackOrSpec;
    return hooks;
  } else {
    return [
      callbackOrSpec.earlyRead,
      callbackOrSpec.write,
      callbackOrSpec.mixedReadWrite,
      callbackOrSpec.read,
    ];
  }
}

/**
 * Shared implementation for `afterRender` and `afterNextRender`.
 */
function afterRenderImpl(
  callbackOrSpec:
    | VoidFunction
    | {
        earlyRead?: () => unknown;
        write?: (r?: unknown) => unknown;
        mixedReadWrite?: (r?: unknown) => unknown;
        read?: (r?: unknown) => void;
      },
  injector: Injector,
  options: AfterRenderOptions | undefined,
  once: boolean,
): AfterRenderRef {
  const manager = injector.get(AfterRenderManager);
  // Lazily initialize the handler implementation, if necessary. This is so that it can be
  // tree-shaken if `afterRender` and `afterNextRender` aren't used.
  manager.impl ??= injector.get(AfterRenderImpl);

  const hooks = options?.phase ?? AfterRenderPhase.MixedReadWrite;
  const destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;
  const sequence = new AfterRenderSequence(
    manager.impl,
    getHooks(callbackOrSpec, hooks),
    once,
    destroyRef,
  );
  manager.impl.register(sequence);
  return sequence;
}

/** `AfterRenderRef` that does nothing. */
export const NOOP_AFTER_RENDER_REF: AfterRenderRef = {
  destroy() {},
};


/**
 * @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
 */

// This file contains types that will be published to npm in library typings files.

// Formatting does horrible things to these declarations.

/**
 * @publicApi
 */
export type ɵɵDirectiveDeclaration<
  T,
  Selector extends string,
  ExportAs extends string[],
  // `string` keys are for backwards compatibility with pre-16 versions.
  InputMap extends {
    [key: string]: string | {alias: string | null; required: boolean; isSignal?: boolean};
  },
  OutputMap extends {[key: string]: string},
  QueryFields extends string[],
  // Optional as this was added to align the `IsStandalone` parameters
  // between directive and component declarations.
  NgContentSelectors extends never = never,
  // Optional as this was added in Angular v14. All pre-existing directives
  // are not standalone.
  IsStandalone extends boolean = false,
  HostDirectives = never,
  IsSignal extends boolean = false,
> = unknown;

/**
 * @publicApi
 */
export type ɵɵComponentDeclaration<
  T,
  Selector extends String,
  ExportAs extends string[],
  // `string` keys are for backwards compatibility with pre-16 versions.
  InputMap extends {[key: string]: string | {alias: string | null; required: boolean}},
  OutputMap extends {[key: string]: string},
  QueryFields extends string[],
  NgContentSelectors extends string[],
  // Optional as this was added in Angular v14. All pre-existing components
  // are not standalone.
  IsStandalone extends boolean = false,
  HostDirectives = never,
  IsSignal extends boolean = false,
> = unknown;

/**
 * @publicApi
 */
export type ɵɵNgModuleDeclaration<T, Declarations, Imports, Exports> = unknown;

/**
 * @publicApi
 */
export type ɵɵPipeDeclaration<
  T,
  Name extends string,
  // Optional as this was added in Angular v14. All pre-existing directives
  // are not standalone.
  IsStandalone extends boolean = false,
> = unknown;

/**
 * @publicApi
 */
export type ɵɵInjectorDeclaration<T> = unknown;

/**
 * @publicApi
 */
export type ɵɵFactoryDeclaration<T, CtorDependencies extends CtorDependency[]> = unknown;

/**
 * An object literal of this type is used to represent the metadata of a constructor dependency.
 * The type itself is never referred to from generated code.
 *
 * @publicApi
 */
export type CtorDependency = {
  /**
   * If an `@Attribute` decorator is used, this represents the injected attribute's name. If the
   * attribute name is a dynamic expression instead of a string literal, this will be the unknown
   * type.
   */
  attribute?: string | unknown;

  /**
   * If `@Optional()` is used, this key is set to true.
   */
  optional?: true;

  /**
   * If `@Host` is used, this key is set to true.
   */
  host?: true;

  /**
   * If `@Self` is used, this key is set to true.
   */
  self?: true;

  /**
   * If `@SkipSelf` is used, this key is set to true.
   */
  skipSelf?: true;
} | null;


/**
 * A reference to an Angular application running on a page.
 *
 * @usageNotes
 * {@a is-stable-examples}
 * ### isStable examples and caveats
 *
 * Note two important points about `isStable`, demonstrated in the examples below:
 * - the application will never be stable if you start any kind
 * of recurrent asynchronous task when the application starts
 * (for example for a polling process, started with a `setInterval`, a `setTimeout`
 * or using RxJS operators like `interval`);
 * - the `isStable` Observable runs outside of the Angular zone.
 *
 * Let's imagine that you start a recurrent task
 * (here incrementing a counter, using RxJS `interval`),
 * and at the same time subscribe to `isStable`.
 *
 * ```
 * constructor(appRef: ApplicationRef) {
 *   appRef.isStable.pipe(
 *      filter(stable => stable)
 *   ).subscribe(() => console.log('App is stable now');
 *   interval(1000).subscribe(counter => console.log(counter));
 * }
 * ```
 * In this example, `isStable` will never emit `true`,
 * and the trace "App is stable now" will never get logged.
 *
 * If you want to execute something when the app is stable,
 * you have to wait for the application to be stable
 * before starting your polling process.
 *
 * ```
 * constructor(appRef: ApplicationRef) {
 *   appRef.isStable.pipe(
 *     first(stable => stable),
 *     tap(stable => console.log('App is stable now')),
 *     switchMap(() => interval(1000))
 *   ).subscribe(counter => console.log(counter));
 * }
 * ```
 * In this example, the trace "App is stable now" will be logged
 * and then the counter starts incrementing every second.
 *
 * Note also that this Observable runs outside of the Angular zone,
 * which means that the code in the subscription
 * to this Observable will not trigger the change detection.
 *
 * Let's imagine that instead of logging the counter value,
 * you update a field of your component
 * and display it in its template.
 *
 * ```
 * constructor(appRef: ApplicationRef) {
 *   appRef.isStable.pipe(
 *     first(stable => stable),
 *     switchMap(() => interval(1000))
 *   ).subscribe(counter => this.value = counter);
 * }
 * ```
 * As the `isStable` Observable runs outside the zone,
 * the `value` field will be updated properly,
 * but the template will not be refreshed!
 *
 * You'll have to manually trigger the change detection to update the template.
 *
 * ```
 * constructor(appRef: ApplicationRef, cd: ChangeDetectorRef) {
 *   appRef.isStable.pipe(
 *     first(stable => stable),
 *     switchMap(() => interval(1000))
 *   ).subscribe(counter => {
 *     this.value = counter;
 *     cd.detectChanges();
 *   });
 * }
 * ```
 *
 * Or make the subscription callback run inside the zone.
 *
 * ```
 * constructor(appRef: ApplicationRef, zone: NgZone) {
 *   appRef.isStable.pipe(
 *     first(stable => stable),
 *     switchMap(() => interval(1000))
 *   ).subscribe(counter => zone.run(() => this.value = counter));
 * }
 * ```
 *
 * @publicApi
 */


/**
 * @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 {InjectionToken} from '../di/injection_token';
import {getDocument} from '../render3/interfaces/document';

/**
 * A DI token representing a string ID, used
 * primarily for prefixing application attributes and CSS styles when
 * {@link ViewEncapsulation#Emulated} is being used.
 *
 * The token is needed in cases when multiple applications are bootstrapped on a page
 * (for example, using `bootstrapApplication` calls). In this case, ensure that those applications
 * have different `APP_ID` value setup. For example:
 *
 * ```
 * bootstrapApplication(ComponentA, {
 *   providers: [
 *     { provide: APP_ID, useValue: 'app-a' },
 *     // ... other providers ...
 *   ]
 * });
 *
 * bootstrapApplication(ComponentB, {
 *   providers: [
 *     { provide: APP_ID, useValue: 'app-b' },
 *     // ... other providers ...
 *   ]
 * });
 * ```
 *
 * By default, when there is only one application bootstrapped, you don't need to provide the
 * `APP_ID` token (the `ng` will be used as an app ID).
 *
 * @publicApi
 */
export const APP_ID = new InjectionToken<string>(ngDevMode ? 'AppId' : '', {
  providedIn: 'root',
  factory: () => DEFAULT_APP_ID,
});

/** Default value of the `APP_ID` token. */
const DEFAULT_APP_ID = 'ng';

/**
 * A function that is executed when a platform is initialized.
 *
 * @deprecated from v19.0.0, use providePlatformInitializer instead
 *
 * @see {@link providePlatformInitializer}
 *
 * @publicApi
 */
export const PLATFORM_INITIALIZER = new InjectionToken<ReadonlyArray<() => void>>(
  ngDevMode ? 'Platform Initializer' : '',
);

/**
 * A token that indicates an opaque platform ID.
 * @publicApi
 */
export const PLATFORM_ID = new InjectionToken<Object>(ngDevMode ? 'Platform ID' : '', {
  providedIn: 'platform',
  factory: () => 'unknown', // set a default platform name, when none set explicitly
});

/**
 * A DI token that indicates the root directory of
 * the application
 * @publicApi
 * @deprecated
 */
export const PACKAGE_ROOT_URL = new InjectionToken<string>(
  ngDevMode ? 'Application Packages Root URL' : '',
);

// We keep this token here, rather than the animations package, so that modules that only care
// about which animations module is loaded (e.g. the CDK) can retrieve it without having to
// include extra dependencies. See #44970 for more context.

/**
 * A [DI token](api/core/InjectionToken) that indicates which animations
 * module has been loaded.
 * @publicApi
 */
export const ANIMATION_MODULE_TYPE = new InjectionToken<'NoopAnimations' | 'BrowserAnimations'>(
  ngDevMode ? 'AnimationModuleType' : '',
);

// TODO(crisbeto): link to CSP guide here.
/**
 * Token used to configure the [Content Security Policy](https://web.dev/strict-csp/) nonce that
 * Angular will apply when inserting inline styles. If not provided, Angular will look up its value
 * from the `ngCspNonce` attribute of the application root node.
 *
 * @publicApi
 */
export const CSP_NONCE = new InjectionToken<string | null>(ngDevMode ? 'CSP nonce' : '', {
  providedIn: 'root',
  factory: () => {
    // Ideally we wouldn't have to use `querySelector` here since we know that the nonce will be on
    // the root node, but because the token value is used in renderers, it has to be available
    // *very* early in the bootstrapping process. This should be a fairly shallow search, because
    // the app won't have been added to the DOM yet. Some approaches that were considered:
    // 1. Find the root node through `ApplicationRef.components[i].location` - normally this would
    // be enough for our purposes, but the token is injected very early so the `components` array
    // isn't populated yet.
    // 2. Find the root `LView` through the current `LView` - renderers are a prerequisite to
    // creating the `LView`. This means that no `LView` will have been entered when this factory is
    // invoked for the root component.
    // 3. Have the token factory return `() => string` which is invoked when a nonce is requested -
    // the slightly later execution does allow us to get an `LView` reference, but the fact that
    // it is a function means that it could be executed at *any* time (including immediately) which
    // may lead to weird bugs.
    // 4. Have the `ComponentFactory` read the attribute and provide it to the injector under the
    // hood - has the same problem as #1 and #2 in that the renderer is used to query for the root
    // node and the nonce value needs to be available when the renderer is created.
    return getDocument().body?.querySelector('[ngCspNonce]')?.getAttribute('ngCspNonce') || null;
  },
});

/**
 * A configuration object for the image-related options. Contains:
 * - breakpoints: An array of integer breakpoints used to generate
 *      srcsets for responsive images.
 * - disableImageSizeWarning: A boolean value. Setting this to true will
 *      disable console warnings about oversized images.
 * - disableImageLazyLoadWarning: A boolean value. Setting this to true will
 *      disable console warnings about LCP images configured with `loading="lazy"`.
 * Learn more about the responsive image configuration in [the NgOptimizedImage
 * guide](guide/image-optimization).
 * Learn more about image warning options in [the related error page](errors/NG0913).
 * @publicApi
 */
export type ImageConfig = {
  breakpoints?: number[];
  placeholderResolution?: number;
  disableImageSizeWarning?: boolean;
  disableImageLazyLoadWarning?: boolean;
};

export const IMAGE_CONFIG_DEFAULTS: ImageConfig = {
  breakpoints: [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  placeholderResolution: 30,
  disableImageSizeWarning: false,
  disableImageLazyLoadWarning: false,
};

/**
 * Injection token that configures the image optimized image functionality.
 * See {@link ImageConfig} for additional information about parameters that
 * can be used.
 *
 * @see {@link NgOptimizedImage}
 * @see {@link ImageConfig}
 * @publicApi
 */
export const IMAGE_CONFIG = new InjectionToken<ImageConfig>(ngDevMode ? 'ImageConfig' : '', {
  providedIn: 'root',
  factory: () => IMAGE_CONFIG_DEFAULTS,
});


/**
 * @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 {Observable} from 'rxjs';

import {
  EnvironmentProviders,
  inject,
  Injectable,
  InjectionToken,
  Injector,
  makeEnvironmentProviders,
  runInInjectionContext,
} from '../di';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {isPromise, isSubscribable} from '../util/lang';

/**
 * A DI token that you can use to provide
 * one or more initialization functions.
 *
 * The provided functions are injected at application startup and executed during
 * app initialization. If any of these functions returns a Promise or an Observable, initialization
 * does not complete until the Promise is resolved or the Observable is completed.
 *
 * You can, for example, create a factory function that loads language data
 * or an external configuration, and provide that function to the `APP_INITIALIZER` token.
 * The function is executed during the application bootstrap process,
 * and the needed data is available on startup.
 *
 * Note that the provided initializer is run in the injection context.
 *
 * @deprecated from v19.0.0, use provideAppInitializer instead
 *
 * @see {@link ApplicationInitStatus}
 * @see {@link provideAppInitializer}
 *
 * @usageNotes
 *
 * The following example illustrates how to configure a multi-provider using `APP_INITIALIZER` token
 * and a function returning a promise.
 * ### Example with NgModule-based application
 * ```
 *  function initializeApp(): Promise<any> {
 *    const http = inject(HttpClient);
 *    return firstValueFrom(
 *      http
 *        .get("https://someUrl.com/api/user")
 *        .pipe(tap(user => { ... }))
 *    );
 *  }
 *
 *  @NgModule({
 *   imports: [BrowserModule],
 *   declarations: [AppComponent],
 *   bootstrap: [AppComponent],
 *   providers: [{
 *     provide: APP_INITIALIZER,
 *     useValue: initializeApp,
 *     multi: true,
 *    }]
 *   })
 *  export class AppModule {}
 * ```
 *
 * ### Example with standalone application
 * ```
 * function initializeApp() {
 *   const http = inject(HttpClient);
 *   return firstValueFrom(
 *     http
 *       .get("https://someUrl.com/api/user")
 *       .pipe(tap(user => { ... }))
 *   );
 * }
 *
 * bootstrapApplication(App, {
 *   providers: [
 *     provideHttpClient(),
 *     {
 *       provide: APP_INITIALIZER,
 *       useValue: initializeApp,
 *       multi: true,
 *     },
 *   ],
 * });

 * ```
 *
 *
 * It's also possible to configure a multi-provider using `APP_INITIALIZER` token and a function
 * returning an observable, see an example below. Note: the `HttpClient` in this example is used for
 * demo purposes to illustrate how the factory function can work with other providers available
 * through DI.
 *
 * ### Example with NgModule-based application
 * ```
 * function initializeApp() {
 *   const http = inject(HttpClient);
 *   return firstValueFrom(
 *     http
 *       .get("https://someUrl.com/api/user")
 *       .pipe(tap(user => { ... }))
 *   );
 * }
 *
 * @NgModule({
 *   imports: [BrowserModule, HttpClientModule],
 *   declarations: [AppComponent],
 *   bootstrap: [AppComponent],
 *   providers: [{
 *     provide: APP_INITIALIZER,
 *     useValue: initializeApp,
 *     multi: true,
 *   }]
 * })
 * export class AppModule {}
 * ```
 *
 * ### Example with standalone application
 * ```
 * function initializeApp() {
 *   const http = inject(HttpClient);
 *   return firstValueFrom(
 *     http
 *       .get("https://someUrl.com/api/user")
 *       .pipe(tap(user => { ... }))
 *   );
 * }
 *
 * bootstrapApplication(App, {
 *   providers: [
 *     provideHttpClient(),
 *     {
 *       provide: APP_INITIALIZER,
 *       useValue: initializeApp,
 *       multi: true,
 *     },
 *   ],
 * });
 * ```
 *
 * @publicApi
 */
export const APP_INITIALIZER = new InjectionToken<
  ReadonlyArray<() => Observable<unknown> | Promise<unknown> | void>
>(ngDevMode ? 'Application Initializer' : '');

/**
 * @description
 * The provided function is injected at application startup and executed during
 * app initialization. If the function returns a Promise or an Observable, initialization
 * does not complete until the Promise is resolved or the Observable is completed.
 *
 * You can, for example, create a function that loads language data
 * or an external configuration, and provide that function using `provideAppInitializer()`.
 * The function is executed during the application bootstrap process,
 * and the needed data is available on startup.
 *
 * Note that the provided initializer is run in the injection context.
 *
 * Previously, this was achieved using the `APP_INITIALIZER` token which is now deprecated.
 *
 * @see {@link APP_INITIALIZER}
 *
 * @usageNotes
 * The following example illustrates how to configure an initialization function using
 * `provideAppInitializer()`
 * ```
 * bootstrapApplication(App, {
 *   providers: [
 *     provideAppInitializer(() => {
 *       const http = inject(HttpClient);
 *       return firstValueFrom(
 *         http
 *           .get("https://someUrl.com/api/user")
 *           .pipe(tap(user => { ... }))
 *       );
 *     }),
 *     provideHttpClient(),
 *   ],
 * });
 * ```
 *
 * @publicApi
 */
export function provideAppInitializer(
  initializerFn: () => Observable<unknown> | Promise<unknown> | void,
): EnvironmentProviders {
  return makeEnvironmentProviders([
    {
      provide: APP_INITIALIZER,
      multi: true,
      useValue: initializerFn,
    },
  ]);
}

/**
 * A class that reflects the state of running {@link APP_INITIALIZER} functions.
 *
 * @publicApi
 */
@Injectable({providedIn: 'root'})
export class ApplicationInitStatus {
  // Using non null assertion, these fields are defined below
  // within the `new Promise` callback (synchronously).
  private resolve!: (...args: any[]) => void;
  private reject!: (...args: any[]) => void;

  private initialized = false;
  public readonly done = false;
  public readonly donePromise: Promise<any> = new Promise((res, rej) => {
    this.resolve = res;
    this.reject = rej;
  });

  private readonly appInits = inject(APP_INITIALIZER, {optional: true}) ?? [];
  private readonly injector = inject(Injector);

  constructor() {
    if ((typeof ngDevMode === 'undefined' || ngDevMode) && !Array.isArray(this.appInits)) {
      throw new RuntimeError(
        RuntimeErrorCode.INVALID_MULTI_PROVIDER,
        'Unexpected type of the `APP_INITIALIZER` token value ' +
          `(expected an array, but got ${typeof this.appInits}). ` +
          'Please check that the `APP_INITIALIZER` token is configured as a ' +
          '`multi: true` provider.',
      );
    }
  }

  /** @internal */
  runInitializers() {
    if (this.initialized) {
      return;
    }

    const asyncInitPromises = [];
    for (const appInits of this.appInits) {
      const initResult = runInInjectionContext(this.injector, appInits);
      if (isPromise(initResult)) {
        asyncInitPromises.push(initResult);
      } else if (isSubscribable(initResult)) {
        const observableAsPromise = new Promise<void>((resolve, reject) => {
          initResult.subscribe({complete: resolve, error: reject});
        });
        asyncInitPromises.push(observableAsPromise);
      }
    }

    const complete = () => {
      // @ts-expect-error overwriting a readonly
      this.done = true;
      this.resolve();
    };

    Promise.all(asyncInitPromises)
      .then(() => {
        complete();
      })
      .catch((e) => {
        this.reject(e);
      });

    if (asyncInitPromises.length === 0) {
      complete();
    }
    this.initialized = true;
  }
}


/**
 * @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 {EnvironmentProviders, ModuleWithProviders, Provider} from '../di/interface/provider';
import {Type} from '../interface/type';
import {SchemaMetadata} from '../metadata/schema';
import {compileNgModule} from '../render3/jit/module';
import {makeDecorator, TypeDecorator} from '../util/decorators';

/**
 * Type of the NgModule decorator / constructor function.
 *
 * @publicApi
 */
export interface NgModuleDecorator {
  /**
   * Decorator that marks a class as an NgModule and supplies configuration metadata.
   */
  (obj?: NgModule): TypeDecorator;
  new (obj?: NgModule): NgModule;
}

/**
 * Type of the NgModule metadata.
 *
 * @publicApi
 */
export interface NgModule {
  /**
   * The set of injectable objects that are available in the injector
   * of this module.
   *
   * @see [Dependency Injection guide](guide/di/dependency-injection
   * @see [NgModule guide](guide/ngmodules/providers)
   *
   * @usageNotes
   *
   * Dependencies whose providers are listed here become available for injection
   * into any component, directive, pipe or service that is a child of this injector.
   * The NgModule used for bootstrapping uses the root injector, and can provide dependencies
   * to any part of the app.
   *
   * A lazy-loaded module has its own injector, typically a child of the app root injector.
   * Lazy-loaded services are scoped to the lazy-loaded module's injector.
   * If a lazy-loaded module also provides the `UserService`, any component created
   * within that module's context (such as by router navigation) gets the local instance
   * of the service, not the instance in the root injector.
   * Components in external modules continue to receive the instance provided by their injectors.
   *
   * ### Example
   *
   * The following example defines a class that is injected in
   * the HelloWorld NgModule:
   *
   * ```
   * class Greeter {
   *    greet(name:string) {
   *      return 'Hello ' + name + '!';
   *    }
   * }
   *
   * @NgModule({
   *   providers: [
   *     Greeter
   *   ]
   * })
   * class HelloWorld {
   *   greeter:Greeter;
   *
   *   constructor(greeter:Greeter) {
   *     this.greeter = greeter;
   *   }
   * }
   * ```
   */
  providers?: Array<Provider | EnvironmentProviders>;

  /**
   * The set of components, directives, and pipes (declarables
   * that belong to this module.
   *
   * @usageNotes
   *
   * The set of selectors that are available to a template include those declared here, and
   * those that are exported from imported NgModules.
   *
   * Declarables must belong to exactly one module.
   * The compiler emits an error if you try to declare the same class in more than one module.
   * Be careful not to declare a class that is imported from another module.
   *
   * ### Example
   *
   * The following example allows the CommonModule to use the `NgFor`
   * directive.
   *
   * ```javascript
   * @NgModule({
   *   declarations: [NgFor]
   * })
   * class CommonModule {
   * }
   * ```
   */
  declarations?: Array<Type<any> | any[]>;

  /**
   * The set of NgModules whose exported declarables
   * are available to templates in this module.
   *
   * @usageNotes
   *
   * A template can use exported declarables from any
   * imported module, including those from modules that are imported indirectly
   * and re-exported.
   * For example, `ModuleA` imports `ModuleB`, and also exports
   * it, which makes the declarables from `ModuleB` available
   * wherever `ModuleA` is imported.
   *
   * ### Example
   *
   * The following example allows MainModule to use anything exported by
   * `CommonModule`:
   *
   * ```javascript
   * @NgModule({
   *   imports: [CommonModule]
   * })
   * class MainModule {
   * }
   * ```
   *
   */
  imports?: Array<Type<any> | ModuleWithProviders<{}> | any[]>;

  /**
   * The set of components, directives, and pipes declared in this
   * NgModule that can be used in the template of any component that is part of an
   * NgModule that imports this NgModule. Exported declarations are the module's public API.
   *
   * A declarable belongs to one and only one NgModule.
   * A module can list another module among its exports, in which case all of that module's
   * public declaration are exported.
   *
   * @usageNotes
   *
   * Declarations are private by default.
   * If this ModuleA does not export UserComponent, then only the components within this
   * ModuleA can use UserComponent.
   *
   * ModuleA can import ModuleB and also export it, making exports from ModuleB
   * available to an NgModule that imports ModuleA.
   *
   * ### Example
   *
   * The following example exports the `NgFor` directive from CommonModule.
   *
   * ```javascript
   * @NgModule({
   *   exports: [NgFor]
   * })
   * class CommonModule {
   * }
   * ```
   */
  exports?: Array<Type<any> | any[]>;

  /**
   * The set of components that are bootstrapped when this module is bootstrapped.
   */
  bootstrap?: Array<Type<any> | any[]>;

  /**
   * The set of schemas that declare elements to be allowed in the NgModule.
   * Elements and properties that are neither Angular components nor directives
   * must be declared in a schema.
   *
   * Allowed value are `NO_ERRORS_SCHEMA` and `CUSTOM_ELEMENTS_SCHEMA`.
   *
   * @security When using one of `NO_ERRORS_SCHEMA` or `CUSTOM_ELEMENTS_SCHEMA`
   * you must ensure that allowed elements and properties securely escape inputs.
   */
  schemas?: Array<SchemaMetadata | any[]>;

  /**
   * A name or path that uniquely identifies this NgModule in `getNgModuleById`.
   * If left `undefined`, the NgModule is not registered with `getNgModuleById`.
   */
  id?: string;

  /**
   * When present, this module is ignored by the AOT compiler.
   * It remains in distributed code, and the JIT compiler attempts to compile it
   * at run time, in the browser.
   * To ensure the correct behavior, the app must import `@angular/compiler`.
   */
  jit?: true;
}

/**
 * @Annotation
 */
export const NgModule: NgModuleDecorator = makeDecorator(
  'NgModule',
  (ngModule: NgModule) => ngModule,
  undefined,
  undefined,
  /**
   * Decorator that marks the following class as an NgModule, and supplies
   * configuration metadata for it.
   *
   * * The `declarations` option configures the compiler
   * with information about what belongs to the NgModule.
   * * The `providers` options configures the NgModule's injector to provide
   * dependencies the NgModule members.
   * * The `imports` and `exports` options bring in members from other modules, and make
   * this module's members available to others.
   */
  (type: Type<any>, meta: NgModule) => compileNgModule(type, meta),
);


export interface Component extends Directive {
  /**
   * The change-detection strategy to use for this component.
   *
   * When a component is instantiated, Angular creates a change detector,
   * which is responsible for propagating the component's bindings.
   * The strategy is one of:
   * - `ChangeDetectionStrategy#OnPush` sets the strategy to `CheckOnce` (on demand).
   * - `ChangeDetectionStrategy#Default` sets the strategy to `CheckAlways`.
   */
  changeDetection?: ChangeDetectionStrategy;

  /**
   * Defines the set of injectable objects that are visible to its view DOM children.
   * See [example](#injecting-a-class-with-a-view-provider).
   *
   */
  viewProviders?: Provider[];

  /**
   * The module ID of the module that contains the component.
   * The component must be able to resolve relative URLs for templates and styles.
   * SystemJS exposes the `__moduleName` variable within each module.
   * In CommonJS, this can  be set to `module.id`.
   *
   * @deprecated This option does not have any effect. Will be removed in Angular v17.
   */
  moduleId?: string;

  /**
   * The relative path or absolute URL of a template file for an Angular component.
   * If provided, do not supply an inline template using `template`.
   *
   */
  templateUrl?: string;

  /**
   * An inline template for an Angular component. If provided,
   * do not supply a template file using `templateUrl`.
   *
   */
  template?: string;

  /**
   * One relative paths or an absolute URL for files containing CSS stylesheet to use
   * in this component.
   */
  styleUrl?: string;

  /**
   * Relative paths or absolute URLs for files containing CSS stylesheets to use in this component.
   */
  styleUrls?: string[];

  /**
   * One or more inline CSS stylesheets to use
   * in this component.
   */
  styles?: string | string[];

  /**
   * One or more animation `trigger()` calls, containing
   * [`state()`](api/animations/state) and `transition()` definitions.
   * See the [Animations guide](guide/animations) and animations API documentation.
   *
   */
  animations?: any[];

  /**
   * An encapsulation policy for the component's styling.
   * Possible values:
   * - `ViewEncapsulation.Emulated`: Apply modified component styles in order to emulate
   *                                 a native Shadow DOM CSS encapsulation behavior.
   * - `ViewEncapsulation.None`: Apply component styles globally without any sort of encapsulation.
   * - `ViewEncapsulation.ShadowDom`: Use the browser's native Shadow DOM API to encapsulate styles.
   *
   * If not supplied, the value is taken from the `CompilerOptions`
   * which defaults to `ViewEncapsulation.Emulated`.
   *
   * If the policy is `ViewEncapsulation.Emulated` and the component has no
   * {@link Component#styles styles} nor {@link Component#styleUrls styleUrls},
   * the policy is automatically switched to `ViewEncapsulation.None`.
   */
  encapsulation?: ViewEncapsulation;

  /**
   * Overrides the default interpolation start and end delimiters (`{{` and `}}`).
   *
   * @deprecated use Angular's default interpolation delimiters instead.
   */
  interpolation?: [string, string];

  /**
   * True to preserve or false to remove potentially superfluous whitespace characters
   * from the compiled template. Whitespace characters are those matching the `\s`
   * character class in JavaScript regular expressions. Default is false, unless
   * overridden in compiler options.
   */
  preserveWhitespaces?: boolean;

  /**
   * Angular components marked as `standalone` do not need to be declared in an NgModule. Such
   * components directly manage their own template dependencies (components, directives, and pipes
   * used in a template) via the imports property.
   *
   * More information about standalone components, directives, and pipes can be found in [this
   * guide](guide/components/importing).
   */
  standalone?: boolean;

  /**
   * The imports property specifies the standalone component's template dependencies — those
   * directives, components, and pipes that can be used within its template. Standalone components
   * can import other standalone components, directives, and pipes as well as existing NgModules.
   *
   * This property is only available for standalone components - specifying it for components
   * declared in an NgModule generates a compilation error.
   *
   * More information about standalone components, directives, and pipes can be found in [this
   * guide](guide/components/importing).
   */
  imports?: (Type<any> | ReadonlyArray<any>)[];

  /**
   * The `deferredImports` property specifies a standalone component's template dependencies,
   * which should be defer-loaded as a part of the `@defer` block. Angular *always* generates
   * dynamic imports for such symbols and removes the regular/eager import. Make sure that imports
   * which bring symbols used in the `deferredImports` don't contain other symbols.
   *
   * Note: this is an internal-only field, use regular `@Component.imports` field instead.
   * @internal
   */
  deferredImports?: (Type<any> | ReadonlyArray<any>)[];

  /**
   * The set of schemas that declare elements to be allowed in a standalone component. Elements and
   * properties that are neither Angular components nor directives must be declared in a schema.
   *
   * This property is only available for standalone components - specifying it for components
   * declared in an NgModule generates a compilation error.
   *
   * More information about standalone components, directives, and pipes can be found in [this
   * guide](guide/components/importing).
   */
  schemas?: SchemaMetadata[];
}

/**
 * Component decorator and metadata.
 *
 * @Annotation
 * @publicApi
 */
export const Component: ComponentDecorator = makeDecorator(
  'Component',
  (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
  Directive,
  undefined,
  (type: Type<any>, meta: Component) => compileComponent(type, meta),
);

/**
 * Type of the Pipe decorator / constructor function.
 *
 * @publicApi
 */
export interface PipeDecorator {
  /**
   *
   * Decorator that marks a class as pipe and supplies configuration metadata.
   *
   * A pipe class must implement the `PipeTransform` interface.
   * For example, if the name is "myPipe", use a template binding expression
   * such as the following:
   *
   * ```
   * {{ exp | myPipe }}
   * ```
   *
   * The result of the expression is passed to the pipe's `transform()` method.
   *
   * A pipe must belong to an NgModule in order for it to be available
   * to a template. To make it a member of an NgModule,
   * list it in the `declarations` field of the `NgModule` metadata.
   *
   * @see [Style Guide: Pipe Names](style-guide#02-09)
   *
   */
  (obj: Pipe): TypeDecorator;

  /**
   * See the `Pipe` decorator.
   */
  new (obj: Pipe): Pipe;
}

/**
 * Type of the Pipe metadata.
 *
 * @publicApi
 */
export interface Pipe {
  /**
   * The pipe name to use in template bindings.
   * Typically uses lowerCamelCase
   * because the name cannot contain hyphens.
   */
  name: string;

  /**
   * When true, the pipe is pure, meaning that the
   * `transform()` method is invoked only when its input arguments
   * change. Pipes are pure by default.
   *
   * If the pipe has internal state (that is, the result
   * depends on state other than its arguments), set `pure` to false.
   * In this case, the pipe is invoked on each change-detection cycle,
   * even if the arguments have not changed.
   */
  pure?: boolean;

  /**
   * Angular pipes marked as `standalone` do not need to be declared in an NgModule. Such
   * pipes don't depend on any "intermediate context" of an NgModule (ex. configured providers).
   *
   * More information about standalone components, directives, and pipes can be found in [this
   * guide](guide/components/importing).
   */
  standalone?: boolean;
}

/**
 * @Annotation
 * @publicApi
 */
export const Pipe: PipeDecorator = makeDecorator(
  'Pipe',
  (p: Pipe) => ({pure: true, ...p}),
  undefined,
  undefined,
  (type: Type<any>, meta: Pipe) => compilePipe(type, meta),
);

/**
 * @publicApi
 */
e


Supports delivery of Angular apps on a server, for use with server-side rendering.

For more information, see [Server-side Rendering: An intro to Angular SSR](guide/ssr).


cribe('Router', () => {
      it('should wait for lazy routes before serializing', async () => {
        const ngZone = TestBed.inject(NgZone);

        @Component({
          standalone: true,
          selector: 'lazy',
          template: `LazyCmp content`,
        })
        class LazyCmp {}

        const routes: Routes = [
          {
            path: '',
            loadComponent: () => {
              return ngZone.runOutsideAngular(() => {
                return new Promise((resolve) => {
                  setTimeout(() => resolve(LazyCmp), 100);
                });
              });
            },
          },
        ];

        @Component({
          standalone: false,
          selector: 'app',
          template: `
          Works!
          <router-outlet />
        `,
        })
        class MyServerApp {}

        @NgModule({
          declarations: [MyServerApp],
          exports: [MyServerApp],
          imports: [BrowserModule, ServerModule, RouterOutlet],
          providers: [provideRouter(routes)],
          bootstrap: [MyServerApp],
        })
        class MyServerAppModule {}

        const options = {document: '<app></app>'};
        const output = await renderModule(MyServerAppModule, options);

        // Expect serialization to happen once a lazy-loaded route completes loading
        // and a lazy component is rendered.
        expect(output).toContain('<lazy>LazyCmp content</lazy>');
      });
    });

    describe('HttpClient', () => {
      it('can inject HttpClient', async () => {
        const platform = platformServer([
          {provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}},
        ]);
        const ref = await platform.bootstrapModule(HttpClientExampleModule);
        expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy();
      });

      it('can make HttpClient requests', async () => {
        const platform = platformServer([
          {provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}},
        ]);
        await platform.bootstrapModule(HttpClientExampleModule).then((ref) => {
          const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
          const http = ref.injector.get(HttpClient);
          ref.injector.get<NgZone>(NgZone).run(() => {
            http.get<string>('http://localhost/testing').subscribe((body: string) => {
              NgZone.assertInAngularZone();
              expect(body).toEqual('success!');
            });
            mock.expectOne('http://localhost/testing').flush('success!');
          });
        });
      });

      it('can use HttpInterceptor that injects HttpClient', async () => {
        const platform = platformServer([
          {provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}},
        ]);
        await platform.bootstrapModule(HttpInterceptorExampleModule).then((ref) => {
          const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
          const http = ref.injector.get(HttpClient);
          ref.injector.get(NgZone).run(() => {
            http.get<string>('http://localhost/testing').subscribe((body: string) => {
              NgZone.assertInAngularZone();
              expect(body).toEqual('success!');
            });
            mock.expectOne('http://localhost/testing').flush('success!');
          });
        });
      });

      describe(`given 'url' is provided in 'INITIAL_CONFIG'`, () => {
        let mock: HttpTestingController;
        let ref: NgModuleRef<HttpInterceptorExampleModule>;
        let http: HttpClient;

        beforeEach(async () => {
          const platform = platformServer([
            {
              provide: INITIAL_CONFIG,
              useValue: {document: '<app></app>', url: 'http://localhost:4000/foo'},
            },
          ]);

          ref = await platform.bootstrapModule(HttpInterceptorExampleModule);
          mock = ref.injector.get(HttpTestingController);
          http = ref.injector.get(HttpClient);
        });

        it('should resolve relative request URLs to absolute', async () => {
          ref.injector.get(NgZone).run(() => {
            http.get('/testing').subscribe((body) => {
              NgZone.assertInAngularZone();
              expect(body).toEqual('success!');
            });
            mock.expectOne('http://localhost:4000/testing').flush('success!');
          });
        });

        it(`should not replace the baseUrl of a request when it's absolute`, async () => {
          ref.injector.get(NgZone).run(() => {
            http.get('http://localhost/testing').subscribe((body) => {
              NgZone.assertInAngularZone();
              expect(body).toEqual('success!');
            });
            mock.expectOne('http://localhost/testing').flush('success!');
          });
        });
      });
    });
  });
})();


escribe('ViewContainerRef', () => {
          it('should work with ViewContainerRef.createComponent', async () => {
            @Component({
              standalone: true,
              selector: 'dynamic',
              template: `
                This is a content of a dynamic component.
              `,
            })
            class DynamicComponent {}

            @Component({
              standalone: true,
              selector: 'app',
              imports: [NgIf, NgFor],
              template: `

                Hi! This is the main content.
              `,
            })
            class SimpleComponent {
              @ViewChild('target', {read: ViewContainerRef}) vcr!: ViewContainerRef;

              ngAfterViewInit() {
                const compRef = this.vcr.createComponent(DynamicComponent);
                compRef.changeDetectorRef.detectChanges();
              }
            }

            const html = await ssr(SimpleComponent);
            const ssrContents = getAppContents(html);

            expect(ssrContents).toContain('<app ngh');

            resetTViewsFor(SimpleComponent, DynamicComponent);

            const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent);
            const compRef = getComponentRef<SimpleComponent>(appRef);
            appRef.tick();

            const clientRootNode = compRef.location.nativeElement;
            verifyAllNodesClaimedForHydration(clientRootNode);
            verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
          });

          it('should work with ViewContainerRef.createEmbeddedView', async () => {
            @Component({
              standalone: true,
              selector: 'app',
              imports: [NgIf, NgFor],
              template: `
                <ng-template #tmpl>

This is a content of an ng-template.

                </ng-template>
                <ng-container #target></ng-container>
              `,
            })
            class SimpleComponent {
              @ViewChild('target', {read: ViewContainerRef}) vcr!: ViewContainerRef;
              @ViewChild('tmpl', {read: TemplateRef}) tmpl!: TemplateRef<unknown>;

              ngAfterViewInit() {
                const viewRef = this.vcr.createEmbeddedView(this.tmpl);
                viewRef.detectChanges();
              }
            }

            const html = await ssr(SimpleComponent);
            const ssrContents = getAppContents(html);

            expect(ssrContents).toContain('<app ngh');

            resetTViewsFor(SimpleComponent);

            const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent);
            const compRef = getComponentRef<SimpleComponent>(appRef);
            appRef.tick();

            const clientRootNode = compRef.location.nativeElement;
            verifyAllNodesClaimedForHydration(clientRootNode);
            verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
          });

          it('should hydrate dynamically created components using root component as an anchor', async () => {
            @Component({
              standalone: true,
              imports: [CommonModule],
              selector: 'dynamic',
              template: `
                    This is a content of a dynamic component.
                  `,
            })
            class DynamicComponent {}

            @Component({
              standalone: true,
              selector: 'app',
              template: `
                    Hi! This is the main content.
                  `,
            })
            class SimpleComponent {
              vcr = inject(ViewContainerRef);

              ngAfterViewInit() {
                const compRef = this.vcr.createComponent(DynamicComponent);
                compRef.changeDetectorRef.detectChanges();
              }
            }

            const html = await ssr(SimpleComponent);
            const ssrContents = getAppContents(html);

            expect(ssrContents).toContain('<app ngh');

            resetTViewsFor(SimpleComponent, DynamicComponent);

            const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent);
            const compRef = getComponentRef<SimpleComponent>(appRef);
            appRef.tick();

            // Compare output starting from the parent node above the component node,
            // because component host node also acted as a ViewContainerRef anchor,
            // thus there are elements after this node (as next siblings).
            const clientRootNode = compRef.location.nativeElement.parentNode;
            await whenStable(appRef);

            verifyAllChildNodesClaimedForHydration(clientRootNode);
            verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
          });

          it('should hydrate embedded views when using root component as an anchor', async () => {
            @Component({
              standalone: true,
              selector: 'app',
              template: `
                <ng-template #tmpl>

Content of embedded view

                </ng-template>
                Hi! This is the main content.
              `,
            })
            class SimpleComponent {
              @ViewChild('tmpl', {read: TemplateRef}) tmpl!: TemplateRef<unknown>;

              vcr = inject(ViewContainerRef);

              ngAfterViewInit() {
                const viewRef = this.vcr.createEmbeddedView(this.tmpl);
                viewRef.detectChanges();
              }
            }

            const html = await ssr(SimpleComponent);
            const ssrContents = getAppContents(html);

            expect(ssrContents).toContain('<app ngh');

            resetTViewsFor(SimpleComponent);

            const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent);
            const compRef = getComponentRef<SimpleComponent>(appRef);
            appRef.tick();

            // Compare output starting from the parent node above the component node,
            // because component host node also acted as a ViewContainerRef anchor,
            // thus there are elements after this node (as next siblings).
            const clientRootNode = compRef.location.nativeElement.parentNode;
            await whenStable(appRef);

            verifyAllChildNodesClaimedForHydration(clientRootNode);
            verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
          });


t('should SSR and hydrate nested `@defer` blocks', async () => {
      @Component({
        standalone: true,
        selector: 'app',
        template: `

            @defer (on viewport; hydrate on interaction) {

                Main defer block rendered!
                @if (visible) {
                  Defer events work!
                }

                @defer (on viewport; hydrate on interaction) {

Nested defer block

                } @placeholder {
                  Inner block placeholder
                }

            } @placeholder {
              Outer block placeholder
            }

        `,
      })
      class SimpleComponent {
        items = [1, 2, 3];
        visible = false;
        fnA() {}
        showMessage() {
          this.visible = true;
        }
      }

      const appId = 'custom-app-id';
      const providers = [{provide: APP_ID, useValue: appId}];
      const hydrationFeatures = [withIncrementalHydration()];

      const html = await ssr(SimpleComponent, {envProviders: providers, hydrationFeatures});
      const ssrContents = getAppContents(html);

      // Assert that we have `jsaction` annotations and
      // defer blocks are triggered and rendered.

      //  uses "eager" `custom-app-id` namespace.
      expect(ssrContents).toContain('<main jsaction="click:;');
      //
s inside a defer block have `d0` as a namespace.
      expect(ssrContents).toContain('<div jsaction="click:;keydown:;" ngb="d0"');
      expect(ssrContents).toContain(' different namespace.
      expect(ssrContents).toContain('<p jsaction="click:;keydown:;" ngb="d1');
      // There is an extra annotation in the TransferState data.
      expect(ssrContents).toContain(
        '"__nghDeferData__":{"d0":{"p":null,"r":1,"s":2,"t":[]},"d1":{"p":"d0","r":1,"s":2,"t":[]}}',
      );
      // Outer defer block is rendered.
      expect(ssrContents).toContain('Main defer block rendered');
      // Inner defer block is rendered as well.
      expect(ssrContents).toContain('Nested defer block');

      // Internal cleanup before we do server->client transition in this test.
      resetTViewsFor(SimpleComponent);

      ////////////////////////////////
      const doc = getDocument();

      const appRef = await prepareEnvironmentAndHydrate(doc, html, SimpleComponent, {
        envProviders: [...providers, {provide: PLATFORM_ID, useValue: 'browser'}],
        hydrationFeatures,
      });
      const compRef = getComponentRef<SimpleComponent>(appRef);
      appRef.tick();
      await whenStable(appRef);

      const appHostNode = compRef.location.nativeElement;

      // At this point an eager part of an app is hydrated,
      // but defer blocks are still in dehydrated state.

      //  no longer has `jsaction` attribute.
      expect(appHostNode.outerHTML).toContain('');

      // Elements from @defer blocks still have `jsaction` annotations,
      // since they were not triggered yet.
      expect(appHostNode.outerHTML).toContain('<div jsaction="click:;keydown:;" ngb="d0"');
      expect(appHostNode.outerHTML).toContain(
        '<div id="outer-trigger" jsaction="mouseover:;" ngb="d0',
      );
      expect(appHostNode.outerHTML).toContain('<p jsaction="click:;keydown:;" ngb="d1"');

      // Emit an event inside of a defer block, which should result
      // in triggering the defer block (start loading deps, etc) and
      // subsequent hydration.
      const inner = doc.body.querySelector('p')!;
      const clickEvent = new CustomEvent('click', {bubbles: true});
      inner.dispatchEvent(clickEvent);

      await timeout(1000); // wait for defer blocks to resolve

      appRef.tick();

      // An event was replayed after hydration, which resulted in
      // an `@if` block becoming active and its inner content got
      // rendered/
      expect(appHostNode.outerHTML).toContain('Defer events work');

      // Since inner `@defer` block was triggered, all parent blocks
      // were hydrated as well, so all `jsaction` attributes are removed.
      expect(appHostNode.outerHTML).not.toContain('jsaction="');
    }, 100_000);


tes diagnostic when the library does not export the host directive', () => {
    const files = {
      // export post module and component but not the host directive. This is not valid. We won't
      // be able to import the host directive for template type checking.
      'dist/post/index.d.ts': `
      export { PostComponent, PostModule } from './lib/post.component';
    `,
      'dist/post/lib/post.component.d.ts': `
      import * as i0 from "@angular/core";
      export declare class HostBindDirective {
          static ɵdir: i0.ɵɵDirectiveDeclaration<HostBindDirective, never, never, {}, {}, never, never, true, never>;
      }
      export declare class PostComponent {
          static ɵcmp: i0.ɵɵComponentDeclaration<PostComponent, "lib-post", never, {}, {}, never, never, false, [{ directive: typeof HostBindDirective; inputs: {}; outputs: {}; }]>;
      }
      export declare class PostModule {
          static ɵmod: i0.ɵɵNgModuleDeclaration<PostModule, [typeof PostComponent], never, [typeof PostComponent]>;
          static ɵinj: i0.ɵɵInjectorDeclaration<PostModule>;
      }
      `,
      'test.ts': `
      import {Component} from '@angular/core';
      import {PostModule} from 'post';

      @Component({
        templateUrl: './test.ng.html',
        imports: [PostModule],
        standalone: true,
      })
      export class Main { }
       `,
      'test.ng.html': '<lib-post />',
    };

    const tsCompilerOptions = {paths: {'post': ['dist/post']}};
    const project = env.addProject('test', files, {}, tsCompilerOptions);

    const diags = project.getDiagnosticsForFile('test.ng.html');
    expect(diags.length).toBe(1);
    expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '')).toContain(
      'HostBindDirective',
    );
  });
});

function getTextOfDiagnostic(diag: ts.Diagnostic): string {
  expect(diag.file).not.toBeUndefined();
  expect(diag.start).not.toBeUndefined();
  expect(diag.length).not.toBeUndefined();
  return diag.file!.text.substring(diag.start!, diag.start! + diag.length!);
}


friends', () => {
        it('defer', () => {
          expectQuickInfo({
            templateOverride: `@de¦fer { } @placeholder { <input /> }`,
            expectedSpanText: '@defer ',
            expectedDisplayString: '(block) @defer',
          });
        });

        it('defer with condition', () => {
          expectQuickInfo({
            templateOverride: `@de¦fer (on immediate) { } @placeholder { <input /> }`,
            expectedSpanText: '@defer ',
            expectedDisplayString: '(block) @defer',
          });
        });

        it('placeholder', () => {
          expectQuickInfo({
            templateOverride: `@defer { } @pla¦ceholder { <input /> }`,
            expectedSpanText: '@placeholder ',
            expectedDisplayString: '(block) @placeholder',
          });
        });

        it('loading', () => {
          expectQuickInfo({
            templateOverride: `@defer { } @loadin¦g { <input /> }`,
            expectedSpanText: '@loading ',
            expectedDisplayString: '(block) @loading',
          });
        });

        it('error', () => {
          expectQuickInfo({
            templateOverride: `@defer { } @erro¦r { <input /> }`,
            expectedSpanText: '@error ',
            expectedDisplayString: '(block) @error',
          });
        });

        describe('triggers', () => {
          it('viewport', () => {
            expectQuickInfo({
              templateOverride: `@defer (on vie¦wport(x)) { } <div #x></div>`,
              expectedSpanText: 'viewport',
              expectedDisplayString: '(trigger) viewport',
            });
          });

          it('immediate', () => {
            expectQuickInfo({
              templateOverride: `@defer (on imme¦diate) {}`,
              expectedSpanText: 'immediate',
              expectedDisplayString: '(trigger) immediate',
            });
          });

          it('idle', () => {
            expectQuickInfo({
              templateOverride: `@defer (on i¦dle) { } `,
              expectedSpanText: 'idle',
              expectedDisplayString: '(trigger) idle',
            });
          });

          it('hover', () => {
            expectQuickInfo({
              templateOverride: `@defer (on hov¦er(x)) { } <div #x></div> `,
              expectedSpanText: 'hover',
              expectedDisplayString: '(trigger) hover',
            });
          });

          it('timer', () => {
            expectQuickInfo({
              templateOverride: `@defer (on tim¦er(100)) { } `,
              expectedSpanText: 'timer',
              expectedDisplayString: '(trigger) timer',
            });
          });

          it('interaction', () => {
            expectQuickInfo({
              templateOverride: `@defer (on interactio¦n(x)) { } <div #x></div>`,
              expectedSpanText: 'interaction',
              expectedDisplayString: '(trigger) interaction',
            });
          });

          it('when', () => {
            expectQuickInfo({
              templateOverride: `@defer (whe¦n title) { } <div #x></div>`,
              expectedSpanText: 'when',
              expectedDisplayString: '(keyword) when',
            });
          });

          it('prefetch (when)', () => {
            expectQuickInfo({
              templateOverride: `@defer (prefet¦ch when title) { }`,
              expectedSpanText: 'prefetch',
              expectedDisplayString: '(keyword) prefetch',
            });
          });

          it('hydrate (when)', () => {
            expectQuickInfo({
              templateOverride: `@defer (hydra¦te when title) { }`,
              expectedSpanText: 'hydrate',
              expectedDisplayString: '(keyword) hydrate',
            });
          });

          it('on', () => {
            expectQuickInfo({
              templateOverride: `@defer (o¦n immediate) { } `,
              expectedSpanText: 'on',
              expectedDisplayString: '(keyword) on',
            });
          });

          it('prefetch (on)', () => {
            expectQuickInfo({
              templateOverride: `@defer (prefet¦ch on immediate) { }`,
              expectedSpanText: 'prefetch',
              expectedDisplayString: '(keyword) prefetch',
            });
          });

          it('hydrate (on)', () => {
            expectQuickInfo({
              templateOverride: `@defer (hydra¦te on immediate) { }`,
              expectedSpanText: 'hydrate',
              expectedDisplayString: '(keyword) hydrate',
            });
          });
        });
      });

      it('empty', () => {
        expectQuickInfo({
          templateOverride: `@for (name of constNames; track $index) {} @em¦pty {}`,
          expectedSpanText: '@empty ',
          expectedDisplayString: '(block) @empty',
        });
      });

      it('track keyword', () => {
        expectQuickInfo({
          templateOverride: `@for (name of constNames; tr¦ack $index) {}`,
          expectedSpanText: 'track',
          expectedDisplayString: '(keyword) track',
        });
      });

      it('implicit variable assignment', () => {
        expectQuickInfo({
          templateOverride: `@for (name of constNames; track $index; let od¦d = $odd) {}`,
          expectedSpanText: 'odd',
          expectedDisplayString: '(variable) odd: boolean',
        });
      });

      it('implicit variable assignment in comma separated list', () => {
        expectQuickInfo({
          templateOverride: `@for (name of constNames; track index; let odd = $odd,  ind¦ex  =   $index) {}`,
          expectedSpanText: 'index',
          expectedDisplayString: '(variable) index: number',
        });
      });

      it('if block alias variable', () => {
        expectQuickInfo({
          templateOverride: `@if (constNames; as al¦iasName) {}`,
          expectedSpanText: 'aliasName',
          expectedDisplayString: '(variable) aliasName: [{ readonly name: "name"; }]',
        });
      });

      it('if block alias variable', () => {
        expectQuickInfo({
          templateOverride: `@if (someObject.some¦Signal(); as aliasName) {}`,
          expectedSpanText: 'someSignal',
          expectedDisplayString: '(property) someSignal: WritableSignal\n() => number',
        });
      });
    });

    describe('let declarations', () => {
      it('should get quick info for a let declaration', () => {
        expectQuickInfo({
          templateOverride: `@let na¦me = 'Frodo'; {{name}}`,
          expectedSpanText: `@let name = 'Frodo'`,
          expectedDisplayString: `(let) name: "Frodo"`,
        });
      });
    });

    it('should work for object literal with shorthand property declarations', () => {
      initMockFileSystem('Native');
      env = LanguageServiceTestEnv.setup();
      project = env.addProject(
        'test',
        {
          'app.ts': `
            import {Component, NgModule} from '@angular/core';
            import {CommonModule} from '@angular/common';

            @Component({
              selector: 'some-cmp',
              templateUrl: './app.html',
              standalone: false,
            })
            export class SomeCmp {
              val1 = 'one';
              val2 = 2;

              doSomething(obj: {val1: string, val2: number}) {}
            }

            @NgModule({
              declarations: [SomeCmp],
              imports: [CommonModule],
            })
            export class AppModule{
            }
          `,
          'app.html': `{{doSomething({val1, val2})}}`,
        },
        {strictTemplates: true},
      );
      env.expectNoSourceDiagnostics();
      project.expectNoSourceDiagnostics();

      const template = project.openFile('app.html');
      template.moveCursorToText('val¦1');
      const quickInfo = template.getQuickInfoAtPosition();
      expect(toText(quickInfo!.displayParts)).toEqual('(property) SomeCmp.val1: string');
      template.moveCursorToText('val¦2');
      const quickInfo2 = template.getQuickInfoAtPosition();
      expect(toText(quickInfo2!.displayParts)).toEqual('(property) SomeCmp.val2: number');
    });
  });

  describe('generics', () => {
    beforeEach(() => {
      initMockFileSys


### Disclaimer

The localize tools are consumed via the Angular CLI. The programmatic APIs are not considered officially
supported though and are not subject to the breaking change guarantees of SemVer.


# @angular/localize schematic for `ng add`

This schematic will be executed when an Angular CLI user runs `ng add @angular/localize`.

It will search their `angular.json` file, and add `types: ["@angular/localize"]` in the TypeScript
configuration files of the project.

If the user specifies that they want to use `$localize` at runtime then the dependency will be
added to the `dependencies` section of `package.json` rather than in the `devDependencies` which
is the default.


/**
 * @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 {EmptyTree, Tree} from '@angular-devkit/schematics';
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
import {runfiles} from '@bazel/runfiles';
import ts from 'typescript';

interface TsConfig {
  compilerOptions?: ts.CompilerOptions;
}

describe('ng-add schematic', () => {
  const localizeTripleSlashType = `/// <reference types="@angular/localize" />`;

  const defaultOptions = {project: 'demo'};
  const schematicRunner = new SchematicTestRunner(
    '@angular/localize',
    runfiles.resolvePackageRelative('../collection.json'),
  );
  let host: Tree;

  beforeEach(() => {
    host = new EmptyTree();

    host.create(
      'package.json',
      JSON.stringify({
        'devDependencies': {
          // The default (according to `ng-add` in its package.json) is for `@angular/localize` to be
          // saved to `devDependencies`.
          '@angular/localize': '~0.0.0-PLACEHOLDER',
        },
      }),
    );

    host.create(
      'main.ts',
      `
      import { enableProdMode } from '@angular/core';
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    `,
    );

    host.create(
      'angular.json',
      JSON.stringify({
        version: 1,
        projects: {
          'demo': {
            root: '',
            architect: {
              application: {
                builder: '@angular-devkit/build-angular:application',
                options: {
                  browser: './main.ts',
                  tsConfig: './tsconfig.application.json',
                  polyfills: ['zone.js'],
                },
              },
              build: {
                builder: '@angular-devkit/build-angular:browser',
                options: {
                  main: './main.ts',
                  tsConfig: './tsconfig.app.json',
                },
              },
              test: {
                builder: '@angular-devkit/build-angular:karma',
                options: {
                  tsConfig: './tsconfig.spec.json',
                  polyfills: 'zone.js',
                },
              },
              server: {
                builder: '@angular-devkit/build-angular:server',
                options: {
                  tsConfig: './tsconfig.server.json',
                },
              },
              unknown: {
                builder: '@custom-builder/build-angular:unknown',
                options: {
                  tsConfig: './tsconfig.unknown.json',
                },
              },
            },
          },
        },
      }),
    );
  });

  it(`should add '@angular/localize' type reference in 'main.ts'`, async () => {
    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    expect(host.readText('main.ts')).toContain(localizeTripleSlashType);
  });

  it(`should not add '@angular/localize' type reference in 'main.ts' if already present`, async () => {
    const mainContentInput = `
      ${localizeTripleSlashType}
      import { enableProdMode } from '@angular/core';
    `;
    host.overwrite('main.ts', mainContentInput);
    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    expect(host.readText('main.ts')).toBe(mainContentInput);
  });

  it(`should not add '@angular/localize' in 'types' tsconfigs referenced in non official builders`, async () => {
    const tsConfig = JSON.stringify({
      compilerOptions: {
        types: ['node'],
      },
    });

    host.create('tsconfig.unknown.json', tsConfig);

    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const {compilerOptions} = host.readJson('tsconfig.unknown.json') as TsConfig;
    const types = compilerOptions?.types;
    expect(types).not.toContain('@angular/localize');
    expect(types).toHaveSize(1);
  });

  it(`should add '@angular/localize' in 'types' tsconfigs referenced in browser builder`, async () => {
    const tsConfig = JSON.stringify({
      compilerOptions: {
        types: ['node'],
      },
    });

    host.create('tsconfig.app.json', tsConfig);

    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const {compilerOptions} = host.readJson('tsconfig.app.json') as TsConfig;
    const types = compilerOptions?.types;
    expect(types).toContain('@angular/localize');
    expect(types).toHaveSize(2);
  });

  it(`should add '@angular/localize' in 'types' tsconfigs referenced in application builder`, async () => {
    const tsConfig = JSON.stringify({
      compilerOptions: {
        types: ['node'],
      },
    });

    host.create('tsconfig.application.json', tsConfig);

    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const {compilerOptions} = host.readJson('tsconfig.application.json') as TsConfig;
    const types = compilerOptions?.types;
    expect(types).toContain('@angular/localize');
    expect(types).toHaveSize(2);
  });

  it(`should add '@angular/localize/init' in 'polyfills' in application builder`, async () => {
    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const workspace = host.readJson('angular.json') as any;
    const polyfills = workspace.projects['demo'].architect.application.options.polyfills;
    expect(polyfills).toEqual(['zone.js', '@angular/localize/init']);
  });

  it(`should add '@angular/localize/init' in 'polyfills' in karma builder`, async () => {
    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const workspace = host.readJson('angular.json') as any;
    const polyfills = workspace.projects['demo'].architect.test.options.polyfills;
    expect(polyfills).toEqual(['zone.js', '@angular/localize/init']);
  });

  it(`should add '@angular/localize/init' in 'polyfills' in browser builder`, async () => {
    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const workspace = host.readJson('angular.json') as any;
    const polyfills = workspace.projects['demo'].architect.build.options.polyfills;
    expect(polyfills).toEqual(['@angular/localize/init']);
  });

  it(`should add '@angular/localize' in 'types' tsconfigs referenced in karma builder`, async () => {
    const tsConfig = JSON.stringify({
      compilerOptions: {
        types: ['node'],
      },
    });

    host.create('tsconfig.spec.json', tsConfig);

    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const {compilerOptions} = host.readJson('tsconfig.spec.json') as TsConfig;
    const types = compilerOptions?.types;
    expect(types).toContain('@angular/localize');
    expect(types).toHaveSize(2);
  });

  it(`should add '@angular/localize' in 'types' tsconfigs referenced in server builder`, async () => {
    const tsConfig = JSON.stringify({
      compilerOptions: {},
    });

    host.create('tsconfig.server.json', tsConfig);

    host = await schematicRunner.runSchematic('ng-add', defaultOptions, host);
    const {compilerOptions} = host.readJson('tsconfig.server.json') as TsConfig;
    const types = compilerOptions?.types;
    expect(types).toContain('@angular/localize');
    expect(types).toHaveSize(1);
  });

  it('should add package to `dependencies` if `useAtRuntime` is `true`', async () => {
    host = await schematicRunner.runSchematic(
      'ng-add',
      {...defaultOptions, useAtRuntime: true},
      host,
    );

    const {devDependencies, dependencies} = host.readJson('/package.json') as {
      devDependencies: {[key: string]: string};
      dependencies: {[key: string]: string};
    };
    expect(dependencies?.['@angular/localize']).toBe('~0.0.0-PLACEHOLDER');
    expect(devDependencies?.['@angular/localize']).toBeUndefined();
  });
});


Implements fundamental Angular framework functionality, including directives and pipes, location services used in routing, HTTP services, localization support, and so on.

The `CommonModule` exports are re-exported by `BrowserModule`, which is included automatically in the root `AppModule` when you create a new app with the CLI `new` command.