diff --git a/.ng-dev/format.mts b/.ng-dev/format.mts index 2510185df46da..f19e28de734e9 100644 --- a/.ng-dev/format.mts +++ b/.ng-dev/format.mts @@ -18,7 +18,7 @@ export const format: FormatConfig = { 'packages/benchpress/**/*.{js,ts}', 'packages/common/**/*.{js,ts}', 'packages/compiler/**/*.{js,ts}', - 'packages/core/primitives/**/*.{js,ts}', + 'packages/core/**/*.{js,ts}', 'packages/docs/**/*.{js,ts}', 'packages/elements/**/*.{js,ts}', 'packages/examples/**/*.{js,ts}', @@ -40,6 +40,11 @@ export const format: FormatConfig = { // not be modified. '!third_party/**', '!.yarn/**', + // Do not format the locale files which are checked-in for Google3, but generated using + // the `generate-locales-tool` from `packages/common/locales`. + '!packages/core/src/i18n/locale_en.ts', + '!packages/common/locales/closure-locale.ts', + '!packages/common/src/i18n/currencies.ts', ], }, 'clang-format': { @@ -77,7 +82,7 @@ export const format: FormatConfig = { '!packages/benchpress/**/*.{js,ts}', '!packages/common/**/*.{js,ts}', '!packages/compiler/**/*.{js,ts}', - '!packages/core/primitives/**/*.{js,ts}', + '!packages/core/**/*.{js,ts}', '!packages/docs/**/*.{js,ts}', '!packages/elements/**/*.{js,ts}', '!packages/examples/**/*.{js,ts}', diff --git a/goldens/public-api/core/index.md b/goldens/public-api/core/index.md index 529eaf6133525..dc04a3bb73079 100644 --- a/goldens/public-api/core/index.md +++ b/goldens/public-api/core/index.md @@ -1205,7 +1205,7 @@ export class NgProbeToken { // @public export class NgZone { - constructor({ enableLongStackTrace, shouldCoalesceEventChangeDetection, shouldCoalesceRunChangeDetection }: { + constructor({ enableLongStackTrace, shouldCoalesceEventChangeDetection, shouldCoalesceRunChangeDetection, }: { enableLongStackTrace?: boolean | undefined; shouldCoalesceEventChangeDetection?: boolean | undefined; shouldCoalesceRunChangeDetection?: boolean | undefined; diff --git a/packages/core/rxjs-interop/src/output_from_observable.ts b/packages/core/rxjs-interop/src/output_from_observable.ts index e8617e3212015..2e3e1b44a900e 100644 --- a/packages/core/rxjs-interop/src/output_from_observable.ts +++ b/packages/core/rxjs-interop/src/output_from_observable.ts @@ -6,7 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertInInjectionContext, DestroyRef, inject, OutputOptions, OutputRef, OutputRefSubscription, ɵRuntimeError, ɵRuntimeErrorCode} from '@angular/core'; +import { + assertInInjectionContext, + DestroyRef, + inject, + OutputOptions, + OutputRef, + OutputRefSubscription, + ɵRuntimeError, + ɵRuntimeErrorCode, +} from '@angular/core'; import {Observable} from 'rxjs'; import {takeUntilDestroyed} from './take_until_destroyed'; @@ -31,15 +40,16 @@ class OutputFromObservableRef implements OutputRef { subscribe(callbackFn: (value: T) => void): OutputRefSubscription { if (this.destroyed) { throw new ɵRuntimeError( - ɵRuntimeErrorCode.OUTPUT_REF_DESTROYED, - ngDevMode && - 'Unexpected subscription to destroyed `OutputRef`. ' + - 'The owning directive/component is destroyed.'); + ɵRuntimeErrorCode.OUTPUT_REF_DESTROYED, + ngDevMode && + 'Unexpected subscription to destroyed `OutputRef`. ' + + 'The owning directive/component is destroyed.', + ); } // Stop yielding more values when the directive/component is already destroyed. const subscription = this.source.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ - next: value => callbackFn(value), + next: (value) => callbackFn(value), }); return { @@ -73,7 +83,9 @@ class OutputFromObservableRef implements OutputRef { * @developerPreview */ export function outputFromObservable( - observable: Observable, opts?: OutputOptions): OutputRef { + observable: Observable, + opts?: OutputOptions, +): OutputRef { ngDevMode && assertInInjectionContext(outputFromObservable); return new OutputFromObservableRef(observable); } diff --git a/packages/core/rxjs-interop/src/output_to_observable.ts b/packages/core/rxjs-interop/src/output_to_observable.ts index 0a94ea0491221..b3e481a06d400 100644 --- a/packages/core/rxjs-interop/src/output_to_observable.ts +++ b/packages/core/rxjs-interop/src/output_to_observable.ts @@ -20,13 +20,13 @@ import {Observable} from 'rxjs'; export function outputToObservable(ref: OutputRef): Observable { const destroyRef = ɵgetOutputDestroyRef(ref); - return new Observable(observer => { + return new Observable((observer) => { // Complete the observable upon directive/component destroy. // Note: May be `undefined` if an `EventEmitter` is declared outside // of an injection context. destroyRef?.onDestroy(() => observer.complete()); - const subscription = ref.subscribe(v => observer.next(v)); + const subscription = ref.subscribe((v) => observer.next(v)); return () => subscription.unsubscribe(); }); } diff --git a/packages/core/rxjs-interop/src/take_until_destroyed.ts b/packages/core/rxjs-interop/src/take_until_destroyed.ts index 73a71b7ad70d1..ab493802dc88b 100644 --- a/packages/core/rxjs-interop/src/take_until_destroyed.ts +++ b/packages/core/rxjs-interop/src/take_until_destroyed.ts @@ -26,7 +26,7 @@ export function takeUntilDestroyed(destroyRef?: DestroyRef): MonoTypeOperator destroyRef = inject(DestroyRef); } - const destroyed$ = new Observable(observer => { + const destroyed$ = new Observable((observer) => { const unregisterFn = destroyRef!.onDestroy(observer.next.bind(observer)); return unregisterFn; }); diff --git a/packages/core/rxjs-interop/src/to_observable.ts b/packages/core/rxjs-interop/src/to_observable.ts index 1e1ecd810937e..6851dd1626591 100644 --- a/packages/core/rxjs-interop/src/to_observable.ts +++ b/packages/core/rxjs-interop/src/to_observable.ts @@ -6,7 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertInInjectionContext, DestroyRef, effect, inject, Injector, Signal, untracked} from '@angular/core'; +import { + assertInInjectionContext, + DestroyRef, + effect, + inject, + Injector, + Signal, + untracked, +} from '@angular/core'; import {Observable, ReplaySubject} from 'rxjs'; /** @@ -33,24 +41,24 @@ export interface ToObservableOptions { * * @developerPreview */ -export function toObservable( - source: Signal, - options?: ToObservableOptions, - ): Observable { +export function toObservable(source: Signal, options?: ToObservableOptions): Observable { !options?.injector && assertInInjectionContext(toObservable); const injector = options?.injector ?? inject(Injector); const subject = new ReplaySubject(1); - const watcher = effect(() => { - let value: T; - try { - value = source(); - } catch (err) { - untracked(() => subject.error(err)); - return; - } - untracked(() => subject.next(value)); - }, {injector, manualCleanup: true}); + const watcher = effect( + () => { + let value: T; + try { + value = source(); + } catch (err) { + untracked(() => subject.error(err)); + return; + } + untracked(() => subject.next(value)); + }, + {injector, manualCleanup: true}, + ); injector.get(DestroyRef).onDestroy(() => { watcher.destroy(); diff --git a/packages/core/rxjs-interop/src/to_signal.ts b/packages/core/rxjs-interop/src/to_signal.ts index cd4defa5a625a..260aa1da9b14f 100644 --- a/packages/core/rxjs-interop/src/to_signal.ts +++ b/packages/core/rxjs-interop/src/to_signal.ts @@ -6,7 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertInInjectionContext, assertNotInReactiveContext, computed, DestroyRef, inject, Injector, signal, Signal, WritableSignal, ɵRuntimeError, ɵRuntimeErrorCode} from '@angular/core'; +import { + assertInInjectionContext, + assertNotInReactiveContext, + computed, + DestroyRef, + inject, + Injector, + signal, + Signal, + WritableSignal, + ɵRuntimeError, + ɵRuntimeErrorCode, +} from '@angular/core'; import {Observable, Subscribable} from 'rxjs'; /** @@ -61,23 +73,27 @@ export interface ToSignalOptions { } // Base case: no options -> `undefined` in the result type. -export function toSignal(source: Observable|Subscribable): Signal; +export function toSignal(source: Observable | Subscribable): Signal; // Options with `undefined` initial value and no `requiredSync` -> `undefined`. export function toSignal( - source: Observable|Subscribable, - options: ToSignalOptions&{initialValue?: undefined, requireSync?: false}): Signal; + source: Observable | Subscribable, + options: ToSignalOptions & {initialValue?: undefined; requireSync?: false}, +): Signal; // Options with `null` initial value -> `null`. export function toSignal( - source: Observable|Subscribable, - options: ToSignalOptions&{initialValue?: null, requireSync?: false}): Signal; + source: Observable | Subscribable, + options: ToSignalOptions & {initialValue?: null; requireSync?: false}, +): Signal; // Options with `undefined` initial value and `requiredSync` -> strict result type. export function toSignal( - source: Observable|Subscribable, - options: ToSignalOptions&{initialValue?: undefined, requireSync: true}): Signal; + source: Observable | Subscribable, + options: ToSignalOptions & {initialValue?: undefined; requireSync: true}, +): Signal; // Options with a more specific initial value type. export function toSignal( - source: Observable|Subscribable, - options: ToSignalOptions&{initialValue: U, requireSync?: false}): Signal; + source: Observable | Subscribable, + options: ToSignalOptions & {initialValue: U; requireSync?: false}, +): Signal; /** * Get the current value of an `Observable` as a reactive `Signal`. @@ -104,28 +120,31 @@ export function toSignal( * @developerPreview */ export function toSignal( - source: Observable|Subscribable, - options?: ToSignalOptions&{initialValue?: U}): Signal { + source: Observable | Subscribable, + options?: ToSignalOptions & {initialValue?: U}, +): Signal { ngDevMode && - assertNotInReactiveContext( - toSignal, - 'Invoking `toSignal` causes new subscriptions every time. ' + - 'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.'); + assertNotInReactiveContext( + toSignal, + 'Invoking `toSignal` causes new subscriptions every time. ' + + 'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.', + ); const requiresCleanup = !options?.manualCleanup; requiresCleanup && !options?.injector && assertInInjectionContext(toSignal); - const cleanupRef = - requiresCleanup ? options?.injector?.get(DestroyRef) ?? inject(DestroyRef) : null; + const cleanupRef = requiresCleanup + ? options?.injector?.get(DestroyRef) ?? inject(DestroyRef) + : null; // Note: T is the Observable value type, and U is the initial value type. They don't have to be // the same - the returned signal gives values of type `T`. - let state: WritableSignal>; + let state: WritableSignal>; if (options?.requireSync) { // Initially the signal is in a `NoValue` state. state = signal({kind: StateKind.NoValue}); } else { // If an initial value was passed, use it. Otherwise, use `undefined` as the initial value. - state = signal>({kind: StateKind.Value, value: options?.initialValue as U}); + state = signal>({kind: StateKind.Value, value: options?.initialValue as U}); } // Note: This code cannot run inside a reactive context (see assertion above). If we'd support @@ -135,8 +154,8 @@ export function toSignal( // subscription. Additional context (related to async pipe): // https://github.com/angular/angular/pull/50522. const sub = source.subscribe({ - next: value => state.set({kind: StateKind.Value, value}), - error: error => { + next: (value) => state.set({kind: StateKind.Value, value}), + error: (error) => { if (options?.rejectErrors) { // Kick the error back to RxJS. It will be caught and rethrown in a macrotask, which causes // the error to end up as an uncaught exception. @@ -150,8 +169,9 @@ export function toSignal( if (ngDevMode && options?.requireSync && state().kind === StateKind.NoValue) { throw new ɵRuntimeError( - ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT, - '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.'); + ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT, + '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.', + ); } // Unsubscribe when the current context is destroyed, if requested. @@ -170,8 +190,9 @@ export function toSignal( // This shouldn't really happen because the error is thrown on creation. // TODO(alxhub): use a RuntimeError when we finalize the error semantics throw new ɵRuntimeError( - ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT, - '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.'); + ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT, + '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.', + ); } }); } @@ -196,4 +217,4 @@ interface ErrorState { error: unknown; } -type State = NoValueState|ValueState|ErrorState; +type State = NoValueState | ValueState | ErrorState; diff --git a/packages/core/rxjs-interop/test/output_from_observable_spec.ts b/packages/core/rxjs-interop/test/output_from_observable_spec.ts index 54f07d961ebd2..4d9904733b188 100644 --- a/packages/core/rxjs-interop/test/output_from_observable_spec.ts +++ b/packages/core/rxjs-interop/test/output_from_observable_spec.ts @@ -14,14 +14,14 @@ import {outputFromObservable} from '../src'; describe('outputFromObservable()', () => { // Safety clean-up as we are patching `onUnhandledError` in this test. - afterEach(() => config.onUnhandledError = null); + afterEach(() => (config.onUnhandledError = null)); it('should support emitting values via BehaviorSubject', () => { const subject = new BehaviorSubject(0); const output = TestBed.runInInjectionContext(() => outputFromObservable(subject)); const values: number[] = []; - output.subscribe(v => values.push(v)); + output.subscribe((v) => values.push(v)); expect(values).toEqual([0]); @@ -38,7 +38,7 @@ describe('outputFromObservable()', () => { subject.next(1); const values: number[] = []; - output.subscribe(v => values.push(v)); + output.subscribe((v) => values.push(v)); expect(values).toEqual([1]); @@ -55,7 +55,7 @@ describe('outputFromObservable()', () => { subject.next(1); const values: number[] = []; - output.subscribe(v => values.push(v)); + output.subscribe((v) => values.push(v)); expect(values).toEqual([]); @@ -72,7 +72,7 @@ describe('outputFromObservable()', () => { emitter.next(1); const values: number[] = []; - output.subscribe(v => values.push(v)); + output.subscribe((v) => values.push(v)); expect(values).toEqual([]); @@ -89,7 +89,7 @@ describe('outputFromObservable()', () => { expect(subject.observed).toBe(false); - const subscription = output.subscribe(v => values.push(v)); + const subscription = output.subscribe((v) => values.push(v)); expect(subject.observed).toBe(true); expect(values).toEqual([]); @@ -109,7 +109,7 @@ describe('outputFromObservable()', () => { expect(subject.observed).toBe(false); - output.subscribe(v => values.push(v)); + output.subscribe((v) => values.push(v)); expect(subject.observed).toBe(true); expect(values).toEqual([]); @@ -130,17 +130,17 @@ describe('outputFromObservable()', () => { // initiate destroy. TestBed.resetTestingModule(); - expect(() => output.subscribe(() => {})) - .toThrowError(/Unexpected subscription to destroyed `OutputRef`/); + expect(() => output.subscribe(() => {})).toThrowError( + /Unexpected subscription to destroyed `OutputRef`/, + ); }); - it('should be a noop when the source observable completes', () => { const subject = new Subject(); const outputRef = TestBed.runInInjectionContext(() => outputFromObservable(subject)); const values: number[] = []; - outputRef.subscribe(v => values.push(v)); + outputRef.subscribe((v) => values.push(v)); subject.next(1); subject.next(2); @@ -157,13 +157,13 @@ describe('outputFromObservable()', () => { const outputRef = TestBed.runInInjectionContext(() => outputFromObservable(subject)); const values: number[] = []; - outputRef.subscribe(v => values.push(v)); + outputRef.subscribe((v) => values.push(v)); subject.next(1); subject.next(2); expect(values).toEqual([1, 2]); - config.onUnhandledError = err => { + config.onUnhandledError = (err) => { config.onUnhandledError = null; expect((err as Error).message).toEqual('test error message'); diff --git a/packages/core/rxjs-interop/test/output_to_observable_spec.ts b/packages/core/rxjs-interop/test/output_to_observable_spec.ts index 73bab185ef435..1b9e415334a24 100644 --- a/packages/core/rxjs-interop/test/output_to_observable_spec.ts +++ b/packages/core/rxjs-interop/test/output_to_observable_spec.ts @@ -18,7 +18,7 @@ describe('outputToObservable()', () => { const observable = outputToObservable(outputRef); const values: number[] = []; - observable.subscribe({next: v => values.push(v)}); + observable.subscribe({next: (v) => values.push(v)}); expect(values).toEqual([]); outputRef.emit(1); @@ -33,7 +33,7 @@ describe('outputToObservable()', () => { let completed = false; const subscription = observable.subscribe({ - complete: () => completed = true, + complete: () => (completed = true), }); outputRef.emit(1); @@ -56,7 +56,7 @@ describe('outputToObservable()', () => { const observable = outputToObservable(outputRef); const values: number[] = []; - observable.subscribe({next: v => values.push(v)}); + observable.subscribe({next: (v) => values.push(v)}); expect(values).toEqual([]); subject.next(1); @@ -72,7 +72,7 @@ describe('outputToObservable()', () => { let completed = false; const subscription = observable.subscribe({ - complete: () => completed = true, + complete: () => (completed = true), }); subject.next(1); @@ -90,32 +90,34 @@ describe('outputToObservable()', () => { expect(subject.observed).toBe(false); }); - it('may not complete the observable with an improperly ' + - 'configured `OutputRef` without a destroy ref as source', - () => { - const outputRef = new EventEmitter(); - const observable = outputToObservable(outputRef); - - let completed = false; - const subscription = observable.subscribe({ - complete: () => completed = true, - }); - - outputRef.next(1); - outputRef.next(2); - - expect(completed).toBe(false); - expect(subscription.closed).toBe(false); - expect(outputRef.observed).toBe(true); - - // destroy `EnvironmentInjector`. - TestBed.resetTestingModule(); - - expect(completed) - .withContext('Should not be completed as there is no known time when to destroy') - .toBe(false); - expect(subscription.closed).toBe(false); - expect(outputRef.observed).toBe(true); - }); + it( + 'may not complete the observable with an improperly ' + + 'configured `OutputRef` without a destroy ref as source', + () => { + const outputRef = new EventEmitter(); + const observable = outputToObservable(outputRef); + + let completed = false; + const subscription = observable.subscribe({ + complete: () => (completed = true), + }); + + outputRef.next(1); + outputRef.next(2); + + expect(completed).toBe(false); + expect(subscription.closed).toBe(false); + expect(outputRef.observed).toBe(true); + + // destroy `EnvironmentInjector`. + TestBed.resetTestingModule(); + + expect(completed) + .withContext('Should not be completed as there is no known time when to destroy') + .toBe(false); + expect(subscription.closed).toBe(false); + expect(outputRef.observed).toBe(true); + }, + ); }); }); diff --git a/packages/core/rxjs-interop/test/take_until_destroyed_spec.ts b/packages/core/rxjs-interop/test/take_until_destroyed_spec.ts index 6353f275a1001..df330109959f9 100644 --- a/packages/core/rxjs-interop/test/take_until_destroyed_spec.ts +++ b/packages/core/rxjs-interop/test/take_until_destroyed_spec.ts @@ -26,7 +26,7 @@ describe('takeUntilDestroyed', () => { }, complete() { completed = true; - } + }, }); source$.next(1); @@ -52,7 +52,7 @@ describe('takeUntilDestroyed', () => { }, complete() { completed = true; - } + }, }); source$.next(1); diff --git a/packages/core/rxjs-interop/test/to_observable_spec.ts b/packages/core/rxjs-interop/test/to_observable_spec.ts index a1919ad0eb1a3..f31806e558191 100644 --- a/packages/core/rxjs-interop/test/to_observable_spec.ts +++ b/packages/core/rxjs-interop/test/to_observable_spec.ts @@ -6,7 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, computed, createEnvironmentInjector, EnvironmentInjector, Injector, Signal, signal} from '@angular/core'; +import { + Component, + computed, + createEnvironmentInjector, + EnvironmentInjector, + Injector, + Signal, + signal, +} from '@angular/core'; import {toObservable} from '@angular/core/rxjs-interop'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {take, toArray} from 'rxjs/operators'; @@ -19,8 +27,7 @@ describe('toObservable()', () => { template: '', standalone: true, }) - class Cmp { - } + class Cmp {} beforeEach(() => { fixture = TestBed.createComponent(Cmp); @@ -67,8 +74,8 @@ describe('toObservable()', () => { let currentError: any = null; const sub = counter$.subscribe({ - next: value => currentValue = value, - error: err => currentError = err, + next: (value) => (currentValue = value), + error: (err) => (currentError = err), }); flushEffects(); @@ -163,7 +170,7 @@ describe('toObservable()', () => { // Read emits. If we are still tracked in the effect, this will cause an infinite loop by // triggering the effect again. emits(); - emits.update(v => v + 1); + emits.update((v) => v + 1); }); flushEffects(); expect(emits()).toBe(1); diff --git a/packages/core/rxjs-interop/test/to_signal_spec.ts b/packages/core/rxjs-interop/test/to_signal_spec.ts index 423323f30619f..7826436c2b743 100644 --- a/packages/core/rxjs-interop/test/to_signal_spec.ts +++ b/packages/core/rxjs-interop/test/to_signal_spec.ts @@ -6,85 +6,114 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, computed, EnvironmentInjector, Injector, runInInjectionContext, Signal} from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + EnvironmentInjector, + Injector, + runInInjectionContext, + Signal, +} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import {TestBed} from '@angular/core/testing'; -import {BehaviorSubject, Observable, Observer, ReplaySubject, Subject, Subscribable, Unsubscribable} from 'rxjs'; +import { + BehaviorSubject, + Observable, + Observer, + ReplaySubject, + Subject, + Subscribable, + Unsubscribable, +} from 'rxjs'; describe('toSignal()', () => { - it('should reflect the last emitted value of an Observable', test(() => { - const counter$ = new BehaviorSubject(0); - const counter = toSignal(counter$); - - expect(counter()).toBe(0); - counter$.next(1); - expect(counter()).toBe(1); - counter$.next(3); - expect(counter()).toBe(3); - })); - - it('should notify when the last emitted value of an Observable changes', test(() => { - let seenValue: number = 0; - const counter$ = new BehaviorSubject(1); - const counter = toSignal(counter$); - - expect(counter()).toBe(1); - - counter$.next(2); - expect(counter()).toBe(2); - })); - - it('should propagate an error returned by the Observable', test(() => { - const counter$ = new BehaviorSubject(1); - const counter = toSignal(counter$); - - expect(counter()).toBe(1); - - counter$.error('fail'); - expect(counter).toThrow('fail'); - })); - - it('should unsubscribe when the current context is destroyed', test(() => { - const counter$ = new BehaviorSubject(0); - const injector = Injector.create({providers: []}) as EnvironmentInjector; - const counter = runInInjectionContext(injector, () => toSignal(counter$)); - - expect(counter()).toBe(0); - counter$.next(1); - expect(counter()).toBe(1); - - // Destroying the injector should unsubscribe the Observable. - injector.destroy(); - - // The signal should have the last value observed. - expect(counter()).toBe(1); - - // And this value should no longer be updating (unsubscribed). - counter$.next(2); - expect(counter()).toBe(1); - })); + it( + 'should reflect the last emitted value of an Observable', + test(() => { + const counter$ = new BehaviorSubject(0); + const counter = toSignal(counter$); + + expect(counter()).toBe(0); + counter$.next(1); + expect(counter()).toBe(1); + counter$.next(3); + expect(counter()).toBe(3); + }), + ); + + it( + 'should notify when the last emitted value of an Observable changes', + test(() => { + let seenValue: number = 0; + const counter$ = new BehaviorSubject(1); + const counter = toSignal(counter$); + + expect(counter()).toBe(1); + + counter$.next(2); + expect(counter()).toBe(2); + }), + ); + + it( + 'should propagate an error returned by the Observable', + test(() => { + const counter$ = new BehaviorSubject(1); + const counter = toSignal(counter$); + + expect(counter()).toBe(1); + + counter$.error('fail'); + expect(counter).toThrow('fail'); + }), + ); + + it( + 'should unsubscribe when the current context is destroyed', + test(() => { + const counter$ = new BehaviorSubject(0); + const injector = Injector.create({providers: []}) as EnvironmentInjector; + const counter = runInInjectionContext(injector, () => toSignal(counter$)); + + expect(counter()).toBe(0); + counter$.next(1); + expect(counter()).toBe(1); + + // Destroying the injector should unsubscribe the Observable. + injector.destroy(); + // The signal should have the last value observed. + expect(counter()).toBe(1); + // And this value should no longer be updating (unsubscribed). + counter$.next(2); + expect(counter()).toBe(1); + }), + ); - it('should unsubscribe when an explicitly provided injector is destroyed', test(() => { - const counter$ = new BehaviorSubject(0); - const injector = Injector.create({providers: []}) as EnvironmentInjector; - const counter = toSignal(counter$, {injector}); + it( + 'should unsubscribe when an explicitly provided injector is destroyed', + test(() => { + const counter$ = new BehaviorSubject(0); + const injector = Injector.create({providers: []}) as EnvironmentInjector; + const counter = toSignal(counter$, {injector}); - expect(counter()).toBe(0); - counter$.next(1); - expect(counter()).toBe(1); + expect(counter()).toBe(0); + counter$.next(1); + expect(counter()).toBe(1); - // Destroying the injector should unsubscribe the Observable. - injector.destroy(); + // Destroying the injector should unsubscribe the Observable. + injector.destroy(); - // The signal should have the last value observed. - expect(counter()).toBe(1); + // The signal should have the last value observed. + expect(counter()).toBe(1); - // And this value should no longer be updating (unsubscribed). - counter$.next(2); - expect(counter()).toBe(1); - })); + // And this value should no longer be updating (unsubscribed). + counter$.next(2); + expect(counter()).toBe(1); + }), + ); it('should not require an injection context when manualCleanup is passed', () => { const counter$ = new BehaviorSubject(0); @@ -95,8 +124,9 @@ describe('toSignal()', () => { it('should not unsubscribe when manualCleanup is passed', () => { const counter$ = new BehaviorSubject(0); const injector = Injector.create({providers: []}) as EnvironmentInjector; - const counter = - runInInjectionContext(injector, () => toSignal(counter$, {manualCleanup: true})); + const counter = runInInjectionContext(injector, () => + toSignal(counter$, {manualCleanup: true}), + ); injector.destroy(); @@ -117,9 +147,9 @@ describe('toSignal()', () => { return counter() * 2; }); - expect(() => doubleCounter()) - .toThrowError( - /toSignal\(\) cannot be called from within a reactive context. Invoking `toSignal` causes new subscriptions every time./); + expect(() => doubleCounter()).toThrowError( + /toSignal\(\) cannot be called from within a reactive context. Invoking `toSignal` causes new subscriptions every time./, + ); }); it('should throw the error back to RxJS if rejectErrors is set', () => { @@ -145,55 +175,73 @@ describe('toSignal()', () => { }); describe('with no initial value', () => { - it('should return `undefined` if read before a value is emitted', test(() => { - const counter$ = new Subject(); - const counter = toSignal(counter$); - - expect(counter()).toBeUndefined(); - counter$.next(1); - expect(counter()).toBe(1); - })); - - it('should not throw if a value is emitted before called', test(() => { - const counter$ = new Subject(); - const counter = toSignal(counter$); - - counter$.next(1); - expect(() => counter()).not.toThrow(); - })); + it( + 'should return `undefined` if read before a value is emitted', + test(() => { + const counter$ = new Subject(); + const counter = toSignal(counter$); + + expect(counter()).toBeUndefined(); + counter$.next(1); + expect(counter()).toBe(1); + }), + ); + + it( + 'should not throw if a value is emitted before called', + test(() => { + const counter$ = new Subject(); + const counter = toSignal(counter$); + + counter$.next(1); + expect(() => counter()).not.toThrow(); + }), + ); }); describe('with requireSync', () => { - it('should throw if created before a value is emitted', test(() => { - const counter$ = new Subject(); - expect(() => toSignal(counter$, {requireSync: true})).toThrow(); - })); - - it('should not throw if a value emits synchronously on creation', test(() => { - const counter$ = new ReplaySubject(1); - counter$.next(1); - const counter = toSignal(counter$); - expect(counter()).toBe(1); - })); + it( + 'should throw if created before a value is emitted', + test(() => { + const counter$ = new Subject(); + expect(() => toSignal(counter$, {requireSync: true})).toThrow(); + }), + ); + + it( + 'should not throw if a value emits synchronously on creation', + test(() => { + const counter$ = new ReplaySubject(1); + counter$.next(1); + const counter = toSignal(counter$); + expect(counter()).toBe(1); + }), + ); }); describe('with an initial value', () => { - it('should return the initial value if called before a value is emitted', test(() => { - const counter$ = new Subject(); - const counter = toSignal(counter$, {initialValue: null}); - - expect(counter()).toBeNull(); - counter$.next(1); - expect(counter()).toBe(1); - })); - - it('should not return the initial value if called after a value is emitted', test(() => { - const counter$ = new Subject(); - const counter = toSignal(counter$, {initialValue: null}); - - counter$.next(1); - expect(counter()).not.toBeNull(); - })); + it( + 'should return the initial value if called before a value is emitted', + test(() => { + const counter$ = new Subject(); + const counter = toSignal(counter$, {initialValue: null}); + + expect(counter()).toBeNull(); + counter$.next(1); + expect(counter()).toBe(1); + }), + ); + + it( + 'should not return the initial value if called after a value is emitted', + test(() => { + const counter$ = new Subject(); + const counter = toSignal(counter$, {initialValue: null}); + + counter$.next(1); + expect(counter()).not.toBeNull(); + }), + ); }); describe('in a @Component', () => { @@ -223,51 +271,63 @@ describe('toSignal()', () => { describe('type tests', () => { const src = new Subject(); - it('should allow empty array initial values', test(() => { - const res: Signal = toSignal(src as Observable, {initialValue: []}); - expect(res).toBeDefined(); - })); - - it('should allow literal types', test(() => { - type Animal = 'cat'|'dog'; - const res: Signal = toSignal(src as Observable, {initialValue: 'cat'}); - expect(res).toBeDefined(); - })); - - it('should not allow initial values outside of the observable type', test(() => { - type Animal = 'cat'|'dog'; - // @ts-expect-error - const res = toSignal(src as Observable, {initialValue: 'cow'}); - expect(res).toBeDefined(); - })); - - it('allows null as an initial value', test(() => { - const res = toSignal(src as Observable, {initialValue: null}); - const res2: Signal = res; - // @ts-expect-error - const res3: Signal = res; - expect(res2).toBeDefined(); - expect(res3).toBeDefined(); - })); - - - it('allows undefined as an initial value', test(() => { - const res = toSignal(src as Observable, {initialValue: undefined}); - const res2: Signal = res; - // @ts-expect-error - const res3: Signal = res; - expect(res2).toBeDefined(); - expect(res3).toBeDefined(); - })); + it( + 'should allow empty array initial values', + test(() => { + const res: Signal = toSignal(src as Observable, {initialValue: []}); + expect(res).toBeDefined(); + }), + ); + + it( + 'should allow literal types', + test(() => { + type Animal = 'cat' | 'dog'; + const res: Signal = toSignal(src as Observable, {initialValue: 'cat'}); + expect(res).toBeDefined(); + }), + ); + + it( + 'should not allow initial values outside of the observable type', + test(() => { + type Animal = 'cat' | 'dog'; + // @ts-expect-error + const res = toSignal(src as Observable, {initialValue: 'cow'}); + expect(res).toBeDefined(); + }), + ); + + it( + 'allows null as an initial value', + test(() => { + const res = toSignal(src as Observable, {initialValue: null}); + const res2: Signal = res; + // @ts-expect-error + const res3: Signal = res; + expect(res2).toBeDefined(); + expect(res3).toBeDefined(); + }), + ); + + it( + 'allows undefined as an initial value', + test(() => { + const res = toSignal(src as Observable, {initialValue: undefined}); + const res2: Signal = res; + // @ts-expect-error + const res3: Signal = res; + expect(res2).toBeDefined(); + expect(res3).toBeDefined(); + }), + ); }); }); -function test(fn: () => void|Promise): () => Promise { +function test(fn: () => void | Promise): () => Promise { return async () => { const injector = Injector.create({ - providers: [ - {provide: EnvironmentInjector, useFactory: () => injector}, - ] + providers: [{provide: EnvironmentInjector, useFactory: () => injector}], }) as EnvironmentInjector; try { return await runInInjectionContext(injector, fn); diff --git a/packages/core/schematics/migrations/google3/waitForAsyncRule.ts b/packages/core/schematics/migrations/google3/waitForAsyncRule.ts index 66cb36d79b134..e91fe68e63c67 100644 --- a/packages/core/schematics/migrations/google3/waitForAsyncRule.ts +++ b/packages/core/schematics/migrations/google3/waitForAsyncRule.ts @@ -25,18 +25,23 @@ const newFunction = 'waitForAsync'; export class Rule extends Rules.TypedRule { override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { const failures: RuleFailure[] = []; - const asyncImportSpecifier = - getImportSpecifier(sourceFile, '@angular/core/testing', deprecatedFunction); - const asyncImport = - asyncImportSpecifier ? closestNode(asyncImportSpecifier, ts.isNamedImports) : null; + const asyncImportSpecifier = getImportSpecifier( + sourceFile, + '@angular/core/testing', + deprecatedFunction, + ); + const asyncImport = asyncImportSpecifier + ? closestNode(asyncImportSpecifier, ts.isNamedImports) + : null; // If there are no imports of `async`, we can exit early. if (asyncImportSpecifier && asyncImport) { const typeChecker = program.getTypeChecker(); const printer = ts.createPrinter(); failures.push(this._getNamedImportsFailure(asyncImport, sourceFile, printer)); - this.findAsyncReferences(sourceFile, typeChecker, asyncImportSpecifier) - .forEach((node) => failures.push(this._getIdentifierNodeFailure(node, sourceFile))); + this.findAsyncReferences(sourceFile, typeChecker, asyncImportSpecifier).forEach((node) => + failures.push(this._getIdentifierNodeFailure(node, sourceFile)), + ); } return failures; @@ -44,35 +49,52 @@ export class Rule extends Rules.TypedRule { /** Gets a failure for an import of the `async` function. */ private _getNamedImportsFailure( - node: ts.NamedImports, sourceFile: ts.SourceFile, printer: ts.Printer): RuleFailure { + node: ts.NamedImports, + sourceFile: ts.SourceFile, + printer: ts.Printer, + ): RuleFailure { const replacementText = printer.printNode( - ts.EmitHint.Unspecified, replaceImport(node, deprecatedFunction, newFunction), sourceFile); + ts.EmitHint.Unspecified, + replaceImport(node, deprecatedFunction, newFunction), + sourceFile, + ); return new RuleFailure( - sourceFile, node.getStart(), node.getEnd(), - `Imports of the deprecated ${deprecatedFunction} function are not allowed. Use ${ - newFunction} instead.`, - this.ruleName, new Replacement(node.getStart(), node.getWidth(), replacementText)); + sourceFile, + node.getStart(), + node.getEnd(), + `Imports of the deprecated ${deprecatedFunction} function are not allowed. Use ${newFunction} instead.`, + this.ruleName, + new Replacement(node.getStart(), node.getWidth(), replacementText), + ); } /** Gets a failure for an identifier node. */ private _getIdentifierNodeFailure(node: ts.Identifier, sourceFile: ts.SourceFile): RuleFailure { return new RuleFailure( - sourceFile, node.getStart(), node.getEnd(), - `References to the deprecated ${deprecatedFunction} function are not allowed. Use ${ - newFunction} instead.`, - this.ruleName, new Replacement(node.getStart(), node.getWidth(), newFunction)); + sourceFile, + node.getStart(), + node.getEnd(), + `References to the deprecated ${deprecatedFunction} function are not allowed. Use ${newFunction} instead.`, + this.ruleName, + new Replacement(node.getStart(), node.getWidth(), newFunction), + ); } /** Finds calls to the `async` function. */ private findAsyncReferences( - sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, - asyncImportSpecifier: ts.ImportSpecifier) { + sourceFile: ts.SourceFile, + typeChecker: ts.TypeChecker, + asyncImportSpecifier: ts.ImportSpecifier, + ) { const results = new Set(); ts.forEachChild(sourceFile, function visitNode(node: ts.Node) { - if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && - node.expression.text === deprecatedFunction && - isReferenceToImport(typeChecker, node.expression, asyncImportSpecifier)) { + if ( + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === deprecatedFunction && + isReferenceToImport(typeChecker, node.expression, asyncImportSpecifier) + ) { results.add(node.expression); } diff --git a/packages/core/schematics/migrations/http-providers/index.ts b/packages/core/schematics/migrations/http-providers/index.ts index 2fb13b090d0a9..95ee8bd6b1818 100644 --- a/packages/core/schematics/migrations/http-providers/index.ts +++ b/packages/core/schematics/migrations/http-providers/index.ts @@ -13,8 +13,7 @@ import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/com import {migrateFile} from './utils'; - -export default function(): Rule { +export default function (): Rule { return async (tree: Tree) => { const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); const basePath = process.cwd(); @@ -22,7 +21,8 @@ export default function(): Rule { if (!allPaths.length) { throw new SchematicsException( - 'Could not find any tsconfig file. Cannot run the transfer state migration.'); + 'Could not find any tsconfig file. Cannot run the transfer state migration.', + ); } for (const tsconfigPath of allPaths) { @@ -31,16 +31,16 @@ export default function(): Rule { }; } - function runMigration(tree: Tree, tsconfigPath: string, basePath: string) { const program = createMigrationProgram(tree, tsconfigPath, basePath); - const sourceFiles = - program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); + const sourceFiles = program + .getSourceFiles() + .filter((sourceFile) => canMigrateFile(basePath, sourceFile, program)); for (const sourceFile of sourceFiles) { - let update: UpdateRecorder|null = null; + let update: UpdateRecorder | null = null; - const rewriter = (startPos: number, width: number, text: string|null) => { + const rewriter = (startPos: number, width: number, text: string | null) => { if (update === null) { // Lazily initialize update, because most files will not require migration. update = tree.beginUpdate(relative(basePath, sourceFile.fileName)); diff --git a/packages/core/schematics/migrations/http-providers/utils.ts b/packages/core/schematics/migrations/http-providers/utils.ts index 70acc0c6df9e3..5c653377df632 100644 --- a/packages/core/schematics/migrations/http-providers/utils.ts +++ b/packages/core/schematics/migrations/http-providers/utils.ts @@ -36,7 +36,10 @@ const HTTP_TESTING_MODULES = new Set([HTTP_CLIENT_TESTING_MODULE]); export type RewriteFn = (startPos: number, width: number, text: string) => void; export function migrateFile( - sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, rewriteFn: RewriteFn) { + sourceFile: ts.SourceFile, + typeChecker: ts.TypeChecker, + rewriteFn: RewriteFn, +) { const changeTracker = new ChangeTracker(ts.createPrinter()); const addedImports = new Map>([ [COMMON_HTTP, new Set()], @@ -44,12 +47,14 @@ export function migrateFile( ]); const commonHttpIdentifiers = new Set( - getImportSpecifiers(sourceFile, COMMON_HTTP, [...HTTP_MODULES]) - .map((specifier) => specifier.getText()), + getImportSpecifiers(sourceFile, COMMON_HTTP, [...HTTP_MODULES]).map((specifier) => + specifier.getText(), + ), ); const commonHttpTestingIdentifiers = new Set( - getImportSpecifiers(sourceFile, COMMON_HTTP_TESTING, [...HTTP_TESTING_MODULES]) - .map((specifier) => specifier.getText()), + getImportSpecifiers(sourceFile, COMMON_HTTP_TESTING, [...HTTP_TESTING_MODULES]).map( + (specifier) => specifier.getText(), + ), ); ts.forEachChild(sourceFile, function visit(node: ts.Node) { @@ -57,7 +62,7 @@ export function migrateFile( if (ts.isClassDeclaration(node)) { const decorators = getAngularDecorators(typeChecker, ts.getDecorators(node) || []); - decorators.forEach(decorator => { + decorators.forEach((decorator) => { migrateDecorator(decorator, commonHttpIdentifiers, addedImports, changeTracker); }); } @@ -77,9 +82,9 @@ export function migrateFile( ...commonHttpImports.elements.filter((current) => !symbolImportsToRemove.includes(current)), ...[...(addedImports.get(COMMON_HTTP) ?? [])].map((entry) => { return ts.factory.createImportSpecifier( - false, - undefined, - ts.factory.createIdentifier(entry), + false, + undefined, + ts.factory.createIdentifier(entry), ); }), ]); @@ -95,13 +100,13 @@ export function migrateFile( const newHttpTestingImports = ts.factory.updateNamedImports(commonHttpTestingImports, [ ...commonHttpTestingImports.elements.filter( - (current) => !symbolImportsToRemove.includes(current), - ), + (current) => !symbolImportsToRemove.includes(current), + ), ...[...(addedImports.get(COMMON_HTTP_TESTING) ?? [])].map((entry) => { return ts.factory.createImportSpecifier( - false, - undefined, - ts.factory.createIdentifier(entry), + false, + undefined, + ts.factory.createIdentifier(entry), ); }), ]); @@ -117,12 +122,17 @@ export function migrateFile( } function migrateDecorator( - decorator: NgDecorator, commonHttpIdentifiers: Set, - addedImports: Map>, changeTracker: ChangeTracker) { + decorator: NgDecorator, + commonHttpIdentifiers: Set, + addedImports: Map>, + changeTracker: ChangeTracker, +) { // Only @NgModule and @Component support `imports`. // Also skip decorators with no arguments. - if ((decorator.name !== 'NgModule' && decorator.name !== 'Component') || - decorator.node.expression.arguments.length < 1) { + if ( + (decorator.name !== 'NgModule' && decorator.name !== 'Component') || + decorator.node.expression.arguments.length < 1 + ) { return; } @@ -163,10 +173,10 @@ function migrateDecorator( } else { addedImports.get(COMMON_HTTP)?.add(WITH_XSRF_CONFIGURATION); addedProviders.add( - createCallExpression( - WITH_XSRF_CONFIGURATION, - importedModules.xsrfOptions?.options ? [importedModules.xsrfOptions.options] : [], - ), + createCallExpression( + WITH_XSRF_CONFIGURATION, + importedModules.xsrfOptions?.options ? [importedModules.xsrfOptions.options] : [], + ), ); } } @@ -174,9 +184,11 @@ function migrateDecorator( // Removing the imported Http modules from the imports list const newImports = ts.factory.createArrayLiteralExpression([ ...moduleImports.elements.filter( - (item) => item !== importedModules.client && item !== importedModules.clientJsonp && - item !== importedModules.xsrf, - ), + (item) => + item !== importedModules.client && + item !== importedModules.clientJsonp && + item !== importedModules.xsrf, + ), ]); // Adding the new providers @@ -206,15 +218,21 @@ function migrateDecorator( } function migrateTestingModuleImports( - node: ts.Node, commonHttpTestingIdentifiers: Set, - addedImports: Map>, changeTracker: ChangeTracker) { + node: ts.Node, + commonHttpTestingIdentifiers: Set, + addedImports: Map>, + changeTracker: ChangeTracker, +) { // Look for calls to `TestBed.configureTestingModule` with at least one argument. // TODO: this won't work if `TestBed` is aliased or type cast. - if (!ts.isCallExpression(node) || node.arguments.length < 1 || - !ts.isPropertyAccessExpression(node.expression) || - !ts.isIdentifier(node.expression.expression) || - node.expression.expression.text !== 'TestBed' || - node.expression.name.text !== 'configureTestingModule') { + if ( + !ts.isCallExpression(node) || + node.arguments.length < 1 || + !ts.isPropertyAccessExpression(node.expression) || + !ts.isIdentifier(node.expression.expression) || + node.expression.expression.text !== 'TestBed' || + node.expression.name.text !== 'configureTestingModule' + ) { return; } @@ -232,7 +250,7 @@ function migrateTestingModuleImports( // Does the imports array contain the HttpClientTestingModule? const httpClientTesting = importsArray.elements.find( - (elt) => elt.getText() === HTTP_CLIENT_TESTING_MODULE, + (elt) => elt.getText() === HTTP_CLIENT_TESTING_MODULE, ); if (!httpClientTesting || !commonHttpTestingIdentifiers.has(HTTP_CLIENT_TESTING_MODULE)) { return; @@ -306,18 +324,18 @@ function getProvidersFromLiteralExpr(literal: ts.ObjectLiteralExpression) { } function getImportedHttpModules( - imports: ts.ArrayLiteralExpression, - commonHttpIdentifiers: Set, + imports: ts.ArrayLiteralExpression, + commonHttpIdentifiers: Set, ) { - let client: ts.Identifier|ts.CallExpression|null = null; - let clientJsonp: ts.Identifier|ts.CallExpression|null = null; - let xsrf: ts.Identifier|ts.CallExpression|null = null; + let client: ts.Identifier | ts.CallExpression | null = null; + let clientJsonp: ts.Identifier | ts.CallExpression | null = null; + let xsrf: ts.Identifier | ts.CallExpression | null = null; // represents respectively: // HttpClientXsrfModule.disable() // HttpClientXsrfModule.withOptions(options) // base HttpClientXsrfModule - let xsrfOptions: 'disable'|{options: ts.Expression}|null = null; + let xsrfOptions: 'disable' | {options: ts.Expression} | null = null; // Handling the 3 http modules from @angular/common/http and skipping the rest for (const item of imports.elements) { @@ -364,8 +382,8 @@ function getImportedHttpModules( function createCallExpression(functionName: string, args: ts.Expression[] = []) { return ts.factory.createCallExpression( - ts.factory.createIdentifier(functionName), - undefined, - args, + ts.factory.createIdentifier(functionName), + undefined, + args, ); } diff --git a/packages/core/schematics/migrations/invalid-two-way-bindings/analysis.ts b/packages/core/schematics/migrations/invalid-two-way-bindings/analysis.ts index b0cf7d2cf74ca..e8723989d64e0 100644 --- a/packages/core/schematics/migrations/invalid-two-way-bindings/analysis.ts +++ b/packages/core/schematics/migrations/invalid-two-way-bindings/analysis.ts @@ -38,8 +38,9 @@ export class AnalyzedFile { analyzedFiles.set(path, analysis); } - const duplicate = - analysis.ranges.find(current => current[0] === range[0] && current[1] === range[1]); + const duplicate = analysis.ranges.find( + (current) => current[0] === range[0] && current[1] === range[1], + ); if (!duplicate) { analysis.ranges.push(range); @@ -53,20 +54,24 @@ export class AnalyzedFile { * @param analyzedFiles Map in which to store the results. */ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map) { - forEachClass(sourceFile, node => { + forEachClass(sourceFile, (node) => { // Note: we have a utility to resolve the Angular decorators from a class declaration already. // We don't use it here, because it requires access to the type checker which makes it more // time-consuming to run internally. - const decorator = ts.getDecorators(node)?.find(dec => { - return ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && - dec.expression.expression.text === 'Component'; - }) as (ts.Decorator & {expression: ts.CallExpression}) | - undefined; + const decorator = ts.getDecorators(node)?.find((dec) => { + return ( + ts.isCallExpression(dec.expression) && + ts.isIdentifier(dec.expression.expression) && + dec.expression.expression.text === 'Component' + ); + }) as (ts.Decorator & {expression: ts.CallExpression}) | undefined; - const metadata = decorator && decorator.expression.arguments.length > 0 && - ts.isObjectLiteralExpression(decorator.expression.arguments[0]) ? - decorator.expression.arguments[0] : - null; + const metadata = + decorator && + decorator.expression.arguments.length > 0 && + ts.isObjectLiteralExpression(decorator.expression.arguments[0]) + ? decorator.expression.arguments[0] + : null; if (!metadata) { return; @@ -75,17 +80,21 @@ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map { const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); const basePath = process.cwd(); @@ -23,7 +23,8 @@ export default function(): Rule { if (!allPaths.length) { throw new SchematicsException( - 'Could not find any tsconfig file. Cannot run the invalid two-way bindings migration.'); + 'Could not find any tsconfig file. Cannot run the invalid two-way bindings migration.', + ); } for (const tsconfigPath of allPaths) { @@ -34,8 +35,9 @@ export default function(): Rule { function runInvalidTwoWayBindingsMigration(tree: Tree, tsconfigPath: string, basePath: string) { const program = createMigrationProgram(tree, tsconfigPath, basePath); - const sourceFiles = - program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); + const sourceFiles = program + .getSourceFiles() + .filter((sourceFile) => canMigrateFile(basePath, sourceFile, program)); const analysis = new Map(); for (const sourceFile of sourceFiles) { diff --git a/packages/core/schematics/migrations/invalid-two-way-bindings/migration.ts b/packages/core/schematics/migrations/invalid-two-way-bindings/migration.ts index 8695fef23630a..79aec36f0fbfe 100644 --- a/packages/core/schematics/migrations/invalid-two-way-bindings/migration.ts +++ b/packages/core/schematics/migrations/invalid-two-way-bindings/migration.ts @@ -6,7 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {ASTWithSource, BindingType, ParsedEventType, parseTemplate, ReadKeyExpr, ReadPropExpr, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstRecursiveVisitor, TmplAstTemplate} from '@angular/compiler'; +import { + ASTWithSource, + BindingType, + ParsedEventType, + parseTemplate, + ReadKeyExpr, + ReadPropExpr, + TmplAstBoundAttribute, + TmplAstElement, + TmplAstNode, + TmplAstRecursiveVisitor, + TmplAstTemplate, +} from '@angular/compiler'; import ts from 'typescript'; /** @@ -14,13 +26,13 @@ import ts from 'typescript'; * Returns null if no changes had to be made to the file. * @param template Template to be migrated. */ -export function migrateTemplate(template: string): string|null { +export function migrateTemplate(template: string): string | null { // Don't attempt to parse templates that don't contain two-way bindings. if (!template.includes(')]=')) { return null; } - let rootNodes: TmplAstNode[]|null = null; + let rootNodes: TmplAstNode[] | null = null; try { const parsed = parseTemplate(template, '', {allowInvalidAssignmentEvents: true}); @@ -28,8 +40,7 @@ export function migrateTemplate(template: string): string|null { if (parsed.errors === null) { rootNodes = parsed.nodes; } - } catch { - } + } catch {} // Don't migrate invalid templates. if (rootNodes === null) { @@ -37,8 +48,9 @@ export function migrateTemplate(template: string): string|null { } const visitor = new InvalidTwoWayBindingCollector(); - const bindings = visitor.collectInvalidBindings(rootNodes).sort( - (a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset); + const bindings = visitor + .collectInvalidBindings(rootNodes) + .sort((a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset); if (bindings.length === 0) { return null; @@ -79,7 +91,10 @@ function migrateTwoWayInput(binding: TmplAstBoundAttribute, value: string): stri * @param value String value of the binding. */ function migrateTwoWayEvent( - value: string, binding: TmplAstBoundAttribute, printer: ts.Printer): string|null { + value: string, + binding: TmplAstBoundAttribute, + printer: ts.Printer, +): string | null { // Note that we use the TypeScript parser, as opposed to our own, because even though we have // an expression AST here already, our AST is harder to work with in a migration context. // To use it here, we would have to solve the following: @@ -93,15 +108,15 @@ function migrateTwoWayEvent( // we can get away with using the TypeScript AST instead. const sourceFile = ts.createSourceFile('temp.ts', value, ts.ScriptTarget.Latest); const expression = - sourceFile.statements.length === 1 && ts.isExpressionStatement(sourceFile.statements[0]) ? - sourceFile.statements[0].expression : - null; + sourceFile.statements.length === 1 && ts.isExpressionStatement(sourceFile.statements[0]) + ? sourceFile.statements[0].expression + : null; if (expression === null) { return null; } - let migrated: ts.Expression|null = null; + let migrated: ts.Expression | null = null; // Historically the expression parser was handling two-way events by appending `=$event` // to the raw string before attempting to parse it. This has led to bugs over the years (see @@ -113,13 +128,21 @@ function migrateTwoWayEvent( if (ts.isBinaryExpression(expression) && isReadExpression(expression.right)) { // `a && b` -> `a && (b = $event)` migrated = ts.factory.updateBinaryExpression( - expression, expression.left, expression.operatorToken, - wrapInEventAssignment(expression.right)); + expression, + expression.left, + expression.operatorToken, + wrapInEventAssignment(expression.right), + ); } else if (ts.isConditionalExpression(expression) && isReadExpression(expression.whenFalse)) { // `a ? b : c` -> `a ? b : c = $event` migrated = ts.factory.updateConditionalExpression( - expression, expression.condition, expression.questionToken, expression.whenTrue, - expression.colonToken, wrapInEventAssignment(expression.whenFalse)); + expression, + expression.condition, + expression.questionToken, + expression.whenTrue, + expression.colonToken, + wrapInEventAssignment(expression.whenFalse), + ); } else if (isPrefixNot(expression)) { // `!!a` -> `a = $event` let innerExpression = expression.operand; @@ -147,18 +170,24 @@ function migrateTwoWayEvent( /** Wraps an expression in an assignment to `$event`, e.g. `foo.bar = $event`. */ function wrapInEventAssignment(node: ts.Expression): ts.Expression { return ts.factory.createBinaryExpression( - node, ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createIdentifier('$event')); + node, + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier('$event'), + ); } /** * Checks whether an expression is a valid read expression. Note that identifiers * are considered read expressions in Angular templates as well. */ -function isReadExpression(node: ts.Expression): node is ts.Identifier|ts.PropertyAccessExpression| - ts.ElementAccessExpression { - return ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || - ts.isElementAccessExpression(node); +function isReadExpression( + node: ts.Expression, +): node is ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression { + return ( + ts.isIdentifier(node) || + ts.isPropertyAccessExpression(node) || + ts.isElementAccessExpression(node) + ); } /** Checks whether an expression is in the form of `!x`. */ @@ -168,11 +197,11 @@ function isPrefixNot(node: ts.Expression): node is ts.PrefixUnaryExpression { /** Traverses a template AST and collects any invalid two-way bindings. */ class InvalidTwoWayBindingCollector extends TmplAstRecursiveVisitor { - private invalidBindings: TmplAstBoundAttribute[]|null = null; + private invalidBindings: TmplAstBoundAttribute[] | null = null; collectInvalidBindings(rootNodes: TmplAstNode[]): TmplAstBoundAttribute[] { - const result = this.invalidBindings = []; - rootNodes.forEach(node => node.visit(this)); + const result = (this.invalidBindings = []); + rootNodes.forEach((node) => node.visit(this)); this.invalidBindings = null; return result; } @@ -187,7 +216,7 @@ class InvalidTwoWayBindingCollector extends TmplAstRecursiveVisitor { super.visitTemplate(template); } - private visitNodeWithBindings(node: TmplAstElement|TmplAstTemplate) { + private visitNodeWithBindings(node: TmplAstElement | TmplAstTemplate) { const seenOneWayBindings = new Set(); // Collect all of the regular event and input binding @@ -211,8 +240,11 @@ class InvalidTwoWayBindingCollector extends TmplAstRecursiveVisitor { // something like `[(ngModel)]="invalid" (ngModelChange)="foo()"` to // `[ngModel]="invalid" (ngModelChange)="invalid = $event" (ngModelChange)="foo()"` which // would break the app. - if (input.type !== BindingType.TwoWay || seenOneWayBindings.has(input.name) || - seenOneWayBindings.has(input.name + 'Change')) { + if ( + input.type !== BindingType.TwoWay || + seenOneWayBindings.has(input.name) || + seenOneWayBindings.has(input.name + 'Change') + ) { continue; } diff --git a/packages/core/schematics/ng-generate/control-flow-migration/cases.ts b/packages/core/schematics/ng-generate/control-flow-migration/cases.ts index e70100e32192f..0674c398091fa 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/cases.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/cases.ts @@ -8,8 +8,22 @@ import {visitAll} from '@angular/compiler'; -import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types'; -import {calculateNesting, getMainBlock, getOriginals, hasLineBreaks, parseTemplate, reduceNestingOffset} from './util'; +import { + ElementCollector, + ElementToMigrate, + endMarker, + MigrateError, + Result, + startMarker, +} from './types'; +import { + calculateNesting, + getMainBlock, + getOriginals, + hasLineBreaks, + parseTemplate, + reduceNestingOffset, +} from './util'; export const boundcase = '[ngSwitchCase]'; export const switchcase = '*ngSwitchCase'; @@ -17,20 +31,17 @@ export const nakedcase = 'ngSwitchCase'; export const switchdefault = '*ngSwitchDefault'; export const nakeddefault = 'ngSwitchDefault'; -export const cases = [ - boundcase, - switchcase, - nakedcase, - switchdefault, - nakeddefault, -]; +export const cases = [boundcase, switchcase, nakedcase, switchdefault, nakeddefault]; /** * Replaces structural directive ngSwitch instances with new switch. * Returns null if the migration failed (e.g. there was a syntax error). */ -export function migrateCase(template: string): - {migrated: string, errors: MigrateError[], changed: boolean} { +export function migrateCase(template: string): { + migrated: string; + errors: MigrateError[]; + changed: boolean; +} { let errors: MigrateError[] = []; let parsed = parseTemplate(template); if (parsed.tree === undefined) { @@ -88,8 +99,7 @@ function migrateNgSwitchCase(etm: ElementToMigrate, tmpl: string, offset: number const originals = getOriginals(etm, tmpl, offset); const {start, middle, end} = getMainBlock(etm, tmpl, offset); - const startBlock = - `${startMarker}${leadingSpace}@case (${condition}) {${leadingSpace}${lbString}${start}`; + const startBlock = `${startMarker}${leadingSpace}@case (${condition}) {${leadingSpace}${lbString}${start}`; const endBlock = `${end}${lbString}${leadingSpace}}${endMarker}`; const defaultBlock = startBlock + middle + endBlock; diff --git a/packages/core/schematics/ng-generate/control-flow-migration/fors.ts b/packages/core/schematics/ng-generate/control-flow-migration/fors.ts index 9e1b680485018..a9d09d24245a2 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/fors.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/fors.ts @@ -8,15 +8,28 @@ import {visitAll} from '@angular/compiler'; -import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types'; -import {calculateNesting, getMainBlock, getOriginals, getPlaceholder, hasLineBreaks, parseTemplate, PlaceholderKind, reduceNestingOffset} from './util'; +import { + ElementCollector, + ElementToMigrate, + endMarker, + MigrateError, + Result, + startMarker, +} from './types'; +import { + calculateNesting, + getMainBlock, + getOriginals, + getPlaceholder, + hasLineBreaks, + parseTemplate, + PlaceholderKind, + reduceNestingOffset, +} from './util'; export const ngfor = '*ngFor'; export const nakedngfor = 'ngFor'; -const fors = [ - ngfor, - nakedngfor, -]; +const fors = [ngfor, nakedngfor]; export const commaSeparatedSyntax = new Map([ ['(', ')'], @@ -32,8 +45,11 @@ export const stringPairs = new Map([ * Replaces structural directive ngFor instances with new for. * Returns null if the migration failed (e.g. there was a syntax error). */ -export function migrateFor(template: string): - {migrated: string, errors: MigrateError[], changed: boolean} { +export function migrateFor(template: string): { + migrated: string; + errors: MigrateError[]; + changed: boolean; +} { let errors: MigrateError[] = []; let parsed = parseTemplate(template); if (parsed.tree === undefined) { @@ -93,14 +109,15 @@ function migrateStandardNgFor(etm: ElementToMigrate, tmpl: string, offset: numbe // first portion should always be the loop definition prefixed with `let` const condition = parts[0].replace('let ', ''); if (condition.indexOf(' as ') > -1) { - let errorMessage = `Found an aliased collection on an ngFor: "${condition}".` + - ' Collection aliasing is not supported with @for.' + - ' Refactor the code to remove the `as` alias and re-run the migration.'; + let errorMessage = + `Found an aliased collection on an ngFor: "${condition}".` + + ' Collection aliasing is not supported with @for.' + + ' Refactor the code to remove the `as` alias and re-run the migration.'; throw new Error(errorMessage); } const loopVar = condition.split(' of ')[0]; let trackBy = loopVar; - let aliasedIndex: string|null = null; + let aliasedIndex: string | null = null; let tmplPlaceholder = ''; for (let i = 1; i < parts.length; i++) { const part = parts[i].trim(); @@ -146,7 +163,7 @@ function migrateStandardNgFor(etm: ElementToMigrate, tmpl: string, offset: numbe trackBy = trackBy.replace('$index', aliasedIndex); } - const aliasStr = (aliases.length > 0) ? `;${aliases.join(';')}` : ''; + const aliasStr = aliases.length > 0 ? `;${aliases.join(';')}` : ''; let startBlock = `${startMarker}@for (${condition}; track ${trackBy}${aliasStr}) {${lbString}`; let endBlock = `${lbString}}${endMarker}`; @@ -186,7 +203,7 @@ function migrateBoundNgFor(etm: ElementToMigrate, tmpl: string, offset: number): aliasedIndex = key; } } - const aliasStr = (aliases.length > 0) ? `;${aliases.join(';')}` : ''; + const aliasStr = aliases.length > 0 ? `;${aliases.join(';')}` : ''; let trackBy = aliasAttrs.item; if (forAttrs.trackBy !== '') { @@ -220,8 +237,11 @@ function getNgForParts(expression: string): string[] { const isInCommaSeparated = commaSeparatedStack.length === 0; // Any semicolon is a delimiter, as well as any comma outside // of comma-separated syntax, as long as they're outside of a string. - if (isInString && current.length > 0 && - (char === ';' || (char === ',' && isInCommaSeparated))) { + if ( + isInString && + current.length > 0 && + (char === ';' || (char === ',' && isInCommaSeparated)) + ) { parts.push(current); current = ''; continue; @@ -236,8 +256,9 @@ function getNgForParts(expression: string): string[] { if (commaSeparatedSyntax.has(char)) { commaSeparatedStack.push(commaSeparatedSyntax.get(char)!); } else if ( - commaSeparatedStack.length > 0 && - commaSeparatedStack[commaSeparatedStack.length - 1] === char) { + commaSeparatedStack.length > 0 && + commaSeparatedStack[commaSeparatedStack.length - 1] === char + ) { commaSeparatedStack.pop(); } diff --git a/packages/core/schematics/ng-generate/control-flow-migration/identifier-lookup.ts b/packages/core/schematics/ng-generate/control-flow-migration/identifier-lookup.ts index 89602be4f7850..087ca622d9931 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/identifier-lookup.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/identifier-lookup.ts @@ -9,7 +9,9 @@ import ts from 'typescript'; export function lookupIdentifiersInSourceFile( - sourceFile: ts.SourceFile, names: string[]): Set { + sourceFile: ts.SourceFile, + names: string[], +): Set { const results = new Set(); const visit = (node: ts.Node): void => { if (ts.isIdentifier(node) && names.includes(node.text)) { diff --git a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts index 0bd367bd07c68..a8937f32dbff8 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/ifs.ts @@ -8,25 +8,40 @@ import {visitAll} from '@angular/compiler'; -import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types'; -import {calculateNesting, getMainBlock, getOriginals, getPlaceholder, hasLineBreaks, parseTemplate, PlaceholderKind, reduceNestingOffset} from './util'; +import { + ElementCollector, + ElementToMigrate, + endMarker, + MigrateError, + Result, + startMarker, +} from './types'; +import { + calculateNesting, + getMainBlock, + getOriginals, + getPlaceholder, + hasLineBreaks, + parseTemplate, + PlaceholderKind, + reduceNestingOffset, +} from './util'; export const ngif = '*ngIf'; export const boundngif = '[ngIf]'; export const nakedngif = 'ngIf'; -const ifs = [ - ngif, - nakedngif, - boundngif, -]; +const ifs = [ngif, nakedngif, boundngif]; /** * Replaces structural directive ngif instances with new if. * Returns null if the migration failed (e.g. there was a syntax error). */ -export function migrateIf(template: string): - {migrated: string, errors: MigrateError[], changed: boolean} { +export function migrateIf(template: string): { + migrated: string; + errors: MigrateError[]; + changed: boolean; +} { let errors: MigrateError[] = []; let parsed = parseTemplate(template); if (parsed.tree === undefined) { @@ -80,7 +95,7 @@ function migrateNgIf(etm: ElementToMigrate, tmpl: string, offset: number): Resul } else if (matchThen && matchThen.length > 0) { // just then return buildStandardIfThenBlock(etm, tmpl, matchThen[0], offset); - } else if ((matchElse && matchElse.length > 0)) { + } else if (matchElse && matchElse.length > 0) { // just else return buildStandardIfElseBlock(etm, tmpl, matchElse![0], offset); } @@ -98,13 +113,14 @@ function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Resu // includes the mandatory semicolon before as const lbString = etm.hasLineBreaks ? '\n' : ''; let condition = etm.attr.value - .replace(' as ', '; as ') - // replace 'let' with 'as' whatever spaces are between ; and 'let' - .replace(/;\s*let/g, '; as'); + .replace(' as ', '; as ') + // replace 'let' with 'as' whatever spaces are between ; and 'let' + .replace(/;\s*let/g, '; as'); if (aliases.length > 1 || (aliases.length === 1 && condition.indexOf('; as') > -1)) { // only 1 alias allowed throw new Error( - 'Found more than one alias on your ngIf. Remove one of them and re-run the migration.'); + 'Found more than one alias on your ngIf. Remove one of them and re-run the migration.', + ); } else if (aliases.length === 1) { condition += `; as ${aliases[0]}`; } @@ -127,12 +143,17 @@ function buildIfBlock(etm: ElementToMigrate, tmpl: string, offset: number): Resu } function buildStandardIfElseBlock( - etm: ElementToMigrate, tmpl: string, elseString: string, offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + elseString: string, + offset: number, +): Result { // includes the mandatory semicolon before as - const condition = etm.getCondition() - .replace(' as ', '; as ') - // replace 'let' with 'as' whatever spaces are between ; and 'let' - .replace(/;\s*let/g, '; as'); + const condition = etm + .getCondition() + .replace(' as ', '; as ') + // replace 'let' with 'as' whatever spaces are between ; and 'let' + .replace(/;\s*let/g, '; as'); const elsePlaceholder = getPlaceholder(etm.getTemplateName(elseString)); return buildIfElseBlock(etm, tmpl, condition, elsePlaceholder, offset); } @@ -149,7 +170,8 @@ function buildBoundIfElseBlock(etm: ElementToMigrate, tmpl: string, offset: numb if (aliases.length > 1 || (aliases.length === 1 && condition.indexOf('; as') > -1)) { // only 1 alias allowed throw new Error( - 'Found more than one alias on your ngIf. Remove one of them and re-run the migration.'); + 'Found more than one alias on your ngIf. Remove one of them and re-run the migration.', + ); } else if (aliases.length === 1) { condition += `; as ${aliases[0]}`; } @@ -162,8 +184,12 @@ function buildBoundIfElseBlock(etm: ElementToMigrate, tmpl: string, offset: numb } function buildIfElseBlock( - etm: ElementToMigrate, tmpl: string, condition: string, elsePlaceholder: string, - offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + condition: string, + elsePlaceholder: string, + offset: number, +): Result { const lbString = etm.hasLineBreaks ? '\n' : ''; const originals = getOriginals(etm, tmpl, offset); @@ -187,32 +213,47 @@ function buildIfElseBlock( } function buildStandardIfThenElseBlock( - etm: ElementToMigrate, tmpl: string, thenString: string, elseString: string, - offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + thenString: string, + elseString: string, + offset: number, +): Result { // includes the mandatory semicolon before as - const condition = etm.getCondition() - .replace(' as ', '; as ') - // replace 'let' with 'as' whatever spaces are between ; and 'let' - .replace(/;\s*let/g, '; as'); + const condition = etm + .getCondition() + .replace(' as ', '; as ') + // replace 'let' with 'as' whatever spaces are between ; and 'let' + .replace(/;\s*let/g, '; as'); const thenPlaceholder = getPlaceholder(etm.getTemplateName(thenString, elseString)); const elsePlaceholder = getPlaceholder(etm.getTemplateName(elseString)); return buildIfThenElseBlock(etm, tmpl, condition, thenPlaceholder, elsePlaceholder, offset); } function buildStandardIfThenBlock( - etm: ElementToMigrate, tmpl: string, thenString: string, offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + thenString: string, + offset: number, +): Result { // includes the mandatory semicolon before as - const condition = etm.getCondition() - .replace(' as ', '; as ') - // replace 'let' with 'as' whatever spaces are between ; and 'let' - .replace(/;\s*let/g, '; as'); + const condition = etm + .getCondition() + .replace(' as ', '; as ') + // replace 'let' with 'as' whatever spaces are between ; and 'let' + .replace(/;\s*let/g, '; as'); const thenPlaceholder = getPlaceholder(etm.getTemplateName(thenString)); return buildIfThenBlock(etm, tmpl, condition, thenPlaceholder, offset); } function buildIfThenElseBlock( - etm: ElementToMigrate, tmpl: string, condition: string, thenPlaceholder: string, - elsePlaceholder: string, offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + condition: string, + thenPlaceholder: string, + elsePlaceholder: string, + offset: number, +): Result { const lbString = etm.hasLineBreaks ? '\n' : ''; const originals = getOriginals(etm, tmpl, offset); @@ -237,8 +278,12 @@ function buildIfThenElseBlock( } function buildIfThenBlock( - etm: ElementToMigrate, tmpl: string, condition: string, thenPlaceholder: string, - offset: number): Result { + etm: ElementToMigrate, + tmpl: string, + condition: string, + thenPlaceholder: string, + offset: number, +): Result { const lbString = etm.hasLineBreaks ? '\n' : ''; const originals = getOriginals(etm, tmpl, offset); diff --git a/packages/core/schematics/ng-generate/control-flow-migration/index.ts b/packages/core/schematics/ng-generate/control-flow-migration/index.ts index 3717cc725c44f..6d1c98f7087b8 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/index.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/index.ts @@ -21,7 +21,7 @@ interface Options { format: boolean; } -export default function(options: Options): Rule { +export default function (options: Options): Rule { return async (tree: Tree, context: SchematicContext) => { const basePath = process.cwd(); const pathToMigrate = normalizePath(join(basePath, options.path)); @@ -32,14 +32,20 @@ export default function(options: Options): Rule { if (!allPaths.length) { throw new SchematicsException( - 'Could not find any tsconfig file. Cannot run the control flow migration.'); + 'Could not find any tsconfig file. Cannot run the control flow migration.', + ); } let errors: string[] = []; for (const tsconfigPath of allPaths) { - const migrateErrors = - runControlFlowMigration(tree, tsconfigPath, basePath, pathToMigrate, options); + const migrateErrors = runControlFlowMigration( + tree, + tsconfigPath, + basePath, + pathToMigrate, + options, + ); errors = [...errors, ...migrateErrors]; } @@ -53,21 +59,31 @@ export default function(options: Options): Rule { } function runControlFlowMigration( - tree: Tree, tsconfigPath: string, basePath: string, pathToMigrate: string, - schematicOptions: Options): string[] { + tree: Tree, + tsconfigPath: string, + basePath: string, + pathToMigrate: string, + schematicOptions: Options, +): string[] { if (schematicOptions.path.startsWith('..')) { throw new SchematicsException( - 'Cannot run control flow migration outside of the current project.'); + 'Cannot run control flow migration outside of the current project.', + ); } const program = createMigrationProgram(tree, tsconfigPath, basePath); - const sourceFiles = program.getSourceFiles().filter( - sourceFile => sourceFile.fileName.startsWith(pathToMigrate) && - canMigrateFile(basePath, sourceFile, program)); + const sourceFiles = program + .getSourceFiles() + .filter( + (sourceFile) => + sourceFile.fileName.startsWith(pathToMigrate) && + canMigrateFile(basePath, sourceFile, program), + ); if (sourceFiles.length === 0) { - throw new SchematicsException(`Could not find any files to migrate under the path ${ - pathToMigrate}. Cannot run the control flow migration.`); + throw new SchematicsException( + `Could not find any files to migrate under the path ${pathToMigrate}. Cannot run the control flow migration.`, + ); } const analysis = new Map(); @@ -92,8 +108,14 @@ function runControlFlowMigration( const template = content.slice(start, end); const length = (end ?? content.length) - start; - const {migrated, errors} = - migrateTemplate(template, type, node, file, schematicOptions.format, analysis); + const {migrated, errors} = migrateTemplate( + template, + type, + node, + file, + schematicOptions.format, + analysis, + ); if (migrated !== null) { update.remove(start, length); @@ -118,12 +140,12 @@ function runControlFlowMigration( } function sortFilePaths(names: string[]): string[] { - names.sort((a, _) => a.endsWith('.html') ? -1 : 0); + names.sort((a, _) => (a.endsWith('.html') ? -1 : 0)); return names; } function generateErrorMessage(path: string, errors: MigrateError[]): string { let errorMessage = `Template "${path}" encountered ${errors.length} errors during migration:\n`; - errorMessage += errors.map(e => ` - ${e.type}: ${e.error}\n`); + errorMessage += errors.map((e) => ` - ${e.type}: ${e.error}\n`); return errorMessage; } diff --git a/packages/core/schematics/ng-generate/control-flow-migration/migration.ts b/packages/core/schematics/ng-generate/control-flow-migration/migration.ts index c62bc74b8a024..908c572a63841 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/migration.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/migration.ts @@ -12,16 +12,35 @@ import {migrateCase} from './cases'; import {migrateFor} from './fors'; import {migrateIf} from './ifs'; import {migrateSwitch} from './switches'; -import {AnalyzedFile, endI18nMarker, endMarker, MigrateError, startI18nMarker, startMarker} from './types'; -import {canRemoveCommonModule, formatTemplate, parseTemplate, processNgTemplates, removeImports, validateI18nStructure, validateMigratedTemplate} from './util'; +import { + AnalyzedFile, + endI18nMarker, + endMarker, + MigrateError, + startI18nMarker, + startMarker, +} from './types'; +import { + canRemoveCommonModule, + formatTemplate, + parseTemplate, + processNgTemplates, + removeImports, + validateI18nStructure, + validateMigratedTemplate, +} from './util'; /** * Actually migrates a given template to the new syntax */ export function migrateTemplate( - template: string, templateType: string, node: ts.Node, file: AnalyzedFile, - format: boolean = true, - analyzedFiles: Map|null): {migrated: string, errors: MigrateError[]} { + template: string, + templateType: string, + node: ts.Node, + file: AnalyzedFile, + format: boolean = true, + analyzedFiles: Map | null, +): {migrated: string; errors: MigrateError[]} { let errors: MigrateError[] = []; let migrated = template; if (templateType === 'template' || templateType === 'templateUrl') { @@ -38,7 +57,7 @@ export function migrateTemplate( } migrated = templateResult.migrated; const changed = - ifResult.changed || forResult.changed || switchResult.changed || caseResult.changed; + ifResult.changed || forResult.changed || switchResult.changed || caseResult.changed; if (changed) { // determine if migrated template is a valid structure // if it is not, fail out @@ -51,8 +70,10 @@ export function migrateTemplate( if (format && changed) { migrated = formatTemplate(migrated, templateType); } - const markerRegex = - new RegExp(`${startMarker}|${endMarker}|${startI18nMarker}|${endI18nMarker}`, 'gm'); + const markerRegex = new RegExp( + `${startMarker}|${endMarker}|${startI18nMarker}|${endI18nMarker}`, + 'gm', + ); migrated = migrated.replace(markerRegex, ''); file.removeCommonModule = canRemoveCommonModule(template); @@ -61,8 +82,11 @@ export function migrateTemplate( // when migrating an external template, we have to pass back // whether it's safe to remove the CommonModule to the // original component class source file - if (templateType === 'templateUrl' && analyzedFiles !== null && - analyzedFiles.has(file.sourceFile.fileName)) { + if ( + templateType === 'templateUrl' && + analyzedFiles !== null && + analyzedFiles.has(file.sourceFile.fileName) + ) { const componentFile = analyzedFiles.get(file.sourceFile.fileName)!; componentFile.getSortedRanges(); // we have already checked the template file to see if it is safe to remove the imports diff --git a/packages/core/schematics/ng-generate/control-flow-migration/switches.ts b/packages/core/schematics/ng-generate/control-flow-migration/switches.ts index a096f84771c2e..16c89d46a0448 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/switches.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/switches.ts @@ -9,21 +9,36 @@ import {Element, Node, Text, visitAll} from '@angular/compiler'; import {cases} from './cases'; -import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types'; -import {calculateNesting, getMainBlock, getOriginals, hasLineBreaks, parseTemplate, reduceNestingOffset} from './util'; +import { + ElementCollector, + ElementToMigrate, + endMarker, + MigrateError, + Result, + startMarker, +} from './types'; +import { + calculateNesting, + getMainBlock, + getOriginals, + hasLineBreaks, + parseTemplate, + reduceNestingOffset, +} from './util'; export const ngswitch = '[ngSwitch]'; -const switches = [ - ngswitch, -]; +const switches = [ngswitch]; /** * Replaces structural directive ngSwitch instances with new switch. * Returns null if the migration failed (e.g. there was a syntax error). */ -export function migrateSwitch(template: string): - {migrated: string, errors: MigrateError[], changed: boolean} { +export function migrateSwitch(template: string): { + migrated: string; + errors: MigrateError[]; + changed: boolean; +} { let errors: MigrateError[] = []; let parsed = parseTemplate(template); if (parsed.tree === undefined) { @@ -70,8 +85,9 @@ function assertValidSwitchStructure(children: Node[]): void { for (const child of children) { if (child instanceof Text && child.value.trim() !== '') { throw new Error( - `Text node: "${child.value}" would result in invalid migrated @switch block structure. ` + - `@switch can only have @case or @default as children.`); + `Text node: "${child.value}" would result in invalid migrated @switch block structure. ` + + `@switch can only have @case or @default as children.`, + ); } else if (child instanceof Element) { let hasCase = false; for (const attr of child.attrs) { @@ -81,9 +97,9 @@ function assertValidSwitchStructure(children: Node[]): void { } if (!hasCase) { throw new Error( - `Element node: "${ - child.name}" would result in invalid migrated @switch block structure. ` + - `@switch can only have @case or @default as children.`); + `Element node: "${child.name}" would result in invalid migrated @switch block structure. ` + + `@switch can only have @case or @default as children.`, + ); } } } diff --git a/packages/core/schematics/ng-generate/control-flow-migration/types.ts b/packages/core/schematics/ng-generate/control-flow-migration/types.ts index 9788a0b162028..a07cb9ce7bb97 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/types.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/types.ts @@ -24,18 +24,21 @@ export const startI18nMarker = '⚈'; export const endI18nMarker = '⚉'; export const importRemovals = [ - 'NgIf', 'NgIfElse', 'NgIfThenElse', 'NgFor', 'NgForOf', 'NgForTrackBy', 'NgSwitch', - 'NgSwitchCase', 'NgSwitchDefault' + 'NgIf', + 'NgIfElse', + 'NgIfThenElse', + 'NgFor', + 'NgForOf', + 'NgForTrackBy', + 'NgSwitch', + 'NgSwitchCase', + 'NgSwitchDefault', ]; export const importWithCommonRemovals = [...importRemovals, 'CommonModule']; function allFormsOf(selector: string): string[] { - return [ - selector, - `*${selector}`, - `[${selector}]`, - ]; + return [selector, `*${selector}`, `[${selector}]`]; } const commonModuleDirectives = new Set([ @@ -72,25 +75,28 @@ const commonModulePipes = [ 'titlecase', 'percent', 'titlecase', -].map(name => pipeMatchRegExpFor(name)); +].map((name) => pipeMatchRegExpFor(name)); /** * Represents a range of text within a file. Omitting the end * means that it's until the end of the file. */ type Range = { - start: number, - end?: number, node: ts.Node, type: string, remove: boolean, + start: number; + end?: number; + node: ts.Node; + type: string; + remove: boolean; }; export type Offsets = { - pre: number, - post: number, + pre: number; + post: number; }; export type Result = { - tmpl: string, - offsets: Offsets, + tmpl: string; + offsets: Offsets; }; export interface ForAttributes { @@ -104,7 +110,7 @@ export interface AliasAttributes { } export interface ParseResult { - tree: ParseTreeResult|undefined; + tree: ParseTreeResult | undefined; errors: MigrateError[]; } @@ -112,8 +118,8 @@ export interface ParseResult { * Represents an error that happened during migration */ export type MigrateError = { - type: string, - error: unknown, + type: string; + error: unknown; }; /** @@ -122,17 +128,21 @@ export type MigrateError = { export class ElementToMigrate { el: Element; attr: Attribute; - elseAttr: Attribute|undefined; - thenAttr: Attribute|undefined; - forAttrs: ForAttributes|undefined; - aliasAttrs: AliasAttributes|undefined; + elseAttr: Attribute | undefined; + thenAttr: Attribute | undefined; + forAttrs: ForAttributes | undefined; + aliasAttrs: AliasAttributes | undefined; nestCount = 0; hasLineBreaks = false; constructor( - el: Element, attr: Attribute, elseAttr: Attribute|undefined = undefined, - thenAttr: Attribute|undefined = undefined, forAttrs: ForAttributes|undefined = undefined, - aliasAttrs: AliasAttributes|undefined = undefined) { + el: Element, + attr: Attribute, + elseAttr: Attribute | undefined = undefined, + thenAttr: Attribute | undefined = undefined, + forAttrs: ForAttributes | undefined = undefined, + aliasAttrs: AliasAttributes | undefined = undefined, + ) { this.el = el; this.attr = attr; this.elseAttr = elseAttr; @@ -154,15 +164,17 @@ export class ElementToMigrate { condition = condition.slice(0, elseIx); } - let letVar = chunks.find(c => c.search(/\s*let\s/) > -1); + let letVar = chunks.find((c) => c.search(/\s*let\s/) > -1); return condition + (letVar ? ';' + letVar : ''); } getTemplateName(targetStr: string, secondStr?: string): string { const targetLocation = this.attr.value.indexOf(targetStr); const secondTargetLocation = secondStr ? this.attr.value.indexOf(secondStr) : undefined; - let templateName = - this.attr.value.slice(targetLocation + targetStr.length, secondTargetLocation); + let templateName = this.attr.value.slice( + targetLocation + targetStr.length, + secondTargetLocation, + ); if (templateName.startsWith(':')) { templateName = templateName.slice(1).trim(); } @@ -170,24 +182,27 @@ export class ElementToMigrate { } getValueEnd(offset: number): number { - return (this.attr.valueSpan ? (this.attr.valueSpan.end.offset + 1) : - this.attr.keySpan!.end.offset) - - offset; + return ( + (this.attr.valueSpan ? this.attr.valueSpan.end.offset + 1 : this.attr.keySpan!.end.offset) - + offset + ); } hasChildren(): boolean { return this.el.children.length > 0; } - getChildSpan(offset: number): {childStart: number, childEnd: number} { + getChildSpan(offset: number): {childStart: number; childEnd: number} { const childStart = this.el.children[0].sourceSpan.start.offset - offset; const childEnd = this.el.children[this.el.children.length - 1].sourceSpan.end.offset - offset; return {childStart, childEnd}; } shouldRemoveElseAttr(): boolean { - return (this.el.name === 'ng-template' || this.el.name === 'ng-container') && - this.elseAttr !== undefined; + return ( + (this.el.name === 'ng-template' || this.el.name === 'ng-container') && + this.elseAttr !== undefined + ); } getElseAttrStr(): string { @@ -220,10 +235,10 @@ export class Template { count: number = 0; contents: string = ''; children: string = ''; - i18n: Attribute|null = null; + i18n: Attribute | null = null; attributes: Attribute[]; - constructor(el: Element, name: string, i18n: Attribute|null) { + constructor(el: Element, name: string, i18n: Attribute | null) { this.el = el; this.name = name; this.attributes = el.attrs; @@ -231,16 +246,16 @@ export class Template { } get isNgTemplateOutlet() { - return this.attributes.find(attr => attr.name === '*ngTemplateOutlet') !== undefined; + return this.attributes.find((attr) => attr.name === '*ngTemplateOutlet') !== undefined; } get outletContext() { - const letVar = this.attributes.find(attr => attr.name.startsWith('let-')); + const letVar = this.attributes.find((attr) => attr.name.startsWith('let-')); return letVar ? `; context: {$implicit: ${letVar.name.split('-')[1]}}` : ''; } generateTemplateOutlet() { - const attr = this.attributes.find(attr => attr.name === '*ngTemplateOutlet'); + const attr = this.attributes.find((attr) => attr.name === '*ngTemplateOutlet'); const outletValue = attr?.value ?? this.name.slice(1); return ``; } @@ -250,8 +265,9 @@ export class Template { this.children = ''; if (this.el.children.length > 0) { this.children = tmpl.slice( - this.el.children[0].sourceSpan.start.offset, - this.el.children[this.el.children.length - 1].sourceSpan.end.offset); + this.el.children[0].sourceSpan.start.offset, + this.el.children[this.el.children.length - 1].sourceSpan.end.offset, + ); } } } @@ -272,13 +288,14 @@ export class AnalyzedFile { /** Returns the ranges in the order in which they should be migrated. */ getSortedRanges(): Range[] { // templates first for checking on whether certain imports can be safely removed - this.templateRanges = this.ranges.slice() - .filter(x => x.type === 'template' || x.type === 'templateUrl') - .sort((aStart, bStart) => bStart.start - aStart.start); - this.importRanges = - this.ranges.slice() - .filter(x => x.type === 'importDecorator' || x.type === 'importDeclaration') - .sort((aStart, bStart) => bStart.start - aStart.start); + this.templateRanges = this.ranges + .slice() + .filter((x) => x.type === 'template' || x.type === 'templateUrl') + .sort((aStart, bStart) => bStart.start - aStart.start); + this.importRanges = this.ranges + .slice() + .filter((x) => x.type === 'importDecorator' || x.type === 'importDeclaration') + .sort((aStart, bStart) => bStart.start - aStart.start); return [...this.templateRanges, ...this.importRanges]; } @@ -289,8 +306,11 @@ export class AnalyzedFile { * @param range Range to be added. */ static addRange( - path: string, sourceFile: ts.SourceFile, analyzedFiles: Map, - range: Range): void { + path: string, + sourceFile: ts.SourceFile, + analyzedFiles: Map, + range: Range, + ): void { let analysis = analyzedFiles.get(path); if (!analysis) { @@ -298,8 +318,9 @@ export class AnalyzedFile { analyzedFiles.set(path, analysis); } - const duplicate = - analysis.ranges.find(current => current.start === range.start && current.end === range.end); + const duplicate = analysis.ranges.find( + (current) => current.start === range.start && current.end === range.end, + ); if (!duplicate) { analysis.ranges.push(range); @@ -311,7 +332,7 @@ export class AnalyzedFile { * It is only run on .ts files. */ verifyCanRemoveImports() { - const importDeclaration = this.importRanges.find(r => r.type === 'importDeclaration'); + const importDeclaration = this.importRanges.find((r) => r.type === 'importDeclaration'); const instances = lookupIdentifiersInSourceFile(this.sourceFile, importWithCommonRemovals); let foundImportDeclaration = false; let count = 0; @@ -357,7 +378,7 @@ export class CommonCollector extends RecursiveVisitor { } private hasPipes(input: string): boolean { - return commonModulePipes.some(regexp => regexp.test(input)); + return commonModulePipes.some((regexp) => regexp.test(input)); } } @@ -366,7 +387,7 @@ export class i18nCollector extends RecursiveVisitor { readonly elements: Element[] = []; override visitElement(el: Element): void { - if (el.attrs.find(a => a.name === 'i18n') !== undefined) { + if (el.attrs.find((a) => a.name === 'i18n') !== undefined) { this.elements.push(el); } super.visitElement(el, null); @@ -385,13 +406,15 @@ export class ElementCollector extends RecursiveVisitor { if (el.attrs.length > 0) { for (const attr of el.attrs) { if (this._attributes.includes(attr.name)) { - const elseAttr = el.attrs.find(x => x.name === boundngifelse); - const thenAttr = - el.attrs.find(x => x.name === boundngifthenelse || x.name === boundngifthen); + const elseAttr = el.attrs.find((x) => x.name === boundngifelse); + const thenAttr = el.attrs.find( + (x) => x.name === boundngifthenelse || x.name === boundngifthen, + ); const forAttrs = attr.name === nakedngfor ? this.getForAttrs(el) : undefined; const aliasAttrs = this.getAliasAttrs(el); this.elements.push( - new ElementToMigrate(el, attr, elseAttr, thenAttr, forAttrs, aliasAttrs)); + new ElementToMigrate(el, attr, elseAttr, thenAttr, forAttrs, aliasAttrs), + ); } } } @@ -452,8 +475,9 @@ export class TemplateCollector extends RecursiveVisitor { this.elements.push(new ElementToMigrate(el, templateAttr)); } else if (templateAttr !== null) { throw new Error( - `A duplicate ng-template name "${templateAttr.name}" was found. ` + - `The control flow migration requires unique ng-template names within a component.`); + `A duplicate ng-template name "${templateAttr.name}" was found. ` + + `The control flow migration requires unique ng-template names within a component.`, + ); } } super.visitElement(el, null); diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts index 40ab66b95f75e..1d53c0864d092 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts @@ -10,7 +10,23 @@ import {Attribute, Element, HtmlParser, Node, ParseTreeResult, visitAll} from '@ import {dirname, join} from 'path'; import ts from 'typescript'; -import {AnalyzedFile, CommonCollector, ElementCollector, ElementToMigrate, endI18nMarker, endMarker, i18nCollector, importRemovals, importWithCommonRemovals, MigrateError, ParseResult, startI18nMarker, startMarker, Template, TemplateCollector} from './types'; +import { + AnalyzedFile, + CommonCollector, + ElementCollector, + ElementToMigrate, + endI18nMarker, + endMarker, + i18nCollector, + importRemovals, + importWithCommonRemovals, + MigrateError, + ParseResult, + startI18nMarker, + startMarker, + Template, + TemplateCollector, +} from './types'; const startMarkerRegex = new RegExp(startMarker, 'gm'); const endMarkerRegex = new RegExp(endMarker, 'gm'); @@ -24,7 +40,7 @@ const replaceMarkerRegex = new RegExp(`${startMarker}|${endMarker}`, 'gm'); * @param analyzedFiles Map in which to store the results. */ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map) { - forEachClass(sourceFile, node => { + forEachClass(sourceFile, (node) => { if (ts.isClassDeclaration(node)) { analyzeDecorators(node, sourceFile, analyzedFiles); } else { @@ -34,7 +50,7 @@ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map r.type === 'importDeclaration'); + const range = file.importRanges.find((r) => r.type === 'importDeclaration'); if (range === undefined || !range.remove) { return false; } @@ -44,9 +60,12 @@ function checkIfShouldChange(decl: ts.ImportDeclaration, file: AnalyzedFile) { // and that's the only thing there, we should do nothing. const clause = decl.getChildAt(1) as ts.ImportClause; return !( - !file.removeCommonModule && clause.namedBindings && ts.isNamedImports(clause.namedBindings) && - clause.namedBindings.elements.length === 1 && - clause.namedBindings.elements[0].getText() === 'CommonModule'); + !file.removeCommonModule && + clause.namedBindings && + ts.isNamedImports(clause.namedBindings) && + clause.namedBindings.elements.length === 1 && + clause.namedBindings.elements[0].getText() === 'CommonModule' + ); } function updateImportDeclaration(decl: ts.ImportDeclaration, removeCommonModule: boolean): string { @@ -63,26 +82,39 @@ function updateImportDeclaration(decl: ts.ImportDeclaration, removeCommonModule: removeComments: true, }); const updated = ts.factory.updateImportDeclaration( - decl, decl.modifiers, updatedClause, decl.moduleSpecifier, undefined); + decl, + decl.modifiers, + updatedClause, + decl.moduleSpecifier, + undefined, + ); return printer.printNode(ts.EmitHint.Unspecified, updated, clause.getSourceFile()); } -function updateImportClause(clause: ts.ImportClause, removeCommonModule: boolean): ts.ImportClause| - null { +function updateImportClause( + clause: ts.ImportClause, + removeCommonModule: boolean, +): ts.ImportClause | null { if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { const removals = removeCommonModule ? importWithCommonRemovals : importRemovals; - const elements = clause.namedBindings.elements.filter(el => !removals.includes(el.getText())); + const elements = clause.namedBindings.elements.filter((el) => !removals.includes(el.getText())); if (elements.length === 0) { return null; } clause = ts.factory.updateImportClause( - clause, clause.isTypeOnly, clause.name, ts.factory.createNamedImports(elements)); + clause, + clause.isTypeOnly, + clause.name, + ts.factory.createNamedImports(elements), + ); } return clause; } function updateClassImports( - propAssignment: ts.PropertyAssignment, removeCommonModule: boolean): string|null { + propAssignment: ts.PropertyAssignment, + removeCommonModule: boolean, +): string | null { const printer = ts.createPrinter(); const importList = propAssignment.initializer; @@ -92,57 +124,73 @@ function updateClassImports( } const removals = removeCommonModule ? importWithCommonRemovals : importRemovals; - const elements = - importList.elements.filter(el => !ts.isIdentifier(el) || !removals.includes(el.text)); + const elements = importList.elements.filter( + (el) => !ts.isIdentifier(el) || !removals.includes(el.text), + ); if (elements.length === importList.elements.length) { // nothing changed return null; } const updatedElements = ts.factory.updateArrayLiteralExpression(importList, elements); - const updatedAssignment = - ts.factory.updatePropertyAssignment(propAssignment, propAssignment.name, updatedElements); + const updatedAssignment = ts.factory.updatePropertyAssignment( + propAssignment, + propAssignment.name, + updatedElements, + ); return printer.printNode( - ts.EmitHint.Unspecified, updatedAssignment, updatedAssignment.getSourceFile()); + ts.EmitHint.Unspecified, + updatedAssignment, + updatedAssignment.getSourceFile(), + ); } function analyzeImportDeclarations( - node: ts.ImportDeclaration, sourceFile: ts.SourceFile, - analyzedFiles: Map) { + node: ts.ImportDeclaration, + sourceFile: ts.SourceFile, + analyzedFiles: Map, +) { if (node.getText().indexOf('@angular/common') === -1) { return; } const clause = node.getChildAt(1) as ts.ImportClause; if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { - const elements = - clause.namedBindings.elements.filter(el => importWithCommonRemovals.includes(el.getText())); + const elements = clause.namedBindings.elements.filter((el) => + importWithCommonRemovals.includes(el.getText()), + ); if (elements.length > 0) { AnalyzedFile.addRange(sourceFile.fileName, sourceFile, analyzedFiles, { start: node.getStart(), end: node.getEnd(), node, type: 'importDeclaration', - remove: true + remove: true, }); } } } function analyzeDecorators( - node: ts.ClassDeclaration, sourceFile: ts.SourceFile, - analyzedFiles: Map) { + node: ts.ClassDeclaration, + sourceFile: ts.SourceFile, + analyzedFiles: Map, +) { // Note: we have a utility to resolve the Angular decorators from a class declaration already. // We don't use it here, because it requires access to the type checker which makes it more // time-consuming to run internally. - const decorator = ts.getDecorators(node)?.find(dec => { - return ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && - dec.expression.expression.text === 'Component'; - }) as (ts.Decorator & {expression: ts.CallExpression}) | - undefined; - - const metadata = decorator && decorator.expression.arguments.length > 0 && - ts.isObjectLiteralExpression(decorator.expression.arguments[0]) ? - decorator.expression.arguments[0] : - null; + const decorator = ts.getDecorators(node)?.find((dec) => { + return ( + ts.isCallExpression(dec.expression) && + ts.isIdentifier(dec.expression.expression) && + dec.expression.expression.text === 'Component' + ); + }) as (ts.Decorator & {expression: ts.CallExpression}) | undefined; + + const metadata = + decorator && + decorator.expression.arguments.length > 0 && + ts.isObjectLiteralExpression(decorator.expression.arguments[0]) + ? decorator.expression.arguments[0] + : null; if (!metadata) { return; @@ -151,8 +199,10 @@ function analyzeDecorators( for (const prop of metadata.properties) { // All the properties we care about should have static // names and be initialized to a static string. - if (!ts.isPropertyAssignment(prop) || - (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name))) { + if ( + !ts.isPropertyAssignment(prop) || + (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name)) + ) { continue; } @@ -182,9 +232,12 @@ function analyzeDecorators( // Leave the end as undefined which means that the range is until the end of the file. if (ts.isStringLiteralLike(prop.initializer)) { const path = join(dirname(sourceFile.fileName), prop.initializer.text); - AnalyzedFile.addRange( - path, sourceFile, analyzedFiles, - {start: 0, node: prop, type: 'templateUrl', remove: true}); + AnalyzedFile.addRange(path, sourceFile, analyzedFiles, { + start: 0, + node: prop, + type: 'templateUrl', + remove: true, + }); } break; } @@ -198,8 +251,10 @@ function getNestedCount(etm: ElementToMigrate, aggregator: number[]) { if (aggregator.length === 0) { return 0; } - if (etm.el.sourceSpan.start.offset < aggregator[aggregator.length - 1] && - etm.el.sourceSpan.end.offset !== aggregator[aggregator.length - 1]) { + if ( + etm.el.sourceSpan.start.offset < aggregator[aggregator.length - 1] && + etm.el.sourceSpan.end.offset !== aggregator[aggregator.length - 1] + ) { // element is nested aggregator.push(etm.el.sourceSpan.end.offset); return aggregator.length - 1; @@ -231,7 +286,7 @@ export function parseTemplate(template: string): ParseResult { // Don't migrate invalid templates. if (parsed.errors && parsed.errors.length > 0) { - const errors = parsed.errors.map(e => ({type: 'parse', error: e})); + const errors = parsed.errors.map((e) => ({type: 'parse', error: e})); return {tree: undefined, errors}; } } catch (e: any) { @@ -247,8 +302,9 @@ export function validateMigratedTemplate(migrated: string, fileName: string): Mi errors.push({ type: 'parse', error: new Error( - `The migration resulted in invalid HTML for ${fileName}. ` + - `Please check the template for valid HTML structures and run the migration again.`) + `The migration resulted in invalid HTML for ${fileName}. ` + + `Please check the template for valid HTML structures and run the migration again.`, + ), }); } if (parsed.tree) { @@ -260,18 +316,19 @@ export function validateMigratedTemplate(migrated: string, fileName: string): Mi return errors; } -export function validateI18nStructure(parsed: ParseTreeResult, fileName: string): Error|null { +export function validateI18nStructure(parsed: ParseTreeResult, fileName: string): Error | null { const visitor = new i18nCollector(); visitAll(visitor, parsed.rootNodes); - const parents = visitor.elements.filter(el => el.children.length > 0); + const parents = visitor.elements.filter((el) => el.children.length > 0); for (const p of parents) { for (const el of visitor.elements) { if (el === p) continue; if (isChildOf(p, el)) { return new Error( - `i18n Nesting error: The migration would result in invalid i18n nesting for ` + + `i18n Nesting error: The migration would result in invalid i18n nesting for ` + `${fileName}. Element with i18n attribute "${p.name}" would result having a child of ` + - `element with i18n attribute "${el.name}". Please fix and re-run the migration.`); + `element with i18n attribute "${el.name}". Please fix and re-run the migration.`, + ); } } } @@ -279,8 +336,10 @@ export function validateI18nStructure(parsed: ParseTreeResult, fileName: string) } function isChildOf(parent: Element, el: Element): boolean { - return parent.sourceSpan.start.offset < el.sourceSpan.start.offset && - parent.sourceSpan.end.offset > el.sourceSpan.end.offset; + return ( + parent.sourceSpan.start.offset < el.sourceSpan.start.offset && + parent.sourceSpan.end.offset > el.sourceSpan.end.offset + ); } /** Possible placeholders that can be generated by `getPlaceholder`. */ @@ -293,7 +352,9 @@ export enum PlaceholderKind { * Wraps a string in a placeholder that makes it easier to identify during replacement operations. */ export function getPlaceholder( - value: string, kind: PlaceholderKind = PlaceholderKind.Default): string { + value: string, + kind: PlaceholderKind = PlaceholderKind.Default, +): string { const name = `<<<ɵɵngControlFlowMigration_${kind}ɵɵ>>>`; return `___${name}${value}${name}___`; } @@ -302,7 +363,9 @@ export function getPlaceholder( * calculates the level of nesting of the items in the collector */ export function calculateNesting( - visitor: ElementCollector|TemplateCollector, hasLineBreaks: boolean): void { + visitor: ElementCollector | TemplateCollector, + hasLineBreaks: boolean, +): void { // start from top of template // loop through each element let nestedQueue: number[] = []; @@ -323,7 +386,7 @@ export function calculateNesting( } function escapeRegExp(val: string) { - return val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + return val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } /** @@ -337,7 +400,11 @@ export function hasLineBreaks(template: string): boolean { * properly adjusts template offsets based on current nesting levels */ export function reduceNestingOffset( - el: ElementToMigrate, nestLevel: number, offset: number, postOffsets: number[]): number { + el: ElementToMigrate, + nestLevel: number, + offset: number, + postOffsets: number[], +): number { if (el.nestCount <= nestLevel) { const count = nestLevel - el.nestCount; // reduced nesting, add postoffset @@ -373,7 +440,9 @@ export function getTemplates(template: string): Map { } export function updateTemplates( - template: string, templates: Map): Map { + template: string, + templates: Map, +): Map { const updatedTemplates = getTemplates(template); for (let [key, tmpl] of updatedTemplates) { templates.set(key, tmpl); @@ -387,7 +456,9 @@ function wrapIntoI18nContainer(i18nAttr: Attribute, content: string) { } function generatei18nContainer( - i18nAttr: Attribute, middle: string): {start: string, middle: string, end: string} { + i18nAttr: Attribute, + middle: string, +): {start: string; middle: string; end: string} { const i18n = i18nAttr.value === '' ? 'i18n' : `i18n="${i18nAttr.value}"`; return {start: ``, middle, end: ``}; } @@ -395,7 +466,7 @@ function generatei18nContainer( /** * Counts, replaces, and removes any necessary ng-templates post control flow migration */ -export function processNgTemplates(template: string): {migrated: string, err: Error|undefined} { +export function processNgTemplates(template: string): {migrated: string; err: Error | undefined} { // count usage try { const templates = getTemplates(template); @@ -451,10 +522,14 @@ function replaceRemainingPlaceholders(template: string): string { const placeholders = [...template.matchAll(replaceRegex)]; for (let ph of placeholders) { const placeholder = ph[0]; - const name = - placeholder.slice(placeholderStart.length, placeholder.length - placeholderEnd.length); - template = - template.replace(placeholder, ``); + const name = placeholder.slice( + placeholderStart.length, + placeholder.length - placeholderEnd.length, + ); + template = template.replace( + placeholder, + ``, + ); } return template; } @@ -490,42 +565,49 @@ export function removeImports(template: string, node: ts.Node, file: AnalyzedFil * retrieves the original block of text in the template for length comparison during migration * processing */ -export function getOriginals(etm: ElementToMigrate, tmpl: string, offset: number): - {start: string, end: string, childLength: number, children: string[], childNodes: Node[]} { +export function getOriginals( + etm: ElementToMigrate, + tmpl: string, + offset: number, +): {start: string; end: string; childLength: number; children: string[]; childNodes: Node[]} { // original opening block if (etm.el.children.length > 0) { const childStart = etm.el.children[0].sourceSpan.start.offset - offset; const childEnd = etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset; const start = tmpl.slice( - etm.el.sourceSpan.start.offset - offset, - etm.el.children[0].sourceSpan.start.offset - offset); + etm.el.sourceSpan.start.offset - offset, + etm.el.children[0].sourceSpan.start.offset - offset, + ); // original closing block const end = tmpl.slice( - etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset, - etm.el.sourceSpan.end.offset - offset); + etm.el.children[etm.el.children.length - 1].sourceSpan.end.offset - offset, + etm.el.sourceSpan.end.offset - offset, + ); const childLength = childEnd - childStart; return { start, end, childLength, children: getOriginalChildren(etm.el.children, tmpl, offset), - childNodes: etm.el.children + childNodes: etm.el.children, }; } // self closing or no children - const start = - tmpl.slice(etm.el.sourceSpan.start.offset - offset, etm.el.sourceSpan.end.offset - offset); + const start = tmpl.slice( + etm.el.sourceSpan.start.offset - offset, + etm.el.sourceSpan.end.offset - offset, + ); // original closing block return {start, end: '', childLength: 0, children: [], childNodes: []}; } function getOriginalChildren(children: Node[], tmpl: string, offset: number) { - return children.map(child => { + return children.map((child) => { return tmpl.slice(child.sourceSpan.start.offset - offset, child.sourceSpan.end.offset - offset); }); } -function isI18nTemplate(etm: ElementToMigrate, i18nAttr: Attribute|undefined): boolean { +function isI18nTemplate(etm: ElementToMigrate, i18nAttr: Attribute | undefined): boolean { let attrCount = countAttributes(etm); const safeToRemove = etm.el.attrs.length === attrCount + (i18nAttr !== undefined ? 1 : 0); return etm.el.name === 'ng-template' && i18nAttr !== undefined && safeToRemove; @@ -555,9 +637,12 @@ function countAttributes(etm: ElementToMigrate): number { /** * builds the proper contents of what goes inside a given control flow block after migration */ -export function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number): - {start: string, middle: string, end: string} { - const i18nAttr = etm.el.attrs.find(x => x.name === 'i18n'); +export function getMainBlock( + etm: ElementToMigrate, + tmpl: string, + offset: number, +): {start: string; middle: string; end: string} { + const i18nAttr = etm.el.attrs.find((x) => x.name === 'i18n'); // removable containers are ng-templates or ng-containers that no longer need to exist // post migration @@ -584,8 +669,9 @@ export function getMainBlock(etm: ElementToMigrate, tmpl: string, offset: number const valEnd = etm.getValueEnd(offset); // the index of the children start and end span, if they exist. Otherwise use the value end. - const {childStart, childEnd} = - etm.hasChildren() ? etm.getChildSpan(offset) : {childStart: valEnd, childEnd: valEnd}; + const {childStart, childEnd} = etm.hasChildren() + ? etm.getChildSpan(offset) + : {childStart: valEnd, childEnd: valEnd}; // the beginning of the updated string in the main block, for example:
let start = tmpl.slice(etm.start(offset), attrStart) + tmpl.slice(valEnd, childStart); @@ -627,8 +713,13 @@ function generateI18nMarkers(tmpl: string): string { function addI18nMarkers(tmpl: string, el: Element, offset: number): string { const startPos = el.children[0].sourceSpan.start.offset + offset; const endPos = el.children[el.children.length - 1].sourceSpan.end.offset + offset; - return tmpl.slice(0, startPos) + startI18nMarker + tmpl.slice(startPos, endPos) + endI18nMarker + - tmpl.slice(endPos); + return ( + tmpl.slice(0, startPos) + + startI18nMarker + + tmpl.slice(startPos, endPos) + + endI18nMarker + + tmpl.slice(endPos) + ); } const selfClosingList = 'input|br|img|base|wbr|area|col|embed|hr|link|meta|param|source|track'; @@ -706,18 +797,25 @@ export function formatTemplate(tmpl: string, templateType: string): string { let isDoubleQuotes = false; for (let [index, line] of lines.entries()) { depth += - [...line.matchAll(startMarkerRegex)].length - [...line.matchAll(endMarkerRegex)].length; + [...line.matchAll(startMarkerRegex)].length - [...line.matchAll(endMarkerRegex)].length; inMigratedBlock = depth > 0; - i18nDepth += [...line.matchAll(startI18nMarkerRegex)].length - - [...line.matchAll(endI18nMarkerRegex)].length; + i18nDepth += + [...line.matchAll(startI18nMarkerRegex)].length - + [...line.matchAll(endI18nMarkerRegex)].length; let lineWasMigrated = false; if (line.match(replaceMarkerRegex)) { line = line.replace(replaceMarkerRegex, ''); lineWasMigrated = true; } - if ((line.trim() === '' && index !== 0 && index !== lines.length - 1) && - (inMigratedBlock || lineWasMigrated) && !inI18nBlock && !inAttribute) { + if ( + line.trim() === '' && + index !== 0 && + index !== lines.length - 1 && + (inMigratedBlock || lineWasMigrated) && + !inI18nBlock && + !inAttribute + ) { // skip blank lines except if it's the first line or last line // this preserves leading and trailing spaces if they are already present continue; @@ -726,15 +824,18 @@ export function formatTemplate(tmpl: string, templateType: string): string { if (templateType === 'template' && index <= 1) { // first real line of an inline template const ind = line.search(/\S/); - mindent = (ind > -1) ? line.slice(0, ind) : ''; + mindent = ind > -1 ? line.slice(0, ind) : ''; } // if a block closes, an element closes, and it's not an element on a single line or the end // of a self closing tag - if ((closeBlockRegex.test(line) || - (closeElRegex.test(line) && - (!singleLineElRegex.test(line) && !closeMultiLineElRegex.test(line)))) && - indent !== '') { + if ( + (closeBlockRegex.test(line) || + (closeElRegex.test(line) && + !singleLineElRegex.test(line) && + !closeMultiLineElRegex.test(line))) && + indent !== '' + ) { // close block, reduce indent indent = indent.slice(2); } @@ -750,14 +851,18 @@ export function formatTemplate(tmpl: string, templateType: string): string { isDoubleQuotes = false; } - const newLine = (inI18nBlock || inAttribute) ? - line : - mindent + (line.trim() !== '' ? indent : '') + line.trim(); + const newLine = + inI18nBlock || inAttribute + ? line + : mindent + (line.trim() !== '' ? indent : '') + line.trim(); formatted.push(newLine); - if (!isOpenDoubleAttr && !isOpenSingleAttr && - ((inAttribute && isDoubleQuotes && closeAttrDoubleRegex.test(line)) || - (inAttribute && !isDoubleQuotes && closeAttrSingleRegex.test(line)))) { + if ( + !isOpenDoubleAttr && + !isOpenSingleAttr && + ((inAttribute && isDoubleQuotes && closeAttrDoubleRegex.test(line)) || + (inAttribute && !isDoubleQuotes && closeAttrSingleRegex.test(line))) + ) { inAttribute = false; } @@ -778,8 +883,12 @@ export function formatTemplate(tmpl: string, templateType: string): string { // this matches an open control flow block, an open HTML element, but excludes single line // self closing tags - if ((openBlockRegex.test(line) || openElRegex.test(line)) && !singleLineElRegex.test(line) && - !selfClosingRegex.test(line) && !openSelfClosingRegex.test(line)) { + if ( + (openBlockRegex.test(line) || openElRegex.test(line)) && + !singleLineElRegex.test(line) && + !selfClosingRegex.test(line) && + !openSelfClosingRegex.test(line) + ) { // open block, increase indent indent += ' '; } @@ -799,7 +908,9 @@ export function formatTemplate(tmpl: string, templateType: string): string { /** Executes a callback on each class declaration in a file. */ function forEachClass( - sourceFile: ts.SourceFile, callback: (node: ts.ClassDeclaration|ts.ImportDeclaration) => void) { + sourceFile: ts.SourceFile, + callback: (node: ts.ClassDeclaration | ts.ImportDeclaration) => void, +) { sourceFile.forEachChild(function walk(node) { if (ts.isClassDeclaration(node) || ts.isImportDeclaration(node)) { callback(node); diff --git a/packages/core/schematics/ng-generate/standalone-migration/index.ts b/packages/core/schematics/ng-generate/standalone-migration/index.ts index 9b04ff6c87ae3..2e16704904ab9 100644 --- a/packages/core/schematics/ng-generate/standalone-migration/index.ts +++ b/packages/core/schematics/ng-generate/standalone-migration/index.ts @@ -32,7 +32,7 @@ interface Options { mode: MigrationMode; } -export default function(options: Options): Rule { +export default function (options: Options): Rule { return async (tree, context) => { const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); const basePath = process.cwd(); @@ -44,7 +44,8 @@ export default function(options: Options): Rule { if (!allPaths.length) { throw new SchematicsException( - 'Could not find any tsconfig file. Cannot run the standalone migration.'); + 'Could not find any tsconfig file. Cannot run the standalone migration.', + ); } for (const tsconfigPath of allPaths) { @@ -52,69 +53,109 @@ export default function(options: Options): Rule { } if (migratedFiles === 0) { - throw new SchematicsException(`Could not find any files to migrate under the path ${ - pathToMigrate}. Cannot run the standalone migration.`); + throw new SchematicsException( + `Could not find any files to migrate under the path ${pathToMigrate}. Cannot run the standalone migration.`, + ); } context.logger.info('🎉 Automated migration step has finished! 🎉'); context.logger.info( - 'IMPORTANT! Please verify manually that your application builds and behaves as expected.'); + 'IMPORTANT! Please verify manually that your application builds and behaves as expected.', + ); context.logger.info( - `See https://angular.dev/reference/migrations/standalone for more information.`); + `See https://angular.dev/reference/migrations/standalone for more information.`, + ); }; } function standaloneMigration( - tree: Tree, tsconfigPath: string, basePath: string, pathToMigrate: string, - schematicOptions: Options, oldProgram?: NgtscProgram): number { + tree: Tree, + tsconfigPath: string, + basePath: string, + pathToMigrate: string, + schematicOptions: Options, + oldProgram?: NgtscProgram, +): number { if (schematicOptions.path.startsWith('..')) { throw new SchematicsException( - 'Cannot run standalone migration outside of the current project.'); + 'Cannot run standalone migration outside of the current project.', + ); } const {host, options, rootNames} = createProgramOptions( - tree, tsconfigPath, basePath, undefined, undefined, - { - _enableTemplateTypeChecker: true, // Required for the template type checker to work. - compileNonExportedClasses: true, // We want to migrate non-exported classes too. - // Avoid checking libraries to speed up the migration. - skipLibCheck: true, - skipDefaultLibCheck: true, - }); + tree, + tsconfigPath, + basePath, + undefined, + undefined, + { + _enableTemplateTypeChecker: true, // Required for the template type checker to work. + compileNonExportedClasses: true, // We want to migrate non-exported classes too. + // Avoid checking libraries to speed up the migration. + skipLibCheck: true, + skipDefaultLibCheck: true, + }, + ); const referenceLookupExcludedFiles = /node_modules|\.ngtypecheck\.ts/; const program = createProgram({rootNames, host, options, oldProgram}) as NgtscProgram; const printer = ts.createPrinter(); if (existsSync(pathToMigrate) && !statSync(pathToMigrate).isDirectory()) { - throw new SchematicsException(`Migration path ${ - pathToMigrate} has to be a directory. Cannot run the standalone migration.`); + throw new SchematicsException( + `Migration path ${pathToMigrate} has to be a directory. Cannot run the standalone migration.`, + ); } - const sourceFiles = program.getTsProgram().getSourceFiles().filter( - sourceFile => sourceFile.fileName.startsWith(pathToMigrate) && - canMigrateFile(basePath, sourceFile, program.getTsProgram())); + const sourceFiles = program + .getTsProgram() + .getSourceFiles() + .filter( + (sourceFile) => + sourceFile.fileName.startsWith(pathToMigrate) && + canMigrateFile(basePath, sourceFile, program.getTsProgram()), + ); if (sourceFiles.length === 0) { return 0; } let pendingChanges: ChangesByFile; - let filesToRemove: Set|null = null; + let filesToRemove: Set | null = null; if (schematicOptions.mode === MigrationMode.pruneModules) { const result = pruneNgModules( - program, host, basePath, rootNames, sourceFiles, printer, undefined, - referenceLookupExcludedFiles); + program, + host, + basePath, + rootNames, + sourceFiles, + printer, + undefined, + referenceLookupExcludedFiles, + ); pendingChanges = result.pendingChanges; filesToRemove = result.filesToRemove; } else if (schematicOptions.mode === MigrationMode.standaloneBootstrap) { pendingChanges = toStandaloneBootstrap( - program, host, basePath, rootNames, sourceFiles, printer, undefined, - referenceLookupExcludedFiles, knownInternalAliasRemapper); + program, + host, + basePath, + rootNames, + sourceFiles, + printer, + undefined, + referenceLookupExcludedFiles, + knownInternalAliasRemapper, + ); } else { // This shouldn't happen, but default to `MigrationMode.toStandalone` just in case. - pendingChanges = - toStandalone(sourceFiles, program, printer, undefined, knownInternalAliasRemapper); + pendingChanges = toStandalone( + sourceFiles, + program, + printer, + undefined, + knownInternalAliasRemapper, + ); } for (const [file, changes] of pendingChanges.entries()) { @@ -125,7 +166,7 @@ function standaloneMigration( const update = tree.beginUpdate(relative(basePath, file.fileName)); - changes.forEach(change => { + changes.forEach((change) => { if (change.removeLength != null) { update.remove(change.start, change.removeLength); } @@ -145,10 +186,16 @@ function standaloneMigration( // Note that we can't run the module pruning internally without propagating the changes to disk, // because there may be conflicting AST node changes. if (schematicOptions.mode === MigrationMode.standaloneBootstrap) { - return standaloneMigration( - tree, tsconfigPath, basePath, pathToMigrate, - {...schematicOptions, mode: MigrationMode.pruneModules}, program) + - sourceFiles.length; + return ( + standaloneMigration( + tree, + tsconfigPath, + basePath, + pathToMigrate, + {...schematicOptions, mode: MigrationMode.pruneModules}, + program, + ) + sourceFiles.length + ); } return sourceFiles.length; diff --git a/packages/core/schematics/ng-generate/standalone-migration/prune-modules.ts b/packages/core/schematics/ng-generate/standalone-migration/prune-modules.ts index e3016c520ab51..79bd01ea42c86 100644 --- a/packages/core/schematics/ng-generate/standalone-migration/prune-modules.ts +++ b/packages/core/schematics/ng-generate/standalone-migration/prune-modules.ts @@ -13,7 +13,14 @@ import {ChangeTracker, ImportRemapper} from '../../utils/change_tracker'; import {getAngularDecorators, NgDecorator} from '../../utils/ng_decorators'; import {closestNode} from '../../utils/typescript/nodes'; -import {findClassDeclaration, findLiteralProperty, getNodeLookup, offsetsToNodes, ReferenceResolver, UniqueItemTracker} from './util'; +import { + findClassDeclaration, + findLiteralProperty, + getNodeLookup, + offsetsToNodes, + ReferenceResolver, + UniqueItemTracker, +} from './util'; /** Keeps track of the places from which we need to remove AST nodes. */ interface RemovalLocations { @@ -24,20 +31,31 @@ interface RemovalLocations { } export function pruneNgModules( - program: NgtscProgram, host: ts.CompilerHost, basePath: string, rootFileNames: string[], - sourceFiles: ts.SourceFile[], printer: ts.Printer, importRemapper?: ImportRemapper, - referenceLookupExcludedFiles?: RegExp) { + program: NgtscProgram, + host: ts.CompilerHost, + basePath: string, + rootFileNames: string[], + sourceFiles: ts.SourceFile[], + printer: ts.Printer, + importRemapper?: ImportRemapper, + referenceLookupExcludedFiles?: RegExp, +) { const filesToRemove = new Set(); const tracker = new ChangeTracker(printer, importRemapper); const tsProgram = program.getTsProgram(); const typeChecker = tsProgram.getTypeChecker(); - const referenceResolver = - new ReferenceResolver(program, host, rootFileNames, basePath, referenceLookupExcludedFiles); + const referenceResolver = new ReferenceResolver( + program, + host, + rootFileNames, + basePath, + referenceLookupExcludedFiles, + ); const removalLocations: RemovalLocations = { arrays: new UniqueItemTracker(), imports: new UniqueItemTracker(), exports: new UniqueItemTracker(), - unknown: new Set() + unknown: new Set(), }; const classesToRemove = new Set(); const barrelExports = new UniqueItemTracker(); @@ -48,10 +66,15 @@ export function pruneNgModules( collectRemovalLocations(node, removalLocations, referenceResolver, program); classesToRemove.add(node); } else if ( - ts.isExportDeclaration(node) && !node.exportClause && node.moduleSpecifier && - ts.isStringLiteralLike(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith('.')) { - const exportedSourceFile = - typeChecker.getSymbolAtLocation(node.moduleSpecifier)?.valueDeclaration?.getSourceFile(); + ts.isExportDeclaration(node) && + !node.exportClause && + node.moduleSpecifier && + ts.isStringLiteralLike(node.moduleSpecifier) && + node.moduleSpecifier.text.startsWith('.') + ) { + const exportedSourceFile = typeChecker + .getSymbolAtLocation(node.moduleSpecifier) + ?.valueDeclaration?.getSourceFile(); if (exportedSourceFile) { barrelExports.track(exportedSourceFile, node); @@ -105,8 +128,11 @@ export function pruneNgModules( * @param program */ function collectRemovalLocations( - ngModule: ts.ClassDeclaration, removalLocations: RemovalLocations, - referenceResolver: ReferenceResolver, program: NgtscProgram) { + ngModule: ts.ClassDeclaration, + removalLocations: RemovalLocations, + referenceResolver: ReferenceResolver, + program: NgtscProgram, +) { const refsByFile = referenceResolver.findReferencesInProject(ngModule.name!); const tsProgram = program.getTsProgram(); const nodes = new Set(); @@ -148,14 +174,18 @@ function collectRemovalLocations( * @param tracker Tracker in which to register the changes. */ function removeArrayReferences( - locations: UniqueItemTracker, - tracker: ChangeTracker): void { + locations: UniqueItemTracker, + tracker: ChangeTracker, +): void { for (const [array, toRemove] of locations.getEntries()) { const newElements = filterRemovedElements(array.elements, toRemove); tracker.replaceNode( + array, + ts.factory.updateArrayLiteralExpression( array, - ts.factory.updateArrayLiteralExpression( - array, ts.factory.createNodeArray(newElements, array.elements.hasTrailingComma))); + ts.factory.createNodeArray(newElements, array.elements.hasTrailingComma), + ), + ); } } @@ -165,7 +195,9 @@ function removeArrayReferences( * @param tracker Tracker in which to register the changes. */ function removeImportReferences( - locations: UniqueItemTracker, tracker: ChangeTracker) { + locations: UniqueItemTracker, + tracker: ChangeTracker, +) { for (const [namedImports, toRemove] of locations.getEntries()) { const newElements = filterRemovedElements(namedImports.elements, toRemove); @@ -177,9 +209,14 @@ function removeImportReferences( // e.g. `import Foo, {ModuleToRemove} from './foo';` becomes `import Foo from './foo';`. if (importClause && importClause.name) { tracker.replaceNode( + importClause, + ts.factory.updateImportClause( importClause, - ts.factory.updateImportClause( - importClause, importClause.isTypeOnly, importClause.name, undefined)); + importClause.isTypeOnly, + importClause.name, + undefined, + ), + ); } else { // Otherwise we can drop the entire declaration. const declaration = closestNode(namedImports, ts.isImportDeclaration); @@ -201,7 +238,9 @@ function removeImportReferences( * @param tracker Tracker in which to register the changes. */ function removeExportReferences( - locations: UniqueItemTracker, tracker: ChangeTracker) { + locations: UniqueItemTracker, + tracker: ChangeTracker, +) { for (const [namedExports, toRemove] of locations.getEntries()) { const newElements = filterRemovedElements(namedExports.elements, toRemove); @@ -238,14 +277,16 @@ function canRemoveClass(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker): } // Unsupported case, e.g. `@NgModule(SOME_VALUE)`. - if (decorator.expression.arguments.length > 0 && - !ts.isObjectLiteralExpression(decorator.expression.arguments[0])) { + if ( + decorator.expression.arguments.length > 0 && + !ts.isObjectLiteralExpression(decorator.expression.arguments[0]) + ) { return false; } // We can't remove modules that have class members. We make an exception for an // empty constructor which may have been generated by a tool and forgotten. - if (node.members.length > 0 && node.members.some(member => !isEmptyConstructor(member))) { + if (node.members.length > 0 && node.members.some((member) => !isEmptyConstructor(member))) { return false; } @@ -266,13 +307,17 @@ function canRemoveClass(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker): } const depDeclaration = findClassDeclaration(dep, typeChecker); - const depNgModule = - depDeclaration ? findNgModuleDecorator(depDeclaration, typeChecker) : null; + const depNgModule = depDeclaration + ? findNgModuleDecorator(depDeclaration, typeChecker) + : null; // If any of the dependencies of the class is an `NgModule` that can't be removed, the class // itself can't be removed either, because it may be part of a transitive dependency chain. - if (depDeclaration !== null && depNgModule !== null && - !canRemoveClass(depDeclaration, typeChecker)) { + if ( + depDeclaration !== null && + depNgModule !== null && + !canRemoveClass(depDeclaration, typeChecker) + ) { return false; } } @@ -282,9 +327,12 @@ function canRemoveClass(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker): // Also err on the side of caution and don't remove modules where any of the aforementioned // properties aren't initialized to an array literal. for (const prop of literal.properties) { - if (isNonEmptyNgModuleProperty(prop) && - (prop.name.text === 'declarations' || prop.name.text === 'providers' || - prop.name.text === 'bootstrap')) { + if ( + isNonEmptyNgModuleProperty(prop) && + (prop.name.text === 'declarations' || + prop.name.text === 'providers' || + prop.name.text === 'bootstrap') + ) { return false; } } @@ -298,10 +346,15 @@ function canRemoveClass(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker): * element. * @param node Node to be checked. */ -function isNonEmptyNgModuleProperty(node: ts.Node): node is ts.PropertyAssignment& - {name: ts.Identifier, initializer: ts.ArrayLiteralExpression} { - return ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && - ts.isArrayLiteralExpression(node.initializer) && node.initializer.elements.length > 0; +function isNonEmptyNgModuleProperty( + node: ts.Node, +): node is ts.PropertyAssignment & {name: ts.Identifier; initializer: ts.ArrayLiteralExpression} { + return ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + ts.isArrayLiteralExpression(node.initializer) && + node.initializer.elements.length > 0 + ); } /** @@ -316,9 +369,11 @@ function canRemoveFile(sourceFile: ts.SourceFile, nodesToBeRemoved: Set continue; } - if (ts.isExportDeclaration(node) || - (ts.canHaveModifiers(node) && - ts.getModifiers(node)?.some(m => m.kind === ts.SyntaxKind.ExportKeyword))) { + if ( + ts.isExportDeclaration(node) || + (ts.canHaveModifiers(node) && + ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) + ) { return false; } } @@ -332,9 +387,12 @@ function canRemoveFile(sourceFile: ts.SourceFile, nodesToBeRemoved: Set * @param child Child node that is being checked. */ function contains(parent: ts.Node, child: ts.Node): boolean { - return parent === child || - (parent.getSourceFile().fileName === child.getSourceFile().fileName && - child.getStart() >= parent.getStart() && child.getStart() <= parent.getEnd()); + return ( + parent === child || + (parent.getSourceFile().fileName === child.getSourceFile().fileName && + child.getStart() >= parent.getStart() && + child.getStart() <= parent.getEnd()) + ); } /** @@ -343,8 +401,10 @@ function contains(parent: ts.Node, child: ts.Node): boolean { * @param toRemove Nodes that should be removed. */ function filterRemovedElements( - elements: ts.NodeArray, toRemove: Set): T[] { - return elements.filter(el => { + elements: ts.NodeArray, + toRemove: Set, +): T[] { + return elements.filter((el) => { for (const node of toRemove) { // Check that the element contains the node, despite knowing with relative certainty that it // does, because this allows us to unwrap some nodes. E.g. if we have `[((toRemove))]`, we @@ -359,8 +419,11 @@ function filterRemovedElements( /** Returns whether a node as an empty constructor. */ function isEmptyConstructor(node: ts.Node): boolean { - return ts.isConstructorDeclaration(node) && node.parameters.length === 0 && - (node.body == null || node.body.statements.length === 0); + return ( + ts.isConstructorDeclaration(node) && + node.parameters.length === 0 && + (node.body == null || node.body.statements.length === 0) + ); } /** @@ -376,14 +439,18 @@ function addRemovalTodos(nodes: Set, tracker: ChangeTracker) { // the same node. In practice it is unlikely, because the second time the node won't be picked // up by the language service as a reference, because the class won't exist anymore. tracker.insertText( - node.getSourceFile(), node.getFullStart(), - ` /* TODO(standalone-migration): clean up removed NgModule reference manually. */ `); + node.getSourceFile(), + node.getFullStart(), + ` /* TODO(standalone-migration): clean up removed NgModule reference manually. */ `, + ); } } /** Finds the `NgModule` decorator in a class, if it exists. */ -function findNgModuleDecorator(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker): NgDecorator| - null { +function findNgModuleDecorator( + node: ts.ClassDeclaration, + typeChecker: ts.TypeChecker, +): NgDecorator | null { const decorators = getAngularDecorators(typeChecker, ts.getDecorators(node) || []); - return decorators.find(decorator => decorator.name === 'NgModule') || null; + return decorators.find((decorator) => decorator.name === 'NgModule') || null; } diff --git a/packages/core/schematics/ng-generate/standalone-migration/standalone-bootstrap.ts b/packages/core/schematics/ng-generate/standalone-migration/standalone-bootstrap.ts index 147603dce4c10..e87fa89db8e25 100644 --- a/packages/core/schematics/ng-generate/standalone-migration/standalone-bootstrap.ts +++ b/packages/core/schematics/ng-generate/standalone-migration/standalone-bootstrap.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {NgtscProgram} from '@angular/compiler-cli'; import {TemplateTypeChecker} from '@angular/compiler-cli/private/migrations'; import {dirname, join} from 'path'; @@ -16,8 +15,26 @@ import {ChangeTracker, ImportRemapper} from '../../utils/change_tracker'; import {getAngularDecorators} from '../../utils/ng_decorators'; import {closestNode} from '../../utils/typescript/nodes'; -import {ComponentImportsRemapper, convertNgModuleDeclarationToStandalone, extractDeclarationsFromModule, findTestObjectsToMigrate, migrateTestDeclarations} from './to-standalone'; -import {closestOrSelf, findClassDeclaration, findLiteralProperty, getNodeLookup, getRelativeImportPath, isClassReferenceInAngularModule, NamedClassDeclaration, NodeLookup, offsetsToNodes, ReferenceResolver, UniqueItemTracker} from './util'; +import { + ComponentImportsRemapper, + convertNgModuleDeclarationToStandalone, + extractDeclarationsFromModule, + findTestObjectsToMigrate, + migrateTestDeclarations, +} from './to-standalone'; +import { + closestOrSelf, + findClassDeclaration, + findLiteralProperty, + getNodeLookup, + getRelativeImportPath, + isClassReferenceInAngularModule, + NamedClassDeclaration, + NodeLookup, + offsetsToNodes, + ReferenceResolver, + UniqueItemTracker, +} from './util'; /** Information extracted from a `bootstrapModule` call necessary to migrate it. */ interface BootstrapCallAnalysis { @@ -34,29 +51,44 @@ interface BootstrapCallAnalysis { } export function toStandaloneBootstrap( - program: NgtscProgram, host: ts.CompilerHost, basePath: string, rootFileNames: string[], - sourceFiles: ts.SourceFile[], printer: ts.Printer, importRemapper?: ImportRemapper, - referenceLookupExcludedFiles?: RegExp, componentImportRemapper?: ComponentImportsRemapper) { + program: NgtscProgram, + host: ts.CompilerHost, + basePath: string, + rootFileNames: string[], + sourceFiles: ts.SourceFile[], + printer: ts.Printer, + importRemapper?: ImportRemapper, + referenceLookupExcludedFiles?: RegExp, + componentImportRemapper?: ComponentImportsRemapper, +) { const tracker = new ChangeTracker(printer, importRemapper); const typeChecker = program.getTsProgram().getTypeChecker(); const templateTypeChecker = program.compiler.getTemplateTypeChecker(); - const referenceResolver = - new ReferenceResolver(program, host, rootFileNames, basePath, referenceLookupExcludedFiles); + const referenceResolver = new ReferenceResolver( + program, + host, + rootFileNames, + basePath, + referenceLookupExcludedFiles, + ); const bootstrapCalls: BootstrapCallAnalysis[] = []; const testObjects = new Set(); const allDeclarations = new Set(); // `bootstrapApplication` doesn't include Protractor support by default // anymore so we have to opt the app in, if we detect it being used. - const additionalProviders = hasImport(program, rootFileNames, 'protractor') ? - new Map([['provideProtractorTestingSupport', '@angular/platform-browser']]) : - null; + const additionalProviders = hasImport(program, rootFileNames, 'protractor') + ? new Map([['provideProtractorTestingSupport', '@angular/platform-browser']]) + : null; for (const sourceFile of sourceFiles) { sourceFile.forEachChild(function walk(node) { - if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && - node.expression.name.text === 'bootstrapModule' && - isClassReferenceInAngularModule(node.expression, 'PlatformRef', 'core', typeChecker)) { + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + node.expression.name.text === 'bootstrapModule' && + isClassReferenceInAngularModule(node.expression, 'PlatformRef', 'core', typeChecker) + ) { const call = analyzeBootstrapCall(node, typeChecker, templateTypeChecker); if (call) { @@ -66,20 +98,31 @@ export function toStandaloneBootstrap( node.forEachChild(walk); }); - findTestObjectsToMigrate(sourceFile, typeChecker).forEach(obj => testObjects.add(obj)); + findTestObjectsToMigrate(sourceFile, typeChecker).forEach((obj) => testObjects.add(obj)); } for (const call of bootstrapCalls) { - call.declarations.forEach(decl => allDeclarations.add(decl)); + call.declarations.forEach((decl) => allDeclarations.add(decl)); migrateBootstrapCall( - call, tracker, additionalProviders, referenceResolver, typeChecker, printer); + call, + tracker, + additionalProviders, + referenceResolver, + typeChecker, + printer, + ); } // The previous migrations explicitly skip over bootstrapped // declarations so we have to migrate them now. for (const declaration of allDeclarations) { convertNgModuleDeclarationToStandalone( - declaration, allDeclarations, tracker, templateTypeChecker, componentImportRemapper); + declaration, + allDeclarations, + tracker, + templateTypeChecker, + componentImportRemapper, + ); } migrateTestDeclarations(testObjects, allDeclarations, tracker, templateTypeChecker, typeChecker); @@ -94,8 +137,10 @@ export function toStandaloneBootstrap( * @param templateTypeChecker */ function analyzeBootstrapCall( - call: ts.CallExpression, typeChecker: ts.TypeChecker, - templateTypeChecker: TemplateTypeChecker): BootstrapCallAnalysis|null { + call: ts.CallExpression, + typeChecker: ts.TypeChecker, + templateTypeChecker: TemplateTypeChecker, +): BootstrapCallAnalysis | null { if (call.arguments.length === 0 || !ts.isIdentifier(call.arguments[0])) { return null; } @@ -106,21 +151,28 @@ function analyzeBootstrapCall( return null; } - const decorator = getAngularDecorators(typeChecker, ts.getDecorators(declaration) || []) - .find(decorator => decorator.name === 'NgModule'); + const decorator = getAngularDecorators(typeChecker, ts.getDecorators(declaration) || []).find( + (decorator) => decorator.name === 'NgModule', + ); - if (!decorator || decorator.node.expression.arguments.length === 0 || - !ts.isObjectLiteralExpression(decorator.node.expression.arguments[0])) { + if ( + !decorator || + decorator.node.expression.arguments.length === 0 || + !ts.isObjectLiteralExpression(decorator.node.expression.arguments[0]) + ) { return null; } const metadata = decorator.node.expression.arguments[0]; const bootstrapProp = findLiteralProperty(metadata, 'bootstrap'); - if (!bootstrapProp || !ts.isPropertyAssignment(bootstrapProp) || - !ts.isArrayLiteralExpression(bootstrapProp.initializer) || - bootstrapProp.initializer.elements.length === 0 || - !ts.isIdentifier(bootstrapProp.initializer.elements[0])) { + if ( + !bootstrapProp || + !ts.isPropertyAssignment(bootstrapProp) || + !ts.isArrayLiteralExpression(bootstrapProp.initializer) || + bootstrapProp.initializer.elements.length === 0 || + !ts.isIdentifier(bootstrapProp.initializer.elements[0]) + ) { return null; } @@ -132,7 +184,7 @@ function analyzeBootstrapCall( metadata, component: component as NamedClassDeclaration, call, - declarations: extractDeclarationsFromModule(declaration, templateTypeChecker) + declarations: extractDeclarationsFromModule(declaration, templateTypeChecker), }; } @@ -150,9 +202,13 @@ function analyzeBootstrapCall( * @param printer */ function migrateBootstrapCall( - analysis: BootstrapCallAnalysis, tracker: ChangeTracker, - additionalProviders: Map|null, referenceResolver: ReferenceResolver, - typeChecker: ts.TypeChecker, printer: ts.Printer) { + analysis: BootstrapCallAnalysis, + tracker: ChangeTracker, + additionalProviders: Map | null, + referenceResolver: ReferenceResolver, + typeChecker: ts.TypeChecker, + printer: ts.Printer, +) { const sourceFile = analysis.call.getSourceFile(); const moduleSourceFile = analysis.metadata.getSourceFile(); const providers = findLiteralProperty(analysis.metadata, 'providers'); @@ -160,13 +216,15 @@ function migrateBootstrapCall( const nodesToCopy = new Set(); const providersInNewCall: ts.Expression[] = []; const moduleImportsInNewCall: ts.Expression[] = []; - let nodeLookup: NodeLookup|null = null; + let nodeLookup: NodeLookup | null = null; // Comment out the metadata so that it'll be removed when we run the module pruning afterwards. // If the pruning is left for some reason, the user will still have an actionable TODO. tracker.insertText( - moduleSourceFile, analysis.metadata.getStart(), - '/* TODO(standalone-migration): clean up removed NgModule class manually. \n'); + moduleSourceFile, + analysis.metadata.getStart(), + '/* TODO(standalone-migration): clean up removed NgModule class manually. \n', + ); tracker.insertText(moduleSourceFile, analysis.metadata.getEnd(), ' */'); if (providers && ts.isPropertyAssignment(providers)) { @@ -184,20 +242,33 @@ function migrateBootstrapCall( if (imports && ts.isPropertyAssignment(imports)) { nodeLookup = nodeLookup || getNodeLookup(moduleSourceFile); migrateImportsForBootstrapCall( - sourceFile, imports, nodeLookup, moduleImportsInNewCall, providersInNewCall, tracker, - nodesToCopy, referenceResolver, typeChecker); + sourceFile, + imports, + nodeLookup, + moduleImportsInNewCall, + providersInNewCall, + tracker, + nodesToCopy, + referenceResolver, + typeChecker, + ); } if (additionalProviders) { additionalProviders.forEach((moduleSpecifier, name) => { - providersInNewCall.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, name, moduleSpecifier), undefined, undefined)); + providersInNewCall.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, name, moduleSpecifier), + undefined, + undefined, + ), + ); }); } if (nodesToCopy.size > 0) { let text = '\n\n'; - nodesToCopy.forEach(node => { + nodesToCopy.forEach((node) => { const transformedNode = remapDynamicImports(sourceFile.fileName, node); // Use `getText` to try an preserve the original formatting. This only works if the node @@ -223,45 +294,66 @@ function migrateBootstrapCall( * @param tracker Object keeping track of the changes to the different files. */ function replaceBootstrapCallExpression( - analysis: BootstrapCallAnalysis, providers: ts.Expression[], modules: ts.Expression[], - tracker: ChangeTracker): void { + analysis: BootstrapCallAnalysis, + providers: ts.Expression[], + modules: ts.Expression[], + tracker: ChangeTracker, +): void { const sourceFile = analysis.call.getSourceFile(); - const componentPath = - getRelativeImportPath(sourceFile.fileName, analysis.component.getSourceFile().fileName); + const componentPath = getRelativeImportPath( + sourceFile.fileName, + analysis.component.getSourceFile().fileName, + ); const args = [tracker.addImport(sourceFile, analysis.component.name.text, componentPath)]; - const bootstrapExpression = - tracker.addImport(sourceFile, 'bootstrapApplication', '@angular/platform-browser'); + const bootstrapExpression = tracker.addImport( + sourceFile, + 'bootstrapApplication', + '@angular/platform-browser', + ); if (providers.length > 0 || modules.length > 0) { const combinedProviders: ts.Expression[] = []; if (modules.length > 0) { - const importProvidersExpression = - tracker.addImport(sourceFile, 'importProvidersFrom', '@angular/core'); + const importProvidersExpression = tracker.addImport( + sourceFile, + 'importProvidersFrom', + '@angular/core', + ); combinedProviders.push( - ts.factory.createCallExpression(importProvidersExpression, [], modules)); + ts.factory.createCallExpression(importProvidersExpression, [], modules), + ); } // Push the providers after `importProvidersFrom` call for better readability. combinedProviders.push(...providers); const providersArray = ts.factory.createNodeArray( - combinedProviders, - analysis.metadata.properties.hasTrailingComma && combinedProviders.length > 2); + combinedProviders, + analysis.metadata.properties.hasTrailingComma && combinedProviders.length > 2, + ); const initializer = remapDynamicImports( - sourceFile.fileName, - ts.factory.createArrayLiteralExpression(providersArray, combinedProviders.length > 1)); - - args.push(ts.factory.createObjectLiteralExpression( - [ts.factory.createPropertyAssignment('providers', initializer)], true)); + sourceFile.fileName, + ts.factory.createArrayLiteralExpression(providersArray, combinedProviders.length > 1), + ); + + args.push( + ts.factory.createObjectLiteralExpression( + [ts.factory.createPropertyAssignment('providers', initializer)], + true, + ), + ); } tracker.replaceNode( - analysis.call, ts.factory.createCallExpression(bootstrapExpression, [], args), - // Note: it's important to pass in the source file that the nodes originated from! - // Otherwise TS won't print out literals inside of the providers that we're copying - // over from the module file. - undefined, analysis.metadata.getSourceFile()); + analysis.call, + ts.factory.createCallExpression(bootstrapExpression, [], args), + // Note: it's important to pass in the source file that the nodes originated from! + // Otherwise TS won't print out literals inside of the providers that we're copying + // over from the module file. + undefined, + analysis.metadata.getSourceFile(), + ); } /** @@ -278,10 +370,16 @@ function replaceBootstrapCallExpression( * @param typeChecker */ function migrateImportsForBootstrapCall( - sourceFile: ts.SourceFile, imports: ts.PropertyAssignment, nodeLookup: NodeLookup, - importsForNewCall: ts.Expression[], providersInNewCall: ts.Expression[], tracker: ChangeTracker, - nodesToCopy: Set, referenceResolver: ReferenceResolver, - typeChecker: ts.TypeChecker): void { + sourceFile: ts.SourceFile, + imports: ts.PropertyAssignment, + nodeLookup: NodeLookup, + importsForNewCall: ts.Expression[], + providersInNewCall: ts.Expression[], + tracker: ChangeTracker, + nodesToCopy: Set, + referenceResolver: ReferenceResolver, + typeChecker: ts.TypeChecker, +): void { if (!ts.isArrayLiteralExpression(imports.initializer)) { importsForNewCall.push(imports.initializer); return; @@ -289,21 +387,39 @@ function migrateImportsForBootstrapCall( for (const element of imports.initializer.elements) { // If the reference is to a `RouterModule.forRoot` call, we can try to migrate it. - if (ts.isCallExpression(element) && ts.isPropertyAccessExpression(element.expression) && - element.arguments.length > 0 && element.expression.name.text === 'forRoot' && - isClassReferenceInAngularModule( - element.expression.expression, 'RouterModule', 'router', typeChecker)) { + if ( + ts.isCallExpression(element) && + ts.isPropertyAccessExpression(element.expression) && + element.arguments.length > 0 && + element.expression.name.text === 'forRoot' && + isClassReferenceInAngularModule( + element.expression.expression, + 'RouterModule', + 'router', + typeChecker, + ) + ) { const options = element.arguments[1] as ts.Expression | undefined; const features = options ? getRouterModuleForRootFeatures(sourceFile, options, tracker) : []; // If the features come back as null, it means that the router // has a configuration that can't be migrated automatically. if (features !== null) { - providersInNewCall.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, 'provideRouter', '@angular/router'), [], - [element.arguments[0], ...features])); + providersInNewCall.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, 'provideRouter', '@angular/router'), + [], + [element.arguments[0], ...features], + ), + ); addNodesToCopy( - sourceFile, element.arguments[0], nodeLookup, tracker, nodesToCopy, referenceResolver); + sourceFile, + element.arguments[0], + nodeLookup, + tracker, + nodesToCopy, + referenceResolver, + ); if (options) { addNodesToCopy(sourceFile, options, nodeLookup, tracker, nodesToCopy, referenceResolver); } @@ -316,52 +432,85 @@ function migrateImportsForBootstrapCall( const animationsModule = 'platform-browser/animations'; const animationsImport = `@angular/${animationsModule}`; - if (isClassReferenceInAngularModule( - element, 'BrowserAnimationsModule', animationsModule, typeChecker)) { - providersInNewCall.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, 'provideAnimations', animationsImport), [], [])); + if ( + isClassReferenceInAngularModule( + element, + 'BrowserAnimationsModule', + animationsModule, + typeChecker, + ) + ) { + providersInNewCall.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, 'provideAnimations', animationsImport), + [], + [], + ), + ); continue; } // `NoopAnimationsModule` can be replaced with `provideNoopAnimations`. - if (isClassReferenceInAngularModule( - element, 'NoopAnimationsModule', animationsModule, typeChecker)) { - providersInNewCall.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, 'provideNoopAnimations', animationsImport), [], [])); + if ( + isClassReferenceInAngularModule( + element, + 'NoopAnimationsModule', + animationsModule, + typeChecker, + ) + ) { + providersInNewCall.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, 'provideNoopAnimations', animationsImport), + [], + [], + ), + ); continue; } // `HttpClientModule` can be replaced with `provideHttpClient()`. const httpClientModule = 'common/http'; const httpClientImport = `@angular/${httpClientModule}`; - if (isClassReferenceInAngularModule( - element, 'HttpClientModule', httpClientModule, typeChecker)) { + if ( + isClassReferenceInAngularModule(element, 'HttpClientModule', httpClientModule, typeChecker) + ) { const callArgs = [ // we add `withInterceptorsFromDi()` to the call to ensure that class-based interceptors // still work ts.factory.createCallExpression( - tracker.addImport(sourceFile, 'withInterceptorsFromDi', httpClientImport), [], []) + tracker.addImport(sourceFile, 'withInterceptorsFromDi', httpClientImport), + [], + [], + ), ]; - providersInNewCall.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, 'provideHttpClient', httpClientImport), [], callArgs)); + providersInNewCall.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, 'provideHttpClient', httpClientImport), + [], + callArgs, + ), + ); continue; } } const target = - // If it's a call, it'll likely be a `ModuleWithProviders` - // expression so the target is going to be call's expression. - ts.isCallExpression(element) && ts.isPropertyAccessExpression(element.expression) ? - element.expression.expression : - element; + // If it's a call, it'll likely be a `ModuleWithProviders` + // expression so the target is going to be call's expression. + ts.isCallExpression(element) && ts.isPropertyAccessExpression(element.expression) + ? element.expression.expression + : element; const classDeclaration = findClassDeclaration(target, typeChecker); - const decorators = classDeclaration ? - getAngularDecorators(typeChecker, ts.getDecorators(classDeclaration) || []) : - undefined; - - if (!decorators || decorators.length === 0 || - decorators.every( - ({name}) => name !== 'Directive' && name !== 'Component' && name !== 'Pipe')) { + const decorators = classDeclaration + ? getAngularDecorators(typeChecker, ts.getDecorators(classDeclaration) || []) + : undefined; + + if ( + !decorators || + decorators.length === 0 || + decorators.every(({name}) => name !== 'Directive' && name !== 'Component' && name !== 'Pipe') + ) { importsForNewCall.push(element); addNodesToCopy(sourceFile, element, nodeLookup, tracker, nodesToCopy, referenceResolver); } @@ -377,8 +526,10 @@ function migrateImportsForBootstrapCall( * @returns Null if the options can't be migrated, otherwise an array of call expressions. */ function getRouterModuleForRootFeatures( - sourceFile: ts.SourceFile, options: ts.Expression, tracker: ChangeTracker): ts.CallExpression[]| - null { + sourceFile: ts.SourceFile, + options: ts.Expression, + tracker: ChangeTracker, +): ts.CallExpression[] | null { // Options that aren't a static object literal can't be migrated. if (!ts.isObjectLiteralExpression(options)) { return null; @@ -387,12 +538,14 @@ function getRouterModuleForRootFeatures( const featureExpressions: ts.CallExpression[] = []; const configOptions: ts.PropertyAssignment[] = []; const inMemoryScrollingOptions: ts.PropertyAssignment[] = []; - const features = new UniqueItemTracker(); + const features = new UniqueItemTracker(); for (const prop of options.properties) { // We can't migrate options that we can't easily analyze. - if (!ts.isPropertyAssignment(prop) || - (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name))) { + if ( + !ts.isPropertyAssignment(prop) || + (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name)) + ) { return null; } @@ -451,8 +604,9 @@ function getRouterModuleForRootFeatures( if (inMemoryScrollingOptions.length > 0) { features.track( - 'withInMemoryScrolling', - ts.factory.createObjectLiteralExpression(inMemoryScrollingOptions)); + 'withInMemoryScrolling', + ts.factory.createObjectLiteralExpression(inMemoryScrollingOptions), + ); } if (configOptions.length > 0) { @@ -461,13 +615,18 @@ function getRouterModuleForRootFeatures( for (const [feature, featureArgs] of features.getEntries()) { const callArgs: ts.Expression[] = []; - featureArgs.forEach(arg => { + featureArgs.forEach((arg) => { if (arg !== null) { callArgs.push(arg); } }); - featureExpressions.push(ts.factory.createCallExpression( - tracker.addImport(sourceFile, feature, '@angular/router'), [], callArgs)); + featureExpressions.push( + ts.factory.createCallExpression( + tracker.addImport(sourceFile, feature, '@angular/router'), + [], + callArgs, + ), + ); } return featureExpressions; @@ -484,38 +643,51 @@ function getRouterModuleForRootFeatures( * @param referenceResolver */ function addNodesToCopy( - targetFile: ts.SourceFile, rootNode: ts.Node, nodeLookup: NodeLookup, tracker: ChangeTracker, - nodesToCopy: Set, referenceResolver: ReferenceResolver): void { + targetFile: ts.SourceFile, + rootNode: ts.Node, + nodeLookup: NodeLookup, + tracker: ChangeTracker, + nodesToCopy: Set, + referenceResolver: ReferenceResolver, +): void { const refs = findAllSameFileReferences(rootNode, nodeLookup, referenceResolver); for (const ref of refs) { const importSpecifier = closestOrSelf(ref, ts.isImportSpecifier); - const importDeclaration = - importSpecifier ? closestNode(importSpecifier, ts.isImportDeclaration) : null; + const importDeclaration = importSpecifier + ? closestNode(importSpecifier, ts.isImportDeclaration) + : null; // If the reference is in an import, we need to add an import to the main file. - if (importDeclaration && importSpecifier && - ts.isStringLiteralLike(importDeclaration.moduleSpecifier)) { - const moduleName = importDeclaration.moduleSpecifier.text.startsWith('.') ? - remapRelativeImport(targetFile.fileName, importDeclaration.moduleSpecifier) : - importDeclaration.moduleSpecifier.text; - const symbolName = importSpecifier.propertyName ? importSpecifier.propertyName.text : - importSpecifier.name.text; + if ( + importDeclaration && + importSpecifier && + ts.isStringLiteralLike(importDeclaration.moduleSpecifier) + ) { + const moduleName = importDeclaration.moduleSpecifier.text.startsWith('.') + ? remapRelativeImport(targetFile.fileName, importDeclaration.moduleSpecifier) + : importDeclaration.moduleSpecifier.text; + const symbolName = importSpecifier.propertyName + ? importSpecifier.propertyName.text + : importSpecifier.name.text; const alias = importSpecifier.propertyName ? importSpecifier.name.text : null; tracker.addImport(targetFile, symbolName, moduleName, alias); continue; } const variableDeclaration = closestOrSelf(ref, ts.isVariableDeclaration); - const variableStatement = - variableDeclaration ? closestNode(variableDeclaration, ts.isVariableStatement) : null; + const variableStatement = variableDeclaration + ? closestNode(variableDeclaration, ts.isVariableStatement) + : null; // If the reference is a variable, we can attempt to import it or copy it over. if (variableDeclaration && variableStatement && ts.isIdentifier(variableDeclaration.name)) { if (isExported(variableStatement)) { tracker.addImport( - targetFile, variableDeclaration.name.text, - getRelativeImportPath(targetFile.fileName, ref.getSourceFile().fileName)); + targetFile, + variableDeclaration.name.text, + getRelativeImportPath(targetFile.fileName, ref.getSourceFile().fileName), + ); } else { nodesToCopy.add(variableStatement); } @@ -528,8 +700,10 @@ function addNodesToCopy( if (closestExportable) { if (isExported(closestExportable) && closestExportable.name) { tracker.addImport( - targetFile, closestExportable.name.text, - getRelativeImportPath(targetFile.fileName, ref.getSourceFile().fileName)); + targetFile, + closestExportable.name.text, + getRelativeImportPath(targetFile.fileName, ref.getSourceFile().fileName), + ); } else { nodesToCopy.add(closestExportable); } @@ -544,7 +718,10 @@ function addNodesToCopy( * @param referenceResolver */ function findAllSameFileReferences( - rootNode: ts.Node, nodeLookup: NodeLookup, referenceResolver: ReferenceResolver): Set { + rootNode: ts.Node, + nodeLookup: NodeLookup, + referenceResolver: ReferenceResolver, +): Set { const results = new Set(); const traversedTopLevelNodes = new Set(); const excludeStart = rootNode.getStart(); @@ -557,7 +734,12 @@ function findAllSameFileReferences( } const refs = referencesToNodeWithinSameFile( - node, nodeLookup, excludeStart, excludeEnd, referenceResolver); + node, + nodeLookup, + excludeStart, + excludeEnd, + referenceResolver, + ); if (refs === null) { return; @@ -578,9 +760,15 @@ function findAllSameFileReferences( // Keep searching, starting from the closest top-level node. We skip import declarations, // because we already know about them and they may put the search into an infinite loop. - if (!ts.isImportDeclaration(closestTopLevel) && - isOutsideRange( - excludeStart, excludeEnd, closestTopLevel.getStart(), closestTopLevel.getEnd())) { + if ( + !ts.isImportDeclaration(closestTopLevel) && + isOutsideRange( + excludeStart, + excludeEnd, + closestTopLevel.getStart(), + closestTopLevel.getEnd(), + ) + ) { traversedTopLevelNodes.add(closestTopLevel); walk(closestTopLevel); } @@ -599,11 +787,15 @@ function findAllSameFileReferences( * @param referenceResolver */ function referencesToNodeWithinSameFile( - node: ts.Identifier, nodeLookup: NodeLookup, excludeStart: number, excludeEnd: number, - referenceResolver: ReferenceResolver): Set|null { - const offsets = - referenceResolver.findSameFileReferences(node, node.getSourceFile().fileName) - .filter(([start, end]) => isOutsideRange(excludeStart, excludeEnd, start, end)); + node: ts.Identifier, + nodeLookup: NodeLookup, + excludeStart: number, + excludeEnd: number, + referenceResolver: ReferenceResolver, +): Set | null { + const offsets = referenceResolver + .findSameFileReferences(node, node.getSourceFile().fileName) + .filter(([start, end]) => isOutsideRange(excludeStart, excludeEnd, start, end)); if (offsets.length > 0) { const nodes = offsetsToNodes(nodeLookup, offsets, new Set()); @@ -625,20 +817,26 @@ function referencesToNodeWithinSameFile( */ function remapDynamicImports(targetFileName: string, rootNode: T): T { let hasChanged = false; - const transformer: ts.TransformerFactory = context => { - return sourceFile => ts.visitNode(sourceFile, function walk(node: ts.Node): ts.Node { - if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && - node.arguments.length > 0 && ts.isStringLiteralLike(node.arguments[0]) && - node.arguments[0].text.startsWith('.')) { - hasChanged = true; - return context.factory.updateCallExpression(node, node.expression, node.typeArguments, [ - context.factory.createStringLiteral( - remapRelativeImport(targetFileName, node.arguments[0])), - ...node.arguments.slice(1) - ]); - } - return ts.visitEachChild(node, walk, context); - }); + const transformer: ts.TransformerFactory = (context) => { + return (sourceFile) => + ts.visitNode(sourceFile, function walk(node: ts.Node): ts.Node { + if ( + ts.isCallExpression(node) && + node.expression.kind === ts.SyntaxKind.ImportKeyword && + node.arguments.length > 0 && + ts.isStringLiteralLike(node.arguments[0]) && + node.arguments[0].text.startsWith('.') + ) { + hasChanged = true; + return context.factory.updateCallExpression(node, node.expression, node.typeArguments, [ + context.factory.createStringLiteral( + remapRelativeImport(targetFileName, node.arguments[0]), + ), + ...node.arguments.slice(1), + ]); + } + return ts.visitEachChild(node, walk, context); + }); }; const result = ts.transform(rootNode, [transformer]).transformed[0] as T; @@ -659,9 +857,11 @@ function isTopLevelStatement(node: ts.Node): node is ts.Node { * @param node Node to be checked. */ function isReferenceIdentifier(node: ts.Node): node is ts.Identifier { - return ts.isIdentifier(node) && - (!ts.isPropertyAssignment(node.parent) && !ts.isParameter(node.parent) || - node.parent.name !== node); + return ( + ts.isIdentifier(node) && + ((!ts.isPropertyAssignment(node.parent) && !ts.isParameter(node.parent)) || + node.parent.name !== node) + ); } /** @@ -672,7 +872,11 @@ function isReferenceIdentifier(node: ts.Node): node is ts.Identifier { * @param end End of the range that is being checked. */ function isOutsideRange( - excludeStart: number, excludeEnd: number, start: number, end: number): boolean { + excludeStart: number, + excludeEnd: number, + start: number, + end: number, +): boolean { return (start < excludeStart && end < excludeStart) || start > excludeEnd; } @@ -683,7 +887,9 @@ function isOutsideRange( */ function remapRelativeImport(targetFileName: string, specifier: ts.StringLiteralLike): string { return getRelativeImportPath( - targetFileName, join(dirname(specifier.getSourceFile().fileName), specifier.text)); + targetFileName, + join(dirname(specifier.getSourceFile().fileName), specifier.text), + ); } /** @@ -691,9 +897,9 @@ function remapRelativeImport(targetFileName: string, specifier: ts.StringLiteral * @param node Node to be checked. */ function isExported(node: ts.Node): node is ts.Node { - return ts.canHaveModifiers(node) && node.modifiers ? - node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword) : - false; + return ts.canHaveModifiers(node) && node.modifiers + ? node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) + : false; } /** @@ -701,11 +907,21 @@ function isExported(node: ts.Node): node is ts.Node { * it can be safely copied into another file. * @param node Node to be checked. */ -function isExportableDeclaration(node: ts.Node): node is ts.EnumDeclaration|ts.ClassDeclaration| - ts.FunctionDeclaration|ts.InterfaceDeclaration|ts.TypeAliasDeclaration { - return ts.isEnumDeclaration(node) || ts.isClassDeclaration(node) || - ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || - ts.isTypeAliasDeclaration(node); +function isExportableDeclaration( + node: ts.Node, +): node is + | ts.EnumDeclaration + | ts.ClassDeclaration + | ts.FunctionDeclaration + | ts.InterfaceDeclaration + | ts.TypeAliasDeclaration { + return ( + ts.isEnumDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isFunctionDeclaration(node) || + ts.isInterfaceDeclaration(node) || + ts.isTypeAliasDeclaration(node) + ); } /** @@ -739,9 +955,12 @@ function hasImport(program: NgtscProgram, rootFileNames: string[], moduleName: s } for (const statement of sourceFile.statements) { - if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier) && - (statement.moduleSpecifier.text === moduleName || - statement.moduleSpecifier.text.startsWith(deepImportStart))) { + if ( + ts.isImportDeclaration(statement) && + ts.isStringLiteralLike(statement.moduleSpecifier) && + (statement.moduleSpecifier.text === moduleName || + statement.moduleSpecifier.text.startsWith(deepImportStart)) + ) { return true; } } diff --git a/packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts b/packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts index 2dcb7b9f1a4fa..e856978fdd323 100644 --- a/packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts +++ b/packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts @@ -7,7 +7,13 @@ */ import {NgtscProgram} from '@angular/compiler-cli'; -import {PotentialImport, PotentialImportKind, PotentialImportMode, Reference, TemplateTypeChecker} from '@angular/compiler-cli/private/migrations'; +import { + PotentialImport, + PotentialImportKind, + PotentialImportMode, + Reference, + TemplateTypeChecker, +} from '@angular/compiler-cli/private/migrations'; import ts from 'typescript'; import {ChangesByFile, ChangeTracker, ImportRemapper} from '../../utils/change_tracker'; @@ -16,14 +22,21 @@ import {getImportSpecifier} from '../../utils/typescript/imports'; import {closestNode} from '../../utils/typescript/nodes'; import {isReferenceToImport} from '../../utils/typescript/symbol'; -import {findClassDeclaration, findLiteralProperty, isClassReferenceInAngularModule, NamedClassDeclaration} from './util'; +import { + findClassDeclaration, + findLiteralProperty, + isClassReferenceInAngularModule, + NamedClassDeclaration, +} from './util'; /** * Function that can be used to prcess the dependencies that * are going to be added to the imports of a component. */ -export type ComponentImportsRemapper = - (imports: PotentialImport[], component: ts.ClassDeclaration) => PotentialImport[]; +export type ComponentImportsRemapper = ( + imports: PotentialImport[], + component: ts.ClassDeclaration, +) => PotentialImport[]; /** * Converts all declarations in the specified files to standalone. @@ -35,9 +48,12 @@ export type ComponentImportsRemapper = * imports. */ export function toStandalone( - sourceFiles: ts.SourceFile[], program: NgtscProgram, printer: ts.Printer, - fileImportRemapper?: ImportRemapper, - componentImportRemapper?: ComponentImportsRemapper): ChangesByFile { + sourceFiles: ts.SourceFile[], + program: NgtscProgram, + printer: ts.Printer, + fileImportRemapper?: ImportRemapper, + componentImportRemapper?: ComponentImportsRemapper, +): ChangesByFile { const templateTypeChecker = program.compiler.getTemplateTypeChecker(); const typeChecker = program.getTsProgram().getTypeChecker(); const modulesToMigrate = new Set(); @@ -52,20 +68,29 @@ export function toStandalone( for (const module of modules) { const allModuleDeclarations = extractDeclarationsFromModule(module, templateTypeChecker); const unbootstrappedDeclarations = filterNonBootstrappedDeclarations( - allModuleDeclarations, module, templateTypeChecker, typeChecker); + allModuleDeclarations, + module, + templateTypeChecker, + typeChecker, + ); if (unbootstrappedDeclarations.length > 0) { modulesToMigrate.add(module); - unbootstrappedDeclarations.forEach(decl => declarations.add(decl)); + unbootstrappedDeclarations.forEach((decl) => declarations.add(decl)); } } - testObjects.forEach(obj => testObjectsToMigrate.add(obj)); + testObjects.forEach((obj) => testObjectsToMigrate.add(obj)); } for (const declaration of declarations) { convertNgModuleDeclarationToStandalone( - declaration, declarations, tracker, templateTypeChecker, componentImportRemapper); + declaration, + declarations, + tracker, + templateTypeChecker, + componentImportRemapper, + ); } for (const node of modulesToMigrate) { @@ -73,7 +98,12 @@ export function toStandalone( } migrateTestDeclarations( - testObjectsToMigrate, declarations, tracker, templateTypeChecker, typeChecker); + testObjectsToMigrate, + declarations, + tracker, + templateTypeChecker, + typeChecker, + ); return tracker.recordChanges(); } @@ -86,8 +116,12 @@ export function toStandalone( * @param importRemapper */ export function convertNgModuleDeclarationToStandalone( - decl: ts.ClassDeclaration, allDeclarations: Set, tracker: ChangeTracker, - typeChecker: TemplateTypeChecker, importRemapper?: ComponentImportsRemapper): void { + decl: ts.ClassDeclaration, + allDeclarations: Set, + tracker: ChangeTracker, + typeChecker: TemplateTypeChecker, + importRemapper?: ComponentImportsRemapper, +): void { const directiveMeta = typeChecker.getDirectiveMetadata(decl); if (directiveMeta && directiveMeta.decorator && !directiveMeta.isStandalone) { @@ -95,18 +129,28 @@ export function convertNgModuleDeclarationToStandalone( if (directiveMeta.isComponent) { const importsToAdd = getComponentImportExpressions( - decl, allDeclarations, tracker, typeChecker, importRemapper); + decl, + allDeclarations, + tracker, + typeChecker, + importRemapper, + ); if (importsToAdd.length > 0) { - const hasTrailingComma = importsToAdd.length > 2 && - !!extractMetadataLiteral(directiveMeta.decorator)?.properties.hasTrailingComma; + const hasTrailingComma = + importsToAdd.length > 2 && + !!extractMetadataLiteral(directiveMeta.decorator)?.properties.hasTrailingComma; decorator = addPropertyToAngularDecorator( - decorator, - ts.factory.createPropertyAssignment( - 'imports', - ts.factory.createArrayLiteralExpression( - // Create a multi-line array when it has a trailing comma. - ts.factory.createNodeArray(importsToAdd, hasTrailingComma), hasTrailingComma))); + decorator, + ts.factory.createPropertyAssignment( + 'imports', + ts.factory.createArrayLiteralExpression( + // Create a multi-line array when it has a trailing comma. + ts.factory.createNodeArray(importsToAdd, hasTrailingComma), + hasTrailingComma, + ), + ), + ); } } @@ -130,21 +174,29 @@ export function convertNgModuleDeclarationToStandalone( * @param importRemapper */ function getComponentImportExpressions( - decl: ts.ClassDeclaration, allDeclarations: Set, tracker: ChangeTracker, - typeChecker: TemplateTypeChecker, importRemapper?: ComponentImportsRemapper): ts.Expression[] { + decl: ts.ClassDeclaration, + allDeclarations: Set, + tracker: ChangeTracker, + typeChecker: TemplateTypeChecker, + importRemapper?: ComponentImportsRemapper, +): ts.Expression[] { const templateDependencies = findTemplateDependencies(decl, typeChecker); - const usedDependenciesInMigration = - new Set(templateDependencies.filter(dep => allDeclarations.has(dep.node))); + const usedDependenciesInMigration = new Set( + templateDependencies.filter((dep) => allDeclarations.has(dep.node)), + ); const imports: ts.Expression[] = []; const seenImports = new Set(); const resolvedDependencies: PotentialImport[] = []; for (const dep of templateDependencies) { const importLocation = findImportLocation( - dep as Reference, decl, - usedDependenciesInMigration.has(dep) ? PotentialImportMode.ForceDirect : - PotentialImportMode.Normal, - typeChecker); + dep as Reference, + decl, + usedDependenciesInMigration.has(dep) + ? PotentialImportMode.ForceDirect + : PotentialImportMode.Normal, + typeChecker, + ); if (importLocation && !seenImports.has(importLocation.symbolName)) { seenImports.add(importLocation.symbolName); @@ -152,24 +204,38 @@ function getComponentImportExpressions( } } - const processedDependencies = - importRemapper ? importRemapper(resolvedDependencies, decl) : resolvedDependencies; + const processedDependencies = importRemapper + ? importRemapper(resolvedDependencies, decl) + : resolvedDependencies; for (const importLocation of processedDependencies) { if (importLocation.moduleSpecifier) { const identifier = tracker.addImport( - decl.getSourceFile(), importLocation.symbolName, importLocation.moduleSpecifier); + decl.getSourceFile(), + importLocation.symbolName, + importLocation.moduleSpecifier, + ); imports.push(identifier); } else { const identifier = ts.factory.createIdentifier(importLocation.symbolName); if (importLocation.isForwardReference) { - const forwardRefExpression = - tracker.addImport(decl.getSourceFile(), 'forwardRef', '@angular/core'); + const forwardRefExpression = tracker.addImport( + decl.getSourceFile(), + 'forwardRef', + '@angular/core', + ); const arrowFunction = ts.factory.createArrowFunction( - undefined, undefined, [], undefined, undefined, identifier); + undefined, + undefined, + [], + undefined, + undefined, + identifier, + ); imports.push( - ts.factory.createCallExpression(forwardRefExpression, undefined, [arrowFunction])); + ts.factory.createCallExpression(forwardRefExpression, undefined, [arrowFunction]), + ); } else { imports.push(identifier); } @@ -188,8 +254,12 @@ function getComponentImportExpressions( * @param templateTypeChecker */ function migrateNgModuleClass( - node: ts.ClassDeclaration, allDeclarations: Set, tracker: ChangeTracker, - typeChecker: ts.TypeChecker, templateTypeChecker: TemplateTypeChecker) { + node: ts.ClassDeclaration, + allDeclarations: Set, + tracker: ChangeTracker, + typeChecker: ts.TypeChecker, + templateTypeChecker: TemplateTypeChecker, +) { const decorator = templateTypeChecker.getNgModuleMetadata(node)?.decorator; const metadata = decorator ? extractMetadataLiteral(decorator) : null; @@ -207,9 +277,12 @@ function migrateNgModuleClass( * @param tracker */ function moveDeclarationsToImports( - literal: ts.ObjectLiteralExpression, allDeclarations: Set, - typeChecker: ts.TypeChecker, templateTypeChecker: TemplateTypeChecker, - tracker: ChangeTracker): void { + literal: ts.ObjectLiteralExpression, + allDeclarations: Set, + typeChecker: ts.TypeChecker, + templateTypeChecker: TemplateTypeChecker, + tracker: ChangeTracker, +): void { const declarationsProp = findLiteralProperty(literal, 'declarations'); if (!declarationsProp) { @@ -221,8 +294,11 @@ function moveDeclarationsToImports( const properties: ts.ObjectLiteralElementLike[] = []; const importsProp = findLiteralProperty(literal, 'imports'); const hasAnyArrayTrailingComma = literal.properties.some( - prop => ts.isPropertyAssignment(prop) && ts.isArrayLiteralExpression(prop.initializer) && - prop.initializer.elements.hasTrailingComma); + (prop) => + ts.isPropertyAssignment(prop) && + ts.isArrayLiteralExpression(prop.initializer) && + prop.initializer.elements.hasTrailingComma, + ); // Separate the declarations that we want to keep and ones we need to copy into the `imports`. if (ts.isPropertyAssignment(declarationsProp)) { @@ -233,12 +309,14 @@ function moveDeclarationsToImports( if (ts.isIdentifier(el)) { const correspondingClass = findClassDeclaration(el, typeChecker); - if (!correspondingClass || - // Check whether the declaration is either standalone already or is being converted - // in this migration. We need to check if it's standalone already, in order to correct - // some cases where the main app and the test files are being migrated in separate - // programs. - isStandaloneDeclaration(correspondingClass, allDeclarations, templateTypeChecker)) { + if ( + !correspondingClass || + // Check whether the declaration is either standalone already or is being converted + // in this migration. We need to check if it's standalone already, in order to correct + // some cases where the main app and the test files are being migrated in separate + // programs. + isStandaloneDeclaration(correspondingClass, allDeclarations, templateTypeChecker) + ) { declarationsToCopy.push(el); } else { declarationsToPreserve.push(el); @@ -255,10 +333,17 @@ function moveDeclarationsToImports( // If there are no `imports`, create them with the declarations we want to copy. if (!importsProp && declarationsToCopy.length > 0) { - properties.push(ts.factory.createPropertyAssignment( + properties.push( + ts.factory.createPropertyAssignment( 'imports', - ts.factory.createArrayLiteralExpression(ts.factory.createNodeArray( - declarationsToCopy, hasAnyArrayTrailingComma && declarationsToCopy.length > 2)))); + ts.factory.createArrayLiteralExpression( + ts.factory.createNodeArray( + declarationsToCopy, + hasAnyArrayTrailingComma && declarationsToCopy.length > 2, + ), + ), + ), + ); } for (const prop of literal.properties) { @@ -270,13 +355,21 @@ function moveDeclarationsToImports( // If we have declarations to preserve, update the existing property, otherwise drop it. if (prop === declarationsProp) { if (declarationsToPreserve.length > 0) { - const hasTrailingComma = ts.isArrayLiteralExpression(prop.initializer) ? - prop.initializer.elements.hasTrailingComma : - hasAnyArrayTrailingComma; - properties.push(ts.factory.updatePropertyAssignment( - prop, prop.name, - ts.factory.createArrayLiteralExpression(ts.factory.createNodeArray( - declarationsToPreserve, hasTrailingComma && declarationsToPreserve.length > 2)))); + const hasTrailingComma = ts.isArrayLiteralExpression(prop.initializer) + ? prop.initializer.elements.hasTrailingComma + : hasAnyArrayTrailingComma; + properties.push( + ts.factory.updatePropertyAssignment( + prop, + prop.name, + ts.factory.createArrayLiteralExpression( + ts.factory.createNodeArray( + declarationsToPreserve, + hasTrailingComma && declarationsToPreserve.length > 2, + ), + ), + ), + ); } continue; } @@ -288,16 +381,21 @@ function moveDeclarationsToImports( if (ts.isArrayLiteralExpression(prop.initializer)) { initializer = ts.factory.updateArrayLiteralExpression( - prop.initializer, - ts.factory.createNodeArray( - [...prop.initializer.elements, ...declarationsToCopy], - prop.initializer.elements.hasTrailingComma)); + prop.initializer, + ts.factory.createNodeArray( + [...prop.initializer.elements, ...declarationsToCopy], + prop.initializer.elements.hasTrailingComma, + ), + ); } else { - initializer = ts.factory.createArrayLiteralExpression(ts.factory.createNodeArray( + initializer = ts.factory.createArrayLiteralExpression( + ts.factory.createNodeArray( [ts.factory.createSpreadElement(prop.initializer), ...declarationsToCopy], // Expect the declarations to be greater than 1 since // we have the pre-existing initializer already. - hasAnyArrayTrailingComma && declarationsToCopy.length > 1)); + hasAnyArrayTrailingComma && declarationsToCopy.length > 1, + ), + ); } properties.push(ts.factory.updatePropertyAssignment(prop, prop.name, initializer)); @@ -309,18 +407,24 @@ function moveDeclarationsToImports( } tracker.replaceNode( + literal, + ts.factory.updateObjectLiteralExpression( literal, - ts.factory.updateObjectLiteralExpression( - literal, ts.factory.createNodeArray(properties, literal.properties.hasTrailingComma)), - ts.EmitHint.Expression); + ts.factory.createNodeArray(properties, literal.properties.hasTrailingComma), + ), + ts.EmitHint.Expression, + ); } /** Adds `standalone: true` to a decorator node. */ function addStandaloneToDecorator(node: ts.Decorator): ts.Decorator { return addPropertyToAngularDecorator( - node, - ts.factory.createPropertyAssignment( - 'standalone', ts.factory.createToken(ts.SyntaxKind.TrueKeyword))); + node, + ts.factory.createPropertyAssignment( + 'standalone', + ts.factory.createToken(ts.SyntaxKind.TrueKeyword), + ), + ); } /** @@ -329,7 +433,9 @@ function addStandaloneToDecorator(node: ts.Decorator): ts.Decorator { * @param property Property to add. */ function addPropertyToAngularDecorator( - node: ts.Decorator, property: ts.PropertyAssignment): ts.Decorator { + node: ts.Decorator, + property: ts.PropertyAssignment, +): ts.Decorator { // Invalid decorator. if (!ts.isCallExpression(node.expression) || node.expression.arguments.length > 1) { return node; @@ -350,16 +456,20 @@ function addPropertyToAngularDecorator( // Use `createDecorator` instead of `updateDecorator`, because // the latter ends up duplicating the node's leading comment. - return ts.factory.createDecorator(ts.factory.createCallExpression( - node.expression.expression, node.expression.typeArguments, - [ts.factory.createObjectLiteralExpression( - ts.factory.createNodeArray(literalProperties, hasTrailingComma), - literalProperties.length > 1)])); + return ts.factory.createDecorator( + ts.factory.createCallExpression(node.expression.expression, node.expression.typeArguments, [ + ts.factory.createObjectLiteralExpression( + ts.factory.createNodeArray(literalProperties, hasTrailingComma), + literalProperties.length > 1, + ), + ]), + ); } /** Checks if a node is a `PropertyAssignment` with a name. */ -function isNamedPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment& - {name: ts.Identifier} { +function isNamedPropertyAssignment( + node: ts.Node, +): node is ts.PropertyAssignment & {name: ts.Identifier} { return ts.isPropertyAssignment(node) && node.name && ts.isIdentifier(node.name); } @@ -371,11 +481,14 @@ function isNamedPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment * @param typeChecker */ function findImportLocation( - target: Reference, inComponent: ts.ClassDeclaration, - importMode: PotentialImportMode, typeChecker: TemplateTypeChecker): PotentialImport|null { + target: Reference, + inComponent: ts.ClassDeclaration, + importMode: PotentialImportMode, + typeChecker: TemplateTypeChecker, +): PotentialImport | null { const importLocations = typeChecker.getPotentialImportsFor(target, inComponent, importMode); - let firstSameFileImport: PotentialImport|null = null; - let firstModuleImport: PotentialImport|null = null; + let firstSameFileImport: PotentialImport | null = null; + let firstModuleImport: PotentialImport | null = null; for (const location of importLocations) { // Prefer a standalone import, if we can find one. @@ -386,9 +499,12 @@ function findImportLocation( if (!location.moduleSpecifier && !firstSameFileImport) { firstSameFileImport = location; } - if (location.kind === PotentialImportKind.NgModule && !firstModuleImport && - // ɵ is used for some internal Angular modules that we want to skip over. - !location.symbolName.startsWith('ɵ')) { + if ( + location.kind === PotentialImportKind.NgModule && + !firstModuleImport && + // ɵ is used for some internal Angular modules that we want to skip over. + !location.symbolName.startsWith('ɵ') + ) { firstModuleImport = location; } } @@ -401,10 +517,13 @@ function findImportLocation( * E.g. `declarations: [Foo]` or `declarations: SOME_VAR` would match this description, * but not `declarations: []`. */ -function hasNgModuleMetadataElements(node: ts.Node): node is ts.PropertyAssignment& - {initializer: ts.ArrayLiteralExpression} { - return ts.isPropertyAssignment(node) && - (!ts.isArrayLiteralExpression(node.initializer) || node.initializer.elements.length > 0); +function hasNgModuleMetadataElements( + node: ts.Node, +): node is ts.PropertyAssignment & {initializer: ts.ArrayLiteralExpression} { + return ( + ts.isPropertyAssignment(node) && + (!ts.isArrayLiteralExpression(node.initializer) || node.initializer.elements.length > 0) + ); } /** Finds all modules whose declarations can be migrated. */ @@ -414,8 +533,9 @@ function findNgModuleClassesToMigrate(sourceFile: ts.SourceFile, typeChecker: ts if (getImportSpecifier(sourceFile, '@angular/core', 'NgModule')) { sourceFile.forEachChild(function walk(node) { if (ts.isClassDeclaration(node)) { - const decorator = getAngularDecorators(typeChecker, ts.getDecorators(node) || []) - .find(current => current.name === 'NgModule'); + const decorator = getAngularDecorators(typeChecker, ts.getDecorators(node) || []).find( + (current) => current.name === 'NgModule', + ); const metadata = decorator ? extractMetadataLiteral(decorator.node) : null; if (metadata) { @@ -442,23 +562,32 @@ export function findTestObjectsToMigrate(sourceFile: ts.SourceFile, typeChecker: if (testBedImport || catalystImport) { sourceFile.forEachChild(function walk(node) { - const isObjectLiteralCall = ts.isCallExpression(node) && node.arguments.length > 0 && - // `arguments[0]` is the testing module config. - ts.isObjectLiteralExpression(node.arguments[0]); - const config = isObjectLiteralCall ? node.arguments[0] as ts.ObjectLiteralExpression : null; - const isTestBedCall = isObjectLiteralCall && - (testBedImport && ts.isPropertyAccessExpression(node.expression) && - node.expression.name.text === 'configureTestingModule' && - isReferenceToImport(typeChecker, node.expression.expression, testBedImport)); - const isCatalystCall = isObjectLiteralCall && - (catalystImport && ts.isIdentifier(node.expression) && - isReferenceToImport(typeChecker, node.expression, catalystImport)); + const isObjectLiteralCall = + ts.isCallExpression(node) && + node.arguments.length > 0 && + // `arguments[0]` is the testing module config. + ts.isObjectLiteralExpression(node.arguments[0]); + const config = isObjectLiteralCall ? (node.arguments[0] as ts.ObjectLiteralExpression) : null; + const isTestBedCall = + isObjectLiteralCall && + testBedImport && + ts.isPropertyAccessExpression(node.expression) && + node.expression.name.text === 'configureTestingModule' && + isReferenceToImport(typeChecker, node.expression.expression, testBedImport); + const isCatalystCall = + isObjectLiteralCall && + catalystImport && + ts.isIdentifier(node.expression) && + isReferenceToImport(typeChecker, node.expression, catalystImport); if ((isTestBedCall || isCatalystCall) && config) { const declarations = findLiteralProperty(config, 'declarations'); - if (declarations && ts.isPropertyAssignment(declarations) && - ts.isArrayLiteralExpression(declarations.initializer) && - declarations.initializer.elements.length > 0) { + if ( + declarations && + ts.isPropertyAssignment(declarations) && + ts.isArrayLiteralExpression(declarations.initializer) && + declarations.initializer.elements.length > 0 + ) { testObjects.push(config); } } @@ -475,8 +604,10 @@ export function findTestObjectsToMigrate(sourceFile: ts.SourceFile, typeChecker: * @param decl Component in whose template we're looking for dependencies. * @param typeChecker */ -function findTemplateDependencies(decl: ts.ClassDeclaration, typeChecker: TemplateTypeChecker): - Reference[] { +function findTemplateDependencies( + decl: ts.ClassDeclaration, + typeChecker: TemplateTypeChecker, +): Reference[] { const results: Reference[] = []; const usedDirectives = typeChecker.getUsedDirectives(decl); const usedPipes = typeChecker.getUsedPipes(decl); @@ -493,8 +624,10 @@ function findTemplateDependencies(decl: ts.ClassDeclaration, typeChecker: Templa const potentialPipes = typeChecker.getPotentialPipes(decl); for (const pipe of potentialPipes) { - if (ts.isClassDeclaration(pipe.ref.node) && - usedPipes.some(current => pipe.name === current)) { + if ( + ts.isClassDeclaration(pipe.ref.node) && + usedPipes.some((current) => pipe.name === current) + ) { results.push(pipe.ref as Reference); } } @@ -512,11 +645,14 @@ function findTemplateDependencies(decl: ts.ClassDeclaration, typeChecker: Templa * @param typeChecker */ function filterNonBootstrappedDeclarations( - declarations: ts.ClassDeclaration[], ngModule: ts.ClassDeclaration, - templateTypeChecker: TemplateTypeChecker, typeChecker: ts.TypeChecker) { + declarations: ts.ClassDeclaration[], + ngModule: ts.ClassDeclaration, + templateTypeChecker: TemplateTypeChecker, + typeChecker: ts.TypeChecker, +) { const metadata = templateTypeChecker.getNgModuleMetadata(ngModule); const metaLiteral = - metadata && metadata.decorator ? extractMetadataLiteral(metadata.decorator) : null; + metadata && metadata.decorator ? extractMetadataLiteral(metadata.decorator) : null; const bootstrapProp = metaLiteral ? findLiteralProperty(metaLiteral, 'bootstrap') : null; // If there's no `bootstrap`, we can't filter. @@ -526,8 +662,10 @@ function filterNonBootstrappedDeclarations( // If we can't analyze the `bootstrap` property, we can't safely determine which // declarations aren't bootstrapped so we assume that all of them are. - if (!ts.isPropertyAssignment(bootstrapProp) || - !ts.isArrayLiteralExpression(bootstrapProp.initializer)) { + if ( + !ts.isPropertyAssignment(bootstrapProp) || + !ts.isArrayLiteralExpression(bootstrapProp.initializer) + ) { return []; } @@ -545,7 +683,7 @@ function filterNonBootstrappedDeclarations( } } - return declarations.filter(ref => !bootstrappedClasses.has(ref)); + return declarations.filter((ref) => !bootstrappedClasses.has(ref)); } /** @@ -554,12 +692,15 @@ function filterNonBootstrappedDeclarations( * @param templateTypeChecker */ export function extractDeclarationsFromModule( - ngModule: ts.ClassDeclaration, - templateTypeChecker: TemplateTypeChecker): ts.ClassDeclaration[] { + ngModule: ts.ClassDeclaration, + templateTypeChecker: TemplateTypeChecker, +): ts.ClassDeclaration[] { const metadata = templateTypeChecker.getNgModuleMetadata(ngModule); - return metadata ? metadata.declarations.filter(decl => ts.isClassDeclaration(decl.node)) - .map(decl => decl.node) as ts.ClassDeclaration[] : - []; + return metadata + ? (metadata.declarations + .filter((decl) => ts.isClassDeclaration(decl.node)) + .map((decl) => decl.node) as ts.ClassDeclaration[]) + : []; } /** @@ -571,9 +712,12 @@ export function extractDeclarationsFromModule( * @param typeChecker */ export function migrateTestDeclarations( - testObjects: Set, - declarationsOutsideOfTestFiles: Set, tracker: ChangeTracker, - templateTypeChecker: TemplateTypeChecker, typeChecker: ts.TypeChecker) { + testObjects: Set, + declarationsOutsideOfTestFiles: Set, + tracker: ChangeTracker, + templateTypeChecker: TemplateTypeChecker, + typeChecker: ts.TypeChecker, +) { const {decorators, componentImports} = analyzeTestingModules(testObjects, typeChecker); const allDeclarations = new Set(declarationsOutsideOfTestFiles); @@ -595,16 +739,21 @@ export function migrateTestDeclarations( } if (importsToAdd && importsToAdd.size > 0) { - const hasTrailingComma = importsToAdd.size > 2 && - !!extractMetadataLiteral(decorator.node)?.properties.hasTrailingComma; + const hasTrailingComma = + importsToAdd.size > 2 && + !!extractMetadataLiteral(decorator.node)?.properties.hasTrailingComma; const importsArray = ts.factory.createNodeArray(Array.from(importsToAdd), hasTrailingComma); tracker.replaceNode( - decorator.node, - addPropertyToAngularDecorator( - newDecorator, - ts.factory.createPropertyAssignment( - 'imports', ts.factory.createArrayLiteralExpression(importsArray)))); + decorator.node, + addPropertyToAngularDecorator( + newDecorator, + ts.factory.createPropertyAssignment( + 'imports', + ts.factory.createArrayLiteralExpression(importsArray), + ), + ), + ); } else { tracker.replaceNode(decorator.node, newDecorator); } @@ -623,7 +772,9 @@ export function migrateTestDeclarations( * @param testObjects Object literals that should be analyzed. */ function analyzeTestingModules( - testObjects: Set, typeChecker: ts.TypeChecker) { + testObjects: Set, + typeChecker: ts.TypeChecker, +) { const seenDeclarations = new Set(); const decorators: NgDecorator[] = []; const componentImports = new Map>(); @@ -636,18 +787,24 @@ function analyzeTestingModules( } const importsProp = findLiteralProperty(obj, 'imports'); - const importElements = importsProp && hasNgModuleMetadataElements(importsProp) ? - importsProp.initializer.elements.filter(el => { - // Filter out calls since they may be a `ModuleWithProviders`. - return !ts.isCallExpression(el) && + const importElements = + importsProp && hasNgModuleMetadataElements(importsProp) + ? importsProp.initializer.elements.filter((el) => { + // Filter out calls since they may be a `ModuleWithProviders`. + return ( + !ts.isCallExpression(el) && // Also filter out the animations modules since they throw errors if they're imported // multiple times and it's common for apps to use the `NoopAnimationsModule` to // disable animations in screenshot tests. !isClassReferenceInAngularModule( - el, /^BrowserAnimationsModule|NoopAnimationsModule$/, - 'platform-browser/animations', typeChecker); - }) : - null; + el, + /^BrowserAnimationsModule|NoopAnimationsModule$/, + 'platform-browser/animations', + typeChecker, + ) + ); + }) + : null; for (const decl of declarations) { if (seenDeclarations.has(decl)) { @@ -668,7 +825,7 @@ function analyzeTestingModules( imports = new Set(); componentImports.set(decorator.node, imports); } - importElements.forEach(imp => imports!.add(imp)); + importElements.forEach((imp) => imports!.add(imp)); } } } @@ -684,7 +841,9 @@ function analyzeTestingModules( * @param typeChecker */ function extractDeclarationsFromTestObject( - obj: ts.ObjectLiteralExpression, typeChecker: ts.TypeChecker): ts.ClassDeclaration[] { + obj: ts.ObjectLiteralExpression, + typeChecker: ts.TypeChecker, +): ts.ClassDeclaration[] { const results: ts.ClassDeclaration[] = []; const declarations = findLiteralProperty(obj, 'declarations'); @@ -705,12 +864,13 @@ function extractDeclarationsFromTestObject( } /** Extracts the metadata object literal from an Angular decorator. */ -function extractMetadataLiteral(decorator: ts.Decorator): ts.ObjectLiteralExpression|null { +function extractMetadataLiteral(decorator: ts.Decorator): ts.ObjectLiteralExpression | null { // `arguments[0]` is the metadata object literal. - return ts.isCallExpression(decorator.expression) && decorator.expression.arguments.length === 1 && - ts.isObjectLiteralExpression(decorator.expression.arguments[0]) ? - decorator.expression.arguments[0] : - null; + return ts.isCallExpression(decorator.expression) && + decorator.expression.arguments.length === 1 && + ts.isObjectLiteralExpression(decorator.expression.arguments[0]) + ? decorator.expression.arguments[0] + : null; } /** @@ -720,13 +880,15 @@ function extractMetadataLiteral(decorator: ts.Decorator): ts.ObjectLiteralExpres * @param templateTypeChecker */ function isStandaloneDeclaration( - node: ts.ClassDeclaration, declarationsInMigration: Set, - templateTypeChecker: TemplateTypeChecker): boolean { + node: ts.ClassDeclaration, + declarationsInMigration: Set, + templateTypeChecker: TemplateTypeChecker, +): boolean { if (declarationsInMigration.has(node)) { return true; } const metadata = - templateTypeChecker.getDirectiveMetadata(node) || templateTypeChecker.getPipeMetadata(node); + templateTypeChecker.getDirectiveMetadata(node) || templateTypeChecker.getPipeMetadata(node); return metadata != null && metadata.isStandalone; } diff --git a/packages/core/schematics/ng-generate/standalone-migration/util.ts b/packages/core/schematics/ng-generate/standalone-migration/util.ts index d852381712fd5..d80d34bedcd9b 100644 --- a/packages/core/schematics/ng-generate/standalone-migration/util.ts +++ b/packages/core/schematics/ng-generate/standalone-migration/util.ts @@ -18,7 +18,7 @@ import {closestNode} from '../../utils/typescript/nodes'; export type NodeLookup = Map; /** Utility to type a class declaration with a name. */ -export type NamedClassDeclaration = ts.ClassDeclaration&{name: ts.Identifier}; +export type NamedClassDeclaration = ts.ClassDeclaration & {name: ts.Identifier}; /** Text span of an AST node. */ export type ReferenceSpan = [start: number, end: number]; @@ -40,7 +40,7 @@ export class UniqueItemTracker { } } - get(key: K): Set|undefined { + get(key: K): Set | undefined { return this._nodes.get(key); } @@ -51,18 +51,21 @@ export class UniqueItemTracker { /** Resolves references to nodes. */ export class ReferenceResolver { - private _languageService: ts.LanguageService|undefined; + private _languageService: ts.LanguageService | undefined; /** * If set, allows the language service to *only* read a specific file. * Used to speed up single-file lookups. */ - private _tempOnlyFile: string|null = null; + private _tempOnlyFile: string | null = null; constructor( - private _program: NgtscProgram, private _host: ts.CompilerHost, - private _rootFileNames: string[], private _basePath: string, - private _excludedFiles?: RegExp) {} + private _program: NgtscProgram, + private _host: ts.CompilerHost, + private _rootFileNames: string[], + private _basePath: string, + private _excludedFiles?: RegExp, + ) {} /** Finds all references to a node within the entire project. */ findReferencesInProject(node: ts.Node): ReferencesByFile { @@ -89,8 +92,9 @@ export class ReferenceResolver { results.set(ref.fileName, []); } - results.get(ref.fileName)!.push( - [ref.textSpan.start, ref.textSpan.start + ref.textSpan.length]); + results + .get(ref.fileName)! + .push([ref.textSpan.start, ref.textSpan.start + ref.textSpan.length]); } } } @@ -108,13 +112,14 @@ export class ReferenceResolver { const nodeStart = node.getStart(); const results: ReferenceSpan[] = []; - let highlights: ts.DocumentHighlights[]|undefined; + let highlights: ts.DocumentHighlights[] | undefined; // The language service can throw if it fails to read a file. // Silently continue since we're making the lookup on a best effort basis. try { - highlights = - this._getLanguageService().getDocumentHighlights(fileName, nodeStart, [fileName]); + highlights = this._getLanguageService().getDocumentHighlights(fileName, nodeStart, [ + fileName, + ]); } catch (e: any) { console.error('Failed reference lookup for node ' + node.getText(), e.message); } @@ -124,7 +129,10 @@ export class ReferenceResolver { // We are pretty much guaranteed to only have one match from the current file since it is // the only one being passed in `getDocumentHighlight`, but we check here just in case. if (file.fileName === fileName) { - for (const {textSpan: {start, length}, kind} of file.highlightSpans) { + for (const { + textSpan: {start, length}, + kind, + } of file.highlightSpans) { if (kind !== ts.HighlightSpanKind.none) { results.push([start, start + length]); } @@ -140,8 +148,10 @@ export class ReferenceResolver { /** Used by the language service */ private _readFile(path: string) { - if ((this._tempOnlyFile !== null && path !== this._tempOnlyFile) || - this._excludedFiles?.test(path)) { + if ( + (this._tempOnlyFile !== null && path !== this._tempOnlyFile) || + this._excludedFiles?.test(path) + ) { return ''; } return this._host.readFile(path); @@ -152,28 +162,33 @@ export class ReferenceResolver { if (!this._languageService) { const rootFileNames = this._rootFileNames.slice(); - this._program.getTsProgram().getSourceFiles().forEach(({fileName}) => { - if (!this._excludedFiles?.test(fileName) && !rootFileNames.includes(fileName)) { - rootFileNames.push(fileName); - } - }); + this._program + .getTsProgram() + .getSourceFiles() + .forEach(({fileName}) => { + if (!this._excludedFiles?.test(fileName) && !rootFileNames.includes(fileName)) { + rootFileNames.push(fileName); + } + }); this._languageService = ts.createLanguageService( - { - getCompilationSettings: () => this._program.getTsProgram().getCompilerOptions(), - getScriptFileNames: () => rootFileNames, - // The files won't change so we can return the same version. - getScriptVersion: () => '0', - getScriptSnapshot: (path: string) => { - const content = this._readFile(path); - return content ? ts.ScriptSnapshot.fromString(content) : undefined; - }, - getCurrentDirectory: () => this._basePath, - getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), - readFile: path => this._readFile(path), - fileExists: (path: string) => this._host.fileExists(path) + { + getCompilationSettings: () => this._program.getTsProgram().getCompilerOptions(), + getScriptFileNames: () => rootFileNames, + // The files won't change so we can return the same version. + getScriptVersion: () => '0', + getScriptSnapshot: (path: string) => { + const content = this._readFile(path); + return content ? ts.ScriptSnapshot.fromString(content) : undefined; }, - ts.createDocumentRegistry(), ts.LanguageServiceMode.PartialSemantic); + getCurrentDirectory: () => this._basePath, + getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options), + readFile: (path) => this._readFile(path), + fileExists: (path: string) => this._host.fileExists(path), + }, + ts.createDocumentRegistry(), + ts.LanguageServiceMode.PartialSemantic, + ); } return this._languageService; @@ -206,9 +221,12 @@ export function getNodeLookup(sourceFile: ts.SourceFile): NodeLookup { * @param results Set in which to store the results. */ export function offsetsToNodes( - lookup: NodeLookup, offsets: ReferenceSpan[], results: Set): Set { + lookup: NodeLookup, + offsets: ReferenceSpan[], + results: Set, +): Set { for (const [start, end] of offsets) { - const match = lookup.get(start)?.find(node => node.getEnd() === end); + const match = lookup.get(start)?.find((node) => node.getEnd() === end); if (match) { results.add(match); @@ -224,16 +242,22 @@ export function offsetsToNodes( * @param typeChecker */ export function findClassDeclaration( - reference: ts.Node, typeChecker: ts.TypeChecker): ts.ClassDeclaration|null { - return typeChecker.getTypeAtLocation(reference).getSymbol()?.declarations?.find( - ts.isClassDeclaration) || - null; + reference: ts.Node, + typeChecker: ts.TypeChecker, +): ts.ClassDeclaration | null { + return ( + typeChecker + .getTypeAtLocation(reference) + .getSymbol() + ?.declarations?.find(ts.isClassDeclaration) || null + ); } /** Finds a property with a specific name in an object literal expression. */ export function findLiteralProperty(literal: ts.ObjectLiteralExpression, name: string) { return literal.properties.find( - prop => prop.name && ts.isIdentifier(prop.name) && prop.name.text === name); + (prop) => prop.name && ts.isIdentifier(prop.name) && prop.name.text === name, + ); } /** Gets a relative path between two files that can be used inside a TypeScript import. */ @@ -251,10 +275,11 @@ export function getRelativeImportPath(fromFile: string, toFile: string): string /** Function used to remap the generated `imports` for a component to known shorter aliases. */ export function knownInternalAliasRemapper(imports: PotentialImport[]) { - return imports.map( - current => current.moduleSpecifier === '@angular/common' && current.symbolName === 'NgForOf' ? - {...current, symbolName: 'NgFor'} : - current); + return imports.map((current) => + current.moduleSpecifier === '@angular/common' && current.symbolName === 'NgForOf' + ? {...current, symbolName: 'NgFor'} + : current, + ); } /** @@ -263,7 +288,9 @@ export function knownInternalAliasRemapper(imports: PotentialImport[]) { * @param predicate Predicate that the result needs to pass. */ export function closestOrSelf( - node: ts.Node, predicate: (n: ts.Node) => n is T): T|null { + node: ts.Node, + predicate: (n: ts.Node) => n is T, +): T | null { return predicate(node) ? node : closestNode(node, predicate); } @@ -275,24 +302,31 @@ export function closestOrSelf( * @param typeChecker */ export function isClassReferenceInAngularModule( - node: ts.Node, className: string|RegExp, moduleName: string, - typeChecker: ts.TypeChecker): boolean { + node: ts.Node, + className: string | RegExp, + moduleName: string, + typeChecker: ts.TypeChecker, +): boolean { const symbol = typeChecker.getTypeAtLocation(node).getSymbol(); const externalName = `@angular/${moduleName}`; const internalName = `angular2/rc/packages/${moduleName}`; - return !!symbol?.declarations?.some(decl => { + return !!symbol?.declarations?.some((decl) => { const closestClass = closestOrSelf(decl, ts.isClassDeclaration); const closestClassFileName = closestClass?.getSourceFile().fileName; - if (!closestClass || !closestClassFileName || !closestClass.name || - !ts.isIdentifier(closestClass.name) || - (!closestClassFileName.includes(externalName) && - !closestClassFileName.includes(internalName))) { + if ( + !closestClass || + !closestClassFileName || + !closestClass.name || + !ts.isIdentifier(closestClass.name) || + (!closestClassFileName.includes(externalName) && !closestClassFileName.includes(internalName)) + ) { return false; } - return typeof className === 'string' ? closestClass.name.text === className : - className.test(closestClass.name.text); + return typeof className === 'string' + ? closestClass.name.text === className + : className.test(closestClass.name.text); }); } diff --git a/packages/core/schematics/test/all-migrations.spec.ts b/packages/core/schematics/test/all-migrations.spec.ts index ebcb0ea8e3699..ce16f1d2f59db 100644 --- a/packages/core/schematics/test/all-migrations.spec.ts +++ b/packages/core/schematics/test/all-migrations.spec.ts @@ -22,8 +22,9 @@ describe('all migrations', () => { let previousWorkingDir: string; const migrationCollectionPath = runfiles.resolvePackageRelative('../migrations.json'); - const allMigrationSchematics = - Object.keys((JSON.parse(fs.readFileSync(migrationCollectionPath, 'utf8')) as any).schematics); + const allMigrationSchematics = Object.keys( + (JSON.parse(fs.readFileSync(migrationCollectionPath, 'utf8')) as any).schematics, + ); beforeEach(() => { runner = new SchematicTestRunner('test', migrationCollectionPath); @@ -31,13 +32,15 @@ describe('all migrations', () => { tree = new UnitTestTree(new HostTree(host)); writeFile('/node_modules/@angular/core/index.d.ts', `export const MODULE: any;`); - writeFile('/angular.json', JSON.stringify({ - version: 1, - projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}} - })); + writeFile( + '/angular.json', + JSON.stringify({ + version: 1, + projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, + }), + ); writeFile('/tsconfig.json', `{}`); - previousWorkingDir = shx.pwd(); tmpDirPath = getSystemPath(host.root); @@ -63,21 +66,24 @@ describe('all migrations', () => { throw Error('No migration schematics found.'); } - allMigrationSchematics.forEach(name => { + allMigrationSchematics.forEach((name) => { describe(name, () => createTests(name)); }); function createTests(migrationName: string) { // Regression test for: https://github.com/angular/angular/issues/36346. it('should not throw if non-existent symbols are imported with rootDirs', async () => { - writeFile(`/tsconfig.json`, JSON.stringify({ - compilerOptions: { - rootDirs: [ - './generated', - ] - } - })); - writeFile('/index.ts', ` + writeFile( + `/tsconfig.json`, + JSON.stringify({ + compilerOptions: { + rootDirs: ['./generated'], + }, + }), + ); + writeFile( + '/index.ts', + ` import {Renderer} from '@angular/core'; const variableDecl: Renderer = null; @@ -85,7 +91,8 @@ describe('all migrations', () => { export class Test { constructor(renderer: Renderer) {} } - `); + `, + ); let error: any = null; try { diff --git a/packages/core/schematics/test/google3/wait_for_async_spec.ts b/packages/core/schematics/test/google3/wait_for_async_spec.ts index 86b8044d2498e..c6313f0402b67 100644 --- a/packages/core/schematics/test/google3/wait_for_async_spec.ts +++ b/packages/core/schematics/test/google3/wait_for_async_spec.ts @@ -13,8 +13,9 @@ import shx from 'shelljs'; import {Configuration, Linter} from 'tslint'; describe('Google3 waitForAsync TSLint rule', () => { - const rulesDirectory = - dirname(runfiles.resolvePackageRelative('../../migrations/google3/waitForAsyncCjsRule.js')); + const rulesDirectory = dirname( + runfiles.resolvePackageRelative('../../migrations/google3/waitForAsyncCjsRule.js'), + ); let tmpDir: string; @@ -23,19 +24,25 @@ describe('Google3 waitForAsync TSLint rule', () => { shx.mkdir('-p', tmpDir); // We need to declare the Angular symbols we're testing for, otherwise type checking won't work. - writeFile('testing.d.ts', ` + writeFile( + 'testing.d.ts', + ` export declare function async(fn: Function): any; - `); - - writeFile('tsconfig.json', JSON.stringify({ - compilerOptions: { - module: 'es2015', - baseUrl: './', - paths: { - '@angular/core/testing': ['testing.d.ts'], - } - }, - })); + `, + ); + + writeFile( + 'tsconfig.json', + JSON.stringify({ + compilerOptions: { + module: 'es2015', + baseUrl: './', + paths: { + '@angular/core/testing': ['testing.d.ts'], + }, + }, + }), + ); }); afterEach(() => shx.rm('-r', tmpDir)); @@ -45,7 +52,7 @@ describe('Google3 waitForAsync TSLint rule', () => { const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program); const config = Configuration.parseConfigFile({rules: {'wait-for-async-cjs': true}}); - program.getRootFileNames().forEach(fileName => { + program.getRootFileNames().forEach((fileName) => { linter.lint(fileName, program.getSourceFile(fileName)!.getFullText(), config); }); @@ -61,7 +68,9 @@ describe('Google3 waitForAsync TSLint rule', () => { } it('should flag async imports and usages', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async, inject } from '@angular/core/testing'; it('should work', async(() => { @@ -71,10 +80,11 @@ describe('Google3 waitForAsync TSLint rule', () => { it('should also work', async(() => { expect(inject('bar')).toBe('bar'); })); - `); + `, + ); const linter = runTSLint(false); - const failures = linter.getResult().failures.map(failure => failure.getFailure()); + const failures = linter.getResult().failures.map((failure) => failure.getFailure()); expect(failures.length).toBe(3); expect(failures[0]).toMatch(/Imports of the deprecated async function are not allowed/); expect(failures[1]).toMatch(/References to the deprecated async function are not allowed/); @@ -82,42 +92,53 @@ describe('Google3 waitForAsync TSLint rule', () => { }); it('should change async imports to waitForAsync', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async, inject } from '@angular/core/testing'; it('should work', async(() => { expect(inject('foo')).toBe('foo'); })); - `); + `, + ); runTSLint(true); - expect(getFile('/index.ts')) - .toContain(`import { inject, waitForAsync } from '@angular/core/testing';`); + expect(getFile('/index.ts')).toContain( + `import { inject, waitForAsync } from '@angular/core/testing';`, + ); }); it('should change aliased async imports to waitForAsync', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async as renamedAsync, inject } from '@angular/core/testing'; it('should work', renamedAsync(() => { expect(inject('foo')).toBe('foo'); })); - `); + `, + ); runTSLint(true); - expect(getFile('/index.ts')) - .toContain(`import { inject, waitForAsync as renamedAsync } from '@angular/core/testing';`); + expect(getFile('/index.ts')).toContain( + `import { inject, waitForAsync as renamedAsync } from '@angular/core/testing';`, + ); }); it('should not change async imports if they are not from @angular/core/testing', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { inject } from '@angular/core/testing'; import { async } from './my-test-library'; it('should work', async(() => { expect(inject('foo')).toBe('foo'); })); - `); + `, + ); runTSLint(true); const content = getFile('/index.ts'); @@ -126,7 +147,9 @@ describe('Google3 waitForAsync TSLint rule', () => { }); it('should not change imports if waitForAsync was already imported', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async, inject, waitForAsync } from '@angular/core/testing'; it('should work', async(() => { @@ -136,15 +159,19 @@ describe('Google3 waitForAsync TSLint rule', () => { it('should also work', waitForAsync(() => { expect(inject('bar')).toBe('bar'); })); - `); + `, + ); runTSLint(true); - expect(getFile('/index.ts')) - .toContain(`import { async, inject, waitForAsync } from '@angular/core/testing';`); + expect(getFile('/index.ts')).toContain( + `import { async, inject, waitForAsync } from '@angular/core/testing';`, + ); }); it('should change calls from `async` to `waitForAsync`', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async, inject } from '@angular/core/testing'; it('should work', async(() => { @@ -154,7 +181,8 @@ describe('Google3 waitForAsync TSLint rule', () => { it('should also work', async(() => { expect(inject('bar')).toBe('bar'); })); - `); + `, + ); runTSLint(true); @@ -165,19 +193,23 @@ describe('Google3 waitForAsync TSLint rule', () => { }); it('should not change aliased calls', () => { - writeFile('/index.ts', ` + writeFile( + '/index.ts', + ` import { async as renamedAsync, inject } from '@angular/core/testing'; it('should work', renamedAsync(() => { expect(inject('foo')).toBe('foo'); })); - `); + `, + ); runTSLint(true); const content = getFile('/index.ts'); expect(content).toContain( - `import { inject, waitForAsync as renamedAsync } from '@angular/core/testing';`); + `import { inject, waitForAsync as renamedAsync } from '@angular/core/testing';`, + ); expect(content).toContain(`it('should work', renamedAsync(() => {`); }); }); diff --git a/packages/core/schematics/test/helpers.ts b/packages/core/schematics/test/helpers.ts index f2bcfe2d6f381..859023884efc6 100644 --- a/packages/core/schematics/test/helpers.ts +++ b/packages/core/schematics/test/helpers.ts @@ -23,7 +23,7 @@ export function dedent(strings: TemplateStringsArray, ...values: any[]) { return joinedString; } - const minLineIndent = Math.min(...matches.map(el => el.length)); + const minLineIndent = Math.min(...matches.map((el) => el.length)); const omitMinIndentRegex = new RegExp(`^[ \\t]{${minLineIndent}}`, 'gm'); const omitEmptyLineWhitespaceRegex = /^[ \t]+$/gm; const result = minLineIndent > 0 ? joinedString.replace(omitMinIndentRegex, '') : joinedString; diff --git a/packages/core/schematics/test/http_providers_spec.ts b/packages/core/schematics/test/http_providers_spec.ts index 7a7a55c18ce16..d075ec139d0bc 100644 --- a/packages/core/schematics/test/http_providers_spec.ts +++ b/packages/core/schematics/test/http_providers_spec.ts @@ -34,21 +34,21 @@ describe('Http providers migration', () => { tree = new UnitTestTree(new HostTree(host)); writeFile( - '/tsconfig.json', - JSON.stringify({ - compilerOptions: { - lib: ['es2015'], - strictNullChecks: true, - }, - }), + '/tsconfig.json', + JSON.stringify({ + compilerOptions: { + lib: ['es2015'], + strictNullChecks: true, + }, + }), ); writeFile( - '/angular.json', - JSON.stringify({ - version: 1, - projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, - }), + '/angular.json', + JSON.stringify({ + version: 1, + projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, + }), ); previousWorkingDir = shx.pwd(); @@ -66,8 +66,8 @@ describe('Http providers migration', () => { it('should replace HttpClientModule', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@angular/common/http'; import { CommonModule } from '@angular/common'; @@ -96,14 +96,14 @@ describe('Http providers migration', () => { expect(content).toMatch(/import.*withJsonpSupport/); expect(content).toMatch(/import.*withXsrfConfiguration/); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withXsrfConfiguration({ cookieName: 'foobar' }))`, + `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withXsrfConfiguration({ cookieName: 'foobar' }))`, ); }); it('should replace HttpClientModule with existing providers ', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@angular/common/http'; @@ -131,14 +131,14 @@ describe('Http providers migration', () => { expect(content).toContain(`HttpTransferCacheOptions`); expect(content).toContain(`provideConfig({ someConfig: 'foobar' })`); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withXsrfConfiguration({ cookieName: 'foobar' }))`, + `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withXsrfConfiguration({ cookieName: 'foobar' }))`, ); }); it('should replace HttpClientModule & HttpClientXsrfModule.disable()', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@angular/common/http'; @@ -166,14 +166,14 @@ describe('Http providers migration', () => { expect(content).toContain(`HttpTransferCacheOptions`); expect(content).toContain(`provideConfig({ someConfig: 'foobar' })`); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withNoXsrfProtection())`, + `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport(), withNoXsrfProtection())`, ); }); it('should replace HttpClientModule & base HttpClientXsrfModule', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@angular/common/http'; @@ -201,14 +201,14 @@ describe('Http providers migration', () => { expect(content).toContain(`HttpTransferCacheOptions`); expect(content).toContain(`provideConfig({ someConfig: 'foobar' })`); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi(), withXsrfConfiguration())`, + `provideHttpClient(withInterceptorsFromDi(), withXsrfConfiguration())`, ); }); it('should handle a migration with 2 modules in the same file ', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@angular/common/http'; @@ -237,14 +237,14 @@ describe('Http providers migration', () => { expect(content).toContain(`provideConfig({ someConfig: 'foobar' })`); expect(content).toContain(`provideHttpClient(withInterceptorsFromDi(), withJsonpSupport())`); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi(), withNoXsrfProtection())`, + `provideHttpClient(withInterceptorsFromDi(), withNoXsrfProtection())`, ); }); it('should handle a migration for acomponent ', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { Component } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http'; @@ -266,8 +266,8 @@ describe('Http providers migration', () => { it('should not migrate HttpClientModule from another package', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule, HttpClientXsrfModule, HttpTransferCacheOptions } from '@not-angular/common/http'; @@ -295,17 +295,17 @@ describe('Http providers migration', () => { expect(content).toContain(`HttpTransferCacheOptions`); expect(content).toContain(`provideConfig({ someConfig: 'foobar' })`); expect(content).not.toContain( - `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport())`, + `provideHttpClient(withInterceptorsFromDi(), withJsonpSupport())`, ); expect(content).not.toContain( - `provideHttpClient(withInterceptorsFromDi(), withNoXsrfProtection())`, + `provideHttpClient(withInterceptorsFromDi(), withNoXsrfProtection())`, ); }); it('should migrate HttpClientTestingModule', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; @@ -322,13 +322,14 @@ describe('Http providers migration', () => { expect(content).not.toContain(`HttpClientTestingModule`); expect(content).toMatch(/import.*provideHttpClientTesting/); expect(content).toContain( - `provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()`); + `provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()`, + ); }); it('should not migrate HttpClientTestingModule from outside package', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@not-angular/common/http/testing'; @@ -348,8 +349,8 @@ describe('Http providers migration', () => { it('shouldmigrate NgModule + TestBed.configureTestingModule in the same file', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; @@ -378,18 +379,21 @@ describe('Http providers migration', () => { expect(content).toContain('provideHttpClientTesting'); expect(content).toContain('provideHttpClient(withInterceptorsFromDi(), withJsonpSupport())'); expect(content).toContain( - 'provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()'); + 'provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()', + ); expect(content).toContain( - `import { withInterceptorsFromDi, withJsonpSupport, provideHttpClient } from '@angular/common/http';`); + `import { withInterceptorsFromDi, withJsonpSupport, provideHttpClient } from '@angular/common/http';`, + ); expect(content).toContain( - `import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';`); + `import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';`, + ); }); it('should not change a decorator with no arguments', async () => { writeFile( - '/index.ts', - ` + '/index.ts', + ` import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http'; diff --git a/packages/core/schematics/test/invalid_two_way_bindings_spec.ts b/packages/core/schematics/test/invalid_two_way_bindings_spec.ts index 53eb2abd319a5..02bd22429bbbf 100644 --- a/packages/core/schematics/test/invalid_two_way_bindings_spec.ts +++ b/packages/core/schematics/test/invalid_two_way_bindings_spec.ts @@ -34,10 +34,13 @@ describe('Invalid two-way bindings migration', () => { tree = new UnitTestTree(new HostTree(host)); writeFile('/tsconfig.json', '{}'); - writeFile('/angular.json', JSON.stringify({ - version: 1, - projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}} - })); + writeFile( + '/angular.json', + JSON.stringify({ + version: 1, + projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, + }), + ); previousWorkingDir = shx.pwd(); tmpDirPath = getSystemPath(host.root); @@ -53,71 +56,89 @@ describe('Invalid two-way bindings migration', () => { }); it('should migrate a two-way binding with a binary expression', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should migrate a two-way binding with a single unary expression', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should migrate a two-way binding with a nested unary expression', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should migrate a two-way binding with a conditional expression', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should migrate multiple inline templates in the same file', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ @@ -129,124 +150,142 @@ describe('Invalid two-way bindings migration', () => { template: \`\` }) class Comp2 {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should migrate an external template', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ templateUrl: './comp.html' }) class Comp {} - `); + `, + ); - writeFile('/comp.html', [ - `
`, - `hello`, - ``, - ``, - ``, - `
`, - ].join('\n')); + writeFile( + '/comp.html', + [`
`, `hello`, ``, ``, ``, `
`].join('\n'), + ); await runMigration(); const content = tree.readContent('/comp.html'); - expect(content).toBe([ - `
`, - `hello`, - ``, - ``, - ``, - `
`, - ].join('\n')); + expect(content).toBe( + [ + `
`, + `hello`, + ``, + ``, + ``, + `
`, + ].join('\n'), + ); }); it('should migrate a template referenced by multiple components', async () => { - writeFile('/comp-a.ts', ` + writeFile( + '/comp-a.ts', + ` import {Component} from '@angular/core'; @Component({ templateUrl: './comp.html' }) class CompA {} - `); + `, + ); - writeFile('/comp-b.ts', ` + writeFile( + '/comp-b.ts', + ` import {Component} from '@angular/core'; @Component({ templateUrl: './comp.html' }) class CompB {} - `); + `, + ); - writeFile('/comp.html', [ - `
`, - `hello`, - ``, - ``, - ``, - `
`, - ].join('\n')); + writeFile( + '/comp.html', + [`
`, `hello`, ``, ``, ``, `
`].join('\n'), + ); await runMigration(); const content = tree.readContent('/comp.html'); - expect(content).toBe([ - `
`, - `hello`, - ``, - ``, - ``, - `
`, - ].join('\n')); + expect(content).toBe( + [ + `
`, + `hello`, + ``, + ``, + ``, + `
`, + ].join('\n'), + ); }); it('should migrate multiple two-way bindings on the same element', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: \`\`'); + 'template: ``', + ); }); it('should not stop the migration if a file cannot be read', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ templateUrl: './does-not-exist.html' }) class BrokenComp {} - `); + `, + ); - writeFile('/other-comp.ts', ` + writeFile( + '/other-comp.ts', + ` import {Component} from '@angular/core'; @Component({ templateUrl: './comp.html' }) class Comp {} - `); + `, + ); writeFile('/comp.html', ''); @@ -257,7 +296,9 @@ describe('Invalid two-way bindings migration', () => { }); it('should migrate a component that is not at the top level', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; function foo() { @@ -266,76 +307,90 @@ describe('Invalid two-way bindings migration', () => { }) class Comp {} } - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); it('should preserve a valid expression', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain('template: ``'); }); - it('should not migrate an invalid expression if an event listener for the same binding exists', - async () => { - writeFile('/comp.ts', ` + it('should not migrate an invalid expression if an event listener for the same binding exists', async () => { + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); - await runMigration(); - const content = tree.readContent('/comp.ts'); - expect(content).toContain( - 'template: ``'); - }); + await runMigration(); + const content = tree.readContent('/comp.ts'); + expect(content).toContain( + 'template: ``', + ); + }); - it('should not migrate an invalid expression if a property binding for the same binding exists', - async () => { - writeFile('/comp.ts', ` + it('should not migrate an invalid expression if a property binding for the same binding exists', async () => { + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); - await runMigration(); - const content = tree.readContent('/comp.ts'); - expect(content).toContain('template: ``'); - }); + await runMigration(); + const content = tree.readContent('/comp.ts'); + expect(content).toContain('template: ``'); + }); it('should migrate a two-way binding on an ng-template', async () => { - writeFile('/comp.ts', ` + writeFile( + '/comp.ts', + ` import {Component} from '@angular/core'; @Component({ template: \`\` }) class Comp {} - `); + `, + ); await runMigration(); const content = tree.readContent('/comp.ts'); expect(content).toContain( - 'template: ``'); + 'template: ``', + ); }); }); diff --git a/packages/core/schematics/test/project_tsconfig_paths_spec.ts b/packages/core/schematics/test/project_tsconfig_paths_spec.ts index d1e2ec22100ac..02e4a7f3ad6bc 100644 --- a/packages/core/schematics/test/project_tsconfig_paths_spec.ts +++ b/packages/core/schematics/test/project_tsconfig_paths_spec.ts @@ -20,20 +20,24 @@ describe('project tsconfig paths', () => { it('should detect build tsconfig path inside of angular.json file', async () => { testTree.create('/my-custom-config.json', ''); - testTree.create('/angular.json', JSON.stringify({ - version: 1, - projects: { - my_name: {root: '', architect: {build: {options: {tsConfig: './my-custom-config.json'}}}} - } - })); + testTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + my_name: {root: '', architect: {build: {options: {tsConfig: './my-custom-config.json'}}}}, + }, + }), + ); expect((await getProjectTsConfigPaths(testTree)).buildPaths).toEqual(['my-custom-config.json']); }); - it('should be able to read workspace configuration which is using jsconc-parser features', - async () => { - testTree.create('/my-build-config.json', ''); - testTree.create('/angular.json', `{ + it('should be able to read workspace configuration which is using jsconc-parser features', async () => { + testTree.create('/my-build-config.json', ''); + testTree.create( + '/angular.json', + `{ "version": 1, // Comments are supported in the workspace configurations. "projects": { @@ -48,42 +52,51 @@ describe('project tsconfig paths', () => { } } }, - }`); + }`, + ); - expect((await getProjectTsConfigPaths(testTree)).buildPaths).toEqual([ - 'my-build-config.json' - ]); - }); + expect((await getProjectTsConfigPaths(testTree)).buildPaths).toEqual(['my-build-config.json']); + }); it('should detect test tsconfig path inside of angular.json file', async () => { testTree.create('/my-test-config.json', ''); - testTree.create('/angular.json', JSON.stringify({ - version: 1, - projects: - {my_name: {root: '', architect: {test: {options: {tsConfig: './my-test-config.json'}}}}} - })); + testTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + my_name: {root: '', architect: {test: {options: {tsConfig: './my-test-config.json'}}}}, + }, + }), + ); expect((await getProjectTsConfigPaths(testTree)).testPaths).toEqual(['my-test-config.json']); }); it('should detect test tsconfig path inside of .angular.json file', async () => { testTree.create('/my-test-config.json', ''); - testTree.create('/.angular.json', JSON.stringify({ - version: 1, - projects: { - with_tests: {root: '', architect: {test: {options: {tsConfig: './my-test-config.json'}}}} - } - })); + testTree.create( + '/.angular.json', + JSON.stringify({ + version: 1, + projects: { + with_tests: {root: '', architect: {test: {options: {tsConfig: './my-test-config.json'}}}}, + }, + }), + ); expect((await getProjectTsConfigPaths(testTree)).testPaths).toEqual(['my-test-config.json']); }); it('should not return duplicate tsconfig files', async () => { testTree.create('/tsconfig.json', ''); - testTree.create('/.angular.json', JSON.stringify({ - version: 1, - projects: {app: {root: '', architect: {build: {options: {tsConfig: 'tsconfig.json'}}}}} - })); + testTree.create( + '/.angular.json', + JSON.stringify({ + version: 1, + projects: {app: {root: '', architect: {build: {options: {tsConfig: 'tsconfig.json'}}}}}, + }), + ); expect((await getProjectTsConfigPaths(testTree)).buildPaths).toEqual(['tsconfig.json']); }); diff --git a/packages/core/schematics/test/standalone_migration_spec.ts b/packages/core/schematics/test/standalone_migration_spec.ts index a4b37c398623d..361ea71aeed2c 100644 --- a/packages/core/schematics/test/standalone_migration_spec.ts +++ b/packages/core/schematics/test/standalone_migration_spec.ts @@ -37,34 +37,48 @@ describe('standalone migration', () => { host = new TempScopedNodeJsSyncHost(); tree = new UnitTestTree(new HostTree(host)); - writeFile('/tsconfig.json', JSON.stringify({ - compilerOptions: { - lib: ['es2015'], - strictNullChecks: true, - }, - })); - - writeFile('/angular.json', JSON.stringify({ - version: 1, - projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}} - })); + writeFile( + '/tsconfig.json', + JSON.stringify({ + compilerOptions: { + lib: ['es2015'], + strictNullChecks: true, + }, + }), + ); + + writeFile( + '/angular.json', + JSON.stringify({ + version: 1, + projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, + }), + ); // We need to declare the Angular symbols we're testing for, otherwise type checking won't work. - writeFile('/node_modules/@angular/core/index.d.ts', ` + writeFile( + '/node_modules/@angular/core/index.d.ts', + ` export declare class PlatformRef { bootstrapModule(module: any): any; } export declare function forwardRef(fn: () => T): T; - `); + `, + ); - writeFile('/node_modules/@angular/platform-browser/index.d.ts', ` + writeFile( + '/node_modules/@angular/platform-browser/index.d.ts', + ` import {PlatformRef} from '@angular/core'; export const platformBrowser: () => PlatformRef; - `); + `, + ); - writeFile('/node_modules/@angular/platform-browser/animations/index.d.ts', ` + writeFile( + '/node_modules/@angular/platform-browser/animations/index.d.ts', + ` import {ModuleWithProviders} from '@angular/core'; export declare class BrowserAnimationsModule { @@ -72,15 +86,21 @@ describe('standalone migration', () => { } export declare class NoopAnimationsModule {} - `); + `, + ); - writeFile('/node_modules/@angular/platform-browser-dynamic/index.d.ts', ` + writeFile( + '/node_modules/@angular/platform-browser-dynamic/index.d.ts', + ` import {PlatformRef} from '@angular/core'; export const platformBrowserDynamic: () => PlatformRef; - `); + `, + ); - writeFile('/node_modules/@angular/common/index.d.ts', ` + writeFile( + '/node_modules/@angular/common/index.d.ts', + ` import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; export declare class NgIf { @@ -108,9 +128,12 @@ describe('standalone migration', () => { } export {NgForOf as NgFor}; - `); + `, + ); - writeFile('/node_modules/@angular/router/index.d.ts', ` + writeFile( + '/node_modules/@angular/router/index.d.ts', + ` import {ModuleWithProviders, ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; export declare class RouterLink { @@ -122,9 +145,12 @@ describe('standalone migration', () => { static forRoot(routes: any[], config?: any): ModuleWithProviders; static ɵmod: ɵɵNgModuleDeclaration; } - `); + `, + ); - writeFile('/node_modules/@angular/common/http/index.d.ts', ` + writeFile( + '/node_modules/@angular/common/http/index.d.ts', + ` import {ModuleWithProviders} from '@angular/core'; export declare class HttpClientModule { @@ -132,17 +158,24 @@ describe('standalone migration', () => { static ɵmod: i0.ɵɵNgModuleDeclaration; static ɵinj: i0.ɵɵInjectorDeclaration; } - `); + `, + ); - writeFile('/node_modules/@angular/core/testing/index.d.ts', ` + writeFile( + '/node_modules/@angular/core/testing/index.d.ts', + ` export declare class TestBed { static configureTestingModule(config: any): any; } - `); + `, + ); - writeFile('/node_modules/some_internal_path/angular/testing/catalyst/index.d.ts', ` + writeFile( + '/node_modules/some_internal_path/angular/testing/catalyst/index.d.ts', + ` export declare function setupModule(config: any); - `); + `, + ); previousWorkingDir = shx.pwd(); tmpDirPath = getSystemPath(host.root); @@ -158,14 +191,17 @@ describe('standalone migration', () => { }); it('should throw an error if no files match the passed-in path', async () => { - let error: string|null = null; + let error: string | null = null; - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]'}) export class MyDir {} - `); + `, + ); try { await runMigration('convert-to-standalone', './foo'); @@ -174,18 +210,22 @@ describe('standalone migration', () => { } expect(error).toMatch( - /Could not find any files to migrate under the path .*\/foo\. Cannot run the standalone migration/); + /Could not find any files to migrate under the path .*\/foo\. Cannot run the standalone migration/, + ); }); it('should throw an error if a path outside of the project is passed in', async () => { - let error: string|null = null; + let error: string | null = null; - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]'}) export class MyDir {} - `); + `, + ); try { await runMigration('convert-to-standalone', '../foo'); @@ -197,7 +237,7 @@ describe('standalone migration', () => { }); it('should throw an error if the passed in path is a file', async () => { - let error: string|null = null; + let error: string | null = null; writeFile('dir.ts', ''); @@ -208,11 +248,14 @@ describe('standalone migration', () => { } expect(error).toMatch( - /Migration path .*\/dir\.ts has to be a directory\. Cannot run the standalone migration/); + /Migration path .*\/dir\.ts has to be a directory\. Cannot run the standalone migration/, + ); }); it('should create an `imports` array if the module does not have one already', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; @Directive({selector: '[dir]'}) @@ -220,16 +263,20 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [MyDir]})`)); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyDir]})`), + ); }); it('should combine the `declarations` array with a static `imports` array', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -238,17 +285,20 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir], imports: [CommonModule]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [CommonModule, MyDir]})`)); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [CommonModule, MyDir]})`), + ); }); - it('should combine a `declarations` array with a spread expression into the `imports`', - async () => { - writeFile('module.ts', ` + it('should combine a `declarations` array with a spread expression into the `imports`', async () => { + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -262,18 +312,20 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir, ...extraDeclarations], imports: [CommonModule]}) export class Mod {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace( - `@NgModule({imports: [CommonModule, MyDir, ...extraDeclarations]})`)); - }); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [CommonModule, MyDir, ...extraDeclarations]})`), + ); + }); - it('should combine a `declarations` array with an `imports` array that has a spread expression', - async () => { - writeFile('module.ts', ` + it('should combine a `declarations` array with an `imports` array that has a spread expression', async () => { + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -287,18 +339,20 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir], imports: [CommonModule, ...extraImports]}) export class Mod {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain( - stripWhitespace(`@NgModule({imports: [CommonModule, ...extraImports, MyDir]})`)); - }); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [CommonModule, ...extraImports, MyDir]})`), + ); + }); - it('should use a spread expression if the `declarations` is an expression when combining with the `imports`', - async () => { - writeFile('module.ts', ` + it('should use a spread expression if the `declarations` is an expression when combining with the `imports`', async () => { + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -309,17 +363,20 @@ describe('standalone migration', () => { @NgModule({declarations: DECLARATIONS, imports: [CommonModule]}) export class Mod {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [CommonModule, ...DECLARATIONS]})`)); - }); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [CommonModule, ...DECLARATIONS]})`), + ); + }); - it('should use a spread expression if the `imports` is an expression when combining with the `declarations`', - async () => { - writeFile('module.ts', ` + it('should use a spread expression if the `imports` is an expression when combining with the `declarations`', async () => { + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -330,17 +387,20 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir], imports: IMPORTS}) export class Mod {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [...IMPORTS, MyDir]})`)); - }); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [...IMPORTS, MyDir]})`), + ); + }); - it('should use a spread expression if both the `declarations` and the `imports` are not static arrays', - async () => { - writeFile('module.ts', ` + it('should use a spread expression if both the `declarations` and the `imports` are not static arrays', async () => { + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -352,16 +412,20 @@ describe('standalone migration', () => { @NgModule({declarations: DECLARATIONS, imports: IMPORTS}) export class Mod {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [...IMPORTS, ...DECLARATIONS]})`)); - }); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [...IMPORTS, ...DECLARATIONS]})`), + ); + }); it('should convert a directive in the same file as its module to standalone', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; @Directive({selector: '[dir]'}) @@ -369,20 +433,25 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir], exports: [MyDir]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); const result = tree.readContent('module.ts'); - expect(stripWhitespace(result)) - .toContain(stripWhitespace(`@Directive({selector: '[dir]', standalone: true})`)); - expect(stripWhitespace(result)) - .toContain(stripWhitespace(`@NgModule({imports: [MyDir], exports: [MyDir]})`)); + expect(stripWhitespace(result)).toContain( + stripWhitespace(`@Directive({selector: '[dir]', standalone: true})`), + ); + expect(stripWhitespace(result)).toContain( + stripWhitespace(`@NgModule({imports: [MyDir], exports: [MyDir]})`), + ); }); it('should convert a pipe in the same file as its module to standalone', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Pipe} from '@angular/core'; @Pipe({name: 'myPipe'}) @@ -390,16 +459,19 @@ describe('standalone migration', () => { @NgModule({declarations: [MyPipe], exports: [MyPipe]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); const result = tree.readContent('module.ts'); - expect(stripWhitespace(result)) - .toContain(stripWhitespace(`@Pipe({name: 'myPipe', standalone: true})`)); - expect(stripWhitespace(result)) - .toContain(stripWhitespace(`@NgModule({imports: [MyPipe], exports: [MyPipe]})`)); + expect(stripWhitespace(result)).toContain( + stripWhitespace(`@Pipe({name: 'myPipe', standalone: true})`), + ); + expect(stripWhitespace(result)).toContain( + stripWhitespace(`@NgModule({imports: [MyPipe], exports: [MyPipe]})`), + ); }); it('should only migrate declarations under a specific path', async () => { @@ -423,60 +495,79 @@ describe('standalone migration', () => { }); it('should convert a directive in a different file from its module to standalone', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; @NgModule({declarations: [MyDir], exports: [MyDir]}) export class Mod {} - `); + `, + ); - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]'}) export class MyDir {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [MyDir], exports: [MyDir]})`)); - expect(stripWhitespace(tree.readContent('dir.ts'))) - .toContain(stripWhitespace(`@Directive({selector: '[dir]', standalone: true})`)); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyDir], exports: [MyDir]})`), + ); + expect(stripWhitespace(tree.readContent('dir.ts'))).toContain( + stripWhitespace(`@Directive({selector: '[dir]', standalone: true})`), + ); }); it('should convert a component with no template dependencies to standalone', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; @NgModule({declarations: [MyComp], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: '

Hello

'}) export class MyComp {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [MyComp], exports: [MyComp]})`)); - expect(stripWhitespace(tree.readContent('comp.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyComp], exports: [MyComp]})`), + ); + expect(stripWhitespace(tree.readContent('comp.ts'))).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: '

Hello

', standalone: true }) - `)); + `), + ); }); it('should add imports to dependencies within the same module', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyButton} from './button'; @@ -484,28 +575,38 @@ describe('standalone migration', () => { @NgModule({declarations: [MyComp, MyButton, MyTooltip], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); - writeFile('tooltip.ts', ` + writeFile( + 'tooltip.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[tooltip]'}) export class MyTooltip {} - `); + `, + ); await runMigration('convert-to-standalone'); @@ -513,35 +614,45 @@ describe('standalone migration', () => { expect(myCompContent).toContain(`import { MyButton } from './button';`); expect(myCompContent).toContain(`import { MyTooltip } from './tooltip';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [MyButton, MyTooltip] }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace( - `@NgModule({imports: [MyComp, MyButton, MyTooltip], exports: [MyComp]})`)); - expect(stripWhitespace(tree.readContent('button.ts'))) - .toContain(stripWhitespace( - `@Component({selector: 'my-button', template: '', standalone: true})`)); - expect(stripWhitespace(tree.readContent('tooltip.ts'))) - .toContain(stripWhitespace(`@Directive({selector: '[tooltip]', standalone: true})`)); + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyComp, MyButton, MyTooltip], exports: [MyComp]})`), + ); + expect(stripWhitespace(tree.readContent('button.ts'))).toContain( + stripWhitespace( + `@Component({selector: 'my-button', template: '', standalone: true})`, + ), + ); + expect(stripWhitespace(tree.readContent('tooltip.ts'))).toContain( + stripWhitespace(`@Directive({selector: '[tooltip]', standalone: true})`), + ); }); it('should reuse existing import statements when adding imports to a component', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyButton} from './button'; @NgModule({declarations: [MyComp, MyButton], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; import {helper} from './button'; @@ -549,193 +660,245 @@ describe('standalone migration', () => { @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} export function helper() {} - `); + `, + ); await runMigration('convert-to-standalone'); const myCompContent = tree.readContent('comp.ts'); expect(myCompContent).toContain(`import { helper, MyButton } from './button';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [MyButton] }) - `)); + `), + ); }); - it('should refer to pre-existing standalone dependencies directly when adding to the `imports`', - async () => { - writeFile('module.ts', ` + it('should refer to pre-existing standalone dependencies directly when adding to the `imports`', async () => { + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyButton} from './button'; @NgModule({imports: [MyButton], declarations: [MyComp], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; import {MyComp} from './comp'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); - await runMigration('convert-to-standalone'); - const myCompContent = tree.readContent('comp.ts'); + await runMigration('convert-to-standalone'); + const myCompContent = tree.readContent('comp.ts'); - expect(myCompContent).toContain(`import { MyButton } from './button';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(myCompContent).toContain(`import { MyButton } from './button';`); + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [MyButton] }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain( - stripWhitespace('@NgModule({imports: [MyButton, MyComp], exports: [MyComp]})')); - }); + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace('@NgModule({imports: [MyButton, MyComp], exports: [MyComp]})'), + ); + }); it('should refer to dependencies being handled in the same migration directly', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule} from './button.module'; @NgModule({imports: [ButtonModule], declarations: [MyComp], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({declarations: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); await runMigration('convert-to-standalone'); const myCompContent = tree.readContent('comp.ts'); expect(myCompContent).toContain(`import { MyButton } from './button';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [MyButton] }) - `)); - expect(stripWhitespace(tree.readContent('button.ts'))).toContain(stripWhitespace(` + `), + ); + expect(stripWhitespace(tree.readContent('button.ts'))).toContain( + stripWhitespace(` @Component({ selector: 'my-button', template: '', standalone: true }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))).toContain(stripWhitespace(` + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(` @NgModule({imports: [ButtonModule, MyComp], exports: [MyComp]}) - `)); - expect(stripWhitespace(tree.readContent('button.module.ts'))).toContain(stripWhitespace(` + `), + ); + expect(stripWhitespace(tree.readContent('button.module.ts'))).toContain( + stripWhitespace(` @NgModule({imports: [MyButton], exports: [MyButton]}) - `)); + `), + ); }); - it('should refer to dependencies by their module if they have been excluded from the migration', - async () => { - writeFile('./should-migrate/module.ts', ` + it('should refer to dependencies by their module if they have been excluded from the migration', async () => { + writeFile( + './should-migrate/module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule} from '../do-not-migrate/button.module'; @NgModule({imports: [ButtonModule], declarations: [MyComp], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('./do-not-migrate/button.module.ts', ` + writeFile( + './do-not-migrate/button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({declarations: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('./should-migrate/comp.ts', ` + writeFile( + './should-migrate/comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('./do-not-migrate/button.ts', ` + writeFile( + './do-not-migrate/button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); - await runMigration('convert-to-standalone', './should-migrate'); + await runMigration('convert-to-standalone', './should-migrate'); - const myCompContent = tree.readContent('./should-migrate/comp.ts'); + const myCompContent = tree.readContent('./should-migrate/comp.ts'); - expect(myCompContent) - .toContain(`import { ButtonModule } from '../do-not-migrate/button.module';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(myCompContent).toContain( + `import { ButtonModule } from '../do-not-migrate/button.module';`, + ); + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [ButtonModule] }) - `)); - expect(stripWhitespace(tree.readContent('./should-migrate/module.ts'))) - .toContain( - stripWhitespace(`@NgModule({imports: [ButtonModule, MyComp], exports: [MyComp]})`)); - expect(tree.readContent('./do-not-migrate/button.ts')).not.toContain('standalone'); - expect(stripWhitespace(tree.readContent('./do-not-migrate/button.module.ts'))) - .toContain( - stripWhitespace(`@NgModule({declarations: [MyButton], exports: [MyButton]})`)); - }); + `), + ); + expect(stripWhitespace(tree.readContent('./should-migrate/module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [ButtonModule, MyComp], exports: [MyComp]})`), + ); + expect(tree.readContent('./do-not-migrate/button.ts')).not.toContain('standalone'); + expect(stripWhitespace(tree.readContent('./do-not-migrate/button.module.ts'))).toContain( + stripWhitespace(`@NgModule({declarations: [MyButton], exports: [MyButton]})`), + ); + }); it('should add imports to dependencies within the same module', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyButton} from './button'; @@ -743,28 +906,38 @@ describe('standalone migration', () => { @NgModule({declarations: [MyComp, MyButton, MyTooltip], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); - writeFile('tooltip.ts', ` + writeFile( + 'tooltip.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[tooltip]'}) export class MyTooltip {} - `); + `, + ); await runMigration('convert-to-standalone'); @@ -772,35 +945,45 @@ describe('standalone migration', () => { expect(myCompContent).toContain(`import { MyButton } from './button';`); expect(myCompContent).toContain(`import { MyTooltip } from './tooltip';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: 'Hello', standalone: true, imports: [MyButton, MyTooltip] }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace( - `@NgModule({imports: [MyComp, MyButton, MyTooltip], exports: [MyComp]})`)); - expect(stripWhitespace(tree.readContent('button.ts'))) - .toContain(stripWhitespace( - `@Component({selector: 'my-button', template: '', standalone: true})`)); - expect(stripWhitespace(tree.readContent('tooltip.ts'))) - .toContain(stripWhitespace(`@Directive({selector: '[tooltip]', standalone: true})`)); + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyComp, MyButton, MyTooltip], exports: [MyComp]})`), + ); + expect(stripWhitespace(tree.readContent('button.ts'))).toContain( + stripWhitespace( + `@Component({selector: 'my-button', template: '', standalone: true})`, + ), + ); + expect(stripWhitespace(tree.readContent('tooltip.ts'))).toContain( + stripWhitespace(`@Directive({selector: '[tooltip]', standalone: true})`), + ); }); it('should add imports to external dependencies', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {MyComp, MyOtherComp} from './comp'; @NgModule({imports: [CommonModule], declarations: [MyComp, MyOtherComp], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({ @@ -822,14 +1005,16 @@ describe('standalone migration', () => { export class MyOtherComp { isShown = true; } - `); + `, + ); await runMigration('convert-to-standalone'); const myCompContent = tree.readContent('comp.ts'); expect(myCompContent).toContain(`import { NgFor, NgIf } from '@angular/common';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: \` @@ -840,67 +1025,87 @@ describe('standalone migration', () => { standalone: true, imports: [NgFor, NgIf] }) - `)); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + `), + ); + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-other-comp', template: '
', standalone: true, imports: [NgIf] }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace( - `@NgModule({imports: [CommonModule, MyComp, MyOtherComp], exports: [MyComp]})`)); + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace( + `@NgModule({imports: [CommonModule, MyComp, MyOtherComp], exports: [MyComp]})`, + ), + ); }); it('should add imports to pipes that are used in the template', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyPipe} from './pipe'; @NgModule({declarations: [MyComp, MyPipe], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: '{{"hello" | myPipe}}'}) export class MyComp {} - `); + `, + ); - writeFile('pipe.ts', ` + writeFile( + 'pipe.ts', + ` import {Pipe} from '@angular/core'; @Pipe({name: 'myPipe'}) export class MyPipe { transform() {} } - `); + `, + ); await runMigration('convert-to-standalone'); const myCompContent = tree.readContent('comp.ts'); expect(myCompContent).toContain(`import { MyPipe } from './pipe';`); - expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(` + expect(stripWhitespace(myCompContent)).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: '{{"hello" | myPipe}}', standalone: true, imports: [MyPipe] }) - `)); - expect(stripWhitespace(tree.readContent('module.ts'))) - .toContain(stripWhitespace(`@NgModule({imports: [MyComp, MyPipe], exports: [MyComp]})`)); - expect(stripWhitespace(tree.readContent('pipe.ts'))) - .toContain(stripWhitespace(`@Pipe({name: 'myPipe', standalone: true})`)); + `), + ); + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(`@NgModule({imports: [MyComp, MyPipe], exports: [MyComp]})`), + ); + expect(stripWhitespace(tree.readContent('pipe.ts'))).toContain( + stripWhitespace(`@Pipe({name: 'myPipe', standalone: true})`), + ); }); it('should migrate tests with an inline NgModule', async () => { - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; @@ -920,37 +1125,49 @@ describe('standalone migration', () => { expect(fixture.nativeElement.innerHTML).toBe('Hello'); }); }); - `); + `, + ); await runMigration('convert-to-standalone'); const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({selector: 'hello', template: 'Hello', standalone: true}) class Hello {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({template: '', standalone: true, imports: [Hello]}) class App {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @NgModule({imports: [App, Hello], exports: [App, Hello]}) class Mod {} - `)); + `), + ); }); it('should migrate tests where the declaration is already standalone', async () => { - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'comp', template: '', standalone: true}) export class MyComp {} - `); + `, + ); - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {TestBed} from '@angular/core/testing'; import {MyComp} from './comp'; @@ -960,11 +1177,13 @@ describe('standalone migration', () => { expect(() => TestBed.createComponent(MyComp)).not.toThrow(); }); }); - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('app.spec.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('app.spec.ts'))).toContain( + stripWhitespace(` import {TestBed} from '@angular/core/testing'; import {MyComp} from './comp'; @@ -974,27 +1193,36 @@ describe('standalone migration', () => { expect(() => TestBed.createComponent(MyComp)).not.toThrow(); }); }); - `)); + `), + ); }); it('should import the module that declares a template dependency', async () => { - writeFile('./should-migrate/module.ts', ` + writeFile( + './should-migrate/module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule} from '../do-not-migrate/button.module'; @NgModule({imports: [ButtonModule], declarations: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('./should-migrate/comp.ts', ` + writeFile( + './should-migrate/comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('./do-not-migrate/button.module.ts', ` + writeFile( + './do-not-migrate/button.module.ts', + ` import {NgModule, forwardRef} from '@angular/core'; import {MyButton} from './button'; @@ -1006,41 +1234,54 @@ describe('standalone migration', () => { @NgModule({declarations: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('./do-not-migrate/button.ts', ` + writeFile( + './do-not-migrate/button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); await runMigration('convert-to-standalone', './should-migrate'); const myCompContent = tree.readContent('./should-migrate/comp.ts'); - expect(myCompContent) - .toContain(`import { ButtonModule } from '../do-not-migrate/button.module';`); + expect(myCompContent).toContain( + `import { ButtonModule } from '../do-not-migrate/button.module';`, + ); expect(myCompContent).toContain('imports: [ButtonModule]'); }); it('should not reference internal modules', async () => { - writeFile('./should-migrate/module.ts', ` + writeFile( + './should-migrate/module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ɵButtonModule} from '../do-not-migrate/button.module'; @NgModule({imports: [ɵButtonModule], declarations: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('./should-migrate/comp.ts', ` + writeFile( + './should-migrate/comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('./do-not-migrate/button.module.ts', ` + writeFile( + './do-not-migrate/button.module.ts', + ` import {NgModule, forwardRef} from '@angular/core'; import {MyButton} from './button'; @@ -1052,25 +1293,32 @@ describe('standalone migration', () => { @NgModule({declarations: [MyButton], exports: [MyButton]}) export class ɵButtonModule {} - `); + `, + ); - writeFile('./do-not-migrate/button.ts', ` + writeFile( + './do-not-migrate/button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); await runMigration('convert-to-standalone', './should-migrate'); const myCompContent = tree.readContent('./should-migrate/comp.ts'); - expect(myCompContent) - .toContain(`import { ExporterModule } from '../do-not-migrate/button.module';`); + expect(myCompContent).toContain( + `import { ExporterModule } from '../do-not-migrate/button.module';`, + ); expect(myCompContent).toContain('imports: [ExporterModule]'); }); it('should migrate tests with a component declared through TestBed', async () => { - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {ButtonModule} from './button.module'; @@ -1098,13 +1346,15 @@ describe('standalone migration', () => { @Component({template: ''}) class App {} - `); + `, + ); await runMigration('convert-to-standalone'); const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({ selector: 'hello', template: 'Hello', @@ -1112,18 +1362,22 @@ describe('standalone migration', () => { imports: [ButtonModule, MatCardModule] }) class Hello {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({ template: '', standalone: true, imports: [ButtonModule, MatCardModule] }) class App {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` it('should work', () => { TestBed.configureTestingModule({ imports: [ButtonModule, MatCardModule, App, Hello] @@ -1131,19 +1385,24 @@ describe('standalone migration', () => { const fixture = TestBed.createComponent(App); expect(fixture.nativeElement.innerHTML).toBe('Hello'); }); - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` it('should work in a different way', () => { TestBed.configureTestingModule({imports: [MatCardModule, App, Hello]}); const fixture = TestBed.createComponent(App); expect(fixture.nativeElement.innerHTML).toBe('Hello'); }); - `)); + `), + ); }); it('should not add ModuleWithProviders imports to the `imports` in a test', async () => { - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {MatCardModule} from '@angular/material/card'; @@ -1161,18 +1420,22 @@ describe('standalone migration', () => { @Component({template: 'hello'}) class App {} - `); + `, + ); await runMigration('convert-to-standalone'); const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({template: 'hello', standalone: true}) class App {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` it('should work', () => { TestBed.configureTestingModule({ imports: [MatCardModule.forRoot({}), App] @@ -1180,7 +1443,8 @@ describe('standalone migration', () => { const fixture = TestBed.createComponent(App); expect(fixture.nativeElement.innerHTML).toBe('hello'); }); - `)); + `), + ); }); it('should not change testing objects with no declarations', async () => { @@ -1212,7 +1476,9 @@ describe('standalone migration', () => { }); it('should migrate tests with a component declared through Catalyst', async () => { - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {bootstrap, setupModule} from 'some_internal_path/angular/testing/catalyst'; import {ButtonModule} from './button.module'; @@ -1240,13 +1506,15 @@ describe('standalone migration', () => { @Component({template: ''}) class App {} - `); + `, + ); await runMigration('convert-to-standalone'); const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({ selector: 'hello', template: 'Hello', @@ -1254,18 +1522,22 @@ describe('standalone migration', () => { imports: [ButtonModule, MatCardModule] }) class Hello {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` @Component({ template: '', standalone: true, imports: [ButtonModule, MatCardModule] }) class App {} - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` it('should work', () => { setupModule({ imports: [ButtonModule, MatCardModule, App, Hello] @@ -1273,20 +1545,24 @@ describe('standalone migration', () => { const fixture = bootstrap(App); expect(fixture.nativeElement.innerHTML).toBe('Hello'); }); - `)); + `), + ); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` it('should work in a different way', () => { setupModule({imports: [MatCardModule, App, Hello]}); const fixture = bootstrap(App); expect(fixture.nativeElement.innerHTML).toBe('Hello'); }); - `)); + `), + ); }); - it('should not copy over the NoopAnimationsModule into the imports of a test component', - async () => { - writeFile('app.spec.ts', ` + it('should not copy over the NoopAnimationsModule into the imports of a test component', async () => { + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {MatCardModule} from '@angular/material/card'; @@ -1305,26 +1581,32 @@ describe('standalone migration', () => { @Component({template: 'hello'}) class App {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - const content = stripWhitespace(tree.readContent('app.spec.ts')); + const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` TestBed.configureTestingModule({ imports: [MatCardModule, NoopAnimationsModule, App] }); - `)); - expect(content).toContain(stripWhitespace(` + `), + ); + expect(content).toContain( + stripWhitespace(` @Component({template: 'hello', standalone: true, imports: [MatCardModule]}) class App {} - `)); - }); + `), + ); + }); - it('should not copy over the BrowserAnimationsModule into the imports of a test component', - async () => { - writeFile('app.spec.ts', ` + it('should not copy over the BrowserAnimationsModule into the imports of a test component', async () => { + writeFile( + 'app.spec.ts', + ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {MatCardModule} from '@angular/material/card'; @@ -1343,33 +1625,37 @@ describe('standalone migration', () => { @Component({template: 'hello'}) class App {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - const content = stripWhitespace(tree.readContent('app.spec.ts')); + const content = stripWhitespace(tree.readContent('app.spec.ts')); - expect(content).toContain(stripWhitespace(` + expect(content).toContain( + stripWhitespace(` TestBed.configureTestingModule({ imports: [MatCardModule, BrowserAnimationsModule, App] }); - `)); - expect(content).toContain(stripWhitespace(` + `), + ); + expect(content).toContain( + stripWhitespace(` @Component({template: 'hello', standalone: true, imports: [MatCardModule]}) class App {} - `)); - }); + `), + ); + }); - it('should not move declarations that are not being migrated out of the declarations array', - async () => { - const appComponentContent = ` + it('should not move declarations that are not being migrated out of the declarations array', async () => { + const appComponentContent = ` import {Component} from '@angular/core'; @Component({selector: 'app', template: ''}) export class AppComponent {} `; - const appModuleContent = ` + const appModuleContent = ` import {NgModule} from '@angular/core'; import {AppComponent} from './app.component'; @@ -1377,10 +1663,12 @@ describe('standalone migration', () => { export class AppModule {} `; - writeFile('app.component.ts', appComponentContent); - writeFile('app.module.ts', appModuleContent); + writeFile('app.component.ts', appComponentContent); + writeFile('app.module.ts', appModuleContent); - writeFile('app.spec.ts', ` + writeFile( + 'app.spec.ts', + ` import {Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {ButtonModule} from './button.module'; @@ -1400,15 +1688,17 @@ describe('standalone migration', () => { @Component({template: ''}) class TestComp {} - `); + `, + ); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - const testContent = stripWhitespace(tree.readContent('app.spec.ts')); + const testContent = stripWhitespace(tree.readContent('app.spec.ts')); - expect(tree.readContent('app.module.ts')).toBe(appModuleContent); - expect(tree.readContent('app.component.ts')).toBe(appComponentContent); - expect(testContent).toContain(stripWhitespace(` + expect(tree.readContent('app.module.ts')).toBe(appModuleContent); + expect(tree.readContent('app.component.ts')).toBe(appComponentContent); + expect(testContent).toContain( + stripWhitespace(` it('should work', () => { TestBed.configureTestingModule({ declarations: [AppComponent], @@ -1417,20 +1707,22 @@ describe('standalone migration', () => { const fixture = TestBed.createComponent(App); expect(fixture.nativeElement.innerHTML).toBe(''); }); - `)); - expect(testContent).toContain(stripWhitespace(` + `), + ); + expect(testContent).toContain( + stripWhitespace(` @Component({ template: '', standalone: true, imports: [ButtonModule, MatCardModule] }) class TestComp {} - `)); - }); + `), + ); + }); - it('should not migrate `configureTestingModule` with a non-array expression in the `declarations` field', - async () => { - const initialContent = ` + it('should not migrate `configureTestingModule` with a non-array expression in the `declarations` field', async () => { + const initialContent = ` import {NgModule, Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {ButtonModule} from './button.module'; @@ -1463,12 +1755,12 @@ describe('standalone migration', () => { class App {} `; - writeFile('app.spec.ts', initialContent); + writeFile('app.spec.ts', initialContent); - await runMigration('convert-to-standalone'); + await runMigration('convert-to-standalone'); - expect(tree.readContent('app.spec.ts')).toBe(initialContent); - }); + expect(tree.readContent('app.spec.ts')).toBe(initialContent); + }); it('should not migrate modules with a `bootstrap` array', async () => { const initialModule = ` @@ -1489,7 +1781,9 @@ describe('standalone migration', () => { }); it('should migrate a module with an empty `bootstrap` array', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({selector: 'root-comp', template: 'hello'}) @@ -1497,11 +1791,13 @@ describe('standalone migration', () => { @NgModule({declarations: [RootComp], bootstrap: []}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component} from '@angular/core'; @Component({selector: 'root-comp', template: 'hello', standalone: true}) @@ -1509,18 +1805,24 @@ describe('standalone migration', () => { @NgModule({imports: [RootComp], bootstrap: []}) export class Mod {} - `)); + `), + ); }); it('should migrate declarations that are not being bootstrapped in a root module', async () => { - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[foo]'}) export class MyDir {} - `); + `, + ); - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Component} from '@angular/core'; import {MyDir} from './dir'; @@ -1529,18 +1831,22 @@ describe('standalone migration', () => { @NgModule({declarations: [RootComp, MyDir], bootstrap: [RootComp]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('dir.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('dir.ts'))).toBe( + stripWhitespace(` import {Directive} from '@angular/core'; @Directive({selector: '[foo]', standalone: true}) export class MyDir {} - `)); + `), + ); - expect(stripWhitespace(tree.readContent('module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component} from '@angular/core'; import {MyDir} from './dir'; @@ -1549,11 +1855,14 @@ describe('standalone migration', () => { @NgModule({imports: [MyDir], declarations: [RootComp], bootstrap: [RootComp]}) export class Mod {} - `)); + `), + ); }); it('should generate a forwardRef for forward reference within the same file', async () => { - writeFile('decls.ts', ` + writeFile( + 'decls.ts', + ` import {Component, Directive} from '@angular/core'; @Component({ @@ -1564,19 +1873,24 @@ describe('standalone migration', () => { @Directive({selector: '[my-dir]'}) export class MyDir {} - `); + `, + ); - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp, MyDir} from './decls'; @NgModule({declarations: [MyComp, MyDir]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('decls.ts'))).toEqual(stripWhitespace(` + expect(stripWhitespace(tree.readContent('decls.ts'))).toEqual( + stripWhitespace(` import {Component, Directive, forwardRef} from '@angular/core'; @Component({ @@ -1589,11 +1903,14 @@ describe('standalone migration', () => { @Directive({selector: '[my-dir]', standalone: true}) export class MyDir {} - `)); + `), + ); }); it('should not generate a forwardRef for a self import', async () => { - writeFile('decls.ts', ` + writeFile( + 'decls.ts', + ` import {Component} from '@angular/core'; @Component({ @@ -1601,19 +1918,24 @@ describe('standalone migration', () => { template: '' }) export class MyComp {} - `); + `, + ); - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './decls'; @NgModule({declarations: [MyComp]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('decls.ts'))).toEqual(stripWhitespace(` + expect(stripWhitespace(tree.readContent('decls.ts'))).toEqual( + stripWhitespace(` import {Component} from '@angular/core'; @Component({ @@ -1622,11 +1944,14 @@ describe('standalone migration', () => { standalone: true }) export class MyComp {} - `)); + `), + ); }); it('should not generate a forwardRef when adding an imported module dependency', async () => { - writeFile('./comp.ts', ` + writeFile( + './comp.ts', + ` import {Component, NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -1638,11 +1963,13 @@ describe('standalone migration', () => { @NgModule({imports: [RouterModule], declarations: [MyComp]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('comp.ts'))).toEqual(stripWhitespace(` + expect(stripWhitespace(tree.readContent('comp.ts'))).toEqual( + stripWhitespace(` import {Component, NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -1656,11 +1983,14 @@ describe('standalone migration', () => { @NgModule({imports: [RouterModule, MyComp]}) export class Mod {} - `)); + `), + ); }); it('should not duplicate doc strings', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; /** Directive used for testing. */ @@ -1670,11 +2000,13 @@ describe('standalone migration', () => { /** Module used for testing. */ @NgModule({declarations: [MyDir]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toBe( + stripWhitespace(` import {NgModule, Directive} from '@angular/core'; /** Directive used for testing. */ @@ -1684,20 +2016,26 @@ describe('standalone migration', () => { /** Module used for testing. */ @NgModule({imports: [MyDir]}) export class Mod {} - `)); + `), + ); }); it('should use the generated alias if a conflicting symbol already exists', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {MyButton} from './button'; @NgModule({declarations: [MyComp, MyButton], exports: [MyComp]}) export class Mod {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; import {MyButton} from '@external/button'; @@ -1705,18 +2043,23 @@ describe('standalone migration', () => { @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: ''}) export class MyButton {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('comp.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('comp.ts'))).toBe( + stripWhitespace(` import {Component} from '@angular/core'; import {MyButton} from '@external/button'; import {MyButton as MyButton_1} from './button'; @@ -1729,11 +2072,14 @@ describe('standalone migration', () => { imports: [MyButton_1] }) export class MyComp {} - `)); + `), + ); }); it('should preserve the trailing comma when adding an `imports` array', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; @Directive({selector: '[dir]'}) @@ -1744,20 +2090,25 @@ describe('standalone migration', () => { exports: [MyDir], }) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(` @NgModule({ imports: [MyDir], exports: [MyDir], }) - `)); + `), + ); }); it('should preserve the trailing comma when adding to an existing `imports` array', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; import {RouterModule} from '@angular/router'; @@ -1774,11 +2125,13 @@ describe('standalone migration', () => { exports: [MyDir], }) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(` @NgModule({ imports: [ CommonModule, @@ -1787,11 +2140,14 @@ describe('standalone migration', () => { ], exports: [MyDir], }) - `)); + `), + ); }); it('should preserve the trailing comma when marking a directive as standalone', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive} from '@angular/core'; @Directive({ @@ -1802,21 +2158,26 @@ describe('standalone migration', () => { @NgModule({declarations: [MyDir]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(` @Directive({ selector: '[dir]', exportAs: 'dir', standalone: true, }) - `)); + `), + ); }); it('should add a trailing comma when generating an imports array in a component', async () => { - writeFile('module.ts', ` + writeFile( + 'module.ts', + ` import {NgModule, Directive, Component} from '@angular/core'; @Directive({selector: '[dir-one]'}) @@ -1836,11 +2197,13 @@ describe('standalone migration', () => { @NgModule({declarations: [DirOne, DirTwo, DirThree, MyComp]}) export class Mod {} - `); + `, + ); await runMigration('convert-to-standalone'); - expect(stripWhitespace(tree.readContent('module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('module.ts'))).toContain( + stripWhitespace(` @Component({ selector: 'my-comp', template: '
', @@ -1851,40 +2214,53 @@ describe('standalone migration', () => { DirThree, ], }) - `)); + `), + ); }); it('should remove a module that only has imports and exports', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule} from './button.module'; @NgModule({imports: [ButtonModule], declarations: [MyComp], exports: [ButtonModule, MyComp]}) export class AppModule {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({imports: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); await runMigration('prune-ng-modules'); @@ -1892,10 +2268,12 @@ describe('standalone migration', () => { expect(tree.exists('button.module.ts')).toBe(false); expect(appModule).not.toContain('ButtonModule'); - expect(stripWhitespace(appModule)).toContain(stripWhitespace(` + expect(stripWhitespace(appModule)).toContain( + stripWhitespace(` @NgModule({imports: [], declarations: [MyComp], exports: [MyComp]}) export class AppModule {} - `)); + `), + ); }); it('should not remove a module that has declarations', async () => { @@ -1909,12 +2287,15 @@ describe('standalone migration', () => { `; writeFile('app.module.ts', initialAppModule); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); await runMigration('prune-ng-modules'); @@ -1932,12 +2313,15 @@ describe('standalone migration', () => { `; writeFile('app.module.ts', initialAppModule); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); await runMigration('prune-ng-modules'); @@ -1997,7 +2381,9 @@ describe('standalone migration', () => { }); it('should remove a module that only has an empty constructor', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {ButtonModule} from './button.module'; @@ -2006,26 +2392,32 @@ describe('standalone migration', () => { constructor() { } } - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('app.module.ts')).toBe(false); }); it('should remove a module with no arguments passed into NgModule', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; @NgModule() export class AppModule {} - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('app.module.ts')).toBe(false); }); it('should remove a module file where there is unexported code', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; const ONE = 1; @@ -2035,65 +2427,83 @@ describe('standalone migration', () => { @NgModule() export class AppModule {} - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('app.module.ts')).toBe(false); }); - it('should remove a module that passes empty arrays into `declarations`, `providers` and `bootstrap`', - async () => { - writeFile('app.module.ts', ` + it('should remove a module that passes empty arrays into `declarations`, `providers` and `bootstrap`', async () => { + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {ButtonModule} from './button.module'; @NgModule({declarations: [], providers: [], bootstrap: []}) export class AppModule {} - `); - await runMigration('prune-ng-modules'); + `, + ); + await runMigration('prune-ng-modules'); - expect(tree.exists('app.module.ts')).toBe(false); - }); + expect(tree.exists('app.module.ts')).toBe(false); + }); it('should remove a chain of modules that all depend on each other', async () => { - writeFile('a.module.ts', ` + writeFile( + 'a.module.ts', + ` import {NgModule} from '@angular/core'; @NgModule({}) export class ModuleA {} - `); + `, + ); - writeFile('b.module.ts', ` + writeFile( + 'b.module.ts', + ` import {NgModule} from '@angular/core'; import {ModuleA} from './a.module'; @NgModule({imports: [ModuleA]}) export class ModuleB {} - `); + `, + ); - writeFile('c.module.ts', ` + writeFile( + 'c.module.ts', + ` import {NgModule} from '@angular/core'; import {ModuleB} from './b.module'; @NgModule({imports: [ModuleB]}) export class ModuleC {} - `); + `, + ); - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; import {ModuleC} from './c.module'; @NgModule({imports: [ModuleC], declarations: [MyDir]}) export class AppModule {} - `); + `, + ); - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[myDir]'}) export class MyDir {} - `); + `, + ); await runMigration('prune-ng-modules'); @@ -2103,15 +2513,16 @@ describe('standalone migration', () => { expect(tree.exists('b.module.ts')).toBe(false); expect(tree.exists('c.module.ts')).toBe(false); expect(appModule).not.toContain('ModuleC'); - expect(stripWhitespace(appModule)).toContain(stripWhitespace(` + expect(stripWhitespace(appModule)).toContain( + stripWhitespace(` @NgModule({imports: [], declarations: [MyDir]}) export class AppModule {} - `)); + `), + ); }); - it('should not remove a chain of modules if a module in the chain cannot be removed because it has providers', - async () => { - const moduleAContent = ` + it('should not remove a chain of modules if a module in the chain cannot be removed because it has providers', async () => { + const moduleAContent = ` import {NgModule, InjectionToken} from '@angular/core'; export const token = new InjectionToken('token'); @@ -2120,7 +2531,7 @@ describe('standalone migration', () => { export class ModuleA {} `; - const moduleBContent = ` + const moduleBContent = ` import {NgModule} from '@angular/core'; import {ModuleA} from './a.module'; @@ -2128,7 +2539,7 @@ describe('standalone migration', () => { export class ModuleB {} `; - const moduleCContent = ` + const moduleCContent = ` import {NgModule} from '@angular/core'; import {ModuleB} from './b.module'; @@ -2136,7 +2547,7 @@ describe('standalone migration', () => { export class ModuleC {} `; - const appModuleContent = ` + const appModuleContent = ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; import {ModuleC} from './c.module'; @@ -2145,35 +2556,37 @@ describe('standalone migration', () => { export class AppModule {} `; - writeFile('a.module.ts', moduleAContent); - writeFile('b.module.ts', moduleBContent); - writeFile('c.module.ts', moduleCContent); - writeFile('app.module.ts', appModuleContent); - writeFile('dir.ts', ` + writeFile('a.module.ts', moduleAContent); + writeFile('b.module.ts', moduleBContent); + writeFile('c.module.ts', moduleCContent); + writeFile('app.module.ts', appModuleContent); + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[myDir]'}) export class MyDir {} - `); + `, + ); - await runMigration('prune-ng-modules'); + await runMigration('prune-ng-modules'); - expect(tree.readContent('a.module.ts')).toBe(moduleAContent); - expect(tree.readContent('b.module.ts')).toBe(moduleBContent); - expect(tree.readContent('c.module.ts')).toBe(moduleCContent); - expect(tree.readContent('app.module.ts')).toBe(appModuleContent); - }); + expect(tree.readContent('a.module.ts')).toBe(moduleAContent); + expect(tree.readContent('b.module.ts')).toBe(moduleBContent); + expect(tree.readContent('c.module.ts')).toBe(moduleCContent); + expect(tree.readContent('app.module.ts')).toBe(appModuleContent); + }); - it('should not remove a chain of modules if a module in the chain cannot be removed because it is importing a ModuleWithProviders', - async () => { - const moduleAContent = ` + it('should not remove a chain of modules if a module in the chain cannot be removed because it is importing a ModuleWithProviders', async () => { + const moduleAContent = ` import {NgModule} from '@angular/core'; @NgModule({imports: [RouterModule.forRoot([{path: '/foo'}])]}) export class ModuleA {} `; - const moduleBContent = ` + const moduleBContent = ` import {NgModule} from '@angular/core'; import {ModuleA} from './a.module'; @@ -2181,7 +2594,7 @@ describe('standalone migration', () => { export class ModuleB {} `; - const moduleCContent = ` + const moduleCContent = ` import {NgModule} from '@angular/core'; import {ModuleB} from './b.module'; @@ -2189,7 +2602,7 @@ describe('standalone migration', () => { export class ModuleC {} `; - const appModuleContent = ` + const appModuleContent = ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; import {ModuleC} from './c.module'; @@ -2198,27 +2611,32 @@ describe('standalone migration', () => { export class AppModule {} `; - writeFile('a.module.ts', moduleAContent); - writeFile('b.module.ts', moduleBContent); - writeFile('c.module.ts', moduleCContent); - writeFile('app.module.ts', appModuleContent); - writeFile('dir.ts', ` + writeFile('a.module.ts', moduleAContent); + writeFile('b.module.ts', moduleBContent); + writeFile('c.module.ts', moduleCContent); + writeFile('app.module.ts', appModuleContent); + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[myDir]'}) export class MyDir {} - `); + `, + ); - await runMigration('prune-ng-modules'); + await runMigration('prune-ng-modules'); - expect(tree.readContent('a.module.ts')).toBe(moduleAContent); - expect(tree.readContent('b.module.ts')).toBe(moduleBContent); - expect(tree.readContent('c.module.ts')).toBe(moduleCContent); - expect(tree.readContent('app.module.ts')).toBe(appModuleContent); - }); + expect(tree.readContent('a.module.ts')).toBe(moduleAContent); + expect(tree.readContent('b.module.ts')).toBe(moduleBContent); + expect(tree.readContent('c.module.ts')).toBe(moduleCContent); + expect(tree.readContent('app.module.ts')).toBe(appModuleContent); + }); it('should not remove the module file if it contains other exported code', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {sum, ButtonModule, multiply} from './button.module'; @@ -2227,9 +2645,12 @@ describe('standalone migration', () => { export class AppModule {} console.log(sum(1, 2), multiply(3, 4)); - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @@ -2243,25 +2664,33 @@ describe('standalone migration', () => { export function multiply(a: number, b: number) { return a * b; } - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); await runMigration('prune-ng-modules'); - expect(stripWhitespace(tree.readContent('button.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('button.module.ts'))).toBe( + stripWhitespace(` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @@ -2272,8 +2701,10 @@ describe('standalone migration', () => { export function multiply(a: number, b: number) { return a * b; } - `)); - expect(stripWhitespace(tree.readContent('app.module.ts'))).toBe(stripWhitespace(` + `), + ); + expect(stripWhitespace(tree.readContent('app.module.ts'))).toBe( + stripWhitespace(` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {sum, multiply} from './button.module'; @@ -2282,11 +2713,14 @@ describe('standalone migration', () => { export class AppModule {} console.log(sum(1, 2), multiply(3, 4)); - `)); + `), + ); }); it('should delete a file that contains multiple modules that are being deleted', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule, TooltipModule} from './shared-modules'; @@ -2297,16 +2731,22 @@ describe('standalone migration', () => { exports: [ButtonModule, MyComp, TooltipModule] }) export class AppModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: ''}) export class MyComp {} - `); + `, + ); - writeFile('shared-modules.ts', ` + writeFile( + 'shared-modules.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; import {MyTooltip} from './tooltip'; @@ -2316,21 +2756,28 @@ describe('standalone migration', () => { @NgModule({imports: [MyTooltip], exports: [MyTooltip]}) export class TooltipModule {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[my-button]' standalone: true}) export class MyButton {} - `); + `, + ); - writeFile('tooltip.ts', ` + writeFile( + 'tooltip.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[my-tooltip]' standalone: true}) export class MyTooltip {} - `); + `, + ); await runMigration('prune-ng-modules'); @@ -2339,73 +2786,94 @@ describe('standalone migration', () => { expect(tree.exists('shared-modules.ts')).toBe(false); expect(appModule).not.toContain('ButtonModule'); expect(appModule).not.toContain('TooltipModule'); - expect(stripWhitespace(appModule)).toContain(stripWhitespace(` + expect(stripWhitespace(appModule)).toContain( + stripWhitespace(` @NgModule({ imports: [], declarations: [MyComp], exports: [MyComp] }) export class AppModule {} - `)); + `), + ); }); - it('should preserve an import that has one NgModule that is being deleted, in addition to a named import', - async () => { - writeFile('app.module.ts', ` + it('should preserve an import that has one NgModule that is being deleted, in addition to a named import', async () => { + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import Foo, {ButtonModule} from './button.module'; @NgModule({imports: [ButtonModule], declarations: [MyComp], exports: [ButtonModule, MyComp]}) export class AppModule {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({imports: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); - await runMigration('prune-ng-modules'); + await runMigration('prune-ng-modules'); - expect(tree.exists('button.module.ts')).toBe(false); - expect(tree.readContent('app.module.ts')).toContain(`import Foo from './button.module';`); - }); + expect(tree.exists('button.module.ts')).toBe(false); + expect(tree.readContent('app.module.ts')).toContain(`import Foo from './button.module';`); + }); it('should remove module references from export expressions', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; @NgModule({imports: [MyComp]}) export class AppModule {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({imports: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; import {MyButton} from './button'; @@ -2416,50 +2884,67 @@ describe('standalone migration', () => { imports: [MyButton] }) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); - writeFile('index.ts', ` + writeFile( + 'index.ts', + ` export {AppModule} from './app.module'; export {MyComp} from './comp'; export {ButtonModule} from './button.module'; export {MyButton} from './button'; - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('app.module.ts')).toBe(false); expect(tree.exists('button.module.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('index.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('index.ts'))).toBe( + stripWhitespace(` export {MyComp} from './comp'; export {MyButton} from './button'; - `)); + `), + ); }); it('should remove barrel export if the corresponding file is deleted', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; @NgModule({imports: [MyComp]}) export class AppModule {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; import {MyButton} from './button'; @NgModule({imports: [MyButton], exports: [MyButton]}) export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; import {MyButton} from './button'; @@ -2470,45 +2955,60 @@ describe('standalone migration', () => { imports: [MyButton] }) export class MyComp {} - `); + `, + ); - writeFile('button.ts', ` + writeFile( + 'button.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-button', template: '', standalone: true}) export class MyButton {} - `); + `, + ); - writeFile('index.ts', ` + writeFile( + 'index.ts', + ` export * from './app.module'; export {MyComp} from './comp'; export {ButtonModule} from './button.module'; - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('app.module.ts')).toBe(false); expect(tree.exists('button.module.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('index.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('index.ts'))).toBe( + stripWhitespace(` export {MyComp} from './comp'; - `)); + `), + ); }); it('should remove barrel files referring to other barrel files that were deleted', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; @NgModule({imports: [MyDir]}) export class AppModule {} - `); + `, + ); - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]', standalone: true}) export class MyDir {} - `); + `, + ); writeFile('index.ts', `export * from './app.module';`); writeFile('index-2.ts', `export * from './index';`); @@ -2521,45 +3021,60 @@ describe('standalone migration', () => { expect(tree.exists('index-3.ts')).toBe(false); }); - it('should not delete dependent barrel files if they have some barrel exports that will not be removed', - async () => { - writeFile('app.module.ts', ` + it('should not delete dependent barrel files if they have some barrel exports that will not be removed', async () => { + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyDir} from './dir'; @NgModule({imports: [MyDir]}) export class AppModule {} - `); + `, + ); - writeFile('dir.ts', ` + writeFile( + 'dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]', standalone: true}) export class MyDir {} - `); + `, + ); - writeFile('utils.ts', ` + writeFile( + 'utils.ts', + ` export function sum(a: number, b: number) { return a + b; } - `); + `, + ); - writeFile('index.ts', `export * from './app.module';`); - writeFile('index-2.ts', ` + writeFile('index.ts', `export * from './app.module';`); + writeFile( + 'index-2.ts', + ` export * from './index'; export * from './utils'; - `); - writeFile('index-3.ts', `export * from './index-2';`); + `, + ); + writeFile('index-3.ts', `export * from './index-2';`); - await runMigration('prune-ng-modules'); + await runMigration('prune-ng-modules'); - expect(tree.exists('index.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('index-2.ts'))) - .toBe(stripWhitespace(`export * from './utils';`)); - expect(stripWhitespace(tree.readContent('index-3.ts'))) - .toBe(stripWhitespace(`export * from './index-2';`)); - }); + expect(tree.exists('index.ts')).toBe(false); + expect(stripWhitespace(tree.readContent('index-2.ts'))).toBe( + stripWhitespace(`export * from './utils';`), + ); + expect(stripWhitespace(tree.readContent('index-3.ts'))).toBe( + stripWhitespace(`export * from './index-2';`), + ); + }); it('should add a comment to locations that cannot be removed automatically', async () => { - writeFile('app.module.ts', ` + writeFile( + 'app.module.ts', + ` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; import {ButtonModule} from './button.module'; @@ -2574,26 +3089,34 @@ describe('standalone migration', () => { @NgModule({imports: [ButtonModule], declarations: [MyComp], exports: [ButtonModule, MyComp]}) export class AppModule {} - `); + `, + ); - writeFile('button.module.ts', ` + writeFile( + 'button.module.ts', + ` import {NgModule} from '@angular/core'; @NgModule() export class ButtonModule {} - `); + `, + ); - writeFile('comp.ts', ` + writeFile( + 'comp.ts', + ` import {Component} from '@angular/core'; @Component({selector: 'my-comp', template: 'Hello'}) export class MyComp {} - `); + `, + ); await runMigration('prune-ng-modules'); expect(tree.exists('button.module.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('app.module.ts'))).toBe( + stripWhitespace(` import {NgModule} from '@angular/core'; import {MyComp} from './comp'; @@ -2607,7 +3130,8 @@ describe('standalone migration', () => { @NgModule({imports: [], declarations: [MyComp], exports: [MyComp]}) export class AppModule {} - `)); + `), + ); }); it('should preserve the trailing comma when deleting a module', async () => { @@ -2636,7 +3160,8 @@ describe('standalone migration', () => { await runMigration('prune-ng-modules'); - expect(stripWhitespace(tree.readContent('app.module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('app.module.ts'))).toContain( + stripWhitespace(` @NgModule({ imports: [ CommonModule, @@ -2644,18 +3169,24 @@ describe('standalone migration', () => { ], providers: [{provide: token, useValue: 123}], }) - `)); + `), + ); }); it('should switch a platformBrowser().bootstrapModule call to bootstrapApplication', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello'}) @@ -2663,35 +3194,44 @@ describe('standalone migration', () => { @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(AppComponent).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); + `), + ); }); - it('should switch a platformBrowserDynamic().bootstrapModule call to bootstrapApplication', - async () => { - writeFile('main.ts', ` + it('should switch a platformBrowserDynamic().bootstrapModule call to bootstrapApplication', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello'}) @@ -2699,37 +3239,47 @@ describe('standalone migration', () => { @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(AppComponent).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); - }); + `), + ); + }); it('should switch a PlatformRef.bootstrapModule call to bootstrapApplication', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {PlatformRef} from '@angular/core'; const foo: PlatformRef = null!; foo.bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello'}) @@ -2737,11 +3287,13 @@ describe('standalone migration', () => { @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {PlatformRef} from '@angular/core'; import {bootstrapApplication} from '@angular/platform-browser'; @@ -2749,41 +3301,55 @@ describe('standalone migration', () => { const foo: PlatformRef = null!; bootstrapApplication(AppComponent).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component} from '@angular/core'; @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); + `), + ); }); it('should convert the root module declarations to standalone', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.component.ts', ` + writeFile( + './app/app.component.ts', + ` import {Component} from '@angular/core'; @Component({template: '
hello
'}) export class AppComponent { show = true; } - `); + `, + ); - writeFile('./app/dir.ts', ` + writeFile( + './app/dir.ts', + ` import {Directive} from '@angular/core'; @Directive({selector: '[dir]'}) export class Dir {} - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {AppComponent} from './app.component'; @@ -2795,12 +3361,14 @@ describe('standalone migration', () => { bootstrap: [AppComponent] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); expect(tree.exists('./app/app.module.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {importProvidersFrom} from '@angular/core'; import {AppComponent} from './app/app.component'; @@ -2809,9 +3377,11 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [importProvidersFrom(CommonModule)] }).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe( + stripWhitespace(` import {Component} from '@angular/core'; import {Dir} from './dir'; import {NgIf} from '@angular/common'; @@ -2824,32 +3394,43 @@ describe('standalone migration', () => { export class AppComponent { show = true; } - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/dir.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/dir.ts'))).toBe( + stripWhitespace(` import {Directive} from '@angular/core'; @Directive({selector: '[dir]', standalone: true}) export class Dir {} - `)); + `), + ); }); it('should migrate the root component tests when converting to standalone', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.component.ts', ` + writeFile( + './app/app.component.ts', + ` import {Component} from '@angular/core'; @Component({template: 'hello'}) export class AppComponent {} - `); + `, + ); - writeFile('./app/app.component.spec.ts', ` + writeFile( + './app/app.component.spec.ts', + ` import {TestBed} from '@angular/core/testing'; import {AppComponent} from './app.component'; @@ -2860,26 +3441,33 @@ describe('standalone migration', () => { expect(fixture.nativeElement.innerHTML).toBe('hello'); }); }); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule} from '@angular/core'; import {AppComponent} from './app.component'; @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe( + stripWhitespace(` import {Component} from '@angular/core'; @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.component.spec.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.component.spec.ts'))).toBe( + stripWhitespace(` import {TestBed} from '@angular/core/testing'; import {AppComponent} from './app.component'; @@ -2890,18 +3478,24 @@ describe('standalone migration', () => { expect(fixture.nativeElement.innerHTML).toBe('hello'); }); }); - `)); + `), + ); }); it('should copy providers and the symbols they depend on to the main file', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component, InjectionToken} from '@angular/core'; import {externalToken} from './externals/token'; import {externalToken as aliasedExternalToken} from './externals/other-token'; @@ -2940,13 +3534,15 @@ describe('standalone migration', () => { ] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); // Note that this leaves a couple of unused imports from `app.module`. // The schematic optimizes for safety, rather than avoiding unused imports. - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {exportedToken, exportedExtraProviders, ExportedClass, exportedFactory, AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {ExternalInterface} from '@external/interfaces'; @@ -2969,9 +3565,11 @@ describe('standalone migration', () => { ...exportedExtraProviders ] }).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component, InjectionToken} from '@angular/core'; import {externalToken} from './externals/token'; import {externalToken as aliasedExternalToken} from './externals/other-token'; @@ -2997,18 +3595,24 @@ describe('standalone migration', () => { @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); + `), + ); }); it('should not copy over non-declaration references to the main file', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component, InjectionToken} from '@angular/core'; export const token = new InjectionToken('token'); @@ -3024,11 +3628,13 @@ describe('standalone migration', () => { providers: [{provide: token, useValue: 'hello'}] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {token, AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {InjectionToken} from '@angular/core'; @@ -3036,9 +3642,11 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [{ provide: token, useValue: 'hello' }] }).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toBe( + stripWhitespace(` import {NgModule, Component, InjectionToken} from '@angular/core'; export const token = new InjectionToken('token'); @@ -3047,19 +3655,24 @@ describe('standalone migration', () => { @Component({template: 'hello', standalone: true}) export class AppComponent {} - `)); + `), + ); }); - it('should update dynamic imports from the `providers` that are copied to the main file', - async () => { - writeFile('main.ts', ` + it('should update dynamic imports from the `providers` that are copied to the main file', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {ROUTES} from '@angular/router'; @@ -3078,11 +3691,13 @@ describe('standalone migration', () => { }] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {ROUTES} from '@angular/router'; @@ -3096,32 +3711,43 @@ describe('standalone migration', () => { ] }] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); - it('should copy modules from the `imports` array to the `providers` and wrap them in `importProvidersFrom`', - async () => { - writeFile('main.ts', ` + it('should copy modules from the `imports` array to the `providers` and wrap them in `importProvidersFrom`', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('token.ts', ` + writeFile( + 'token.ts', + ` import {InjectionToken} from '@angular/core'; export const token = new InjectionToken('token'); - `); + `, + ); - writeFile('./modules/internal.module.ts', ` + writeFile( + './modules/internal.module.ts', + ` import {NgModule} from '@angular/core'; import {token} from '../token'; @NgModule({providers: [{provide: token, useValue: 'InternalModule'}]}) export class InternalModule {} - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {CommonModule} from '@angular/common'; import {InternalModule} from '../modules/internal.module'; @@ -3139,11 +3765,13 @@ describe('standalone migration', () => { bootstrap: [AppComponent] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {SameFileModule, AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {token} from './token'; @@ -3154,18 +3782,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [importProvidersFrom(CommonModule, InternalModule, SameFileModule)] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); it('should switch RouterModule.forRoot calls with one argument to provideRouter', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3183,11 +3817,13 @@ describe('standalone migration', () => { ] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {provideRouter} from '@angular/router'; @@ -3198,18 +3834,24 @@ describe('standalone migration', () => { {path: 'external-comp', loadComponent: () => import('@external/external-comp').then(c => c.ExternalComp)} ])] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should migrate a RouterModule.forRoot call with an empty config', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; import {APP_ROUTES} from './routes'; @@ -3223,11 +3865,13 @@ describe('standalone migration', () => { imports: [RouterModule.forRoot(APP_ROUTES, {})] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {APP_ROUTES} from './app/routes'; @@ -3236,18 +3880,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter(APP_ROUTES)] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should migrate a router config with a preloadingStrategy to withPreloading', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; import {of} from 'rxjs'; @@ -3263,11 +3913,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {of} from 'rxjs'; @@ -3276,18 +3928,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withPreloading(() => of(true)))] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should migrate a router config with enableTracing to withDebugTracing', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3302,11 +3960,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withDebugTracing, provideRouter} from '@angular/router'; @@ -3314,19 +3974,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withDebugTracing())] }).catch(e => console.error(e)); - `)); + `), + ); }); - it('should migrate a router config with `initialNavigation: "enabledBlocking"` to withEnabledBlockingInitialNavigation', - async () => { - writeFile('main.ts', ` + it('should migrate a router config with `initialNavigation: "enabledBlocking"` to withEnabledBlockingInitialNavigation', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3341,11 +4006,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withEnabledBlockingInitialNavigation, provideRouter} from '@angular/router'; @@ -3353,19 +4020,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withEnabledBlockingInitialNavigation())] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); - it('should migrate a router config with `initialNavigation: "enabled"` to withEnabledBlockingInitialNavigation', - async () => { - writeFile('main.ts', ` + it('should migrate a router config with `initialNavigation: "enabled"` to withEnabledBlockingInitialNavigation', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3380,11 +4052,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withEnabledBlockingInitialNavigation, provideRouter} from '@angular/router'; @@ -3392,19 +4066,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withEnabledBlockingInitialNavigation())] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); - it('should migrate a router config with `initialNavigation: "disabled"` to withDisabledInitialNavigation', - async () => { - writeFile('main.ts', ` + it('should migrate a router config with `initialNavigation: "disabled"` to withDisabledInitialNavigation', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3419,11 +4098,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withDisabledInitialNavigation, provideRouter} from '@angular/router'; @@ -3431,18 +4112,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withDisabledInitialNavigation())] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); it('should migrate a router config with useHash to withHashLocation', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3457,11 +4144,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withHashLocation, provideRouter} from '@angular/router'; @@ -3469,18 +4158,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withHashLocation())] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should migrate a router config with errorHandler to withNavigationErrorHandler', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3495,11 +4190,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withNavigationErrorHandler, provideRouter} from '@angular/router'; @@ -3507,19 +4204,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideRouter([], withNavigationErrorHandler(() => {}))] }).catch(e => console.error(e)); - `)); + `), + ); }); - it('should combine the anchorScrolling and scrollPositionRestoration of a router config into withInMemoryScrolling', - async () => { - writeFile('main.ts', ` + it('should combine the anchorScrolling and scrollPositionRestoration of a router config into withInMemoryScrolling', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3535,11 +4237,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withInMemoryScrolling, provideRouter} from '@angular/router'; @@ -3550,18 +4254,24 @@ describe('standalone migration', () => { scrollPositionRestoration: 'top' }))] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); it('should copy properties that do not map to a feature into withRouterConfig', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3578,11 +4288,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withHashLocation, withRouterConfig, provideRouter} from '@angular/router'; @@ -3593,19 +4305,24 @@ describe('standalone migration', () => { paramsInheritanceStrategy: 'always' }))] }).catch(e => console.error(e)); - `)); + `), + ); }); - it('should preserve a RouterModule.forRoot where the config cannot be statically analyzed', - async () => { - writeFile('main.ts', ` + it('should preserve a RouterModule.forRoot where the config cannot be statically analyzed', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RouterModule} from '@angular/router'; @@ -3623,11 +4340,13 @@ describe('standalone migration', () => { })] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {importProvidersFrom} from '@angular/core'; @@ -3641,18 +4360,24 @@ describe('standalone migration', () => { ...extraOptions }))] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); it('should convert BrowserAnimationsModule references to provideAnimations', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; @@ -3665,11 +4390,13 @@ describe('standalone migration', () => { imports: [BrowserAnimationsModule] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {provideAnimations} from '@angular/platform-browser/animations'; @@ -3677,18 +4404,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideAnimations()] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should preserve BrowserAnimationsModule.withConfig calls', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; @@ -3701,11 +4434,13 @@ describe('standalone migration', () => { imports: [BrowserAnimationsModule.withConfig({disableAnimations: true})] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {importProvidersFrom} from '@angular/core'; @@ -3714,18 +4449,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: true}))] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should convert NoopAnimationsModule references to provideNoopAnimations', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; @@ -3738,11 +4479,13 @@ describe('standalone migration', () => { imports: [NoopAnimationsModule] }) export class AppModule {} - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; @@ -3750,19 +4493,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideNoopAnimations()] }).catch(e => console.error(e)); - `)); + `), + ); }); - it('should convert HttpClientModule references to provideHttpClient(withInterceptorsFromDi())', - async () => { - writeFile('main.ts', ` + it('should convert HttpClientModule references to provideHttpClient(withInterceptorsFromDi())', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {HttpClientModule} from '@angular/common/http'; @@ -3775,11 +4523,13 @@ describe('standalone migration', () => { imports: [HttpClientModule] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {withInterceptorsFromDi, provideHttpClient} from '@angular/common/http'; @@ -3787,19 +4537,24 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [provideHttpClient(withInterceptorsFromDi())] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); - it('should omit standalone directives from the imports array from the importProvidersFrom call', - async () => { - writeFile('main.ts', ` + it('should omit standalone directives from the imports array from the importProvidersFrom call', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component, Directive} from '@angular/core'; import {CommonModule} from '@angular/common'; @@ -3811,11 +4566,13 @@ describe('standalone migration', () => { @NgModule({imports: [Dir, CommonModule], declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {importProvidersFrom} from '@angular/core'; @@ -3824,24 +4581,31 @@ describe('standalone migration', () => { bootstrapApplication(AppComponent, { providers: [importProvidersFrom(CommonModule)] }).catch(e => console.error(e)); - `)); + `), + ); - expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toContain(stripWhitespace(` + expect(stripWhitespace(tree.readContent('./app/app.module.ts'))).toContain( + stripWhitespace(` @Component({template: '', standalone: true, imports: [Dir]}) export class AppComponent {} - `)); - }); + `), + ); + }); - it('should be able to migrate a bootstrapModule call where the root component does not belong to the bootstrapped component', - async () => { - writeFile('main.ts', ` + it('should be able to migrate a bootstrapModule call where the root component does not belong to the bootstrapped component', async () => { + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/root.module.ts', ` + writeFile( + './app/root.module.ts', + ` import {NgModule, Component, InjectionToken} from '@angular/core'; const token = new InjectionToken('token'); @@ -3855,9 +4619,12 @@ describe('standalone migration', () => { providers: [{provide: token, useValue: 'hello'}] }) export class RootModule {} - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; import {RootModule, Root} from './root.module'; @@ -3866,12 +4633,14 @@ describe('standalone migration', () => { bootstrap: [Root] }) export class AppModule {} - `); + `, + ); - await runMigration('standalone-bootstrap'); + await runMigration('standalone-bootstrap'); - expect(tree.exists('./app/app.module.ts')).toBe(false); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(tree.exists('./app/app.module.ts')).toBe(false); + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {platformBrowser, bootstrapApplication} from '@angular/platform-browser'; import {importProvidersFrom} from '@angular/core'; import {RootModule, Root} from './app/root.module'; @@ -3879,18 +4648,24 @@ describe('standalone migration', () => { bootstrapApplication(Root, { providers: [importProvidersFrom(RootModule)] }).catch(e => console.error(e)); - `)); - }); + `), + ); + }); it('should add Protractor support if any tests are detected', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({selector: 'app', template: 'hello'}) @@ -3898,9 +4673,12 @@ describe('standalone migration', () => { @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); - writeFile('./app/app.e2e.spec.ts', ` + writeFile( + './app/app.e2e.spec.ts', + ` import {browser, by, element} from 'protractor'; describe('app', () => { @@ -3913,29 +4691,37 @@ describe('standalone migration', () => { expect(await rootSelector.isPresent()).toBe(true); }); }); - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, provideProtractorTestingSupport, bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(AppComponent, { providers: [provideProtractorTestingSupport()] }).catch(e => console.error(e)); - `)); + `), + ); }); it('should add Protractor support if any tests with deep imports are detected', async () => { - writeFile('main.ts', ` + writeFile( + 'main.ts', + ` import {AppModule} from './app/app.module'; import {platformBrowser} from '@angular/platform-browser'; platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e)); - `); + `, + ); - writeFile('./app/app.module.ts', ` + writeFile( + './app/app.module.ts', + ` import {NgModule, Component} from '@angular/core'; @Component({selector: 'app', template: 'hello'}) @@ -3943,9 +4729,12 @@ describe('standalone migration', () => { @NgModule({declarations: [AppComponent], bootstrap: [AppComponent]}) export class AppModule {} - `); + `, + ); - writeFile('./app/app.e2e.spec.ts', ` + writeFile( + './app/app.e2e.spec.ts', + ` import {browser, by, element} from 'protractor/some/deep-import'; describe('app', () => { @@ -3958,17 +4747,20 @@ describe('standalone migration', () => { expect(await rootSelector.isPresent()).toBe(true); }); }); - `); + `, + ); await runMigration('standalone-bootstrap'); - expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(` + expect(stripWhitespace(tree.readContent('main.ts'))).toBe( + stripWhitespace(` import {AppComponent} from './app/app.module'; import {platformBrowser, provideProtractorTestingSupport, bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(AppComponent, { providers: [provideProtractorTestingSupport()] }).catch(e => console.error(e)); - `)); + `), + ); }); }); diff --git a/packages/core/schematics/utils/change_tracker.ts b/packages/core/schematics/utils/change_tracker.ts index 7d46b37b3c997..e22f4f21fa7ba 100644 --- a/packages/core/schematics/utils/change_tracker.ts +++ b/packages/core/schematics/utils/change_tracker.ts @@ -34,14 +34,18 @@ export class ChangeTracker { private readonly _changes = new Map(); private readonly _importManager: ImportManager; - constructor(private _printer: ts.Printer, private _importRemapper?: ImportRemapper) { + constructor( + private _printer: ts.Printer, + private _importRemapper?: ImportRemapper, + ) { this._importManager = new ImportManager( - currentFile => ({ - addNewImport: (start, text) => this.insertText(currentFile, start, text), - updateExistingImport: (namedBindings, text) => this.replaceText( - currentFile, namedBindings.getStart(), namedBindings.getWidth(), text), - }), - this._printer); + (currentFile) => ({ + addNewImport: (start, text) => this.insertText(currentFile, start, text), + updateExistingImport: (namedBindings, text) => + this.replaceText(currentFile, namedBindings.getStart(), namedBindings.getWidth(), text), + }), + this._printer, + ); } /** @@ -75,12 +79,18 @@ export class ChangeTracker { * without it. */ replaceNode( - oldNode: ts.Node, newNode: ts.Node, emitHint = ts.EmitHint.Unspecified, - sourceFileWhenPrinting?: ts.SourceFile): void { + oldNode: ts.Node, + newNode: ts.Node, + emitHint = ts.EmitHint.Unspecified, + sourceFileWhenPrinting?: ts.SourceFile, + ): void { const sourceFile = oldNode.getSourceFile(); this.replaceText( - sourceFile, oldNode.getStart(), oldNode.getWidth(), - this._printer.printNode(emitHint, newNode, sourceFileWhenPrinting || sourceFile)); + sourceFile, + oldNode.getStart(), + oldNode.getWidth(), + this._printer.printNode(emitHint, newNode, sourceFileWhenPrinting || sourceFile), + ); } /** @@ -88,8 +98,11 @@ export class ChangeTracker { * @param node Node whose text should be removed. */ removeNode(node: ts.Node): void { - this._trackChange( - node.getSourceFile(), {start: node.getStart(), removeLength: node.getWidth(), text: ''}); + this._trackChange(node.getSourceFile(), { + start: node.getStart(), + removeLength: node.getWidth(), + text: '', + }); } /** @@ -99,8 +112,12 @@ export class ChangeTracker { * @param moduleName Module from which the symbol is imported. */ addImport( - sourceFile: ts.SourceFile, symbolName: string, moduleName: string, alias: string|null = null, - keepSymbolName = false): ts.Expression { + sourceFile: ts.SourceFile, + symbolName: string, + moduleName: string, + alias: string | null = null, + keepSymbolName = false, + ): ts.Expression { if (this._importRemapper) { moduleName = this._importRemapper(moduleName, sourceFile.fileName); } @@ -111,7 +128,13 @@ export class ChangeTracker { moduleName = normalizePath(moduleName); return this._importManager.addImportToSourceFile( - sourceFile, symbolName, moduleName, alias, false, keepSymbolName); + sourceFile, + symbolName, + moduleName, + alias, + false, + keepSymbolName, + ); } /** @@ -142,7 +165,7 @@ export class ChangeTracker { // Insert the changes in reverse so that they're applied in reverse order. // This ensures that the offsets of subsequent changes aren't affected by // previous changes changing the file's text. - const insertIndex = changes.findIndex(current => current.start <= change.start); + const insertIndex = changes.findIndex((current) => current.start <= change.start); if (insertIndex === -1) { changes.push(change); diff --git a/packages/core/schematics/utils/extract_metadata.ts b/packages/core/schematics/utils/extract_metadata.ts index d6b0b5ff4cef9..6ce04ba0aebb7 100644 --- a/packages/core/schematics/utils/extract_metadata.ts +++ b/packages/core/schematics/utils/extract_metadata.ts @@ -13,13 +13,15 @@ import {unwrapExpression} from './typescript/functions'; /** Interface describing metadata of an Angular class. */ export interface AngularClassMetadata { - type: 'component'|'directive'; + type: 'component' | 'directive'; node: ts.ObjectLiteralExpression; } /** Extracts `@Directive` or `@Component` metadata from the given class. */ export function extractAngularClassMetadata( - typeChecker: ts.TypeChecker, node: ts.ClassDeclaration): AngularClassMetadata|null { + typeChecker: ts.TypeChecker, + node: ts.ClassDeclaration, +): AngularClassMetadata | null { const decorators = ts.getDecorators(node); if (!decorators || !decorators.length) { @@ -27,8 +29,8 @@ export function extractAngularClassMetadata( } const ngDecorators = getAngularDecorators(typeChecker, decorators); - const componentDecorator = ngDecorators.find(dec => dec.name === 'Component'); - const directiveDecorator = ngDecorators.find(dec => dec.name === 'Directive'); + const componentDecorator = ngDecorators.find((dec) => dec.name === 'Component'); + const directiveDecorator = ngDecorators.find((dec) => dec.name === 'Directive'); const decorator = componentDecorator ?? directiveDecorator; // In case no decorator could be found on the current class, skip. diff --git a/packages/core/schematics/utils/import_manager.ts b/packages/core/schematics/utils/import_manager.ts index 144517b8e9b3f..282c577dc1280 100644 --- a/packages/core/schematics/utils/import_manager.ts +++ b/packages/core/schematics/utils/import_manager.ts @@ -28,16 +28,21 @@ const enum QuoteStyle { */ export class ImportManager { /** Map of import declarations that need to be updated to include the given symbols. */ - private updatedImports = - new Map(); + private updatedImports = new Map< + ts.ImportDeclaration, + {propertyName?: ts.Identifier; importName: ts.Identifier}[] + >(); /** Map of source-files and their previously used identifier names. */ private usedIdentifierNames = new Map(); /** Map of source files and the new imports that have to be added to them. */ - private newImports: Map, - namedImports: Map, - }> = new Map(); + private newImports: Map< + ts.SourceFile, + { + importStartIndex: number; + defaultImports: Map; + namedImports: Map; + } + > = new Map(); /** Map between a file and the implied quote style for imports. */ private quoteStyles: Record = {}; @@ -46,33 +51,43 @@ export class ImportManager { * the same identifier without checking the source-file again. */ private importCache: { - sourceFile: ts.SourceFile, - symbolName: string|null, - alias: string|null, - moduleName: string, - identifier: ts.Identifier + sourceFile: ts.SourceFile; + symbolName: string | null; + alias: string | null; + moduleName: string; + identifier: ts.Identifier; }[] = []; constructor( - private getUpdateRecorder: (sf: ts.SourceFile) => ImportManagerUpdateRecorder, - private printer: ts.Printer) {} + private getUpdateRecorder: (sf: ts.SourceFile) => ImportManagerUpdateRecorder, + private printer: ts.Printer, + ) {} /** * Adds an import to the given source-file and returns the TypeScript * identifier that can be used to access the newly imported symbol. */ addImportToSourceFile( - sourceFile: ts.SourceFile, symbolName: string|null, moduleName: string, - alias: string|null = null, typeImport = false, keepSymbolName = false): ts.Expression { + sourceFile: ts.SourceFile, + symbolName: string | null, + moduleName: string, + alias: string | null = null, + typeImport = false, + keepSymbolName = false, + ): ts.Expression { const sourceDir = dirname(sourceFile.fileName); let importStartIndex = 0; - let existingImport: ts.ImportDeclaration|null = null; + let existingImport: ts.ImportDeclaration | null = null; // In case the given import has been already generated previously, we just return // the previous generated identifier in order to avoid duplicate generated imports. const cachedImport = this.importCache.find( - c => c.sourceFile === sourceFile && c.symbolName === symbolName && - c.moduleName === moduleName && c.alias === alias); + (c) => + c.sourceFile === sourceFile && + c.symbolName === symbolName && + c.moduleName === moduleName && + c.alias === alias, + ); if (cachedImport) { return cachedImport.identifier; } @@ -84,8 +99,11 @@ export class ImportManager { for (let i = sourceFile.statements.length - 1; i >= 0; i--) { const statement = sourceFile.statements[i]; - if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || - !statement.importClause) { + if ( + !ts.isImportDeclaration(statement) || + !ts.isStringLiteral(statement.moduleSpecifier) || + !statement.importClause + ) { continue; } @@ -95,9 +113,11 @@ export class ImportManager { const moduleSpecifier = statement.moduleSpecifier.text; - if (moduleSpecifier.startsWith('.') && - resolve(sourceDir, moduleSpecifier) !== resolve(sourceDir, moduleName) || - moduleSpecifier !== moduleName) { + if ( + (moduleSpecifier.startsWith('.') && + resolve(sourceDir, moduleSpecifier) !== resolve(sourceDir, moduleName)) || + moduleSpecifier !== moduleName + ) { continue; } @@ -108,10 +128,11 @@ export class ImportManager { // because these only export symbols available at runtime (no types) if (ts.isNamespaceImport(namedBindings) && !typeImport) { return ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(namedBindings.name.text), - ts.factory.createIdentifier(alias || symbolName || 'default')); + ts.factory.createIdentifier(namedBindings.name.text), + ts.factory.createIdentifier(alias || symbolName || 'default'), + ); } else if (ts.isNamedImports(namedBindings) && symbolName) { - const existingElement = namedBindings.elements.find(e => { + const existingElement = namedBindings.elements.find((e) => { // TODO(crisbeto): if an alias conflicts with an existing import, it may cause invalid // code to be generated. This is unlikely, but we may want to revisit it in the future. if (alias) { @@ -135,8 +156,12 @@ export class ImportManager { } if (existingImport) { - const {propertyName, name} = - this._getImportParts(sourceFile, symbolName!, alias, keepSymbolName); + const {propertyName, name} = this._getImportParts( + sourceFile, + symbolName!, + alias, + keepSymbolName, + ); // Since it can happen that multiple classes need to be imported within the // specified source file and we want to add the identifiers to the existing @@ -145,8 +170,9 @@ export class ImportManager { // would throw off the recorder offsets. We need to keep track of the new identifiers // for the import and perform the import transformation as batches per source-file. this.updatedImports.set( - existingImport, - (this.updatedImports.get(existingImport) || []).concat({propertyName, importName: name})); + existingImport, + (this.updatedImports.get(existingImport) || []).concat({propertyName, importName: name}), + ); // Keep track of all updated imports so that we don't generate duplicate // similar imports as these can't be statically analyzed in the source-file yet. @@ -155,7 +181,7 @@ export class ImportManager { return name; } - let identifier: ts.Identifier|null = null; + let identifier: ts.Identifier | null = null; if (!this.newImports.has(sourceFile)) { this.newImports.set(sourceFile, { @@ -166,8 +192,12 @@ export class ImportManager { } if (symbolName) { - const {propertyName, name} = - this._getImportParts(sourceFile, symbolName, alias, keepSymbolName); + const {propertyName, name} = this._getImportParts( + sourceFile, + symbolName, + alias, + keepSymbolName, + ); const importMap = this.newImports.get(sourceFile)!.namedImports; identifier = name; @@ -200,13 +230,19 @@ export class ImportManager { const recorder = this.getUpdateRecorder(sourceFile); const namedBindings = importDecl.importClause!.namedBindings as ts.NamedImports; const newNamedBindings = ts.factory.updateNamedImports( - namedBindings, - namedBindings.elements.concat(expressions.map( - ({propertyName, importName}) => - ts.factory.createImportSpecifier(false, propertyName, importName)))); - - const newNamedBindingsText = - this.printer.printNode(ts.EmitHint.Unspecified, newNamedBindings, sourceFile); + namedBindings, + namedBindings.elements.concat( + expressions.map(({propertyName, importName}) => + ts.factory.createImportSpecifier(false, propertyName, importName), + ), + ), + ); + + const newNamedBindingsText = this.printer.printNode( + ts.EmitHint.Unspecified, + newNamedBindings, + sourceFile, + ); recorder.updateExistingImport(namedBindings, newNamedBindingsText); }); @@ -216,22 +252,32 @@ export class ImportManager { defaultImports.forEach((identifier, moduleName) => { const newImport = ts.factory.createImportDeclaration( - undefined, ts.factory.createImportClause(false, identifier, undefined), - ts.factory.createStringLiteral(moduleName, useSingleQuotes)); + undefined, + ts.factory.createImportClause(false, identifier, undefined), + ts.factory.createStringLiteral(moduleName, useSingleQuotes), + ); recorder.addNewImport( - importStartIndex, this._getNewImportText(importStartIndex, newImport, sourceFile)); + importStartIndex, + this._getNewImportText(importStartIndex, newImport, sourceFile), + ); }); namedImports.forEach((specifiers, moduleName) => { const newImport = ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause( + false, undefined, - ts.factory.createImportClause( - false, undefined, ts.factory.createNamedImports(specifiers)), - ts.factory.createStringLiteral(moduleName, useSingleQuotes)); + ts.factory.createNamedImports(specifiers), + ), + ts.factory.createStringLiteral(moduleName, useSingleQuotes), + ); recorder.addNewImport( - importStartIndex, this._getNewImportText(importStartIndex, newImport, sourceFile)); + importStartIndex, + this._getNewImportText(importStartIndex, newImport, sourceFile), + ); }); }); } @@ -258,8 +304,10 @@ export class ImportManager { * source file. */ private isUniqueIdentifierName(sourceFile: ts.SourceFile, name: string) { - if (this.usedIdentifierNames.has(sourceFile) && - this.usedIdentifierNames.get(sourceFile)!.indexOf(name) !== -1) { + if ( + this.usedIdentifierNames.has(sourceFile) && + this.usedIdentifierNames.get(sourceFile)!.indexOf(name) !== -1 + ) { return false; } @@ -269,10 +317,13 @@ export class ImportManager { const nodeQueue: ts.Node[] = [sourceFile]; while (nodeQueue.length) { const node = nodeQueue.shift()!; - if (ts.isIdentifier(node) && node.text === name && - // Identifiers that are aliased in an import aren't - // problematic since they're used under a different name. - (!ts.isImportSpecifier(node.parent) || node.parent.propertyName !== node)) { + if ( + ts.isIdentifier(node) && + node.text === name && + // Identifiers that are aliased in an import aren't + // problematic since they're used under a different name. + (!ts.isImportSpecifier(node.parent) || node.parent.propertyName !== node) + ) { return false; } nodeQueue.push(...node.getChildren()); @@ -282,7 +333,9 @@ export class ImportManager { private _recordUsedIdentifier(sourceFile: ts.SourceFile, identifierName: string) { this.usedIdentifierNames.set( - sourceFile, (this.usedIdentifierNames.get(sourceFile) || []).concat(identifierName)); + sourceFile, + (this.usedIdentifierNames.get(sourceFile) || []).concat(identifierName), + ); } /** @@ -300,8 +353,10 @@ export class ImportManager { /** Gets the text that should be added to the file for a newly-created import declaration. */ private _getNewImportText( - importStartIndex: number, newImport: ts.ImportDeclaration, - sourceFile: ts.SourceFile): string { + importStartIndex: number, + newImport: ts.ImportDeclaration, + sourceFile: ts.SourceFile, + ): string { const text = this.printer.printNode(ts.EmitHint.Unspecified, newImport, sourceFile); // If the import is generated at the start of the source file, we want to add @@ -321,12 +376,16 @@ export class ImportManager { * corresponds to `import {name}`. */ private _getImportParts( - sourceFile: ts.SourceFile, symbolName: string, alias: string|null, keepSymbolName: boolean) { + sourceFile: ts.SourceFile, + symbolName: string, + alias: string | null, + keepSymbolName: boolean, + ) { const symbolIdentifier = ts.factory.createIdentifier(symbolName); const aliasIdentifier = alias ? ts.factory.createIdentifier(alias) : null; const generatedUniqueIdentifier = this._getUniqueIdentifier(sourceFile, alias || symbolName); const needsGeneratedUniqueName = generatedUniqueIdentifier.text !== (alias || symbolName); - let propertyName: ts.Identifier|undefined; + let propertyName: ts.Identifier | undefined; let name: ts.Identifier; if (needsGeneratedUniqueName && !keepSymbolName) { @@ -345,16 +404,18 @@ export class ImportManager { /** Gets the quote style that is used for a file's imports. */ private _getQuoteStyle(sourceFile: ts.SourceFile): QuoteStyle { if (!this.quoteStyles.hasOwnProperty(sourceFile.fileName)) { - let quoteStyle: QuoteStyle|undefined; + let quoteStyle: QuoteStyle | undefined; // Walk through the top-level imports and try to infer the quotes. for (const statement of sourceFile.statements) { - if (ts.isImportDeclaration(statement) && - ts.isStringLiteralLike(statement.moduleSpecifier)) { + if ( + ts.isImportDeclaration(statement) && + ts.isStringLiteralLike(statement.moduleSpecifier) + ) { // Use `getText` instead of the actual text since it includes the quotes. - quoteStyle = statement.moduleSpecifier.getText().trim().startsWith('"') ? - QuoteStyle.Double : - QuoteStyle.Single; + quoteStyle = statement.moduleSpecifier.getText().trim().startsWith('"') + ? QuoteStyle.Double + : QuoteStyle.Single; break; } } diff --git a/packages/core/schematics/utils/line_mappings.ts b/packages/core/schematics/utils/line_mappings.ts index 56301b7c4c121..01444805dc3cc 100644 --- a/packages/core/schematics/utils/line_mappings.ts +++ b/packages/core/schematics/utils/line_mappings.ts @@ -43,7 +43,11 @@ export function computeLineStartsMap(text: string): number[] { /** Finds the closest line start for the given position. */ function findClosestLineStartPosition( - linesMap: T[], position: T, low = 0, high = linesMap.length - 1) { + linesMap: T[], + position: T, + low = 0, + high = linesMap.length - 1, +) { while (low <= high) { const pivotIdx = Math.floor((low + high) / 2); const pivotEl = linesMap[pivotIdx]; diff --git a/packages/core/schematics/utils/load_esm.ts b/packages/core/schematics/utils/load_esm.ts index 5ba0bc6fc2b51..3f17a987a499e 100644 --- a/packages/core/schematics/utils/load_esm.ts +++ b/packages/core/schematics/utils/load_esm.ts @@ -21,9 +21,10 @@ import {URL} from 'url'; * @param modulePath The path of the module to load. * @returns A Promise that resolves to the dynamically imported module. */ -export async function loadEsmModule(modulePath: string|URL): Promise { - const namespaceObject = - (await new Function('modulePath', `return import(modulePath);`)(modulePath)); +export async function loadEsmModule(modulePath: string | URL): Promise { + const namespaceObject = await new Function('modulePath', `return import(modulePath);`)( + modulePath, + ); // If it is not ESM then the values needed will be stored in the `default` property. // TODO_ESM: This can be removed once `@angular/*` packages are ESM only. @@ -41,8 +42,9 @@ export async function loadEsmModule(modulePath: string|URL): Promise { * @returns A Promise that resolves to the dynamically imported compiler-cli private migrations * entry or an equivalent object if not available. */ -export async function loadCompilerCliMigrationsModule(): - Promise { +export async function loadCompilerCliMigrationsModule(): Promise< + typeof import('@angular/compiler-cli/private/migrations') +> { try { return await loadEsmModule('@angular/compiler-cli/private/migrations'); } catch { diff --git a/packages/core/schematics/utils/ng_component_template.ts b/packages/core/schematics/utils/ng_component_template.ts index 77c95373b0a1d..5d0234fd13577 100644 --- a/packages/core/schematics/utils/ng_component_template.ts +++ b/packages/core/schematics/utils/ng_component_template.ts @@ -31,7 +31,8 @@ export interface ResolvedTemplate { * character are based on the full source file content. */ getCharacterAndLineOfPosition: (pos: number) => { - character: number, line: number + character: number; + line: number; }; } @@ -42,14 +43,18 @@ export interface ResolvedTemplate { export class NgComponentTemplateVisitor { resolvedTemplates: ResolvedTemplate[] = []; - constructor(public typeChecker: ts.TypeChecker, private _basePath: string, private _tree: Tree) {} + constructor( + public typeChecker: ts.TypeChecker, + private _basePath: string, + private _tree: Tree, + ) {} visitNode(node: ts.Node) { if (node.kind === ts.SyntaxKind.ClassDeclaration) { this.visitClassDeclaration(node as ts.ClassDeclaration); } - ts.forEachChild(node, n => this.visitNode(n)); + ts.forEachChild(node, (n) => this.visitNode(n)); } private visitClassDeclaration(node: ts.ClassDeclaration) { @@ -63,7 +68,7 @@ export class NgComponentTemplateVisitor { // Walk through all component metadata properties and determine the referenced // HTML templates (either external or inline) - metadata.node.properties.forEach(property => { + metadata.node.properties.forEach((property) => { if (!ts.isPropertyAssignment(property)) { return; } @@ -90,8 +95,8 @@ export class NgComponentTemplateVisitor { content, inline: true, start: start, - getCharacterAndLineOfPosition: pos => - ts.getLineAndCharacterOfPosition(sourceFile, pos + start) + getCharacterAndLineOfPosition: (pos) => + ts.getLineAndCharacterOfPosition(sourceFile, pos + start), }); } if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) { @@ -118,7 +123,8 @@ export class NgComponentTemplateVisitor { content: fileContent, inline: false, start: 0, - getCharacterAndLineOfPosition: pos => getLineAndCharacterFromPosition(lineStartsMap, pos), + getCharacterAndLineOfPosition: (pos) => + getLineAndCharacterFromPosition(lineStartsMap, pos), }); } }); diff --git a/packages/core/schematics/utils/ng_decorators.ts b/packages/core/schematics/utils/ng_decorators.ts index 43b4fa62bdec3..d5a026733cc45 100644 --- a/packages/core/schematics/utils/ng_decorators.ts +++ b/packages/core/schematics/utils/ng_decorators.ts @@ -9,7 +9,7 @@ import ts from 'typescript'; import {getCallDecoratorImport} from './typescript/decorators'; -export type CallExpressionDecorator = ts.Decorator&{ +export type CallExpressionDecorator = ts.Decorator & { expression: ts.CallExpression; }; @@ -25,13 +25,16 @@ export interface NgDecorator { * from a list of decorators. */ export function getAngularDecorators( - typeChecker: ts.TypeChecker, decorators: ReadonlyArray): NgDecorator[] { - return decorators.map(node => ({node, importData: getCallDecoratorImport(typeChecker, node)})) - .filter(({importData}) => importData && importData.importModule.startsWith('@angular/')) - .map(({node, importData}) => ({ - node: node as CallExpressionDecorator, - name: importData!.name, - moduleName: importData!.importModule, - importNode: importData!.node - })); + typeChecker: ts.TypeChecker, + decorators: ReadonlyArray, +): NgDecorator[] { + return decorators + .map((node) => ({node, importData: getCallDecoratorImport(typeChecker, node)})) + .filter(({importData}) => importData && importData.importModule.startsWith('@angular/')) + .map(({node, importData}) => ({ + node: node as CallExpressionDecorator, + name: importData!.name, + moduleName: importData!.importModule, + importNode: importData!.node, + })); } diff --git a/packages/core/schematics/utils/parse_html.ts b/packages/core/schematics/utils/parse_html.ts index 34ddf63b6bc1e..b97a2ca55830e 100644 --- a/packages/core/schematics/utils/parse_html.ts +++ b/packages/core/schematics/utils/parse_html.ts @@ -13,8 +13,10 @@ import type {TmplAstNode} from '@angular/compiler'; * fails, null is being returned. */ export function parseHtmlGracefully( - htmlContent: string, filePath: string, - compilerModule: typeof import('@angular/compiler')): TmplAstNode[]|null { + htmlContent: string, + filePath: string, + compilerModule: typeof import('@angular/compiler'), +): TmplAstNode[] | null { try { return compilerModule.parseTemplate(htmlContent, filePath).nodes; } catch { diff --git a/packages/core/schematics/utils/project_tsconfig_paths.ts b/packages/core/schematics/utils/project_tsconfig_paths.ts index ce0f992e5ed23..6ca3dde231cb2 100644 --- a/packages/core/schematics/utils/project_tsconfig_paths.ts +++ b/packages/core/schematics/utils/project_tsconfig_paths.ts @@ -13,8 +13,9 @@ import {Tree} from '@angular-devkit/schematics'; * Gets all tsconfig paths from a CLI project by reading the workspace configuration * and looking for common tsconfig locations. */ -export async function getProjectTsConfigPaths(tree: Tree): - Promise<{buildPaths: string[]; testPaths: string[];}> { +export async function getProjectTsConfigPaths( + tree: Tree, +): Promise<{buildPaths: string[]; testPaths: string[]}> { // Start with some tsconfig paths that are generally used within CLI projects. Note // that we are not interested in IDE-specific tsconfig files (e.g. /tsconfig.json) const buildPaths = new Set(); @@ -50,9 +51,9 @@ export async function getProjectTsConfigPaths(tree: Tree): } /** Get options for all configurations for the passed builder target. */ -function* - allTargetOptions(target: workspaces.TargetDefinition): - Iterable<[string | undefined, Record]> { +function* allTargetOptions( + target: workspaces.TargetDefinition, +): Iterable<[string | undefined, Record]> { if (target.options) { yield [undefined, target.options]; } diff --git a/packages/core/schematics/utils/template_ast_visitor.ts b/packages/core/schematics/utils/template_ast_visitor.ts index e9004ad3ba042..b136ea5e7c69d 100644 --- a/packages/core/schematics/utils/template_ast_visitor.ts +++ b/packages/core/schematics/utils/template_ast_visitor.ts @@ -6,7 +6,33 @@ * found in the LICENSE file at https://angular.io/license */ -import type {TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstIfBlockBranch, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable, TmplAstUnknownBlock} from '@angular/compiler'; +import type { + TmplAstBoundAttribute, + TmplAstBoundEvent, + TmplAstBoundText, + TmplAstContent, + TmplAstDeferredBlock, + TmplAstDeferredBlockError, + TmplAstDeferredBlockLoading, + TmplAstDeferredBlockPlaceholder, + TmplAstDeferredTrigger, + TmplAstElement, + TmplAstIfBlockBranch, + TmplAstForLoopBlock, + TmplAstForLoopBlockEmpty, + TmplAstIcu, + TmplAstIfBlock, + TmplAstNode, + TmplAstRecursiveVisitor, + TmplAstReference, + TmplAstSwitchBlock, + TmplAstSwitchBlockCase, + TmplAstTemplate, + TmplAstText, + TmplAstTextAttribute, + TmplAstVariable, + TmplAstUnknownBlock, +} from '@angular/compiler'; /** * A base class that can be used to implement a Render3 Template AST visitor. diff --git a/packages/core/schematics/utils/tslint/tslint_html_source_file.ts b/packages/core/schematics/utils/tslint/tslint_html_source_file.ts index c09515cdcd674..28dafa0f04cf6 100644 --- a/packages/core/schematics/utils/tslint/tslint_html_source_file.ts +++ b/packages/core/schematics/utils/tslint/tslint_html_source_file.ts @@ -23,7 +23,7 @@ export function createHtmlSourceFile(filePath: string, content: string): ts.Sour // At the time of writing, TSLint loads files manually if the actual rule source file is not // equal to the source file of the replacement. This means that the replacements need proper // offsets without the string literal quote symbols. - sourceFile.getFullText = function() { + sourceFile.getFullText = function () { return sourceFile.text.substring(1, sourceFile.text.length - 1); }; diff --git a/packages/core/schematics/utils/typescript/class_declaration.ts b/packages/core/schematics/utils/typescript/class_declaration.ts index 711c0e3d4536f..b903cde70f532 100644 --- a/packages/core/schematics/utils/typescript/class_declaration.ts +++ b/packages/core/schematics/utils/typescript/class_declaration.ts @@ -9,19 +9,20 @@ import ts from 'typescript'; /** Determines the base type identifiers of a specified class declaration. */ -export function getBaseTypeIdentifiers(node: ts.ClassDeclaration): ts.Identifier[]|null { +export function getBaseTypeIdentifiers(node: ts.ClassDeclaration): ts.Identifier[] | null { if (!node.heritageClauses) { return null; } - return node.heritageClauses.filter(clause => clause.token === ts.SyntaxKind.ExtendsKeyword) - .reduce((types, clause) => types.concat(clause.types), [] as ts.ExpressionWithTypeArguments[]) - .map(typeExpression => typeExpression.expression) - .filter(ts.isIdentifier); + return node.heritageClauses + .filter((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword) + .reduce((types, clause) => types.concat(clause.types), [] as ts.ExpressionWithTypeArguments[]) + .map((typeExpression) => typeExpression.expression) + .filter(ts.isIdentifier); } /** Gets the first found parent class declaration of a given node. */ -export function findParentClassDeclaration(node: ts.Node): ts.ClassDeclaration|null { +export function findParentClassDeclaration(node: ts.Node): ts.ClassDeclaration | null { while (!ts.isClassDeclaration(node)) { if (ts.isSourceFile(node)) { return null; diff --git a/packages/core/schematics/utils/typescript/compiler_host.ts b/packages/core/schematics/utils/typescript/compiler_host.ts index cdb3cadcd68b6..c5d7ccc7e20b2 100644 --- a/packages/core/schematics/utils/typescript/compiler_host.ts +++ b/packages/core/schematics/utils/typescript/compiler_host.ts @@ -11,7 +11,7 @@ import ts from 'typescript'; import {parseTsconfigFile} from './parse_tsconfig'; -type FakeReadFileFn = (fileName: string) => string|undefined; +type FakeReadFileFn = (fileName: string) => string | undefined; /** * Creates a TypeScript program instance for a TypeScript project within @@ -24,10 +24,19 @@ type FakeReadFileFn = (fileName: string) => string|undefined; * @param additionalFiles Additional file paths that should be added to the program. */ export function createMigrationProgram( - tree: Tree, tsconfigPath: string, basePath: string, fakeFileRead?: FakeReadFileFn, - additionalFiles?: string[]) { - const {rootNames, options, host} = - createProgramOptions(tree, tsconfigPath, basePath, fakeFileRead, additionalFiles); + tree: Tree, + tsconfigPath: string, + basePath: string, + fakeFileRead?: FakeReadFileFn, + additionalFiles?: string[], +) { + const {rootNames, options, host} = createProgramOptions( + tree, + tsconfigPath, + basePath, + fakeFileRead, + additionalFiles, + ); return ts.createProgram(rootNames, options, host); } @@ -42,8 +51,13 @@ export function createMigrationProgram( * @param optionOverrides Overrides of the parsed compiler options. */ export function createProgramOptions( - tree: Tree, tsconfigPath: string, basePath: string, fakeFileRead?: FakeReadFileFn, - additionalFiles?: string[], optionOverrides?: ts.CompilerOptions) { + tree: Tree, + tsconfigPath: string, + basePath: string, + fakeFileRead?: FakeReadFileFn, + additionalFiles?: string[], + optionOverrides?: ts.CompilerOptions, +) { // Resolve the tsconfig path to an absolute path. This is needed as TypeScript otherwise // is not able to resolve root directories in the given tsconfig. More details can be found // in the following issue: https://github.com/microsoft/TypeScript/issues/37731. @@ -55,8 +69,11 @@ export function createProgramOptions( } function createMigrationCompilerHost( - tree: Tree, options: ts.CompilerOptions, basePath: string, - fakeRead?: FakeReadFileFn): ts.CompilerHost { + tree: Tree, + options: ts.CompilerOptions, + basePath: string, + fakeRead?: FakeReadFileFn, +): ts.CompilerHost { const host = ts.createCompilerHost(options, true); const defaultReadFile = host.readFile; @@ -64,15 +81,16 @@ function createMigrationCompilerHost( // program to be based on the file contents in the virtual file tree. Otherwise // if we run multiple migrations we might have intersecting changes and // source files. - host.readFile = fileName => { + host.readFile = (fileName) => { const treeRelativePath = relative(basePath, fileName); - let result: string|undefined = fakeRead?.(treeRelativePath); + let result: string | undefined = fakeRead?.(treeRelativePath); if (typeof result !== 'string') { // If the relative path resolved to somewhere outside of the tree, fall back to // TypeScript's default file reading function since the `tree` will throw an error. - result = treeRelativePath.startsWith('..') ? defaultReadFile.call(host, fileName) : - tree.read(treeRelativePath)?.toString(); + result = treeRelativePath.startsWith('..') + ? defaultReadFile.call(host, fileName) + : tree.read(treeRelativePath)?.toString(); } // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset, @@ -91,10 +109,16 @@ function createMigrationCompilerHost( * @param program Program that includes the source file. */ export function canMigrateFile( - basePath: string, sourceFile: ts.SourceFile, program: ts.Program): boolean { + basePath: string, + sourceFile: ts.SourceFile, + program: ts.Program, +): boolean { // We shouldn't migrate .d.ts files, files from an external library or type checking files. - if (sourceFile.fileName.endsWith('.ngtypecheck.ts') || sourceFile.isDeclarationFile || - program.isSourceFileFromExternalLibrary(sourceFile)) { + if ( + sourceFile.fileName.endsWith('.ngtypecheck.ts') || + sourceFile.isDeclarationFile || + program.isSourceFileFromExternalLibrary(sourceFile) + ) { return false; } diff --git a/packages/core/schematics/utils/typescript/decorators.ts b/packages/core/schematics/utils/typescript/decorators.ts index 920e7e3924d07..fa59dd3123643 100644 --- a/packages/core/schematics/utils/typescript/decorators.ts +++ b/packages/core/schematics/utils/typescript/decorators.ts @@ -11,11 +11,15 @@ import ts from 'typescript'; import {getImportOfIdentifier, Import} from './imports'; export function getCallDecoratorImport( - typeChecker: ts.TypeChecker, decorator: ts.Decorator): Import|null { + typeChecker: ts.TypeChecker, + decorator: ts.Decorator, +): Import | null { // Note that this does not cover the edge case where decorators are called from // a namespace import: e.g. "@core.Component()". This is not handled by Ngtsc either. - if (!ts.isCallExpression(decorator.expression) || - !ts.isIdentifier(decorator.expression.expression)) { + if ( + !ts.isCallExpression(decorator.expression) || + !ts.isIdentifier(decorator.expression.expression) + ) { return null; } diff --git a/packages/core/schematics/utils/typescript/find_base_classes.ts b/packages/core/schematics/utils/typescript/find_base_classes.ts index 08837359f9c42..afea5d084f12e 100644 --- a/packages/core/schematics/utils/typescript/find_base_classes.ts +++ b/packages/core/schematics/utils/typescript/find_base_classes.ts @@ -11,7 +11,7 @@ import {getBaseTypeIdentifiers} from './class_declaration'; /** Gets all base class declarations of the specified class declaration. */ export function findBaseClassDeclarations(node: ts.ClassDeclaration, typeChecker: ts.TypeChecker) { - const result: {identifier: ts.Identifier, node: ts.ClassDeclaration}[] = []; + const result: {identifier: ts.Identifier; node: ts.ClassDeclaration}[] = []; let currentClass = node; while (currentClass) { diff --git a/packages/core/schematics/utils/typescript/functions.ts b/packages/core/schematics/utils/typescript/functions.ts index f5baedd6c7ba8..7305b9017b119 100644 --- a/packages/core/schematics/utils/typescript/functions.ts +++ b/packages/core/schematics/utils/typescript/functions.ts @@ -10,9 +10,14 @@ import ts from 'typescript'; /** Checks whether a given node is a function like declaration. */ export function isFunctionLikeDeclaration(node: ts.Node): node is ts.FunctionLikeDeclaration { - return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || - ts.isArrowFunction(node) || ts.isFunctionExpression(node) || - ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node); + return ( + ts.isFunctionDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isArrowFunction(node) || + ts.isFunctionExpression(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + ); } /** @@ -20,7 +25,7 @@ export function isFunctionLikeDeclaration(node: ts.Node): node is ts.FunctionLik * parentheses or as expression. e.g. "(((({exp}))))()". The function should return the * TypeScript node referring to the inner expression. e.g "exp". */ -export function unwrapExpression(node: ts.Expression|ts.ParenthesizedExpression): ts.Expression { +export function unwrapExpression(node: ts.Expression | ts.ParenthesizedExpression): ts.Expression { if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node)) { return unwrapExpression(node.expression); } else { diff --git a/packages/core/schematics/utils/typescript/imports.ts b/packages/core/schematics/utils/typescript/imports.ts index dd91b65b54cdb..68642077246b3 100644 --- a/packages/core/schematics/utils/typescript/imports.ts +++ b/packages/core/schematics/utils/typescript/imports.ts @@ -9,14 +9,16 @@ import ts from 'typescript'; export type Import = { - name: string, - importModule: string, - node: ts.ImportDeclaration + name: string; + importModule: string; + node: ts.ImportDeclaration; }; /** Gets import information about the specified identifier by using the Type checker. */ -export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Identifier): Import| - null { +export function getImportOfIdentifier( + typeChecker: ts.TypeChecker, + node: ts.Identifier, +): Import | null { const symbol = typeChecker.getSymbolAtLocation(node); if (!symbol || symbol.declarations === undefined || !symbol.declarations.length) { @@ -39,11 +41,10 @@ export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Iden // Handles aliased imports: e.g. "import {Component as myComp} from ..."; name: decl.propertyName ? decl.propertyName.text : decl.name.text, importModule: importDecl.moduleSpecifier.text, - node: importDecl + node: importDecl, }; } - /** * Gets a top-level import specifier with a specific name that is imported from a particular module. * E.g. given a file that looks like: @@ -62,19 +63,25 @@ export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Iden * their original name. */ export function getImportSpecifier( - sourceFile: ts.SourceFile, moduleName: string|RegExp, - specifierName: string): ts.ImportSpecifier|null { + sourceFile: ts.SourceFile, + moduleName: string | RegExp, + specifierName: string, +): ts.ImportSpecifier | null { return getImportSpecifiers(sourceFile, moduleName, [specifierName])[0] ?? null; } export function getImportSpecifiers( - sourceFile: ts.SourceFile, moduleName: string|RegExp, - specifierNames: string[]): ts.ImportSpecifier[] { + sourceFile: ts.SourceFile, + moduleName: string | RegExp, + specifierNames: string[], +): ts.ImportSpecifier[] { const matches: ts.ImportSpecifier[] = []; for (const node of sourceFile.statements) { if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { - const isMatch = typeof moduleName === 'string' ? node.moduleSpecifier.text === moduleName : - moduleName.test(node.moduleSpecifier.text); + const isMatch = + typeof moduleName === 'string' + ? node.moduleSpecifier.text === moduleName + : moduleName.test(node.moduleSpecifier.text); const namedBindings = node.importClause?.namedBindings; if (isMatch && namedBindings && ts.isNamedImports(namedBindings)) { for (const specifierName of specifierNames) { @@ -90,11 +97,15 @@ export function getImportSpecifiers( } export function getNamedImports( - sourceFile: ts.SourceFile, moduleName: string|RegExp): ts.NamedImports|null { + sourceFile: ts.SourceFile, + moduleName: string | RegExp, +): ts.NamedImports | null { for (const node of sourceFile.statements) { if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { - const isMatch = typeof moduleName === 'string' ? node.moduleSpecifier.text === moduleName : - moduleName.test(node.moduleSpecifier.text); + const isMatch = + typeof moduleName === 'string' + ? node.moduleSpecifier.text === moduleName + : moduleName.test(node.moduleSpecifier.text); const namedBindings = node.importClause?.namedBindings; if (isMatch && namedBindings && ts.isNamedImports(namedBindings)) { return namedBindings; @@ -104,7 +115,6 @@ export function getNamedImports( return null; } - /** * Replaces an import inside a named imports node with a different one. * @@ -113,7 +123,10 @@ export function getNamedImports( * @param newImportName Import that should be inserted. */ export function replaceImport( - node: ts.NamedImports, existingImport: string, newImportName: string) { + node: ts.NamedImports, + existingImport: string, + newImportName: string, +) { const isAlreadyImported = findImportSpecifier(node.elements, newImportName); if (isAlreadyImported) { return node; @@ -124,15 +137,17 @@ export function replaceImport( return node; } - const importPropertyName = - existingImportNode.propertyName ? ts.factory.createIdentifier(newImportName) : undefined; - const importName = existingImportNode.propertyName ? existingImportNode.name : - ts.factory.createIdentifier(newImportName); + const importPropertyName = existingImportNode.propertyName + ? ts.factory.createIdentifier(newImportName) + : undefined; + const importName = existingImportNode.propertyName + ? existingImportNode.name + : ts.factory.createIdentifier(newImportName); return ts.factory.updateNamedImports(node, [ - ...node.elements.filter(current => current !== existingImportNode), + ...node.elements.filter((current) => current !== existingImportNode), // Create a new import while trying to preserve the alias of the old one. - ts.factory.createImportSpecifier(false, importPropertyName, importName) + ts.factory.createImportSpecifier(false, importPropertyName, importName), ]); } @@ -146,14 +161,16 @@ export function replaceImport( */ export function removeSymbolFromNamedImports(node: ts.NamedImports, symbol: ts.ImportSpecifier) { return ts.factory.updateNamedImports(node, [ - ...node.elements.filter(current => current !== symbol), + ...node.elements.filter((current) => current !== symbol), ]); } /** Finds an import specifier with a particular name. */ export function findImportSpecifier( - nodes: ts.NodeArray, specifierName: string): ts.ImportSpecifier|undefined { - return nodes.find(element => { + nodes: ts.NodeArray, + specifierName: string, +): ts.ImportSpecifier | undefined { + return nodes.find((element) => { const {name, propertyName} = element; return propertyName ? propertyName.text === specifierName : name.text === specifierName; }); diff --git a/packages/core/schematics/utils/typescript/nodes.ts b/packages/core/schematics/utils/typescript/nodes.ts index 11798c49a931b..2a8ad43d0cbcd 100644 --- a/packages/core/schematics/utils/typescript/nodes.ts +++ b/packages/core/schematics/utils/typescript/nodes.ts @@ -10,13 +10,18 @@ import ts from 'typescript'; /** Checks whether the given TypeScript node has the specified modifier set. */ export function hasModifier(node: ts.Node, modifierKind: ts.SyntaxKind) { - return ts.canHaveModifiers(node) && !!node.modifiers && - node.modifiers.some(m => m.kind === modifierKind); + return ( + ts.canHaveModifiers(node) && + !!node.modifiers && + node.modifiers.some((m) => m.kind === modifierKind) + ); } /** Find the closest parent node of a particular kind. */ -export function closestNode(node: ts.Node, predicate: (n: ts.Node) => n is T): T| - null { +export function closestNode( + node: ts.Node, + predicate: (n: ts.Node) => n is T, +): T | null { let current = node.parent; while (current && !ts.isSourceFile(current)) { @@ -45,8 +50,11 @@ export function isNullCheck(node: ts.Node): boolean { // `foo.bar && foo.bar.parent && foo.bar.parent.value` // where `node` is `foo.bar`. - if (node.parent.parent && ts.isBinaryExpression(node.parent.parent) && - node.parent.parent.left === node.parent) { + if ( + node.parent.parent && + ts.isBinaryExpression(node.parent.parent) && + node.parent.parent.left === node.parent + ) { return true; } @@ -65,6 +73,10 @@ export function isNullCheck(node: ts.Node): boolean { /** Checks whether a property access is safe (e.g. `foo.parent?.value`). */ export function isSafeAccess(node: ts.Node): boolean { - return node.parent != null && ts.isPropertyAccessExpression(node.parent) && - node.parent.expression === node && node.parent.questionDotToken != null; + return ( + node.parent != null && + ts.isPropertyAccessExpression(node.parent) && + node.parent.expression === node && + node.parent.questionDotToken != null + ); } diff --git a/packages/core/schematics/utils/typescript/property_name.ts b/packages/core/schematics/utils/typescript/property_name.ts index 746d6ae48d4af..df8ab42908cc1 100644 --- a/packages/core/schematics/utils/typescript/property_name.ts +++ b/packages/core/schematics/utils/typescript/property_name.ts @@ -15,7 +15,7 @@ type PropertyNameWithText = Exclude; * Gets the text of the given property name. Returns null if the property * name couldn't be determined statically. */ -export function getPropertyNameText(node: ts.PropertyName): string|null { +export function getPropertyNameText(node: ts.PropertyName): string | null { if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) { return node.text; } diff --git a/packages/core/schematics/utils/typescript/symbol.ts b/packages/core/schematics/utils/typescript/symbol.ts index afc62902305ad..82228e9269717 100644 --- a/packages/core/schematics/utils/typescript/symbol.ts +++ b/packages/core/schematics/utils/typescript/symbol.ts @@ -8,8 +8,10 @@ import ts from 'typescript'; -export function getValueSymbolOfDeclaration(node: ts.Node, typeChecker: ts.TypeChecker): ts.Symbol| - undefined { +export function getValueSymbolOfDeclaration( + node: ts.Node, + typeChecker: ts.TypeChecker, +): ts.Symbol | undefined { let symbol = typeChecker.getSymbolAtLocation(node); while (symbol && symbol.flags & ts.SymbolFlags.Alias) { @@ -21,11 +23,16 @@ export function getValueSymbolOfDeclaration(node: ts.Node, typeChecker: ts.TypeC /** Checks whether a node is referring to a specific import specifier. */ export function isReferenceToImport( - typeChecker: ts.TypeChecker, node: ts.Node, importSpecifier: ts.ImportSpecifier): boolean { + typeChecker: ts.TypeChecker, + node: ts.Node, + importSpecifier: ts.ImportSpecifier, +): boolean { const nodeSymbol = typeChecker.getTypeAtLocation(node).getSymbol(); const importSymbol = typeChecker.getTypeAtLocation(importSpecifier).getSymbol(); - return !!(nodeSymbol?.declarations?.[0] && importSymbol?.declarations?.[0]) && - nodeSymbol.declarations[0] === importSymbol.declarations[0]; + return ( + !!(nodeSymbol?.declarations?.[0] && importSymbol?.declarations?.[0]) && + nodeSymbol.declarations[0] === importSymbol.declarations[0] + ); } /** Checks whether a node's type is nullable (`null`, `undefined` or `void`). */ @@ -44,9 +51,11 @@ export function isNullableType(typeChecker: ts.TypeChecker, node: ts.Node) { // through all of its sub-nodes and look for nullable types. if (typeNode) { (function walk(current: ts.Node) { - if (current.kind === ts.SyntaxKind.NullKeyword || - current.kind === ts.SyntaxKind.UndefinedKeyword || - current.kind === ts.SyntaxKind.VoidKeyword) { + if ( + current.kind === ts.SyntaxKind.NullKeyword || + current.kind === ts.SyntaxKind.UndefinedKeyword || + current.kind === ts.SyntaxKind.VoidKeyword + ) { hasSeenNullableType = true; // Note that we don't descend into type literals, because it may cause // us to mis-identify the root type as nullable, because it has a nullable @@ -65,10 +74,14 @@ export function isNullableType(typeChecker: ts.TypeChecker, node: ts.Node) { * type that has the same name as one of the passed-in ones. */ export function hasOneOfTypes( - typeChecker: ts.TypeChecker, node: ts.Node, types: string[]): boolean { + typeChecker: ts.TypeChecker, + node: ts.Node, + types: string[], +): boolean { const type = typeChecker.getTypeAtLocation(node); - const typeNode = - type ? typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.None) : undefined; + const typeNode = type + ? typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.None) + : undefined; let hasMatch = false; if (typeNode) { (function walk(current: ts.Node) { diff --git a/packages/core/src/application/application_config.ts b/packages/core/src/application/application_config.ts index 587c9f0f3fef9..d847dbe3e4299 100644 --- a/packages/core/src/application/application_config.ts +++ b/packages/core/src/application/application_config.ts @@ -1,4 +1,3 @@ - /** * @license * Copyright Google LLC All Rights Reserved. @@ -18,7 +17,7 @@ export interface ApplicationConfig { /** * List of providers that should be available to the root component and all its children. */ - providers: Array; + providers: Array; } /** @@ -30,7 +29,10 @@ export interface ApplicationConfig { * @publicApi */ export function mergeApplicationConfig(...configs: ApplicationConfig[]): ApplicationConfig { - return configs.reduce((prev, curr) => { - return Object.assign(prev, curr, {providers: [...prev.providers, ...curr.providers]}); - }, {providers: []}); + return configs.reduce( + (prev, curr) => { + return Object.assign(prev, curr, {providers: [...prev.providers, ...curr.providers]}); + }, + {providers: []}, + ); } diff --git a/packages/core/src/application/application_init.ts b/packages/core/src/application/application_init.ts index 205c975073879..61f0c6a7857ce 100644 --- a/packages/core/src/application/application_init.ts +++ b/packages/core/src/application/application_init.ts @@ -131,9 +131,9 @@ import {isPromise, isSubscribable} from '../util/lang'; * * @publicApi */ -export const APP_INITIALIZER = - new InjectionToken Observable| Promise| void>>( - ngDevMode ? 'Application Initializer' : ''); +export const APP_INITIALIZER = new InjectionToken< + ReadonlyArray<() => Observable | Promise | void> +>(ngDevMode ? 'Application Initializer' : ''); /** * A class that reflects the state of running {@link APP_INITIALIZER} functions. @@ -159,11 +159,12 @@ export class ApplicationInitStatus { 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.'); + 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.', + ); } } @@ -193,12 +194,12 @@ export class ApplicationInitStatus { }; Promise.all(asyncInitPromises) - .then(() => { - complete(); - }) - .catch(e => { - this.reject(e); - }); + .then(() => { + complete(); + }) + .catch((e) => { + this.reject(e); + }); if (asyncInitPromises.length === 0) { complete(); diff --git a/packages/core/src/application/application_ngmodule_factory_compiler.ts b/packages/core/src/application/application_ngmodule_factory_compiler.ts index 7864f7f20da4f..fa38901bdb484 100644 --- a/packages/core/src/application/application_ngmodule_factory_compiler.ts +++ b/packages/core/src/application/application_ngmodule_factory_compiler.ts @@ -11,14 +11,19 @@ import {Injector} from '../di/injector'; import {Type} from '../interface/type'; import {COMPILER_OPTIONS, CompilerOptions} from '../linker/compiler'; import {NgModuleFactory} from '../linker/ng_module_factory'; -import {isComponentResourceResolutionQueueEmpty, resolveComponentResources} from '../metadata/resource_loading'; +import { + isComponentResourceResolutionQueueEmpty, + resolveComponentResources, +} from '../metadata/resource_loading'; import {assertNgModuleType} from '../render3/assert'; import {setJitOptions} from '../render3/jit/jit_options'; import {NgModuleFactory as R3NgModuleFactory} from '../render3/ng_module_ref'; export function compileNgModuleFactory( - injector: Injector, options: CompilerOptions, - moduleType: Type): Promise> { + injector: Injector, + options: CompilerOptions, + moduleType: Type, +): Promise> { ngDevMode && assertNgModuleType(moduleType); const moduleFactory = new R3NgModuleFactory(moduleType); @@ -34,8 +39,8 @@ export function compileNgModuleFactory( // are bootstrapped with incompatible options, as a component can only be compiled according to // a single set of options. setJitOptions({ - defaultEncapsulation: _lastDefined(compilerOptions.map(opts => opts.defaultEncapsulation)), - preserveWhitespaces: _lastDefined(compilerOptions.map(opts => opts.preserveWhitespaces)), + defaultEncapsulation: _lastDefined(compilerOptions.map((opts) => opts.defaultEncapsulation)), + preserveWhitespaces: _lastDefined(compilerOptions.map((opts) => opts.preserveWhitespaces)), }); if (isComponentResourceResolutionQueueEmpty()) { @@ -61,11 +66,12 @@ export function compileNgModuleFactory( const resourceLoader = compilerInjector.get(compiler.ResourceLoader); // The resource loader can also return a string while the "resolveComponentResources" // always expects a promise. Therefore we need to wrap the returned value in a promise. - return resolveComponentResources(url => Promise.resolve(resourceLoader.get(url))) - .then(() => moduleFactory); + return resolveComponentResources((url) => Promise.resolve(resourceLoader.get(url))).then( + () => moduleFactory, + ); } -function _lastDefined(args: T[]): T|undefined { +function _lastDefined(args: T[]): T | undefined { for (let i = args.length - 1; i >= 0; i--) { if (args[i] !== undefined) { return args[i]; diff --git a/packages/core/src/application/application_ref.ts b/packages/core/src/application/application_ref.ts index f0995da2406aa..4b097b827b69f 100644 --- a/packages/core/src/application/application_ref.ts +++ b/packages/core/src/application/application_ref.ts @@ -8,7 +8,10 @@ import '../util/ng_jit_mode'; -import {setActiveConsumer, setThrowInvalidWriteToSignalError} from '@angular/core/primitives/signals'; +import { + setActiveConsumer, + setThrowInvalidWriteToSignalError, +} from '@angular/core/primitives/signals'; import {Observable, Subject} from 'rxjs'; import {first, map} from 'rxjs/operators'; @@ -51,9 +54,9 @@ import {ApplicationInitStatus} from './application_init'; * * @publicApi */ -export const APP_BOOTSTRAP_LISTENER = - new InjectionToken) => void>>( - ngDevMode ? 'appBootstrapListener' : ''); +export const APP_BOOTSTRAP_LISTENER = new InjectionToken< + ReadonlyArray<(compRef: ComponentRef) => void> +>(ngDevMode ? 'appBootstrapListener' : ''); export function publishDefaultGlobalUtils() { ngDevMode && _publishDefaultGlobalUtils(); @@ -65,10 +68,11 @@ export function publishDefaultGlobalUtils() { export function publishSignalConfiguration(): void { setThrowInvalidWriteToSignalError(() => { throw new RuntimeError( - RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT, - ngDevMode && - 'Writing to signals is not allowed in a `computed` or an `effect` by default. ' + - 'Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.'); + RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT, + ngDevMode && + 'Writing to signals is not allowed in a `computed` or an `effect` by default. ' + + 'Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.', + ); }); } @@ -83,7 +87,10 @@ export function isBoundToModule(cf: ComponentFactory): boolean { * @publicApi */ export class NgProbeToken { - constructor(public name: string, public token: any) {} + constructor( + public name: string, + public token: any, + ) {} } /** @@ -99,7 +106,7 @@ export interface BootstrapOptions { * - `zone.js` - Use default `NgZone` which requires `Zone.js`. * - `noop` - Use `NoopNgZone` which does nothing. */ - ngZone?: NgZone|'zone.js'|'noop'; + ngZone?: NgZone | 'zone.js' | 'noop'; /** * Optionally specify coalescing event change detections or not. @@ -163,7 +170,10 @@ export interface BootstrapOptions { const MAXIMUM_REFRESH_RERUNS = 10; export function _callAndReportToErrorHandler( - errorHandler: ErrorHandler, ngZone: NgZone, callback: () => any): any { + errorHandler: ErrorHandler, + ngZone: NgZone, + callback: () => any, +): any { try { const result = callback(); if (isPromise(result)) { @@ -182,7 +192,7 @@ export function _callAndReportToErrorHandler( } } -export function optionsReducer(dst: T, objs: T|T[]): T { +export function optionsReducer(dst: T, objs: T | T[]): T { if (Array.isArray(objs)) { return objs.reduce(optionsReducer, dst); } @@ -323,8 +333,9 @@ export class ApplicationRef { /** * Returns an Observable that indicates when the application is stable or unstable. */ - public readonly isStable: Observable = - inject(PendingTasks).hasPendingTasks.pipe(map(pending => !pending)); + public readonly isStable: Observable = inject(PendingTasks).hasPendingTasks.pipe( + map((pending) => !pending), + ); private readonly _injector = inject(EnvironmentInjector); /** @@ -371,7 +382,7 @@ export class ApplicationRef { * * {@example core/ts/platform/platform.ts region='domNode'} */ - bootstrap(component: Type, rootSelectorOrNode?: string|any): ComponentRef; + bootstrap(component: Type, rootSelectorOrNode?: string | any): ComponentRef; /** * Bootstrap a component onto the element identified by its selector or, optionally, to a @@ -413,8 +424,10 @@ export class ApplicationRef { * @deprecated Passing Component factories as the `Application.bootstrap` function argument is * deprecated. Pass Component Types instead. */ - bootstrap(componentFactory: ComponentFactory, rootSelectorOrNode?: string|any): - ComponentRef; + bootstrap( + componentFactory: ComponentFactory, + rootSelectorOrNode?: string | any, + ): ComponentRef; /** * Bootstrap a component onto the element identified by its selector or, optionally, to a @@ -453,19 +466,22 @@ export class ApplicationRef { * * {@example core/ts/platform/platform.ts region='domNode'} */ - bootstrap(componentOrFactory: ComponentFactory|Type, rootSelectorOrNode?: string|any): - ComponentRef { + bootstrap( + componentOrFactory: ComponentFactory | Type, + rootSelectorOrNode?: string | any, + ): ComponentRef { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); const isComponentFactory = componentOrFactory instanceof ComponentFactory; const initStatus = this._injector.get(ApplicationInitStatus); if (!initStatus.done) { const standalone = !isComponentFactory && isStandalone(componentOrFactory); - const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) && - 'Cannot bootstrap as there are still asynchronous initializers running.' + - (standalone ? - '' : - ' Bootstrap components in the `ngDoBootstrap` method of the root module.'); + const errorMessage = + (typeof ngDevMode === 'undefined' || ngDevMode) && + 'Cannot bootstrap as there are still asynchronous initializers running.' + + (standalone + ? '' + : ' Bootstrap components in the `ngDoBootstrap` method of the root module.'); throw new RuntimeError(RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING, errorMessage); } @@ -479,8 +495,9 @@ export class ApplicationRef { this.componentTypes.push(componentFactory.componentType); // Create a factory associated with the current module if it's not bound to some other - const ngModule = - isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef); + const ngModule = isBoundToModule(componentFactory) + ? undefined + : this._injector.get(NgModuleRef); const selectorOrNode = rootSelectorOrNode || componentFactory.selector; const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); const nativeElement = compRef.location.nativeElement; @@ -520,8 +537,9 @@ export class ApplicationRef { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); if (this._runningTick) { throw new RuntimeError( - RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK, - ngDevMode && 'ApplicationRef.tick is called recursively'); + RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK, + ngDevMode && 'ApplicationRef.tick is called recursively', + ); } const prevConsumer = setActiveConsumer(null); @@ -530,7 +548,7 @@ export class ApplicationRef { this.detectChangesInAttachedViews(refreshViews); - if ((typeof ngDevMode === 'undefined' || ngDevMode)) { + if (typeof ngDevMode === 'undefined' || ngDevMode) { for (let view of this._views) { view.checkNoChanges(); } @@ -554,7 +572,11 @@ export class ApplicationRef { this.beforeRender.next(isFirstPass); for (let {_lView, notifyErrorHandler} of this._views) { detectChangesInViewIfRequired( - _lView, notifyErrorHandler, isFirstPass, this.zonelessEnabled); + _lView, + notifyErrorHandler, + isFirstPass, + this.zonelessEnabled, + ); } } runs++; @@ -562,27 +584,33 @@ export class ApplicationRef { afterRenderEffectManager.executeInternalCallbacks(); // If we have a newly dirty view after running internal callbacks, recheck the views again // before running user-provided callbacks - if ([...this.externalTestViews.keys(), ...this._views].some( - ({_lView}) => requiresRefreshOrTraversal(_lView))) { + if ( + [...this.externalTestViews.keys(), ...this._views].some(({_lView}) => + requiresRefreshOrTraversal(_lView), + ) + ) { continue; } afterRenderEffectManager.execute(); // If after running all afterRender callbacks we have no more views that need to be refreshed, // we can break out of the loop - if (![...this.externalTestViews.keys(), ...this._views].some( - ({_lView}) => requiresRefreshOrTraversal(_lView))) { + if ( + ![...this.externalTestViews.keys(), ...this._views].some(({_lView}) => + requiresRefreshOrTraversal(_lView), + ) + ) { break; } } if ((typeof ngDevMode === 'undefined' || ngDevMode) && runs >= MAXIMUM_REFRESH_RERUNS) { throw new RuntimeError( - RuntimeErrorCode.INFINITE_CHANGE_DETECTION, - ngDevMode && - 'Infinite change detection while refreshing application views. ' + - 'Ensure views are not calling `markForCheck` on every template execution or ' + - 'that afterRender hooks always mark views for check.', + RuntimeErrorCode.INFINITE_CHANGE_DETECTION, + ngDevMode && + 'Infinite change detection while refreshing application views. ' + + 'Ensure views are not calling `markForCheck` on every template execution or ' + + 'that afterRender hooks always mark views for check.', ); } } @@ -594,7 +622,7 @@ export class ApplicationRef { */ attachView(viewRef: ViewRef): void { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); - const view = (viewRef as InternalViewRef); + const view = viewRef as InternalViewRef; this._views.push(view); view.attachToAppRef(this); } @@ -604,7 +632,7 @@ export class ApplicationRef { */ detachView(viewRef: ViewRef): void { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); - const view = (viewRef as InternalViewRef); + const view = viewRef as InternalViewRef; remove(this._views, view); view.detachFromAppRef(); } @@ -617,11 +645,12 @@ export class ApplicationRef { const listeners = this._injector.get(APP_BOOTSTRAP_LISTENER, []); if (ngDevMode && !Array.isArray(listeners)) { throw new RuntimeError( - RuntimeErrorCode.INVALID_MULTI_PROVIDER, - 'Unexpected type of the `APP_BOOTSTRAP_LISTENER` token value ' + - `(expected an array, but got ${typeof listeners}). ` + - 'Please check that the `APP_BOOTSTRAP_LISTENER` token is configured as a ' + - '`multi: true` provider.'); + RuntimeErrorCode.INVALID_MULTI_PROVIDER, + 'Unexpected type of the `APP_BOOTSTRAP_LISTENER` token value ' + + `(expected an array, but got ${typeof listeners}). ` + + 'Please check that the `APP_BOOTSTRAP_LISTENER` token is configured as a ' + + '`multi: true` provider.', + ); } [...this._bootstrapListeners, ...listeners].forEach((listener) => listener(componentRef)); } @@ -632,7 +661,7 @@ export class ApplicationRef { try { // Call all the lifecycle hooks. - this._destroyListeners.forEach(listener => listener()); + this._destroyListeners.forEach((listener) => listener()); // Destroy all registered views. this._views.slice().forEach((view) => view.destroy()); @@ -667,13 +696,14 @@ export class ApplicationRef { destroy(): void { if (this._destroyed) { throw new RuntimeError( - RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED, - ngDevMode && 'This instance of the `ApplicationRef` has already been destroyed.'); + RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED, + ngDevMode && 'This instance of the `ApplicationRef` has already been destroyed.', + ); } // This is a temporary type to represent an instance of an R3Injector, which can be destroyed. // The type will be replaced with a different one once destroyable injector type is available. - type DestroyableInjector = Injector&{destroy?: Function, destroyed?: boolean}; + type DestroyableInjector = Injector & {destroy?: Function; destroyed?: boolean}; const injector = this._injector as DestroyableInjector; @@ -694,9 +724,12 @@ export class ApplicationRef { private warnIfDestroyed() { if ((typeof ngDevMode === 'undefined' || ngDevMode) && this._destroyed) { - console.warn(formatRuntimeError( + console.warn( + formatRuntimeError( RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED, - 'This instance of the `ApplicationRef` has already been destroyed.')); + 'This instance of the `ApplicationRef` has already been destroyed.', + ), + ); } } } @@ -708,7 +741,7 @@ export function remove(list: T[], el: T): void { } } -let whenStableStore: WeakMap>|undefined; +let whenStableStore: WeakMap> | undefined; /** * Returns a Promise that resolves when the application becomes stable after this method is called * the first time. @@ -720,8 +753,10 @@ export function whenStable(applicationRef: ApplicationRef): Promise { return cachedWhenStable; } - const whenStablePromise = - applicationRef.isStable.pipe(first((isStable) => isStable)).toPromise().then(() => void 0); + const whenStablePromise = applicationRef.isStable + .pipe(first((isStable) => isStable)) + .toPromise() + .then(() => void 0); whenStableStore.set(applicationRef, whenStablePromise); // Be a good citizen and clean the store `onDestroy` even though we are using `WeakMap`. @@ -730,20 +765,24 @@ export function whenStable(applicationRef: ApplicationRef): Promise { return whenStablePromise; } - export function detectChangesInViewIfRequired( - lView: LView, notifyErrorHandler: boolean, isFirstPass: boolean, zonelessEnabled: boolean) { + lView: LView, + notifyErrorHandler: boolean, + isFirstPass: boolean, + zonelessEnabled: boolean, +) { // When re-checking, only check views which actually need it. if (!isFirstPass && !requiresRefreshOrTraversal(lView)) { return; } - const mode = (isFirstPass && !zonelessEnabled) ? - // The first pass is always in Global mode, which includes `CheckAlways` views. - // When using zoneless, all root views must be explicitly marked for refresh, even if they are - // `CheckAlways`. - ChangeDetectionMode.Global : - // Only refresh views with the `RefreshView` flag or views is a changed signal - ChangeDetectionMode.Targeted; + const mode = + isFirstPass && !zonelessEnabled + ? // The first pass is always in Global mode, which includes `CheckAlways` views. + // When using zoneless, all root views must be explicitly marked for refresh, even if they are + // `CheckAlways`. + ChangeDetectionMode.Global + : // Only refresh views with the `RefreshView` flag or views is a changed signal + ChangeDetectionMode.Targeted; detectChangesInternal(lView, notifyErrorHandler, mode); } diff --git a/packages/core/src/application/application_tokens.ts b/packages/core/src/application/application_tokens.ts index 2edeb8bedad4c..9711e9378e6fe 100644 --- a/packages/core/src/application/application_tokens.ts +++ b/packages/core/src/application/application_tokens.ts @@ -51,8 +51,9 @@ const DEFAULT_APP_ID = 'ng'; * A function that is executed when a platform is initialized. * @publicApi */ -export const PLATFORM_INITIALIZER = - new InjectionToken void>>(ngDevMode ? 'Platform Initializer' : ''); +export const PLATFORM_INITIALIZER = new InjectionToken void>>( + ngDevMode ? 'Platform Initializer' : '', +); /** * A token that indicates an opaque platform ID. @@ -60,7 +61,7 @@ export const PLATFORM_INITIALIZER = */ export const PLATFORM_ID = new InjectionToken(ngDevMode ? 'Platform ID' : '', { providedIn: 'platform', - factory: () => 'unknown', // set a default platform name, when none set explicitly + factory: () => 'unknown', // set a default platform name, when none set explicitly }); /** @@ -69,8 +70,9 @@ export const PLATFORM_ID = new InjectionToken(ngDevMode ? 'Platform ID' * @publicApi * @deprecated */ -export const PACKAGE_ROOT_URL = - new InjectionToken(ngDevMode ? 'Application Packages Root URL' : ''); +export const PACKAGE_ROOT_URL = new InjectionToken( + 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 @@ -81,8 +83,9 @@ export const PACKAGE_ROOT_URL = * module has been loaded. * @publicApi */ -export const ANIMATION_MODULE_TYPE = new InjectionToken<'NoopAnimations'|'BrowserAnimations'>( - ngDevMode ? 'AnimationModuleType' : ''); +export const ANIMATION_MODULE_TYPE = new InjectionToken<'NoopAnimations' | 'BrowserAnimations'>( + ngDevMode ? 'AnimationModuleType' : '', +); // TODO(crisbeto): link to CSP guide here. /** @@ -92,7 +95,7 @@ export const ANIMATION_MODULE_TYPE = new InjectionToken<'NoopAnimations'|'Browse * * @publicApi */ -export const CSP_NONCE = new InjectionToken(ngDevMode ? 'CSP nonce' : '', { +export const CSP_NONCE = new InjectionToken(ngDevMode ? 'CSP nonce' : '', { providedIn: 'root', factory: () => { // Ideally we wouldn't have to use `querySelector` here since we know that the nonce will be on @@ -130,10 +133,10 @@ export const CSP_NONCE = new InjectionToken(ngDevMode ? 'CSP nonce' * @publicApi */ export type ImageConfig = { - breakpoints?: number[], - placeholderResolution?: number, - disableImageSizeWarning?: boolean, - disableImageLazyLoadWarning?: boolean, + breakpoints?: number[]; + placeholderResolution?: number; + disableImageSizeWarning?: boolean; + disableImageLazyLoadWarning?: boolean; }; export const IMAGE_CONFIG_DEFAULTS: ImageConfig = { @@ -152,5 +155,7 @@ export const IMAGE_CONFIG_DEFAULTS: ImageConfig = { * @see {@link ImageConfig} * @publicApi */ -export const IMAGE_CONFIG = new InjectionToken( - ngDevMode ? 'ImageConfig' : '', {providedIn: 'root', factory: () => IMAGE_CONFIG_DEFAULTS}); +export const IMAGE_CONFIG = new InjectionToken(ngDevMode ? 'ImageConfig' : '', { + providedIn: 'root', + factory: () => IMAGE_CONFIG_DEFAULTS, +}); diff --git a/packages/core/src/application/create_application.ts b/packages/core/src/application/create_application.ts index c9970197d6987..a13534bc52649 100644 --- a/packages/core/src/application/create_application.ts +++ b/packages/core/src/application/create_application.ts @@ -41,7 +41,7 @@ import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref'; export function internalCreateApplication(config: { rootComponent?: Type; - appProviders?: Array; + appProviders?: Array; platformProviders?: Provider[]; }): Promise { try { @@ -55,14 +55,11 @@ export function internalCreateApplication(config: { // Create root application injector based on a set of providers configured at the platform // bootstrap level as well as providers passed to the bootstrap call by a user. - const allAppProviders = [ - provideZoneChangeDetection(), - ...(appProviders || []), - ]; + const allAppProviders = [provideZoneChangeDetection(), ...(appProviders || [])]; const adapter = new EnvironmentNgModuleRefAdapter({ providers: allAppProviders, parent: platformInjector as EnvironmentInjector, - debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '', + debugName: typeof ngDevMode === 'undefined' || ngDevMode ? 'Environment Injector' : '', // We skip environment initializers because we need to run them inside the NgZone, which // happens after we get the NgZone instance from the Injector. runEnvironmentInitializers: false, @@ -72,11 +69,12 @@ export function internalCreateApplication(config: { return ngZone.run(() => { envInjector.resolveInjectorInitializers(); - const exceptionHandler: ErrorHandler|null = envInjector.get(ErrorHandler, null); + const exceptionHandler: ErrorHandler | null = envInjector.get(ErrorHandler, null); if ((typeof ngDevMode === 'undefined' || ngDevMode) && !exceptionHandler) { throw new RuntimeError( - RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, - 'No `ErrorHandler` found in the Dependency Injection tree.'); + RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, + 'No `ErrorHandler` found in the Dependency Injection tree.', + ); } let onErrorSubscription: Subscription; @@ -84,7 +82,7 @@ export function internalCreateApplication(config: { onErrorSubscription = ngZone.onError.subscribe({ next: (error: any) => { exceptionHandler!.handleError(error); - } + }, }); }); diff --git a/packages/core/src/authoring.ts b/packages/core/src/authoring.ts index af0bb0087fbfe..1c2390a90620b 100644 --- a/packages/core/src/authoring.ts +++ b/packages/core/src/authoring.ts @@ -10,11 +10,21 @@ // https://docs.google.com/document/d/1RXb1wYwsbJotO1KBgSDsAtKpduGmIHod9ADxuXcAvV4/edit?tab=t.0. export {InputFunction} from './authoring/input/input'; -export {InputOptions, InputOptionsWithoutTransform, InputOptionsWithTransform, InputSignal, InputSignalWithTransform, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from './authoring/input/input_signal'; +export { + InputOptions, + InputOptionsWithoutTransform, + InputOptionsWithTransform, + InputSignal, + InputSignalWithTransform, + ɵINPUT_SIGNAL_BRAND_WRITE_TYPE, +} from './authoring/input/input_signal'; export {ɵUnwrapDirectiveSignalInputs} from './authoring/input/input_type_checking'; export {ModelFunction} from './authoring/model/model'; export {ModelOptions, ModelSignal} from './authoring/model/model_signal'; export {output, OutputOptions} from './authoring/output/output'; -export {getOutputDestroyRef as ɵgetOutputDestroyRef, OutputEmitterRef} from './authoring/output/output_emitter_ref'; +export { + getOutputDestroyRef as ɵgetOutputDestroyRef, + OutputEmitterRef, +} from './authoring/output/output_emitter_ref'; export {OutputRef, OutputRefSubscription} from './authoring/output/output_ref'; export {ContentChildFunction, ViewChildFunction} from './authoring/queries'; diff --git a/packages/core/src/authoring/input/input.ts b/packages/core/src/authoring/input/input.ts index d4b5798bd8f3c..f169b79f190d4 100644 --- a/packages/core/src/authoring/input/input.ts +++ b/packages/core/src/authoring/input/input.ts @@ -8,18 +8,27 @@ import {assertInInjectionContext} from '../../di'; -import {createInputSignal, InputOptions, InputOptionsWithoutTransform, InputOptionsWithTransform, InputSignal, InputSignalWithTransform} from './input_signal'; +import { + createInputSignal, + InputOptions, + InputOptionsWithoutTransform, + InputOptionsWithTransform, + InputSignal, + InputSignalWithTransform, +} from './input_signal'; import {REQUIRED_UNSET_VALUE} from './input_signal_node'; export function inputFunction( - initialValue?: ReadT, - opts?: InputOptions): InputSignalWithTransform { + initialValue?: ReadT, + opts?: InputOptions, +): InputSignalWithTransform { ngDevMode && assertInInjectionContext(input); return createInputSignal(initialValue, opts); } -export function inputRequiredFunction(opts?: InputOptions): - InputSignalWithTransform { +export function inputRequiredFunction( + opts?: InputOptions, +): InputSignalWithTransform { ngDevMode && assertInInjectionContext(input); return createInputSignal(REQUIRED_UNSET_VALUE as never, opts); } @@ -39,7 +48,7 @@ export interface InputFunction { * Initializes an input of type `T` with an initial value of `undefined`. * Angular will implicitly use `undefined` as initial value. */ - (): InputSignal; + (): InputSignal; /** Declares an input of type `T` with an explicit initial value. */ (initialValue: T, opts?: InputOptionsWithoutTransform): InputSignal; /** @@ -49,8 +58,10 @@ export interface InputFunction { * The input accepts values of type `TransformT` and the given * transform function will transform the value to type `T`. */ - (initialValue: T, opts: InputOptionsWithTransform): - InputSignalWithTransform; + ( + initialValue: T, + opts: InputOptionsWithTransform, + ): InputSignalWithTransform; /** * Initializes a required input. @@ -69,8 +80,9 @@ export interface InputFunction { * The input accepts values of type `TransformT` and the given * transform function will transform the value to type `T`. */ - (opts: InputOptionsWithTransform): - InputSignalWithTransform; + ( + opts: InputOptionsWithTransform, + ): InputSignalWithTransform; }; } @@ -127,5 +139,5 @@ export const input: InputFunction = (() => { // this assignment, unless this `input` constant export is accessed. It's a // self-contained side effect that is local to the user facing`input` export. (inputFunction as any).required = inputRequiredFunction; - return inputFunction as (typeof inputFunction&{required: typeof inputRequiredFunction}); + return inputFunction as typeof inputFunction & {required: typeof inputRequiredFunction}; })(); diff --git a/packages/core/src/authoring/input/input_signal.ts b/packages/core/src/authoring/input/input_signal.ts index 1ceb9270abba1..e3fb2a31e17e5 100644 --- a/packages/core/src/authoring/input/input_signal.ts +++ b/packages/core/src/authoring/input/input_signal.ts @@ -40,15 +40,17 @@ export interface InputOptions { * @developerPreview */ export type InputOptionsWithoutTransform = - // Note: We still keep a notion of `transform` for auto-completion. - Omit, 'transform'>&{transform?: undefined}; + // Note: We still keep a notion of `transform` for auto-completion. + Omit, 'transform'> & {transform?: undefined}; /** * Signal input options with the transform option required. * * @developerPreview */ -export type InputOptionsWithTransform = - Required, 'transform'>>&InputOptions; +export type InputOptionsWithTransform = Required< + Pick, 'transform'> +> & + InputOptions; export const ɵINPUT_SIGNAL_BRAND_READ_TYPE = /* @__PURE__ */ Symbol(); export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol(); @@ -104,8 +106,9 @@ export interface InputSignal extends InputSignalWithTransform {} * @param options Additional options for the input. e.g. a transform, or an alias. */ export function createInputSignal( - initialValue: T, - options?: InputOptions): InputSignalWithTransform { + initialValue: T, + options?: InputOptions, +): InputSignalWithTransform { const node: InputSignalNode = Object.create(INPUT_SIGNAL_NODE); node.value = initialValue; @@ -120,8 +123,9 @@ export function createInputSignal( if (node.value === REQUIRED_UNSET_VALUE) { throw new RuntimeError( - RuntimeErrorCode.REQUIRED_INPUT_NO_VALUE, - ngDevMode && 'Input is required but no value is available yet.'); + RuntimeErrorCode.REQUIRED_INPUT_NO_VALUE, + ngDevMode && 'Input is required but no value is available yet.', + ); } return node.value; diff --git a/packages/core/src/authoring/input/input_signal_node.ts b/packages/core/src/authoring/input/input_signal_node.ts index ed1f4bfe71d05..42a822fc3023b 100644 --- a/packages/core/src/authoring/input/input_signal_node.ts +++ b/packages/core/src/authoring/input/input_signal_node.ts @@ -19,7 +19,7 @@ export interface InputSignalNode extends SignalNode { * User-configured transform that will run whenever a new value is applied * to the input signal node. */ - transformFn: ((value: TransformT) => T)|undefined; + transformFn: ((value: TransformT) => T) | undefined; /** * Applies a new value to the input signal. Expects transforms to be run @@ -42,6 +42,6 @@ export const INPUT_SIGNAL_NODE: InputSignalNode = /* @__PURE__ applyValueToInputSignal(node: InputSignalNode, value: T) { signalSetFn(node, value); - } + }, }; })(); diff --git a/packages/core/src/authoring/input/input_type_checking.ts b/packages/core/src/authoring/input/input_type_checking.ts index a49e211802dde..5a228c0ee2348 100644 --- a/packages/core/src/authoring/input/input_type_checking.ts +++ b/packages/core/src/authoring/input/input_type_checking.ts @@ -10,12 +10,12 @@ import {InputSignalWithTransform} from './input_signal'; /** Retrieves the write type of an `InputSignal` and `InputSignalWithTransform`. */ export type ɵUnwrapInputSignalWriteType = - Field extends InputSignalWithTransform? WriteT : never; + Field extends InputSignalWithTransform ? WriteT : never; /** * Unwraps all `InputSignal`/`InputSignalWithTransform` class fields of * the given directive. */ export type ɵUnwrapDirectiveSignalInputs = { - [P in Fields]: ɵUnwrapInputSignalWriteType + [P in Fields]: ɵUnwrapInputSignalWriteType; }; diff --git a/packages/core/src/authoring/model/model.ts b/packages/core/src/authoring/model/model.ts index 6b5ff34dd3dfa..c171f0a76401f 100644 --- a/packages/core/src/authoring/model/model.ts +++ b/packages/core/src/authoring/model/model.ts @@ -11,7 +11,7 @@ import {REQUIRED_UNSET_VALUE} from '../input/input_signal_node'; import {createModelSignal, ModelOptions, ModelSignal} from './model_signal'; -export function modelFunction(initialValue?: T): ModelSignal { +export function modelFunction(initialValue?: T): ModelSignal { ngDevMode && assertInInjectionContext(model); return createModelSignal(initialValue); @@ -39,7 +39,7 @@ export interface ModelFunction { * Initializes a model of type `T` with an initial value of `undefined`. * Angular will implicitly use `undefined` as initial value. */ - (): ModelSignal; + (): ModelSignal; /** Initializes a model of type `T` with the given initial value. */ (initialValue: T, opts?: ModelOptions): ModelSignal; @@ -106,5 +106,5 @@ export const model: ModelFunction = (() => { // this assignment, unless this `model` constant export is accessed. It's a // self-contained side effect that is local to the user facing `model` export. (modelFunction as any).required = modelRequiredFunction; - return modelFunction as (typeof modelFunction&{required: typeof modelRequiredFunction}); + return modelFunction as typeof modelFunction & {required: typeof modelRequiredFunction}; })(); diff --git a/packages/core/src/authoring/model/model_signal.ts b/packages/core/src/authoring/model/model_signal.ts index fc345b2af1f81..f91150f362bdc 100644 --- a/packages/core/src/authoring/model/model_signal.ts +++ b/packages/core/src/authoring/model/model_signal.ts @@ -10,7 +10,11 @@ import {producerAccessed, SIGNAL, signalSetFn} from '@angular/core/primitives/si import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {Signal} from '../../render3/reactivity/api'; -import {signalAsReadonlyFn, WritableSignal, ɵWRITABLE_SIGNAL} from '../../render3/reactivity/signal'; +import { + signalAsReadonlyFn, + WritableSignal, + ɵWRITABLE_SIGNAL, +} from '../../render3/reactivity/signal'; import {ɵINPUT_SIGNAL_BRAND_READ_TYPE, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from '../input/input_signal'; import {INPUT_SIGNAL_NODE, InputSignalNode, REQUIRED_UNSET_VALUE} from '../input/input_signal_node'; import {OutputEmitterRef} from '../output/output_emitter_ref'; @@ -85,17 +89,21 @@ export function createModelSignal(initialValue: T): ModelSignal { getter.toString = () => `[Model Signal: ${getter()}]`; } - return getter as (typeof getter&Pick< - ModelSignal, - typeof ɵINPUT_SIGNAL_BRAND_READ_TYPE|typeof ɵINPUT_SIGNAL_BRAND_WRITE_TYPE| - typeof ɵWRITABLE_SIGNAL>); + return getter as typeof getter & + Pick< + ModelSignal, + | typeof ɵINPUT_SIGNAL_BRAND_READ_TYPE + | typeof ɵINPUT_SIGNAL_BRAND_WRITE_TYPE + | typeof ɵWRITABLE_SIGNAL + >; } /** Asserts that a model's value is set. */ function assertModelSet(value: unknown): void { if (value === REQUIRED_UNSET_VALUE) { throw new RuntimeError( - RuntimeErrorCode.REQUIRED_MODEL_NO_VALUE, - ngDevMode && 'Model is required but no value is available yet.'); + RuntimeErrorCode.REQUIRED_MODEL_NO_VALUE, + ngDevMode && 'Model is required but no value is available yet.', + ); } } diff --git a/packages/core/src/authoring/output/output_emitter_ref.ts b/packages/core/src/authoring/output/output_emitter_ref.ts index 2cbce3f5c8f02..723da95d4c62a 100644 --- a/packages/core/src/authoring/output/output_emitter_ref.ts +++ b/packages/core/src/authoring/output/output_emitter_ref.ts @@ -30,7 +30,7 @@ import {OutputRef, OutputRefSubscription} from './output_ref'; */ export class OutputEmitterRef implements OutputRef { private destroyed = false; - private listeners: Array<(value: T) => void>|null = null; + private listeners: Array<(value: T) => void> | null = null; private errorHandler = inject(ErrorHandler, {optional: true}); /** @internal */ @@ -47,10 +47,11 @@ export class OutputEmitterRef implements OutputRef { subscribe(callback: (value: T) => void): OutputRefSubscription { if (this.destroyed) { throw new RuntimeError( - RuntimeErrorCode.OUTPUT_REF_DESTROYED, - ngDevMode && - 'Unexpected subscription to destroyed `OutputRef`. ' + - 'The owning directive/component is destroyed.'); + RuntimeErrorCode.OUTPUT_REF_DESTROYED, + ngDevMode && + 'Unexpected subscription to destroyed `OutputRef`. ' + + 'The owning directive/component is destroyed.', + ); } (this.listeners ??= []).push(callback); @@ -61,7 +62,7 @@ export class OutputEmitterRef implements OutputRef { if (idx !== undefined && idx !== -1) { this.listeners?.splice(idx, 1); } - } + }, }; } @@ -69,10 +70,11 @@ export class OutputEmitterRef implements OutputRef { emit(value: T): void { if (this.destroyed) { throw new RuntimeError( - RuntimeErrorCode.OUTPUT_REF_DESTROYED, - ngDevMode && - 'Unexpected emit for destroyed `OutputRef`. ' + - 'The owning directive/component is destroyed.'); + RuntimeErrorCode.OUTPUT_REF_DESTROYED, + ngDevMode && + 'Unexpected emit for destroyed `OutputRef`. ' + + 'The owning directive/component is destroyed.', + ); } if (this.listeners === null) { @@ -95,6 +97,6 @@ export class OutputEmitterRef implements OutputRef { } /** Gets the owning `DestroyRef` for the given output. */ -export function getOutputDestroyRef(ref: OutputRef): DestroyRef|undefined { +export function getOutputDestroyRef(ref: OutputRef): DestroyRef | undefined { return ref.destroyRef; } diff --git a/packages/core/src/authoring/output/output_ref.ts b/packages/core/src/authoring/output/output_ref.ts index 09a0641ffe499..7989d9d44e176 100644 --- a/packages/core/src/authoring/output/output_ref.ts +++ b/packages/core/src/authoring/output/output_ref.ts @@ -46,5 +46,5 @@ export interface OutputRef { * * @internal */ - destroyRef: DestroyRef|undefined; + destroyRef: DestroyRef | undefined; } diff --git a/packages/core/src/authoring/queries.ts b/packages/core/src/authoring/queries.ts index 87dc2a6cabb87..b216191b6bd93 100644 --- a/packages/core/src/authoring/queries.ts +++ b/packages/core/src/authoring/queries.ts @@ -8,18 +8,25 @@ import {assertInInjectionContext} from '../di'; import {ProviderToken} from '../di/provider_token'; -import {createMultiResultQuerySignalFn, createSingleResultOptionalQuerySignalFn, createSingleResultRequiredQuerySignalFn} from '../render3/query_reactive'; +import { + createMultiResultQuerySignalFn, + createSingleResultOptionalQuerySignalFn, + createSingleResultRequiredQuerySignalFn, +} from '../render3/query_reactive'; import {Signal} from '../render3/reactivity/api'; function viewChildFn( - locator: ProviderToken|string, - opts?: {read?: ProviderToken}): Signal { + locator: ProviderToken | string, + opts?: {read?: ProviderToken}, +): Signal { ngDevMode && assertInInjectionContext(viewChild); return createSingleResultOptionalQuerySignalFn(); } function viewChildRequiredFn( - locator: ProviderToken|string, opts?: {read?: ProviderToken}): Signal { + locator: ProviderToken | string, + opts?: {read?: ProviderToken}, +): Signal { ngDevMode && assertInInjectionContext(viewChild); return createSingleResultRequiredQuerySignalFn(); } @@ -40,9 +47,11 @@ export interface ViewChildFunction { * * @developerPreview */ - (locator: ProviderToken|string): Signal; - (locator: ProviderToken|string, opts: {read: ProviderToken}): - Signal; + (locator: ProviderToken | string): Signal; + ( + locator: ProviderToken | string, + opts: {read: ProviderToken}, + ): Signal; /** * Initializes a view child query that is expected to always match an element. @@ -50,10 +59,12 @@ export interface ViewChildFunction { * @developerPreview */ required: { - (locator: ProviderToken|string): Signal; + (locator: ProviderToken | string): Signal; - (locator: ProviderToken|string, opts: {read: ProviderToken}): - Signal; + ( + locator: ProviderToken | string, + opts: {read: ProviderToken}, + ): Signal; }; } @@ -84,14 +95,16 @@ export const viewChild: ViewChildFunction = (() => { // this assignment, unless this `viewChild` constant export is accessed. It's a // self-contained side effect that is local to the user facing `viewChild` export. (viewChildFn as any).required = viewChildRequiredFn; - return viewChildFn as (typeof viewChildFn&{required: typeof viewChildRequiredFn}); + return viewChildFn as typeof viewChildFn & {required: typeof viewChildRequiredFn}; })(); -export function viewChildren(locator: ProviderToken| - string): Signal>; +export function viewChildren( + locator: ProviderToken | string, +): Signal>; export function viewChildren( - locator: ProviderToken|string, - opts: {read: ProviderToken}): Signal>; + locator: ProviderToken | string, + opts: {read: ProviderToken}, +): Signal>; /** * Initializes a view children query. @@ -114,22 +127,25 @@ export function viewChildren( * @developerPreview */ export function viewChildren( - locator: ProviderToken|string, - opts?: {read?: ProviderToken}): Signal> { + locator: ProviderToken | string, + opts?: {read?: ProviderToken}, +): Signal> { ngDevMode && assertInInjectionContext(viewChildren); return createMultiResultQuerySignalFn(); } export function contentChildFn( - locator: ProviderToken|string, - opts?: {descendants?: boolean, read?: ProviderToken}): Signal { + locator: ProviderToken | string, + opts?: {descendants?: boolean; read?: ProviderToken}, +): Signal { ngDevMode && assertInInjectionContext(contentChild); return createSingleResultOptionalQuerySignalFn(); } function contentChildRequiredFn( - locator: ProviderToken|string, - opts?: {descendants?: boolean, read?: ProviderToken}): Signal { + locator: ProviderToken | string, + opts?: {descendants?: boolean; read?: ProviderToken}, +): Signal { ngDevMode && assertInInjectionContext(contentChildren); return createSingleResultRequiredQuerySignalFn(); } @@ -150,27 +166,38 @@ export interface ContentChildFunction { * Consider using `contentChild.required` for queries that should always match. * @developerPreview */ - (locator: ProviderToken|string, opts?: { - descendants?: boolean, - read?: undefined - }): Signal; + ( + locator: ProviderToken | string, + opts?: { + descendants?: boolean; + read?: undefined; + }, + ): Signal; - (locator: ProviderToken|string, opts: { - descendants?: boolean, read: ProviderToken - }): Signal; + ( + locator: ProviderToken | string, + opts: { + descendants?: boolean; + read: ProviderToken; + }, + ): Signal; /** * Initializes a content child query that is always expected to match. */ required: { - (locator: ProviderToken|string, opts?: { - descendants?: boolean, - read?: undefined, - }): Signal; + ( + locator: ProviderToken | string, + opts?: { + descendants?: boolean; + read?: undefined; + }, + ): Signal; ( - locator: ProviderToken|string, - opts: {descendants?: boolean, read: ProviderToken}): Signal; + locator: ProviderToken | string, + opts: {descendants?: boolean; read: ProviderToken}, + ): Signal; }; } @@ -200,16 +227,17 @@ export const contentChild: ContentChildFunction = (() => { // this assignment, unless this `viewChild` constant export is accessed. It's a // self-contained side effect that is local to the user facing `viewChild` export. (contentChildFn as any).required = contentChildRequiredFn; - return contentChildFn as (typeof contentChildFn&{required: typeof contentChildRequiredFn}); + return contentChildFn as typeof contentChildFn & {required: typeof contentChildRequiredFn}; })(); - export function contentChildren( - locator: ProviderToken|string, - opts?: {descendants?: boolean, read?: undefined}): Signal>; + locator: ProviderToken | string, + opts?: {descendants?: boolean; read?: undefined}, +): Signal>; export function contentChildren( - locator: ProviderToken|string, - opts: {descendants?: boolean, read: ProviderToken}): Signal>; + locator: ProviderToken | string, + opts: {descendants?: boolean; read: ProviderToken}, +): Signal>; /** * Initializes a content children query. @@ -232,7 +260,8 @@ export function contentChildren( * @developerPreview */ export function contentChildren( - locator: ProviderToken|string, - opts?: {descendants?: boolean, read?: ProviderToken}): Signal> { + locator: ProviderToken | string, + opts?: {descendants?: boolean; read?: ProviderToken}, +): Signal> { return createMultiResultQuerySignalFn(); } diff --git a/packages/core/src/cached_injector_service.ts b/packages/core/src/cached_injector_service.ts index a03fbf552be0c..8a89d85c33299 100644 --- a/packages/core/src/cached_injector_service.ts +++ b/packages/core/src/cached_injector_service.ts @@ -20,15 +20,19 @@ import {createEnvironmentInjector} from './render3/ng_module_ref'; * of a certain type. */ export class CachedInjectorService implements OnDestroy { - private cachedInjectors = new Map(); + private cachedInjectors = new Map(); getOrCreateInjector( - key: unknown, parentInjector: EnvironmentInjector, providers: Provider[], - debugName?: string) { + key: unknown, + parentInjector: EnvironmentInjector, + providers: Provider[], + debugName?: string, + ) { if (!this.cachedInjectors.has(key)) { - const injector = providers.length > 0 ? - createEnvironmentInjector(providers, parentInjector, debugName) : - null; + const injector = + providers.length > 0 + ? createEnvironmentInjector(providers, parentInjector, debugName) + : null; this.cachedInjectors.set(key, injector); } return this.cachedInjectors.get(key)!; diff --git a/packages/core/src/change_detection.ts b/packages/core/src/change_detection.ts index a502b0109d517..299356d844dfa 100644 --- a/packages/core/src/change_detection.ts +++ b/packages/core/src/change_detection.ts @@ -12,4 +12,23 @@ * Change detection enables data binding in Angular. */ -export {ChangeDetectionStrategy, ChangeDetectorRef, DefaultIterableDiffer, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers, NgIterable, PipeTransform, SimpleChange, SimpleChanges, TrackByFunction} from './change_detection/change_detection'; +export { + ChangeDetectionStrategy, + ChangeDetectorRef, + DefaultIterableDiffer, + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDifferFactory, + IterableDiffers, + KeyValueChangeRecord, + KeyValueChanges, + KeyValueDiffer, + KeyValueDifferFactory, + KeyValueDiffers, + NgIterable, + PipeTransform, + SimpleChange, + SimpleChanges, + TrackByFunction, +} from './change_detection/change_detection'; diff --git a/packages/core/src/change_detection/change_detection.ts b/packages/core/src/change_detection/change_detection.ts index 1f8ae89f0dcb4..f12f3f8c6dccf 100644 --- a/packages/core/src/change_detection/change_detection.ts +++ b/packages/core/src/change_detection/change_detection.ts @@ -15,14 +15,29 @@ export {SimpleChange, SimpleChanges} from '../interface/simple_change'; export {devModeEqual} from '../util/comparison'; export {ChangeDetectorRef} from './change_detector_ref'; export {ChangeDetectionStrategy} from './constants'; -export {DefaultIterableDiffer, DefaultIterableDifferFactory} from './differs/default_iterable_differ'; +export { + DefaultIterableDiffer, + DefaultIterableDifferFactory, +} from './differs/default_iterable_differ'; export {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ'; -export {IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, NgIterable, TrackByFunction} from './differs/iterable_differs'; -export {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs'; +export { + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDifferFactory, + IterableDiffers, + NgIterable, + TrackByFunction, +} from './differs/iterable_differs'; +export { + KeyValueChangeRecord, + KeyValueChanges, + KeyValueDiffer, + KeyValueDifferFactory, + KeyValueDiffers, +} from './differs/keyvalue_differs'; export {PipeTransform} from './pipe_transform'; - - /** * Structural diffing for `Object`s and `Map`s. */ diff --git a/packages/core/src/change_detection/change_detector_ref.ts b/packages/core/src/change_detection/change_detector_ref.ts index 27687a6a31daa..14ffd58868509 100644 --- a/packages/core/src/change_detection/change_detector_ref.ts +++ b/packages/core/src/change_detection/change_detector_ref.ts @@ -127,13 +127,13 @@ export abstract class ChangeDetectorRef { static __NG_ELEMENT_ID__: (flags: InjectFlags) => ChangeDetectorRef = injectChangeDetectorRef; } - - /** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */ export function injectChangeDetectorRef(flags: InjectFlags): ChangeDetectorRef { return createViewRef( - getCurrentTNode()!, getLView(), - (flags & InternalInjectFlags.ForPipe) === InternalInjectFlags.ForPipe); + getCurrentTNode()!, + getLView(), + (flags & InternalInjectFlags.ForPipe) === InternalInjectFlags.ForPipe, + ); } /** @@ -148,12 +148,12 @@ function createViewRef(tNode: TNode, lView: LView, isPipe: boolean): ChangeDetec if (isComponentHost(tNode) && !isPipe) { // The LView represents the location where the component is declared. // Instead we want the LView for the component View and so we need to look it up. - const componentView = getComponentLViewByIndex(tNode.index, lView); // look down + const componentView = getComponentLViewByIndex(tNode.index, lView); // look down return new ViewRef(componentView, componentView); } else if (tNode.type & (TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu)) { // The LView represents the location where the injection is requested from. // We need to locate the containing LView (in case where the `lView` is an embedded view) - const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up + const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up return new ViewRef(hostComponentView, lView); } return null!; diff --git a/packages/core/src/change_detection/constants.ts b/packages/core/src/change_detection/constants.ts index caca99c8fe189..6db037110b8a2 100644 --- a/packages/core/src/change_detection/constants.ts +++ b/packages/core/src/change_detection/constants.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - /** * The strategy that the default change detector uses to detect changes. * When set, takes effect the next time change detection is triggered. diff --git a/packages/core/src/change_detection/differs/default_iterable_differ.ts b/packages/core/src/change_detection/differs/default_iterable_differ.ts index 54405452cd5a7..1001e2052c97c 100644 --- a/packages/core/src/change_detection/differs/default_iterable_differ.ts +++ b/packages/core/src/change_detection/differs/default_iterable_differ.ts @@ -11,12 +11,18 @@ import {Writable} from '../../interface/type'; import {isListLikeIterable, iterateListLike} from '../../util/iterable'; import {stringify} from '../../util/stringify'; -import {IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, NgIterable, TrackByFunction} from './iterable_differs'; - +import { + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDifferFactory, + NgIterable, + TrackByFunction, +} from './iterable_differs'; export class DefaultIterableDifferFactory implements IterableDifferFactory { constructor() {} - supports(obj: Object|null|undefined): boolean { + supports(obj: Object | null | undefined): boolean { return isListLikeIterable(obj); } @@ -34,23 +40,23 @@ const trackByIdentity = (index: number, item: any) => item; export class DefaultIterableDiffer implements IterableDiffer, IterableChanges { public readonly length: number = 0; // TODO: confirm the usage of `collection` as it's unused, readonly and on a non public API. - public readonly collection!: V[]|Iterable|null; + public readonly collection!: V[] | Iterable | null; // Keeps track of the used records at any point in time (during & across `_check()` calls) - private _linkedRecords: _DuplicateMap|null = null; + private _linkedRecords: _DuplicateMap | null = null; // Keeps track of the removed records at any point in time during `_check()` calls. - private _unlinkedRecords: _DuplicateMap|null = null; - private _previousItHead: IterableChangeRecord_|null = null; - private _itHead: IterableChangeRecord_|null = null; - private _itTail: IterableChangeRecord_|null = null; - private _additionsHead: IterableChangeRecord_|null = null; - private _additionsTail: IterableChangeRecord_|null = null; - private _movesHead: IterableChangeRecord_|null = null; - private _movesTail: IterableChangeRecord_|null = null; - private _removalsHead: IterableChangeRecord_|null = null; - private _removalsTail: IterableChangeRecord_|null = null; + private _unlinkedRecords: _DuplicateMap | null = null; + private _previousItHead: IterableChangeRecord_ | null = null; + private _itHead: IterableChangeRecord_ | null = null; + private _itTail: IterableChangeRecord_ | null = null; + private _additionsHead: IterableChangeRecord_ | null = null; + private _additionsTail: IterableChangeRecord_ | null = null; + private _movesHead: IterableChangeRecord_ | null = null; + private _movesTail: IterableChangeRecord_ | null = null; + private _removalsHead: IterableChangeRecord_ | null = null; + private _removalsTail: IterableChangeRecord_ | null = null; // Keeps track of records where custom track by is the same, but item identity has changed - private _identityChangesHead: IterableChangeRecord_|null = null; - private _identityChangesTail: IterableChangeRecord_|null = null; + private _identityChangesHead: IterableChangeRecord_ | null = null; + private _identityChangesTail: IterableChangeRecord_ | null = null; private _trackByFn: TrackByFunction; constructor(trackByFn?: TrackByFunction) { @@ -58,28 +64,32 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan } forEachItem(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._itHead; record !== null; record = record._next) { fn(record); } } forEachOperation( - fn: (item: IterableChangeRecord, previousIndex: number|null, currentIndex: number|null) => - void) { + fn: ( + item: IterableChangeRecord, + previousIndex: number | null, + currentIndex: number | null, + ) => void, + ) { let nextIt = this._itHead; let nextRemove = this._removalsHead; let addRemoveOffset = 0; - let moveOffsets: number[]|null = null; + let moveOffsets: number[] | null = null; while (nextIt || nextRemove) { // Figure out which is the next record to process // Order: remove, add, move - const record: IterableChangeRecord = !nextRemove || - nextIt && - nextIt.currentIndex! < - getPreviousIndex(nextRemove, addRemoveOffset, moveOffsets) ? - nextIt! : - nextRemove; + const record: IterableChangeRecord = + !nextRemove || + (nextIt && + nextIt.currentIndex! < getPreviousIndex(nextRemove, addRemoveOffset, moveOffsets)) + ? nextIt! + : nextRemove; const adjPreviousIndex = getPreviousIndex(record, addRemoveOffset, moveOffsets); const currentIndex = record.currentIndex; @@ -117,48 +127,48 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan } forEachPreviousItem(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._previousItHead; record !== null; record = record._nextPrevious) { fn(record); } } forEachAddedItem(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._additionsHead; record !== null; record = record._nextAdded) { fn(record); } } forEachMovedItem(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._movesHead; record !== null; record = record._nextMoved) { fn(record); } } forEachRemovedItem(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._removalsHead; record !== null; record = record._nextRemoved) { fn(record); } } forEachIdentityChange(fn: (record: IterableChangeRecord_) => void) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._identityChangesHead; record !== null; record = record._nextIdentityChange) { fn(record); } } - diff(collection: NgIterable|null|undefined): DefaultIterableDiffer|null { + diff(collection: NgIterable | null | undefined): DefaultIterableDiffer | null { if (collection == null) collection = []; if (!isListLikeIterable(collection)) { throw new RuntimeError( - RuntimeErrorCode.INVALID_DIFFER_INPUT, - ngDevMode && - `Error trying to diff '${ - stringify(collection)}'. Only arrays and iterables are allowed`); + RuntimeErrorCode.INVALID_DIFFER_INPUT, + ngDevMode && + `Error trying to diff '${stringify(collection)}'. Only arrays and iterables are allowed`, + ); } if (this.check(collection)) { @@ -173,7 +183,7 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan check(collection: NgIterable): boolean { this._reset(); - let record: IterableChangeRecord_|null = this._itHead; + let record: IterableChangeRecord_ | null = this._itHead; let mayBeDirty: boolean = false; let index: number; let item: V; @@ -226,8 +236,12 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan * changes. */ get isDirty(): boolean { - return this._additionsHead !== null || this._movesHead !== null || - this._removalsHead !== null || this._identityChangesHead !== null; + return ( + this._additionsHead !== null || + this._movesHead !== null || + this._removalsHead !== null || + this._identityChangesHead !== null + ); } /** @@ -240,7 +254,7 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan */ _reset() { if (this.isDirty) { - let record: IterableChangeRecord_|null; + let record: IterableChangeRecord_ | null; for (record = this._previousItHead = this._itHead; record !== null; record = record._next) { record._nextPrevious = record._next; @@ -273,10 +287,14 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan * * @internal */ - _mismatch(record: IterableChangeRecord_|null, item: V, itemTrackBy: any, index: number): - IterableChangeRecord_ { + _mismatch( + record: IterableChangeRecord_ | null, + item: V, + itemTrackBy: any, + index: number, + ): IterableChangeRecord_ { // The previous record after which we will append the current one. - let previousRecord: IterableChangeRecord_|null; + let previousRecord: IterableChangeRecord_ | null; if (record === null) { previousRecord = this._itTail; @@ -306,8 +324,11 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan this._moveAfter(record, previousRecord, index); } else { // It is a new item: add it. - record = - this._addAfter(new IterableChangeRecord_(item, itemTrackBy), previousRecord, index); + record = this._addAfter( + new IterableChangeRecord_(item, itemTrackBy), + previousRecord, + index, + ); } } return record; @@ -340,10 +361,14 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan * * @internal */ - _verifyReinsertion(record: IterableChangeRecord_, item: V, itemTrackBy: any, index: number): - IterableChangeRecord_ { - let reinsertRecord: IterableChangeRecord_|null = - this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy, null); + _verifyReinsertion( + record: IterableChangeRecord_, + item: V, + itemTrackBy: any, + index: number, + ): IterableChangeRecord_ { + let reinsertRecord: IterableChangeRecord_ | null = + this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy, null); if (reinsertRecord !== null) { record = this._reinsertAfter(reinsertRecord, record._prev!, index); } else if (record.currentIndex != index) { @@ -360,10 +385,10 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan * * @internal */ - _truncate(record: IterableChangeRecord_|null) { + _truncate(record: IterableChangeRecord_ | null) { // Anything after that needs to be removed; while (record !== null) { - const nextRecord: IterableChangeRecord_|null = record._next; + const nextRecord: IterableChangeRecord_ | null = record._next; this._addToRemovals(this._unlink(record)); record = nextRecord; } @@ -390,8 +415,10 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan /** @internal */ _reinsertAfter( - record: IterableChangeRecord_, prevRecord: IterableChangeRecord_|null, - index: number): IterableChangeRecord_ { + record: IterableChangeRecord_, + prevRecord: IterableChangeRecord_ | null, + index: number, + ): IterableChangeRecord_ { if (this._unlinkedRecords !== null) { this._unlinkedRecords.remove(record); } @@ -416,8 +443,10 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan /** @internal */ _moveAfter( - record: IterableChangeRecord_, prevRecord: IterableChangeRecord_|null, - index: number): IterableChangeRecord_ { + record: IterableChangeRecord_, + prevRecord: IterableChangeRecord_ | null, + index: number, + ): IterableChangeRecord_ { this._unlink(record); this._insertAfter(record, prevRecord, index); this._addToMoves(record, index); @@ -426,8 +455,10 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan /** @internal */ _addAfter( - record: IterableChangeRecord_, prevRecord: IterableChangeRecord_|null, - index: number): IterableChangeRecord_ { + record: IterableChangeRecord_, + prevRecord: IterableChangeRecord_ | null, + index: number, + ): IterableChangeRecord_ { this._insertAfter(record, prevRecord, index); if (this._additionsTail === null) { @@ -445,15 +476,17 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan /** @internal */ _insertAfter( - record: IterableChangeRecord_, prevRecord: IterableChangeRecord_|null, - index: number): IterableChangeRecord_ { + record: IterableChangeRecord_, + prevRecord: IterableChangeRecord_ | null, + index: number, + ): IterableChangeRecord_ { // TODO(vicb): // assert(record != prevRecord); // assert(record._next === null); // assert(record._prev === null); - const next: IterableChangeRecord_|null = - prevRecord === null ? this._itHead : prevRecord._next; + const next: IterableChangeRecord_ | null = + prevRecord === null ? this._itHead : prevRecord._next; // TODO(vicb): // assert(next != record); // assert(prevRecord != record); @@ -569,40 +602,42 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan } export class IterableChangeRecord_ implements IterableChangeRecord { - currentIndex: number|null = null; - previousIndex: number|null = null; + currentIndex: number | null = null; + previousIndex: number | null = null; /** @internal */ - _nextPrevious: IterableChangeRecord_|null = null; + _nextPrevious: IterableChangeRecord_ | null = null; /** @internal */ - _prev: IterableChangeRecord_|null = null; + _prev: IterableChangeRecord_ | null = null; /** @internal */ - _next: IterableChangeRecord_|null = null; + _next: IterableChangeRecord_ | null = null; /** @internal */ - _prevDup: IterableChangeRecord_|null = null; + _prevDup: IterableChangeRecord_ | null = null; /** @internal */ - _nextDup: IterableChangeRecord_|null = null; + _nextDup: IterableChangeRecord_ | null = null; /** @internal */ - _prevRemoved: IterableChangeRecord_|null = null; + _prevRemoved: IterableChangeRecord_ | null = null; /** @internal */ - _nextRemoved: IterableChangeRecord_|null = null; + _nextRemoved: IterableChangeRecord_ | null = null; /** @internal */ - _nextAdded: IterableChangeRecord_|null = null; + _nextAdded: IterableChangeRecord_ | null = null; /** @internal */ - _nextMoved: IterableChangeRecord_|null = null; + _nextMoved: IterableChangeRecord_ | null = null; /** @internal */ - _nextIdentityChange: IterableChangeRecord_|null = null; - + _nextIdentityChange: IterableChangeRecord_ | null = null; - constructor(public item: V, public trackById: any) {} + constructor( + public item: V, + public trackById: any, + ) {} } // A linked list of IterableChangeRecords with the same IterableChangeRecord_.item class _DuplicateItemRecordList { /** @internal */ - _head: IterableChangeRecord_|null = null; + _head: IterableChangeRecord_ | null = null; /** @internal */ - _tail: IterableChangeRecord_|null = null; + _tail: IterableChangeRecord_ | null = null; /** * Append the record to the list of duplicates. @@ -627,11 +662,13 @@ class _DuplicateItemRecordList { // Returns a IterableChangeRecord_ having IterableChangeRecord_.trackById == trackById and // IterableChangeRecord_.currentIndex >= atOrAfterIndex - get(trackById: any, atOrAfterIndex: number|null): IterableChangeRecord_|null { - let record: IterableChangeRecord_|null; + get(trackById: any, atOrAfterIndex: number | null): IterableChangeRecord_ | null { + let record: IterableChangeRecord_ | null; for (record = this._head; record !== null; record = record._nextDup) { - if ((atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex!) && - Object.is(record.trackById, trackById)) { + if ( + (atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex!) && + Object.is(record.trackById, trackById) + ) { return record; } } @@ -653,8 +690,8 @@ class _DuplicateItemRecordList { // return false; //}); - const prev: IterableChangeRecord_|null = record._prevDup; - const next: IterableChangeRecord_|null = record._nextDup; + const prev: IterableChangeRecord_ | null = record._prevDup; + const next: IterableChangeRecord_ | null = record._nextDup; if (prev === null) { this._head = next; } else { @@ -690,7 +727,7 @@ class _DuplicateMap { * Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we * have any more `a`s needs to return the second `a`. */ - get(trackById: any, atOrAfterIndex: number|null): IterableChangeRecord_|null { + get(trackById: any, atOrAfterIndex: number | null): IterableChangeRecord_ | null { const key = trackById; const recordList = this.map.get(key); return recordList ? recordList.get(trackById, atOrAfterIndex) : null; @@ -720,7 +757,11 @@ class _DuplicateMap { } } -function getPreviousIndex(item: any, addRemoveOffset: number, moveOffsets: number[]|null): number { +function getPreviousIndex( + item: any, + addRemoveOffset: number, + moveOffsets: number[] | null, +): number { const previousIndex = item.previousIndex; if (previousIndex === null) return previousIndex; let moveOffset = 0; diff --git a/packages/core/src/change_detection/differs/default_keyvalue_differ.ts b/packages/core/src/change_detection/differs/default_keyvalue_differ.ts index e9baefb70a23a..fc70f61507706 100644 --- a/packages/core/src/change_detection/differs/default_keyvalue_differ.ts +++ b/packages/core/src/change_detection/differs/default_keyvalue_differ.ts @@ -10,8 +10,12 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {isJsObject} from '../../util/iterable'; import {stringify} from '../../util/stringify'; -import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs'; - +import { + KeyValueChangeRecord, + KeyValueChanges, + KeyValueDiffer, + KeyValueDifferFactory, +} from './keyvalue_differs'; export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory { constructor() {} @@ -26,65 +30,66 @@ export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyValueChanges { private _records = new Map>(); - private _mapHead: KeyValueChangeRecord_|null = null; + private _mapHead: KeyValueChangeRecord_ | null = null; // _appendAfter is used in the check loop - private _appendAfter: KeyValueChangeRecord_|null = null; - private _previousMapHead: KeyValueChangeRecord_|null = null; - private _changesHead: KeyValueChangeRecord_|null = null; - private _changesTail: KeyValueChangeRecord_|null = null; - private _additionsHead: KeyValueChangeRecord_|null = null; - private _additionsTail: KeyValueChangeRecord_|null = null; - private _removalsHead: KeyValueChangeRecord_|null = null; - private _removalsTail: KeyValueChangeRecord_|null = null; + private _appendAfter: KeyValueChangeRecord_ | null = null; + private _previousMapHead: KeyValueChangeRecord_ | null = null; + private _changesHead: KeyValueChangeRecord_ | null = null; + private _changesTail: KeyValueChangeRecord_ | null = null; + private _additionsHead: KeyValueChangeRecord_ | null = null; + private _additionsTail: KeyValueChangeRecord_ | null = null; + private _removalsHead: KeyValueChangeRecord_ | null = null; + private _removalsTail: KeyValueChangeRecord_ | null = null; get isDirty(): boolean { - return this._additionsHead !== null || this._changesHead !== null || - this._removalsHead !== null; + return ( + this._additionsHead !== null || this._changesHead !== null || this._removalsHead !== null + ); } forEachItem(fn: (r: KeyValueChangeRecord) => void) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; for (record = this._mapHead; record !== null; record = record._next) { fn(record); } } forEachPreviousItem(fn: (r: KeyValueChangeRecord) => void) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; for (record = this._previousMapHead; record !== null; record = record._nextPrevious) { fn(record); } } forEachChangedItem(fn: (r: KeyValueChangeRecord) => void) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; for (record = this._changesHead; record !== null; record = record._nextChanged) { fn(record); } } forEachAddedItem(fn: (r: KeyValueChangeRecord) => void) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; for (record = this._additionsHead; record !== null; record = record._nextAdded) { fn(record); } } forEachRemovedItem(fn: (r: KeyValueChangeRecord) => void) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; for (record = this._removalsHead; record !== null; record = record._nextRemoved) { fn(record); } } - diff(map?: Map|{[k: string]: any}|null): any { + diff(map?: Map | {[k: string]: any} | null): any { if (!map) { map = new Map(); } else if (!(map instanceof Map || isJsObject(map))) { throw new RuntimeError( - RuntimeErrorCode.INVALID_DIFFER_INPUT, - ngDevMode && - `Error trying to diff '${stringify(map)}'. Only maps and objects are allowed`); + RuntimeErrorCode.INVALID_DIFFER_INPUT, + ngDevMode && `Error trying to diff '${stringify(map)}'. Only maps and objects are allowed`, + ); } return this.check(map) ? this : null; @@ -96,7 +101,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal * Check the current state of the map vs the previous. * The algorithm is optimised for when the keys do no change. */ - check(map: Map|{[k: string]: any}): boolean { + check(map: Map | {[k: string]: any}): boolean { this._reset(); let insertBefore = this._mapHead; @@ -121,8 +126,11 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal this._removalsHead = insertBefore; - for (let record: KeyValueChangeRecord_|null = insertBefore; record !== null; - record = record._nextRemoved) { + for ( + let record: KeyValueChangeRecord_ | null = insertBefore; + record !== null; + record = record._nextRemoved + ) { if (record === this._mapHead) { this._mapHead = null; } @@ -151,8 +159,9 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal * - The return value is the new value for the insertion pointer. */ private _insertBeforeOrAppend( - before: KeyValueChangeRecord_|null, - record: KeyValueChangeRecord_): KeyValueChangeRecord_|null { + before: KeyValueChangeRecord_ | null, + record: KeyValueChangeRecord_, + ): KeyValueChangeRecord_ | null { if (before) { const prev = before._prev; record._next = before; @@ -208,7 +217,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal /** @internal */ _reset() { if (this.isDirty) { - let record: KeyValueChangeRecord_|null; + let record: KeyValueChangeRecord_ | null; // let `_previousMapHead` contain the state of the map before the changes this._previousMapHead = this._mapHead; for (record = this._previousMapHead; record !== null; record = record._next) { @@ -258,31 +267,31 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal } /** @internal */ - private _forEach(obj: Map|{[k: string]: V}, fn: (v: V, k: any) => void) { + private _forEach(obj: Map | {[k: string]: V}, fn: (v: V, k: any) => void) { if (obj instanceof Map) { obj.forEach(fn); } else { - Object.keys(obj).forEach(k => fn(obj[k], k)); + Object.keys(obj).forEach((k) => fn(obj[k], k)); } } } class KeyValueChangeRecord_ implements KeyValueChangeRecord { - previousValue: V|null = null; - currentValue: V|null = null; + previousValue: V | null = null; + currentValue: V | null = null; /** @internal */ - _nextPrevious: KeyValueChangeRecord_|null = null; + _nextPrevious: KeyValueChangeRecord_ | null = null; /** @internal */ - _next: KeyValueChangeRecord_|null = null; + _next: KeyValueChangeRecord_ | null = null; /** @internal */ - _prev: KeyValueChangeRecord_|null = null; + _prev: KeyValueChangeRecord_ | null = null; /** @internal */ - _nextAdded: KeyValueChangeRecord_|null = null; + _nextAdded: KeyValueChangeRecord_ | null = null; /** @internal */ - _nextRemoved: KeyValueChangeRecord_|null = null; + _nextRemoved: KeyValueChangeRecord_ | null = null; /** @internal */ - _nextChanged: KeyValueChangeRecord_|null = null; + _nextChanged: KeyValueChangeRecord_ | null = null; constructor(public key: K) {} } diff --git a/packages/core/src/change_detection/differs/iterable_differs.ts b/packages/core/src/change_detection/differs/iterable_differs.ts index 83e385d3874e9..bf707efe225f7 100644 --- a/packages/core/src/change_detection/differs/iterable_differs.ts +++ b/packages/core/src/change_detection/differs/iterable_differs.ts @@ -12,14 +12,12 @@ import {Optional, SkipSelf} from '../../di/metadata'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {DefaultIterableDifferFactory} from '../differs/default_iterable_differ'; - - /** * A type describing supported iterable types. * * @publicApi */ -export type NgIterable = Array|Iterable; +export type NgIterable = Array | Iterable; /** * A strategy for tracking changes over time to an iterable. Used by {@link NgForOf} to @@ -35,7 +33,7 @@ export interface IterableDiffer { * @returns an object describing the difference. The return value is only valid until the next * `diff()` invocation. */ - diff(object: NgIterable|undefined|null): IterableChanges|null; + diff(object: NgIterable | undefined | null): IterableChanges | null; } /** @@ -68,9 +66,12 @@ export interface IterableChanges { * of the item, after applying the operations up to this point. */ forEachOperation( - fn: - (record: IterableChangeRecord, previousIndex: number|null, - currentIndex: number|null) => void): void; + fn: ( + record: IterableChangeRecord, + previousIndex: number | null, + currentIndex: number | null, + ) => void, + ): void; /** * Iterate over changes in the order of original `Iterable` showing where the original items @@ -101,10 +102,10 @@ export interface IterableChanges { */ export interface IterableChangeRecord { /** Current index of the item in `Iterable` or null if removed. */ - readonly currentIndex: number|null; + readonly currentIndex: number | null; /** Previous index of the item in `Iterable` or null if added. */ - readonly previousIndex: number|null; + readonly previousIndex: number | null; /** The item. */ readonly item: V; @@ -168,7 +169,7 @@ export interface TrackByFunction { * @param index The index of the item within the iterable. * @param item The item in the iterable. */ - (index: number, item: T&U): any; + (index: number, item: T & U): any; } /** @@ -192,8 +193,11 @@ export function defaultIterableDiffersFactory() { */ export class IterableDiffers { /** @nocollapse */ - static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable( - {token: IterableDiffers, providedIn: 'root', factory: defaultIterableDiffersFactory}); + static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({ + token: IterableDiffers, + providedIn: 'root', + factory: defaultIterableDiffersFactory, + }); constructor(private factories: IterableDifferFactory[]) {} @@ -229,27 +233,29 @@ export class IterableDiffers { static extend(factories: IterableDifferFactory[]): StaticProvider { return { provide: IterableDiffers, - useFactory: (parent: IterableDiffers|null) => { + useFactory: (parent: IterableDiffers | null) => { // if parent is null, it means that we are in the root injector and we have just overridden // the default injection mechanism for IterableDiffers, in such a case just assume // `defaultIterableDiffersFactory`. return IterableDiffers.create(factories, parent || defaultIterableDiffersFactory()); }, // Dependency technically isn't optional, but we can provide a better error message this way. - deps: [[IterableDiffers, new SkipSelf(), new Optional()]] + deps: [[IterableDiffers, new SkipSelf(), new Optional()]], }; } find(iterable: any): IterableDifferFactory { - const factory = this.factories.find(f => f.supports(iterable)); + const factory = this.factories.find((f) => f.supports(iterable)); if (factory != null) { return factory; } else { throw new RuntimeError( - RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY, - ngDevMode && - `Cannot find a differ supporting object '${iterable}' of type '${ - getTypeNameForDebugging(iterable)}'`); + RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY, + ngDevMode && + `Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging( + iterable, + )}'`, + ); } } } diff --git a/packages/core/src/change_detection/differs/keyvalue_differs.ts b/packages/core/src/change_detection/differs/keyvalue_differs.ts index ae3a0e005ca7a..20971d54c7b1a 100644 --- a/packages/core/src/change_detection/differs/keyvalue_differs.ts +++ b/packages/core/src/change_detection/differs/keyvalue_differs.ts @@ -11,7 +11,6 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {DefaultKeyValueDifferFactory} from './default_keyvalue_differ'; - /** * A differ that tracks changes made to an object over time. * @@ -25,7 +24,7 @@ export interface KeyValueDiffer { * @returns an object describing the difference. The return value is only valid until the next * `diff()` invocation. */ - diff(object: Map): KeyValueChanges|null; + diff(object: Map): KeyValueChanges | null; /** * Compute a difference between the previous state and the new `object` state. @@ -34,7 +33,7 @@ export interface KeyValueDiffer { * @returns an object describing the difference. The return value is only valid until the next * `diff()` invocation. */ - diff(object: {[key: string]: V}): KeyValueChanges|null; + diff(object: {[key: string]: V}): KeyValueChanges | null; // TODO(TS2.1): diff(this: KeyValueDiffer, object: Record): // KeyValueDiffer; } @@ -88,12 +87,12 @@ export interface KeyValueChangeRecord { /** * Current value for the key or `null` if removed. */ - readonly currentValue: V|null; + readonly currentValue: V | null; /** * Previous value for the key or `null` if added. */ - readonly previousValue: V|null; + readonly previousValue: V | null; } /** @@ -124,8 +123,11 @@ export function defaultKeyValueDiffersFactory() { */ export class KeyValueDiffers { /** @nocollapse */ - static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable( - {token: KeyValueDiffers, providedIn: 'root', factory: defaultKeyValueDiffersFactory}); + static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({ + token: KeyValueDiffers, + providedIn: 'root', + factory: defaultKeyValueDiffersFactory, + }); /** * @deprecated v4.0.0 - Should be private. @@ -174,17 +176,18 @@ export class KeyValueDiffers { return KeyValueDiffers.create(factories, parent || defaultKeyValueDiffersFactory()); }, // Dependency technically isn't optional, but we can provide a better error message this way. - deps: [[KeyValueDiffers, new SkipSelf(), new Optional()]] + deps: [[KeyValueDiffers, new SkipSelf(), new Optional()]], }; } find(kv: any): KeyValueDifferFactory { - const factory = this.factories.find(f => f.supports(kv)); + const factory = this.factories.find((f) => f.supports(kv)); if (factory) { return factory; } throw new RuntimeError( - RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY, - ngDevMode && `Cannot find a differ supporting object '${kv}'`); + RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY, + ngDevMode && `Cannot find a differ supporting object '${kv}'`, + ); } } diff --git a/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts b/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts index 5b529b82d49b9..9d07ba9056258 100644 --- a/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts +++ b/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts @@ -9,7 +9,15 @@ import {Subscription} from 'rxjs'; import {ApplicationRef} from '../../application/application_ref'; -import {ENVIRONMENT_INITIALIZER, EnvironmentProviders, inject, Injectable, InjectionToken, makeEnvironmentProviders, StaticProvider} from '../../di'; +import { + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + inject, + Injectable, + InjectionToken, + makeEnvironmentProviders, + StaticProvider, +} from '../../di'; import {ErrorHandler, INTERNAL_APPLICATION_ERROR_HANDLER} from '../../error_handler'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {PendingTasks} from '../../pending_tasks'; @@ -45,7 +53,7 @@ export class NgZoneChangeDetectionScheduler { this.zone.run(() => { this.applicationRef.tick(); }); - } + }, }); } @@ -54,31 +62,39 @@ export class NgZoneChangeDetectionScheduler { } } - /** * Internal token used to verify that `provideZoneChangeDetection` is not used * with the bootstrapModule API. */ export const PROVIDED_NG_ZONE = new InjectionToken( - (typeof ngDevMode === 'undefined' || ngDevMode) ? 'provideZoneChangeDetection token' : ''); + typeof ngDevMode === 'undefined' || ngDevMode ? 'provideZoneChangeDetection token' : '', +); -export function internalProvideZoneChangeDetection( - {ngZoneFactory, ignoreChangesOutsideZone}: - {ngZoneFactory: () => NgZone, ignoreChangesOutsideZone?: boolean}): StaticProvider[] { +export function internalProvideZoneChangeDetection({ + ngZoneFactory, + ignoreChangesOutsideZone, +}: { + ngZoneFactory: () => NgZone; + ignoreChangesOutsideZone?: boolean; +}): StaticProvider[] { return [ {provide: NgZone, useFactory: ngZoneFactory}, { provide: ENVIRONMENT_INITIALIZER, multi: true, useFactory: () => { - const ngZoneChangeDetectionScheduler = - inject(NgZoneChangeDetectionScheduler, {optional: true}); - if ((typeof ngDevMode === 'undefined' || ngDevMode) && - ngZoneChangeDetectionScheduler === null) { + const ngZoneChangeDetectionScheduler = inject(NgZoneChangeDetectionScheduler, { + optional: true, + }); + if ( + (typeof ngDevMode === 'undefined' || ngDevMode) && + ngZoneChangeDetectionScheduler === null + ) { throw new RuntimeError( - RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, - `A required Injectable was not found in the dependency injection tree. ` + - 'If you are bootstrapping an NgModule, make sure that the `BrowserModule` is imported.'); + RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, + `A required Injectable was not found in the dependency injection tree. ` + + 'If you are bootstrapping an NgModule, make sure that the `BrowserModule` is imported.', + ); } return () => ngZoneChangeDetectionScheduler!.initialize(); }, @@ -91,7 +107,7 @@ export function internalProvideZoneChangeDetection( return () => { service.initialize(); }; - } + }, }, {provide: INTERNAL_APPLICATION_ERROR_HANDLER, useFactory: ngZoneApplicationErrorHandlerFactory}, // Always disable scheduler whenever explicitly disabled, even if another place called @@ -99,9 +115,9 @@ export function internalProvideZoneChangeDetection( ignoreChangesOutsideZone === true ? {provide: ZONELESS_SCHEDULER_DISABLED, useValue: true} : [], // TODO(atscott): This should move to the same places that zone change detection is provided by // default instead of being in the zone scheduling providers. - alwaysProvideZonelessScheduler || ignoreChangesOutsideZone === false ? - {provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl} : - [], + alwaysProvideZonelessScheduler || ignoreChangesOutsideZone === false + ? {provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl} + : [], ]; } @@ -141,11 +157,12 @@ export function provideZoneChangeDetection(options?: NgZoneOptions): Environment } return new NgZone(ngZoneOptions); }, - ignoreChangesOutsideZone + ignoreChangesOutsideZone, }); return makeEnvironmentProviders([ - (typeof ngDevMode === 'undefined' || ngDevMode) ? {provide: PROVIDED_NG_ZONE, useValue: true} : - [], + typeof ngDevMode === 'undefined' || ngDevMode + ? {provide: PROVIDED_NG_ZONE, useValue: true} + : [], zoneProviders, ]); } @@ -240,33 +257,40 @@ export class ZoneStablePendingTask { } this.initialized = true; - let task: number|null = null; + let task: number | null = null; if (!this.zone.isStable && !this.zone.hasPendingMacrotasks && !this.zone.hasPendingMicrotasks) { task = this.pendingTasks.add(); } this.zone.runOutsideAngular(() => { - this.subscription.add(this.zone.onStable.subscribe(() => { - NgZone.assertNotInAngularZone(); + this.subscription.add( + this.zone.onStable.subscribe(() => { + NgZone.assertNotInAngularZone(); - // Check whether there are no pending macro/micro tasks in the next tick - // to allow for NgZone to update the state. - queueMicrotask(() => { - if (task !== null && !this.zone.hasPendingMacrotasks && !this.zone.hasPendingMicrotasks) { - this.pendingTasks.remove(task); - task = null; - } - }); - })); + // Check whether there are no pending macro/micro tasks in the next tick + // to allow for NgZone to update the state. + queueMicrotask(() => { + if ( + task !== null && + !this.zone.hasPendingMacrotasks && + !this.zone.hasPendingMicrotasks + ) { + this.pendingTasks.remove(task); + task = null; + } + }); + }), + ); }); - this.subscription.add(this.zone.onUnstable.subscribe(() => { - NgZone.assertInAngularZone(); - task ??= this.pendingTasks.add(); - })); + this.subscription.add( + this.zone.onUnstable.subscribe(() => { + NgZone.assertInAngularZone(); + task ??= this.pendingTasks.add(); + }), + ); } - ngOnDestroy() { this.subscription.unsubscribe(); } diff --git a/packages/core/src/change_detection/scheduling/zoneless_scheduling.ts b/packages/core/src/change_detection/scheduling/zoneless_scheduling.ts index 7a13b6df3bf61..0fede95715fab 100644 --- a/packages/core/src/change_detection/scheduling/zoneless_scheduling.ts +++ b/packages/core/src/change_detection/scheduling/zoneless_scheduling.ts @@ -8,8 +8,6 @@ import {InjectionToken} from '../../di/injection_token'; - - export const enum NotificationSource { // Change detection needs to run in order to synchronize application state // with the DOM when the following notifications are received: @@ -60,8 +58,10 @@ export abstract class ChangeDetectionScheduler { /** Token used to indicate if zoneless was enabled via provideZonelessChangeDetection(). */ export const ZONELESS_ENABLED = new InjectionToken( - typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless enabled' : '', - {providedIn: 'root', factory: () => false}); + typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless enabled' : '', + {providedIn: 'root', factory: () => false}, +); export const ZONELESS_SCHEDULER_DISABLED = new InjectionToken( - typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : ''); + typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : '', +); diff --git a/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts b/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts index 27d8d8ecf7e63..369940fc9593c 100644 --- a/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts +++ b/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts @@ -15,11 +15,19 @@ import {EnvironmentProviders} from '../../di/interface/provider'; import {makeEnvironmentProviders} from '../../di/provider_collection'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {PendingTasks} from '../../pending_tasks'; -import {scheduleCallbackWithMicrotask, scheduleCallbackWithRafRace} from '../../util/callback_scheduler'; +import { + scheduleCallbackWithMicrotask, + scheduleCallbackWithRafRace, +} from '../../util/callback_scheduler'; import {performanceMarkFeature} from '../../util/performance'; import {NgZone, NoopNgZone} from '../../zone/ng_zone'; -import {ChangeDetectionScheduler, NotificationSource, ZONELESS_ENABLED, ZONELESS_SCHEDULER_DISABLED} from './zoneless_scheduling'; +import { + ChangeDetectionScheduler, + NotificationSource, + ZONELESS_ENABLED, + ZONELESS_SCHEDULER_DISABLED, +} from './zoneless_scheduling'; const CONSECUTIVE_MICROTASK_NOTIFICATION_LIMIT = 100; let consecutiveMicrotaskNotifications = 0; @@ -36,10 +44,11 @@ function trackMicrotaskNotificationForDebugging() { if (consecutiveMicrotaskNotifications === CONSECUTIVE_MICROTASK_NOTIFICATION_LIMIT) { throw new RuntimeError( - RuntimeErrorCode.INFINITE_CHANGE_DETECTION, - 'Angular could not stabilize because there were endless change notifications within the browser event loop. ' + - 'The stack from the last several notifications: \n' + - stackFromLastFewNotifications.join('\n')); + RuntimeErrorCode.INFINITE_CHANGE_DETECTION, + 'Angular could not stabilize because there were endless change notifications within the browser event loop. ' + + 'The stack from the last several notifications: \n' + + stackFromLastFewNotifications.join('\n'), + ); } } @@ -50,42 +59,47 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { private readonly ngZone = inject(NgZone); private readonly zonelessEnabled = inject(ZONELESS_ENABLED); private readonly disableScheduling = - inject(ZONELESS_SCHEDULER_DISABLED, {optional: true}) ?? false; + inject(ZONELESS_SCHEDULER_DISABLED, {optional: true}) ?? false; private readonly zoneIsDefined = typeof Zone !== 'undefined' && !!Zone.root.run; private readonly schedulerTickApplyArgs = [{data: {'__scheduler_tick__': true}}]; private readonly subscriptions = new Subscription(); - private cancelScheduledCallback: null|(() => void) = null; + private cancelScheduledCallback: null | (() => void) = null; private shouldRefreshViews = false; - private pendingRenderTaskId: number|null = null; + private pendingRenderTaskId: number | null = null; private useMicrotaskScheduler = false; runningTick = false; constructor() { - this.subscriptions.add(this.appRef.afterTick.subscribe(() => { - // If the scheduler isn't running a tick but the application ticked, that means - // someone called ApplicationRef.tick manually. In this case, we should cancel - // any change detections that had been scheduled so we don't run an extra one. - if (!this.runningTick) { - this.cleanup(); - } - })); - this.subscriptions.add(this.ngZone.onUnstable.subscribe(() => { - // If the zone becomes unstable when we're not running tick (this happens from the zone.run), - // we should cancel any scheduled change detection here because at this point we - // know that the zone will stabilize at some point and run change detection itself. - if (!this.runningTick) { - this.cleanup(); - } - })); + this.subscriptions.add( + this.appRef.afterTick.subscribe(() => { + // If the scheduler isn't running a tick but the application ticked, that means + // someone called ApplicationRef.tick manually. In this case, we should cancel + // any change detections that had been scheduled so we don't run an extra one. + if (!this.runningTick) { + this.cleanup(); + } + }), + ); + this.subscriptions.add( + this.ngZone.onUnstable.subscribe(() => { + // If the zone becomes unstable when we're not running tick (this happens from the zone.run), + // we should cancel any scheduled change detection here because at this point we + // know that the zone will stabilize at some point and run change detection itself. + if (!this.runningTick) { + this.cleanup(); + } + }), + ); // TODO(atscott): These conditions will need to change when zoneless is the default // Instead, they should flip to checking if ZoneJS scheduling is provided - this.disableScheduling ||= !this.zonelessEnabled && - // NoopNgZone without enabling zoneless means no scheduling whatsoever - (this.ngZone instanceof NoopNgZone || - // The same goes for the lack of Zone without enabling zoneless scheduling - !this.zoneIsDefined); + this.disableScheduling ||= + !this.zonelessEnabled && + // NoopNgZone without enabling zoneless means no scheduling whatsoever + (this.ngZone instanceof NoopNgZone || + // The same goes for the lack of Zone without enabling zoneless scheduling + !this.zoneIsDefined); } notify(source: NotificationSource): void { @@ -126,7 +140,7 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { return; } - if ((typeof ngDevMode === 'undefined' || ngDevMode)) { + if (typeof ngDevMode === 'undefined' || ngDevMode) { if (this.useMicrotaskScheduler) { trackMicrotaskNotificationForDebugging(); } else { @@ -135,8 +149,9 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { } } - const scheduleCallback = - this.useMicrotaskScheduler ? scheduleCallbackWithMicrotask : scheduleCallbackWithRafRace; + const scheduleCallback = this.useMicrotaskScheduler + ? scheduleCallbackWithMicrotask + : scheduleCallbackWithRafRace; this.pendingRenderTaskId = this.taskService.add(); if (this.zoneIsDefined) { Zone.root.run(() => { @@ -187,10 +202,14 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { const task = this.taskService.add(); try { - this.ngZone.run(() => { - this.runningTick = true; - this.appRef._tick(shouldRefreshViews); - }, undefined, this.schedulerTickApplyArgs); + this.ngZone.run( + () => { + this.runningTick = true; + this.appRef._tick(shouldRefreshViews); + }, + undefined, + this.schedulerTickApplyArgs, + ); } catch (e: unknown) { this.taskService.remove(task); throw e; @@ -234,7 +253,6 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { } } - /** * Provides change detection without ZoneJS for the application bootstrapped using * `bootstrapApplication`. diff --git a/packages/core/src/compiler/compiler_facade.ts b/packages/core/src/compiler/compiler_facade.ts index d81956de3eff3..53c0a3fbc7fcc 100644 --- a/packages/core/src/compiler/compiler_facade.ts +++ b/packages/core/src/compiler/compiler_facade.ts @@ -16,7 +16,7 @@ export const enum JitCompilerUsage { interface JitCompilerUsageRequest { usage: JitCompilerUsage; - kind: 'directive'|'component'|'pipe'|'injectable'|'NgModule'; + kind: 'directive' | 'component' | 'pipe' | 'injectable' | 'NgModule'; type: Type; } @@ -31,24 +31,17 @@ export function getCompilerFacade(request: JitCompilerUsageRequest): CompilerFac // console. console.error(`JIT compilation failed for ${request.kind}`, request.type); - let message = `The ${request.kind} '${ - request - .type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`; + let message = `The ${request.kind} '${request.type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`; if (request.usage === JitCompilerUsage.PartialDeclaration) { message += `The ${request.kind} is part of a library that has been partially compiled.\n`; - message += - `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`; + message += `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`; message += '\n'; - message += - `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`; + message += `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`; } else { - message += - `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`; + message += `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`; } - message += - `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`; - message += - `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`; + message += `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`; + message += `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`; throw new Error(message); } else { throw new Error('JIT compiler unavailable'); diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index 6330b161ff648..c89bd84815346 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -26,45 +26,83 @@ export interface ExportedCompilerFacade { } export interface CompilerFacade { - compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3PipeMetadataFacade): - any; + compilePipe( + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3PipeMetadataFacade, + ): any; compilePipeDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, declaration: R3DeclarePipeFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + declaration: R3DeclarePipeFacade, + ): any; compileInjectable( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectableMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3InjectableMetadataFacade, + ): any; compileInjectableDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DeclareInjectableFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3DeclareInjectableFacade, + ): any; compileInjector( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3InjectorMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3InjectorMetadataFacade, + ): any; compileInjectorDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, - declaration: R3DeclareInjectorFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + declaration: R3DeclareInjectorFacade, + ): any; compileNgModule( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3NgModuleMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3NgModuleMetadataFacade, + ): any; compileNgModuleDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, - declaration: R3DeclareNgModuleFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + declaration: R3DeclareNgModuleFacade, + ): any; compileDirective( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3DirectiveMetadataFacade, + ): any; compileDirectiveDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, - declaration: R3DeclareDirectiveFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + declaration: R3DeclareDirectiveFacade, + ): any; compileComponent( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3ComponentMetadataFacade, + ): any; compileComponentDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, - declaration: R3DeclareComponentFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + declaration: R3DeclareComponentFacade, + ): any; compileFactory( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3FactoryDefMetadataFacade, + ): any; compileFactoryDeclaration( - angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DeclareFactoryFacade): any; + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3DeclareFactoryFacade, + ): any; createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; FactoryTarget: typeof FactoryTarget; // Note that we do not use `{new(): ResourceLoader}` here because // the resource loader class is abstract and not constructable. - ResourceLoader: Function&{prototype: ResourceLoader}; + ResourceLoader: Function & {prototype: ResourceLoader}; } export interface CoreEnvironment { @@ -72,7 +110,7 @@ export interface CoreEnvironment { } export type ResourceLoader = { - get(url: string): Promise|string; + get(url: string): Promise | string; }; export type Provider = unknown; @@ -89,7 +127,7 @@ export enum FactoryTarget { export interface R3DependencyMetadataFacade { token: OpaqueValue; - attribute: string|null; + attribute: string | null; host: boolean; optional: boolean; self: boolean; @@ -117,7 +155,7 @@ export interface R3InjectableMetadataFacade { name: string; type: Type; typeArgumentCount: number; - providedIn?: Type|'root'|'platform'|'any'|null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; useClass?: OpaqueValue; useFactory?: OpaqueValue; useExisting?: OpaqueValue; @@ -131,8 +169,8 @@ export interface R3NgModuleMetadataFacade { declarations: Function[]; imports: Function[]; exports: Function[]; - schemas: {name: string}[]|null; - id: string|null; + schemas: {name: string}[] | null; + id: string | null; } export interface R3InjectorMetadataFacade { @@ -152,30 +190,30 @@ export interface R3DirectiveMetadataFacade { name: string; type: Type; typeSourceSpan: ParseSourceSpan; - selector: string|null; + selector: string | null; queries: R3QueryMetadataFacade[]; host: {[key: string]: string}; propMetadata: {[key: string]: OpaqueValue[]}; - lifecycle: {usesOnChanges: boolean;}; - inputs: (string|{name: string, alias?: string, required?: boolean})[]; + lifecycle: {usesOnChanges: boolean}; + inputs: (string | {name: string; alias?: string; required?: boolean})[]; outputs: string[]; usesInheritance: boolean; - exportAs: string[]|null; - providers: Provider[]|null; + exportAs: string[] | null; + providers: Provider[] | null; viewQueries: R3QueryMetadataFacade[]; isStandalone: boolean; isSignal: boolean; - hostDirectives: R3HostDirectiveMetadataFacade[]|null; + hostDirectives: R3HostDirectiveMetadataFacade[] | null; } export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { template: string; preserveWhitespaces: boolean; - animations: OpaqueValue[]|undefined; + animations: OpaqueValue[] | undefined; declarations: R3TemplateDependencyFacade[]; styles: string[]; encapsulation: ViewEncapsulation; - viewProviders: Provider[]|null; + viewProviders: Provider[] | null; interpolation?: [string, string]; changeDetection?: ChangeDetectionStrategy; } @@ -183,19 +221,22 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { // TODO(legacy-partial-output-inputs): Remove in v18. // https://github.com/angular/angular/blob/d4b423690210872b5c32a322a6090beda30b05a3/packages/core/src/compiler/compiler_facade_interface.ts#L197-L199 export type LegacyInputPartialMapping = - string|[bindingPropertyName: string, classPropertyName: string, transformFunction?: Function]; + | string + | [bindingPropertyName: string, classPropertyName: string, transformFunction?: Function]; export interface R3DeclareDirectiveFacade { selector?: string; type: Type; inputs?: { - [fieldName: string]: { - classPropertyName: string, - publicName: string, - isSignal: boolean, - isRequired: boolean, - transformFunction: Function|null, - }|LegacyInputPartialMapping; + [fieldName: string]: + | { + classPropertyName: string; + publicName: string; + isSignal: boolean; + isRequired: boolean; + transformFunction: Function | null; + } + | LegacyInputPartialMapping; }; outputs?: {[classPropertyName: string]: string}; host?: { @@ -212,7 +253,7 @@ export interface R3DeclareDirectiveFacade { usesInheritance?: boolean; usesOnChanges?: boolean; isStandalone?: boolean; - hostDirectives?: R3HostDirectiveMetadataFacade[]|null; + hostDirectives?: R3HostDirectiveMetadataFacade[] | null; isSignal?: boolean; } @@ -227,10 +268,9 @@ export interface R3DeclareComponentFacade extends R3DeclareDirectiveFacade { // Pre-standalone libraries have separate component/directive/pipe fields: components?: R3DeclareDirectiveDependencyFacade[]; directives?: R3DeclareDirectiveDependencyFacade[]; - pipes?: {[pipeName: string]: OpaqueValue|(() => OpaqueValue)}; + pipes?: {[pipeName: string]: OpaqueValue | (() => OpaqueValue)}; - - deferBlockDependencies?: (() => Promise| null)[]; + deferBlockDependencies?: (() => Promise | null)[]; viewProviders?: OpaqueValue; animations?: OpaqueValue; changeDetection?: ChangeDetectionStrategy; @@ -240,14 +280,17 @@ export interface R3DeclareComponentFacade extends R3DeclareDirectiveFacade { } export type R3DeclareTemplateDependencyFacade = { - kind: string -}&(R3DeclareDirectiveDependencyFacade|R3DeclarePipeDependencyFacade| - R3DeclareNgModuleDependencyFacade); + kind: string; +} & ( + | R3DeclareDirectiveDependencyFacade + | R3DeclarePipeDependencyFacade + | R3DeclareNgModuleDependencyFacade +); export interface R3DeclareDirectiveDependencyFacade { - kind?: 'directive'|'component'; + kind?: 'directive' | 'component'; selector: string; - type: OpaqueValue|(() => OpaqueValue); + type: OpaqueValue | (() => OpaqueValue); inputs?: string[]; outputs?: string[]; exportAs?: string[]; @@ -256,12 +299,12 @@ export interface R3DeclareDirectiveDependencyFacade { export interface R3DeclarePipeDependencyFacade { kind?: 'pipe'; name: string; - type: OpaqueValue|(() => OpaqueValue); + type: OpaqueValue | (() => OpaqueValue); } export interface R3DeclareNgModuleDependencyFacade { kind: 'ngmodule'; - type: OpaqueValue|(() => OpaqueValue); + type: OpaqueValue | (() => OpaqueValue); } export enum R3TemplateDependencyKind { @@ -272,25 +315,25 @@ export enum R3TemplateDependencyKind { export interface R3TemplateDependencyFacade { kind: R3TemplateDependencyKind; - type: OpaqueValue|(() => OpaqueValue); + type: OpaqueValue | (() => OpaqueValue); } export interface R3FactoryDefMetadataFacade { name: string; type: Type; typeArgumentCount: number; - deps: R3DependencyMetadataFacade[]|null; + deps: R3DependencyMetadataFacade[] | null; target: FactoryTarget; } export interface R3DeclareFactoryFacade { type: Type; - deps: R3DeclareDependencyMetadataFacade[]|'invalid'|null; + deps: R3DeclareDependencyMetadataFacade[] | 'invalid' | null; target: FactoryTarget; } export interface R3DeclareInjectableFacade { type: Type; - providedIn?: Type|'root'|'platform'|'any'|null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; useClass?: OpaqueValue; useFactory?: OpaqueValue; useExisting?: OpaqueValue; @@ -302,7 +345,7 @@ export enum ViewEncapsulation { Emulated = 0, // Historically the 1 value was for `Native` encapsulation which has been removed as of v11. None = 2, - ShadowDom = 3 + ShadowDom = 3, } export type ChangeDetectionStrategy = number; @@ -310,10 +353,10 @@ export type ChangeDetectionStrategy = number; export interface R3QueryMetadataFacade { propertyName: string; first: boolean; - predicate: OpaqueValue|string[]; + predicate: OpaqueValue | string[]; descendants: boolean; emitDistinctChangesOnly: boolean; - read: OpaqueValue|null; + read: OpaqueValue | null; static: boolean; isSignal: boolean; } @@ -321,7 +364,7 @@ export interface R3QueryMetadataFacade { export interface R3DeclareQueryMetadataFacade { propertyName: string; first?: boolean; - predicate: OpaqueValue|string[]; + predicate: OpaqueValue | string[]; descendants?: boolean; read?: OpaqueValue; static?: boolean; @@ -337,10 +380,10 @@ export interface R3DeclareInjectorFacade { export interface R3DeclareNgModuleFacade { type: Type; - bootstrap?: OpaqueValue[]|(() => OpaqueValue[]); - declarations?: OpaqueValue[]|(() => OpaqueValue[]); - imports?: OpaqueValue[]|(() => OpaqueValue[]); - exports?: OpaqueValue[]|(() => OpaqueValue[]); + bootstrap?: OpaqueValue[] | (() => OpaqueValue[]); + declarations?: OpaqueValue[] | (() => OpaqueValue[]); + imports?: OpaqueValue[] | (() => OpaqueValue[]); + exports?: OpaqueValue[] | (() => OpaqueValue[]); schemas?: OpaqueValue[]; id?: OpaqueValue; } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 3d94a7e371617..af451cb3a873a 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -24,24 +24,63 @@ export * from './metadata'; export * from './version'; export {TypeDecorator} from './util/decorators'; export * from './di'; -export {BootstrapOptions, ApplicationRef, NgProbeToken, APP_BOOTSTRAP_LISTENER} from './application/application_ref'; +export { + BootstrapOptions, + ApplicationRef, + NgProbeToken, + APP_BOOTSTRAP_LISTENER, +} from './application/application_ref'; export {PlatformRef} from './platform/platform_ref'; -export {createPlatform, createPlatformFactory, assertPlatform, destroyPlatform, getPlatform} from './platform/platform'; -export {provideZoneChangeDetection, NgZoneOptions} from './change_detection/scheduling/ng_zone_scheduling'; +export { + createPlatform, + createPlatformFactory, + assertPlatform, + destroyPlatform, + getPlatform, +} from './platform/platform'; +export { + provideZoneChangeDetection, + NgZoneOptions, +} from './change_detection/scheduling/ng_zone_scheduling'; export {provideExperimentalZonelessChangeDetection} from './change_detection/scheduling/zoneless_scheduling_impl'; export {ExperimentalPendingTasks} from './pending_tasks'; export {enableProdMode, isDevMode} from './util/is_dev_mode'; -export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, PLATFORM_ID, ANIMATION_MODULE_TYPE, CSP_NONCE} from './application/application_tokens'; +export { + APP_ID, + PACKAGE_ROOT_URL, + PLATFORM_INITIALIZER, + PLATFORM_ID, + ANIMATION_MODULE_TYPE, + CSP_NONCE, +} from './application/application_tokens'; export {APP_INITIALIZER, ApplicationInitStatus} from './application/application_init'; export * from './zone'; export * from './render'; export * from './linker'; export * from './linker/ng_module_factory_loader_impl'; -export {DebugElement, DebugEventListener, DebugNode, asNativeElements, getDebugNode, Predicate} from './debug/debug_node'; -export {GetTestability, Testability, TestabilityRegistry, setTestabilityGetter} from './testability/testability'; +export { + DebugElement, + DebugEventListener, + DebugNode, + asNativeElements, + getDebugNode, + Predicate, +} from './debug/debug_node'; +export { + GetTestability, + Testability, + TestabilityRegistry, + setTestabilityGetter, +} from './testability/testability'; export * from './change_detection'; export * from './platform/platform_core_providers'; -export {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, DEFAULT_CURRENCY_CODE, MissingTranslationStrategy} from './i18n/tokens'; +export { + TRANSLATIONS, + TRANSLATIONS_FORMAT, + LOCALE_ID, + DEFAULT_CURRENCY_CODE, + MissingTranslationStrategy, +} from './i18n/tokens'; export {ApplicationModule} from './application/application_module'; export {AbstractType, Type} from './interface/type'; export {EventEmitter} from './event_emitter'; @@ -51,10 +90,20 @@ export * from './core_render3_private_export'; export * from './core_reactivity_export'; export {SecurityContext} from './sanitization/security'; export {Sanitizer} from './sanitization/sanitizer'; -export {createNgModule, createNgModuleRef, createEnvironmentInjector} from './render3/ng_module_ref'; +export { + createNgModule, + createNgModuleRef, + createEnvironmentInjector, +} from './render3/ng_module_ref'; export {createComponent, reflectComponentType, ComponentMirror} from './render3/component'; export {isStandalone} from './render3/definition'; -export {AfterRenderRef, AfterRenderOptions, AfterRenderPhase, afterRender, afterNextRender} from './render3/after_render_hooks'; +export { + AfterRenderRef, + AfterRenderOptions, + AfterRenderPhase, + afterRender, + afterNextRender, +} from './render3/after_render_hooks'; export {ApplicationConfig, mergeApplicationConfig} from './application/application_config'; export {makeStateKey, StateKey, TransferState} from './transfer_state'; export {booleanAttribute, numberAttribute} from './util/coercion'; @@ -64,12 +113,13 @@ if (typeof ngDevMode !== 'undefined' && ngDevMode) { // This helper is to give a reasonable error message to people upgrading to v9 that have not yet // installed `@angular/localize` in their app. // tslint:disable-next-line: no-toplevel-property-access - global.$localize ??= function() { + global.$localize ??= function () { throw new Error( - 'It looks like your application or one of its dependencies is using i18n.\n' + + 'It looks like your application or one of its dependencies is using i18n.\n' + 'Angular 9 introduced a global `$localize()` function that needs to be loaded.\n' + 'Please run `ng add @angular/localize` from the Angular CLI.\n' + - '(For non-CLI projects, add `import \'@angular/localize/init\';` to your `polyfills.ts` file.\n' + - 'For server-side rendering applications add the import to your `main.server.ts` file.)'); + "(For non-CLI projects, add `import '@angular/localize/init';` to your `polyfills.ts` file.\n" + + 'For server-side rendering applications add the import to your `main.server.ts` file.)', + ); }; } diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 18a6750c546c3..8be806b6efe15 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -7,41 +7,119 @@ */ export {setAlternateWeakRefImpl as ɵsetAlternateWeakRefImpl} from '../primitives/signals'; -export {detectChangesInViewIfRequired as ɵdetectChangesInViewIfRequired, whenStable as ɵwhenStable} from './application/application_ref'; -export {IMAGE_CONFIG as ɵIMAGE_CONFIG, IMAGE_CONFIG_DEFAULTS as ɵIMAGE_CONFIG_DEFAULTS, ImageConfig as ɵImageConfig} from './application/application_tokens'; +export { + detectChangesInViewIfRequired as ɵdetectChangesInViewIfRequired, + whenStable as ɵwhenStable, +} from './application/application_ref'; +export { + IMAGE_CONFIG as ɵIMAGE_CONFIG, + IMAGE_CONFIG_DEFAULTS as ɵIMAGE_CONFIG_DEFAULTS, + ImageConfig as ɵImageConfig, +} from './application/application_tokens'; export {internalCreateApplication as ɵinternalCreateApplication} from './application/create_application'; -export {defaultIterableDiffers as ɵdefaultIterableDiffers, defaultKeyValueDiffers as ɵdefaultKeyValueDiffers} from './change_detection/change_detection'; -export {ChangeDetectionScheduler as ɵChangeDetectionScheduler, NotificationSource as ɵNotificationSource, ZONELESS_ENABLED as ɵZONELESS_ENABLED} from './change_detection/scheduling/zoneless_scheduling'; +export { + defaultIterableDiffers as ɵdefaultIterableDiffers, + defaultKeyValueDiffers as ɵdefaultKeyValueDiffers, +} from './change_detection/change_detection'; +export { + ChangeDetectionScheduler as ɵChangeDetectionScheduler, + NotificationSource as ɵNotificationSource, + ZONELESS_ENABLED as ɵZONELESS_ENABLED, +} from './change_detection/scheduling/zoneless_scheduling'; export {Console as ɵConsole} from './console'; -export {DeferBlockDetails as ɵDeferBlockDetails, getDeferBlocks as ɵgetDeferBlocks} from './defer/discovery'; -export {renderDeferBlockState as ɵrenderDeferBlockState, triggerResourceLoading as ɵtriggerResourceLoading} from './defer/instructions'; -export {DeferBlockBehavior as ɵDeferBlockBehavior, DeferBlockConfig as ɵDeferBlockConfig, DeferBlockState as ɵDeferBlockState} from './defer/interfaces'; -export {convertToBitFlags as ɵconvertToBitFlags, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility'; -export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDeclaration, ɵɵInjectorDef} from './di/interface/defs'; -export {InternalEnvironmentProviders as ɵInternalEnvironmentProviders, isEnvironmentProviders as ɵisEnvironmentProviders} from './di/interface/provider'; +export { + DeferBlockDetails as ɵDeferBlockDetails, + getDeferBlocks as ɵgetDeferBlocks, +} from './defer/discovery'; +export { + renderDeferBlockState as ɵrenderDeferBlockState, + triggerResourceLoading as ɵtriggerResourceLoading, +} from './defer/instructions'; +export { + DeferBlockBehavior as ɵDeferBlockBehavior, + DeferBlockConfig as ɵDeferBlockConfig, + DeferBlockState as ɵDeferBlockState, +} from './defer/interfaces'; +export { + convertToBitFlags as ɵconvertToBitFlags, + setCurrentInjector as ɵsetCurrentInjector, +} from './di/injector_compatibility'; +export { + getInjectableDef as ɵgetInjectableDef, + ɵɵInjectableDeclaration, + ɵɵInjectorDef, +} from './di/interface/defs'; +export { + InternalEnvironmentProviders as ɵInternalEnvironmentProviders, + isEnvironmentProviders as ɵisEnvironmentProviders, +} from './di/interface/provider'; export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope'; export {XSS_SECURITY_URL as ɵXSS_SECURITY_URL} from './error_details_base_url'; -export {formatRuntimeError as ɵformatRuntimeError, RuntimeError as ɵRuntimeError, RuntimeErrorCode as ɵRuntimeErrorCode} from './errors'; +export { + formatRuntimeError as ɵformatRuntimeError, + RuntimeError as ɵRuntimeError, + RuntimeErrorCode as ɵRuntimeErrorCode, +} from './errors'; export {annotateForHydration as ɵannotateForHydration} from './hydration/annotate'; -export {withDomHydration as ɵwithDomHydration, withI18nSupport as ɵwithI18nSupport} from './hydration/api'; +export { + withDomHydration as ɵwithDomHydration, + withI18nSupport as ɵwithI18nSupport, +} from './hydration/api'; export {withEventReplay as ɵwithEventReplay} from './hydration/event_replay'; export {IS_HYDRATION_DOM_REUSE_ENABLED as ɵIS_HYDRATION_DOM_REUSE_ENABLED} from './hydration/tokens'; -export {HydratedNode as ɵHydratedNode, HydrationInfo as ɵHydrationInfo, readHydrationInfo as ɵreadHydrationInfo, SSR_CONTENT_INTEGRITY_MARKER as ɵSSR_CONTENT_INTEGRITY_MARKER} from './hydration/utils'; -export {CurrencyIndex as ɵCurrencyIndex, ExtraLocaleDataIndex as ɵExtraLocaleDataIndex, findLocaleData as ɵfindLocaleData, getLocaleCurrencyCode as ɵgetLocaleCurrencyCode, getLocalePluralCase as ɵgetLocalePluralCase, LocaleDataIndex as ɵLocaleDataIndex, registerLocaleData as ɵregisterLocaleData, unregisterAllLocaleData as ɵunregisterLocaleData} from './i18n/locale_data_api'; +export { + HydratedNode as ɵHydratedNode, + HydrationInfo as ɵHydrationInfo, + readHydrationInfo as ɵreadHydrationInfo, + SSR_CONTENT_INTEGRITY_MARKER as ɵSSR_CONTENT_INTEGRITY_MARKER, +} from './hydration/utils'; +export { + CurrencyIndex as ɵCurrencyIndex, + ExtraLocaleDataIndex as ɵExtraLocaleDataIndex, + findLocaleData as ɵfindLocaleData, + getLocaleCurrencyCode as ɵgetLocaleCurrencyCode, + getLocalePluralCase as ɵgetLocalePluralCase, + LocaleDataIndex as ɵLocaleDataIndex, + registerLocaleData as ɵregisterLocaleData, + unregisterAllLocaleData as ɵunregisterLocaleData, +} from './i18n/locale_data_api'; export {DEFAULT_LOCALE_ID as ɵDEFAULT_LOCALE_ID} from './i18n/localization'; export {Writable as ɵWritable} from './interface/type'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; -export {clearResolutionOfComponentResourcesQueue as ɵclearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution as ɵisComponentDefPendingResolution, resolveComponentResources as ɵresolveComponentResources, restoreComponentResolutionQueue as ɵrestoreComponentResolutionQueue} from './metadata/resource_loading'; +export { + clearResolutionOfComponentResourcesQueue as ɵclearResolutionOfComponentResourcesQueue, + isComponentDefPendingResolution as ɵisComponentDefPendingResolution, + resolveComponentResources as ɵresolveComponentResources, + restoreComponentResolutionQueue as ɵrestoreComponentResolutionQueue, +} from './metadata/resource_loading'; export {PendingTasks as ɵPendingTasks} from './pending_tasks'; export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS} from './platform/platform'; export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; export {AnimationRendererType as ɵAnimationRendererType} from './render/api'; -export {InjectorProfilerContext as ɵInjectorProfilerContext, ProviderRecord as ɵProviderRecord, setInjectorProfilerContext as ɵsetInjectorProfilerContext} from './render3/debug/injector_profiler'; +export { + InjectorProfilerContext as ɵInjectorProfilerContext, + ProviderRecord as ɵProviderRecord, + setInjectorProfilerContext as ɵsetInjectorProfilerContext, +} from './render3/debug/injector_profiler'; export {queueStateUpdate as ɵqueueStateUpdate} from './render3/queue_state_update'; -export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass'; +export { + allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, + BypassType as ɵBypassType, + getSanitizationBypassType as ɵgetSanitizationBypassType, + SafeHtml as ɵSafeHtml, + SafeResourceUrl as ɵSafeResourceUrl, + SafeScript as ɵSafeScript, + SafeStyle as ɵSafeStyle, + SafeUrl as ɵSafeUrl, + SafeValue as ɵSafeValue, + unwrapSafeValue as ɵunwrapSafeValue, +} from './sanitization/bypass'; export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer'; export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer'; -export {TESTABILITY as ɵTESTABILITY, TESTABILITY_GETTER as ɵTESTABILITY_GETTER} from './testability/testability'; +export { + TESTABILITY as ɵTESTABILITY, + TESTABILITY_GETTER as ɵTESTABILITY_GETTER, +} from './testability/testability'; export {booleanAttribute, numberAttribute} from './util/coercion'; export {devModeEqual as ɵdevModeEqual} from './util/comparison'; export {global as ɵglobal} from './util/global'; diff --git a/packages/core/src/core_reactivity_export_internal.ts b/packages/core/src/core_reactivity_export_internal.ts index 0cbba7adb4959..4af08141267f4 100644 --- a/packages/core/src/core_reactivity_export_internal.ts +++ b/packages/core/src/core_reactivity_export_internal.ts @@ -7,24 +7,15 @@ */ // clang-format off -export { - isSignal, - Signal, - ValueEqualityFn, -} from './render3/reactivity/api'; -export { - computed, - CreateComputedOptions, -} from './render3/reactivity/computed'; +export {isSignal, Signal, ValueEqualityFn} from './render3/reactivity/api'; +export {computed, CreateComputedOptions} from './render3/reactivity/computed'; export { CreateSignalOptions, signal, WritableSignal, ɵunwrapWritableSignal, } from './render3/reactivity/signal'; -export { - untracked, -} from './render3/reactivity/untracked'; +export {untracked} from './render3/reactivity/untracked'; export { CreateEffectOptions, effect, @@ -33,7 +24,5 @@ export { EffectCleanupRegisterFn, EffectScheduler as ɵEffectScheduler, } from './render3/reactivity/effect'; -export { - assertNotInReactiveContext, -} from './render3/reactivity/asserts'; +export {assertNotInReactiveContext} from './render3/reactivity/asserts'; // clang-format on diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index c0c4e11df6e6f..76c67dabd1446 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -11,18 +11,10 @@ // performed by rollup while it's creating fesm files. // // no code actually imports these symbols from the @angular/core entry point -export { - isBoundToModule as ɵisBoundToModule -} from './application/application_ref'; -export { - compileNgModuleFactory as ɵcompileNgModuleFactory, -} from './application/application_ngmodule_factory_compiler'; -export { - injectChangeDetectorRef as ɵinjectChangeDetectorRef, -} from './change_detection/change_detector_ref'; -export { - getDebugNode as ɵgetDebugNode, -} from './debug/debug_node'; +export {isBoundToModule as ɵisBoundToModule} from './application/application_ref'; +export {compileNgModuleFactory as ɵcompileNgModuleFactory} from './application/application_ngmodule_factory_compiler'; +export {injectChangeDetectorRef as ɵinjectChangeDetectorRef} from './change_detection/change_detector_ref'; +export {getDebugNode as ɵgetDebugNode} from './debug/debug_node'; export { NG_INJ_DEF as ɵNG_INJ_DEF, NG_PROV_DEF as ɵNG_PROV_DEF, @@ -37,9 +29,7 @@ export { NgModuleDef as ɵNgModuleDef, NgModuleTransitiveScopes as ɵNgModuleTransitiveScopes, } from './metadata/ng_module_def'; -export { - getLContext as ɵgetLContext -} from './render3/context_discovery'; +export {getLContext as ɵgetLContext} from './render3/context_discovery'; export { NG_COMP_DEF as ɵNG_COMP_DEF, NG_DIR_DEF as ɵNG_DIR_DEF, @@ -180,12 +170,10 @@ export { ɵɵresolveDocument, ɵɵresolveWindow, ɵɵrestoreView, - ɵɵrepeater, ɵɵrepeaterCreate, ɵɵrepeaterTrackByIdentity, ɵɵrepeaterTrackByIndex, - ɵɵsetComponentScope, ɵɵsetNgModuleScope, ɵɵgetComponentDepsFactory, @@ -249,24 +237,16 @@ export { ɵgetUnknownElementStrictMode, ɵsetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, - ɵsetUnknownPropertyStrictMode + ɵsetUnknownPropertyStrictMode, } from './render3/index'; -export { - CONTAINER_HEADER_OFFSET as ɵCONTAINER_HEADER_OFFSET, -} from './render3/interfaces/container'; -export { - LContext as ɵLContext, -} from './render3/interfaces/context'; -export { - setDocument as ɵsetDocument -} from './render3/interfaces/document'; +export {CONTAINER_HEADER_OFFSET as ɵCONTAINER_HEADER_OFFSET} from './render3/interfaces/container'; +export {LContext as ɵLContext} from './render3/interfaces/context'; +export {setDocument as ɵsetDocument} from './render3/interfaces/document'; export { compileComponent as ɵcompileComponent, compileDirective as ɵcompileDirective, } from './render3/jit/directive'; -export { - resetJitOptions as ɵresetJitOptions, -} from './render3/jit/jit_options'; +export {resetJitOptions as ɵresetJitOptions} from './render3/jit/jit_options'; export { compileNgModule as ɵcompileNgModule, compileNgModuleDefs as ɵcompileNgModuleDefs, @@ -287,14 +267,10 @@ export { ɵɵngDeclareNgModule, ɵɵngDeclarePipe, } from './render3/jit/partial'; -export { - compilePipe as ɵcompilePipe, -} from './render3/jit/pipe'; -export { - isNgModule as ɵisNgModule -} from './render3/jit/util'; -export { Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent } from './render3/profiler'; -export { GlobalDevModeUtils as ɵGlobalDevModeUtils } from './render3/util/global_utils'; +export {compilePipe as ɵcompilePipe} from './render3/jit/pipe'; +export {isNgModule as ɵisNgModule} from './render3/jit/util'; +export {Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent} from './render3/profiler'; +export {GlobalDevModeUtils as ɵGlobalDevModeUtils} from './render3/util/global_utils'; export {ViewRef as ɵViewRef} from './render3/view_ref'; export { bypassSanitizationTrustHtml as ɵbypassSanitizationTrustHtml, @@ -313,14 +289,16 @@ export { ɵɵtrustConstantHtml, ɵɵtrustConstantResourceUrl, } from './sanitization/sanitization'; +export {ɵɵvalidateIframeAttribute} from './sanitization/iframe_attrs_validation'; +export {noSideEffects as ɵnoSideEffects} from './util/closure'; export { - ɵɵvalidateIframeAttribute, -} from './sanitization/iframe_attrs_validation'; + AfterRenderEventManager as ɵAfterRenderEventManager, + internalAfterNextRender as ɵinternalAfterNextRender, +} from './render3/after_render_hooks'; export { - noSideEffects as ɵnoSideEffects, -} from './util/closure'; -export { AfterRenderEventManager as ɵAfterRenderEventManager, internalAfterNextRender as ɵinternalAfterNextRender } from './render3/after_render_hooks'; -export {depsTracker as ɵdepsTracker, USE_RUNTIME_DEPS_TRACKER_FOR_JIT as ɵUSE_RUNTIME_DEPS_TRACKER_FOR_JIT} from './render3/deps_tracker/deps_tracker'; + depsTracker as ɵdepsTracker, + USE_RUNTIME_DEPS_TRACKER_FOR_JIT as ɵUSE_RUNTIME_DEPS_TRACKER_FOR_JIT, +} from './render3/deps_tracker/deps_tracker'; export {generateStandaloneInDeclarationsError as ɵgenerateStandaloneInDeclarationsError} from './render3/jit/module'; export {getAsyncClassMetadataFn as ɵgetAsyncClassMetadataFn} from './render3/metadata'; diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index 8e60be24b15c1..240d00df1bdfc 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -12,8 +12,23 @@ import {getLContext} from '../render3/context_discovery'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces/container'; import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node'; import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks'; -import {DECLARATION_COMPONENT_VIEW, LView, PARENT, T_HOST, TData, TVIEW} from '../render3/interfaces/view'; -import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, getOwningComponent} from '../render3/util/discovery_utils'; +import { + DECLARATION_COMPONENT_VIEW, + LView, + PARENT, + T_HOST, + TData, + TVIEW, +} from '../render3/interfaces/view'; +import { + getComponent, + getContext, + getInjectionTokens, + getInjector, + getListeners, + getLocalRefs, + getOwningComponent, +} from '../render3/util/discovery_utils'; import {INTERPOLATION_DELIMITER} from '../render3/util/misc_utils'; import {renderStringify} from '../render3/util/stringify_utils'; import {getComponentLViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils'; @@ -23,7 +38,10 @@ import {assertDomNode} from '../util/assert'; * @publicApi */ export class DebugEventListener { - constructor(public name: string, public callback: Function) {} + constructor( + public name: string, + public callback: Function, + ) {} } /** @@ -49,7 +67,7 @@ export class DebugNode { /** * The `DebugElement` parent. Will be `null` if this is the root element. */ - get parent(): DebugElement|null { + get parent(): DebugElement | null { const parent = this.nativeNode.parentNode as Element; return parent ? new DebugElement(parent) : null; } @@ -66,8 +84,9 @@ export class DebugNode { */ get componentInstance(): any { const nativeElement = this.nativeNode; - return nativeElement && - (getComponent(nativeElement as Element) || getOwningComponent(nativeElement)); + return ( + nativeElement && (getComponent(nativeElement as Element) || getOwningComponent(nativeElement)) + ); } /** @@ -87,7 +106,7 @@ export class DebugNode { * properties. */ get listeners(): DebugEventListener[] { - return getListeners(this.nativeNode as Element).filter(listener => listener.type === 'dom'); + return getListeners(this.nativeNode as Element).filter((listener) => listener.type === 'dom'); } /** @@ -124,7 +143,7 @@ export class DebugElement extends DebugNode { * The underlying DOM element at the root of the component. */ get nativeElement(): any { - return this.nativeNode.nodeType == Node.ELEMENT_NODE ? this.nativeNode as Element : null; + return this.nativeNode.nodeType == Node.ELEMENT_NODE ? (this.nativeNode as Element) : null; } /** @@ -155,7 +174,7 @@ export class DebugElement extends DebugNode { * - input property bindings (e.g. `[myCustomInput]="value"`) * - attribute bindings (e.g. `[attr.role]="menu"`) */ - get properties(): {[key: string]: any;} { + get properties(): {[key: string]: any} { const context = getLContext(this.nativeNode)!; const lView = context ? context.lView : null; @@ -179,8 +198,8 @@ export class DebugElement extends DebugNode { * A map of attribute names to attribute values for an element. */ // TODO: replace null by undefined in the return type - get attributes(): {[key: string]: string|null} { - const attributes: {[key: string]: string|null} = {}; + get attributes(): {[key: string]: string | null} { + const attributes: {[key: string]: string | null} = {}; const element = this.nativeElement as Element | undefined; if (!element) { @@ -236,7 +255,7 @@ export class DebugElement extends DebugNode { * The inline styles of the DOM element. */ // TODO: replace null by undefined in the return type - get styles(): {[key: string]: string|null} { + get styles(): {[key: string]: string | null} { const element = this.nativeElement as HTMLElement | null; return (element?.style ?? {}) as {[key: string]: string | null}; } @@ -258,9 +277,9 @@ export class DebugElement extends DebugNode { // SVG elements return an `SVGAnimatedString` instead of a plain string for the `className`. const className = element.className as string | SVGAnimatedString; const classes = - typeof className !== 'string' ? className.baseVal.split(' ') : className.split(' '); + typeof className !== 'string' ? className.baseVal.split(' ') : className.split(' '); - classes.forEach((value: string) => result[value] = true); + classes.forEach((value: string) => (result[value] = true)); return result; } @@ -337,7 +356,7 @@ export class DebugElement extends DebugNode { const node = this.nativeNode as any; const invokedListeners: Function[] = []; - this.listeners.forEach(listener => { + this.listeners.forEach((listener) => { if (listener.name === eventName) { const callback = listener.callback; callback.call(node, eventObj); @@ -360,15 +379,17 @@ export class DebugElement extends DebugNode { // strip the name, turning the condition in to ("" === "") and always returning true. if (listener.toString().indexOf('__ngUnwrap__') !== -1) { const unwrappedListener = listener('__ngUnwrap__'); - return invokedListeners.indexOf(unwrappedListener) === -1 && - unwrappedListener.call(node, eventObj); + return ( + invokedListeners.indexOf(unwrappedListener) === -1 && + unwrappedListener.call(node, eventObj) + ); } }); } } } -function copyDomProperties(element: Element|null, properties: {[name: string]: string}): void { +function copyDomProperties(element: Element | null, properties: {[name: string]: string}): void { if (element) { // Skip own properties (as those are patched) let obj = Object.getPrototypeOf(element); @@ -392,8 +413,12 @@ function copyDomProperties(element: Element|null, properties: {[name: string]: s } function isPrimitiveValue(value: any): boolean { - return typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' || - value === null; + return ( + typeof value === 'string' || + typeof value === 'boolean' || + typeof value === 'number' || + value === null + ); } /** @@ -405,20 +430,35 @@ function isPrimitiveValue(value: any): boolean { * @param elementsOnly whether only elements should be searched */ function _queryAll( - parentElement: DebugElement, predicate: Predicate, matches: DebugElement[], - elementsOnly: true): void; + parentElement: DebugElement, + predicate: Predicate, + matches: DebugElement[], + elementsOnly: true, +): void; function _queryAll( - parentElement: DebugElement, predicate: Predicate, matches: DebugNode[], - elementsOnly: false): void; + parentElement: DebugElement, + predicate: Predicate, + matches: DebugNode[], + elementsOnly: false, +): void; function _queryAll( - parentElement: DebugElement, predicate: Predicate|Predicate, - matches: DebugElement[]|DebugNode[], elementsOnly: boolean) { + parentElement: DebugElement, + predicate: Predicate | Predicate, + matches: DebugElement[] | DebugNode[], + elementsOnly: boolean, +) { const context = getLContext(parentElement.nativeNode)!; const lView = context ? context.lView : null; if (lView !== null) { const parentTNode = lView[TVIEW].data[context.nodeIndex] as TNode; _queryNodeChildren( - parentTNode, lView, predicate, matches, elementsOnly, parentElement.nativeNode); + parentTNode, + lView, + predicate, + matches, + elementsOnly, + parentElement.nativeNode, + ); } else { // If the context is null, then `parentElement` was either created with Renderer2 or native DOM // APIs. @@ -437,8 +477,13 @@ function _queryAll( * @param rootNativeNode the root native node on which predicate should not be matched */ function _queryNodeChildren( - tNode: TNode, lView: LView, predicate: Predicate|Predicate, - matches: DebugElement[]|DebugNode[], elementsOnly: boolean, rootNativeNode: any) { + tNode: TNode, + lView: LView, + predicate: Predicate | Predicate, + matches: DebugElement[] | DebugNode[], + elementsOnly: boolean, + rootNativeNode: any, +) { ngDevMode && assertTNodeForLView(tNode, lView); const nativeNode = getNativeByTNodeOrNull(tNode, lView); // For each type of TNode, specific logic is executed. @@ -452,8 +497,13 @@ function _queryNodeChildren( const componentView = getComponentLViewByIndex(tNode.index, lView); if (componentView && componentView[TVIEW].firstChild) { _queryNodeChildren( - componentView[TVIEW].firstChild!, componentView, predicate, matches, elementsOnly, - rootNativeNode); + componentView[TVIEW].firstChild!, + componentView, + predicate, + matches, + elementsOnly, + rootNativeNode, + ); } } else { if (tNode.child) { @@ -475,7 +525,12 @@ function _queryNodeChildren( const nodeOrContainer = lView[tNode.index]; if (isLContainer(nodeOrContainer)) { _queryNodeChildrenInContainer( - nodeOrContainer, predicate, matches, elementsOnly, rootNativeNode); + nodeOrContainer, + predicate, + matches, + elementsOnly, + rootNativeNode, + ); } } else if (tNode.type & TNodeType.Container) { // Case 2: the TNode is a container @@ -489,8 +544,9 @@ function _queryNodeChildren( // The nodes projected at this location all need to be processed. const componentView = lView![DECLARATION_COMPONENT_VIEW]; const componentHost = componentView[T_HOST] as TElementNode; - const head: TNode|null = - (componentHost.projection as (TNode | null)[])[tNode.projection as number]; + const head: TNode | null = (componentHost.projection as (TNode | null)[])[ + tNode.projection as number + ]; if (Array.isArray(head)) { for (let nativeNode of head) { @@ -510,7 +566,7 @@ function _queryNodeChildren( if (rootNativeNode !== nativeNode) { // To determine the next node to be processed, we need to use the next or the projectionNext // link, depending on whether the current node has been projected. - const nextTNode = (tNode.flags & TNodeFlags.isProjected) ? tNode.projectionNext : tNode.next; + const nextTNode = tNode.flags & TNodeFlags.isProjected ? tNode.projectionNext : tNode.next; if (nextTNode) { _queryNodeChildren(nextTNode, lView, predicate, matches, elementsOnly, rootNativeNode); } @@ -527,8 +583,12 @@ function _queryNodeChildren( * @param rootNativeNode the root native node on which predicate should not be matched */ function _queryNodeChildrenInContainer( - lContainer: LContainer, predicate: Predicate|Predicate, - matches: DebugElement[]|DebugNode[], elementsOnly: boolean, rootNativeNode: any) { + lContainer: LContainer, + predicate: Predicate | Predicate, + matches: DebugElement[] | DebugNode[], + elementsOnly: boolean, + rootNativeNode: any, +) { for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { const childView = lContainer[i] as LView; const firstChild = childView[TVIEW].firstChild; @@ -548,8 +608,12 @@ function _queryNodeChildrenInContainer( * @param rootNativeNode the root native node on which predicate should not be matched */ function _addQueryMatch( - nativeNode: any, predicate: Predicate|Predicate, - matches: DebugElement[]|DebugNode[], elementsOnly: boolean, rootNativeNode: any) { + nativeNode: any, + predicate: Predicate | Predicate, + matches: DebugElement[] | DebugNode[], + elementsOnly: boolean, + rootNativeNode: any, +) { if (rootNativeNode !== nativeNode) { const debugNode = getDebugNode(nativeNode); if (!debugNode) { @@ -558,12 +622,18 @@ function _addQueryMatch( // Type of the "predicate and "matches" array are set based on the value of // the "elementsOnly" parameter. TypeScript is not able to properly infer these // types with generics, so we manually cast the parameters accordingly. - if (elementsOnly && (debugNode instanceof DebugElement) && predicate(debugNode) && - matches.indexOf(debugNode) === -1) { + if ( + elementsOnly && + debugNode instanceof DebugElement && + predicate(debugNode) && + matches.indexOf(debugNode) === -1 + ) { matches.push(debugNode); } else if ( - !elementsOnly && (predicate as Predicate)(debugNode) && - (matches as DebugNode[]).indexOf(debugNode) === -1) { + !elementsOnly && + (predicate as Predicate)(debugNode) && + (matches as DebugNode[]).indexOf(debugNode) === -1 + ) { (matches as DebugNode[]).push(debugNode); } } @@ -578,8 +648,11 @@ function _addQueryMatch( * @param elementsOnly whether only elements should be searched */ function _queryNativeNodeDescendants( - parentNode: any, predicate: Predicate|Predicate, - matches: DebugElement[]|DebugNode[], elementsOnly: boolean) { + parentNode: any, + predicate: Predicate | Predicate, + matches: DebugElement[] | DebugNode[], + elementsOnly: boolean, +) { const nodes = parentNode.childNodes; const length = nodes.length; @@ -588,12 +661,18 @@ function _queryNativeNodeDescendants( const debugNode = getDebugNode(node); if (debugNode) { - if (elementsOnly && (debugNode instanceof DebugElement) && predicate(debugNode) && - matches.indexOf(debugNode) === -1) { + if ( + elementsOnly && + debugNode instanceof DebugElement && + predicate(debugNode) && + matches.indexOf(debugNode) === -1 + ) { matches.push(debugNode); } else if ( - !elementsOnly && (predicate as Predicate)(debugNode) && - (matches as DebugNode[]).indexOf(debugNode) === -1) { + !elementsOnly && + (predicate as Predicate)(debugNode) && + (matches as DebugNode[]).indexOf(debugNode) === -1 + ) { (matches as DebugNode[]).push(debugNode); } @@ -608,7 +687,11 @@ function _queryNativeNodeDescendants( * defined in templates, not in host bindings. */ function collectPropertyBindings( - properties: {[key: string]: string}, tNode: TNode, lView: LView, tData: TData): void { + properties: {[key: string]: string}, + tNode: TNode, + lView: LView, + tData: TData, +): void { let bindingIndexes = tNode.propertyBindings; if (bindingIndexes !== null) { @@ -630,7 +713,6 @@ function collectPropertyBindings( } } - // Need to keep the nodes in a global Map so that multiple angular apps are supported. const _nativeNodeToDebugNode = new Map(); @@ -639,12 +721,13 @@ const NG_DEBUG_PROPERTY = '__ng_debug__'; /** * @publicApi */ -export function getDebugNode(nativeNode: any): DebugNode|null { +export function getDebugNode(nativeNode: any): DebugNode | null { if (nativeNode instanceof Node) { - if (!(nativeNode.hasOwnProperty(NG_DEBUG_PROPERTY))) { - (nativeNode as any)[NG_DEBUG_PROPERTY] = nativeNode.nodeType == Node.ELEMENT_NODE ? - new DebugElement(nativeNode as Element) : - new DebugNode(nativeNode); + if (!nativeNode.hasOwnProperty(NG_DEBUG_PROPERTY)) { + (nativeNode as any)[NG_DEBUG_PROPERTY] = + nativeNode.nodeType == Node.ELEMENT_NODE + ? new DebugElement(nativeNode as Element) + : new DebugNode(nativeNode); } return (nativeNode as any)[NG_DEBUG_PROPERTY]; } diff --git a/packages/core/src/defer/cleanup.ts b/packages/core/src/defer/cleanup.ts index 23c945db1bae7..b4bbef99ca27b 100644 --- a/packages/core/src/defer/cleanup.ts +++ b/packages/core/src/defer/cleanup.ts @@ -6,14 +6,22 @@ * found in the LICENSE file at https://angular.io/license */ -import {LDeferBlockDetails, PREFETCH_TRIGGER_CLEANUP_FNS, TRIGGER_CLEANUP_FNS, TriggerType} from './interfaces'; +import { + LDeferBlockDetails, + PREFETCH_TRIGGER_CLEANUP_FNS, + TRIGGER_CLEANUP_FNS, + TriggerType, +} from './interfaces'; /** * Registers a cleanup function associated with a prefetching trigger * or a regular trigger of a defer block. */ export function storeTriggerCleanupFn( - type: TriggerType, lDetails: LDeferBlockDetails, cleanupFn: VoidFunction) { + type: TriggerType, + lDetails: LDeferBlockDetails, + cleanupFn: VoidFunction, +) { const key = type === TriggerType.Prefetch ? PREFETCH_TRIGGER_CLEANUP_FNS : TRIGGER_CLEANUP_FNS; if (lDetails[key] === null) { lDetails[key] = []; diff --git a/packages/core/src/defer/dom_triggers.ts b/packages/core/src/defer/dom_triggers.ts index 7cc4b17c9faab..340f9a735f6fd 100644 --- a/packages/core/src/defer/dom_triggers.ts +++ b/packages/core/src/defer/dom_triggers.ts @@ -13,18 +13,28 @@ import {CONTAINER_HEADER_OFFSET} from '../render3/interfaces/container'; import {TNode} from '../render3/interfaces/node'; import {isDestroyed} from '../render3/interfaces/type_checks'; import {HEADER_OFFSET, INJECTOR, LView} from '../render3/interfaces/view'; -import {getNativeByIndex, removeLViewOnDestroy, storeLViewOnDestroy, walkUpViews} from '../render3/util/view_utils'; +import { + getNativeByIndex, + removeLViewOnDestroy, + storeLViewOnDestroy, + walkUpViews, +} from '../render3/util/view_utils'; import {assertElement, assertEqual} from '../util/assert'; import {NgZone} from '../zone'; import {storeTriggerCleanupFn} from './cleanup'; -import {DEFER_BLOCK_STATE, DeferBlockInternalState, DeferBlockState, TriggerType} from './interfaces'; +import { + DEFER_BLOCK_STATE, + DeferBlockInternalState, + DeferBlockState, + TriggerType, +} from './interfaces'; import {getLDeferBlockDetails} from './utils'; /** Configuration object used to register passive and capturing events. */ const eventListenerOptions: AddEventListenerOptions = { passive: true, - capture: true + capture: true, }; /** Keeps track of the currently-registered `on hover` triggers. */ @@ -43,7 +53,7 @@ const interactionEventNames = ['click', 'keydown'] as const; const hoverEventNames = ['mouseenter', 'focusin'] as const; /** `IntersectionObserver` used to observe `viewport` triggers. */ -let intersectionObserver: IntersectionObserver|null = null; +let intersectionObserver: IntersectionObserver | null = null; /** Number of elements currently observed with `viewport` triggers. */ let observedViewportElements = 0; @@ -56,7 +66,7 @@ class DeferEventEntry { for (const callback of this.callbacks) { callback(); } - } + }; } /** @@ -144,20 +154,25 @@ export function onHover(trigger: Element, callback: VoidFunction): VoidFunction * @param injector Injector that can be used by the trigger to resolve DI tokens. */ export function onViewport( - trigger: Element, callback: VoidFunction, injector: Injector): VoidFunction { + trigger: Element, + callback: VoidFunction, + injector: Injector, +): VoidFunction { const ngZone = injector.get(NgZone); let entry = viewportTriggers.get(trigger); - intersectionObserver = intersectionObserver || ngZone.runOutsideAngular(() => { - return new IntersectionObserver(entries => { - for (const current of entries) { - // Only invoke the callbacks if the specific element is intersecting. - if (current.isIntersecting && viewportTriggers.has(current.target)) { - ngZone.run(viewportTriggers.get(current.target)!.listener); + intersectionObserver = + intersectionObserver || + ngZone.runOutsideAngular(() => { + return new IntersectionObserver((entries) => { + for (const current of entries) { + // Only invoke the callbacks if the specific element is intersecting. + if (current.isIntersecting && viewportTriggers.has(current.target)) { + ngZone.run(viewportTriggers.get(current.target)!.listener); + } } - } + }); }); - }); if (!entry) { entry = new DeferEventEntry(); @@ -198,7 +213,10 @@ export function onViewport( * value means that the trigger is in the same LView as the deferred block. */ export function getTriggerLView( - deferredHostLView: LView, deferredTNode: TNode, walkUpTimes: number|undefined): LView|null { + deferredHostLView: LView, + deferredTNode: TNode, + walkUpTimes: number | undefined, +): LView | null { // The trigger is in the same view, we don't need to traverse. if (walkUpTimes == null) { return deferredHostLView; @@ -219,8 +237,10 @@ export function getTriggerLView( const lDetails = getLDeferBlockDetails(deferredHostLView, deferredTNode); const renderedState = lDetails[DEFER_BLOCK_STATE]; assertEqual( - renderedState, DeferBlockState.Placeholder, - 'Expected a placeholder to be rendered in this defer block.'); + renderedState, + DeferBlockState.Placeholder, + 'Expected a placeholder to be rendered in this defer block.', + ); assertLView(triggerLView); } @@ -250,9 +270,14 @@ export function getTriggerElement(triggerLView: LView, triggerIndex: number): El * @param type Trigger type to distinguish between regular and prefetch triggers. */ export function registerDomTrigger( - initialLView: LView, tNode: TNode, triggerIndex: number, walkUpTimes: number|undefined, - registerFn: (element: Element, callback: VoidFunction, injector: Injector) => VoidFunction, - callback: VoidFunction, type: TriggerType) { + initialLView: LView, + tNode: TNode, + triggerIndex: number, + walkUpTimes: number | undefined, + registerFn: (element: Element, callback: VoidFunction, injector: Injector) => VoidFunction, + callback: VoidFunction, + type: TriggerType, +) { const injector = initialLView[INJECTOR]!; function pollDomTrigger() { // If the initial view was destroyed, we don't need to do anything. @@ -264,8 +289,10 @@ export function registerDomTrigger( const renderedState = lDetails[DEFER_BLOCK_STATE]; // If the block was loaded before the trigger was resolved, we don't need to do anything. - if (renderedState !== DeferBlockInternalState.Initial && - renderedState !== DeferBlockState.Placeholder) { + if ( + renderedState !== DeferBlockInternalState.Initial && + renderedState !== DeferBlockState.Placeholder + ) { return; } @@ -283,12 +310,16 @@ export function registerDomTrigger( } const element = getTriggerElement(triggerLView, triggerIndex); - const cleanup = registerFn(element, () => { - if (initialLView !== triggerLView) { - removeLViewOnDestroy(triggerLView, cleanup); - } - callback(); - }, injector); + const cleanup = registerFn( + element, + () => { + if (initialLView !== triggerLView) { + removeLViewOnDestroy(triggerLView, cleanup); + } + callback(); + }, + injector, + ); // The trigger and deferred block might be in different LViews. // For the main LView the cleanup would happen as a part of diff --git a/packages/core/src/defer/idle_scheduler.ts b/packages/core/src/defer/idle_scheduler.ts index 94ba16238fccf..396a458bda32c 100644 --- a/packages/core/src/defer/idle_scheduler.ts +++ b/packages/core/src/defer/idle_scheduler.ts @@ -32,9 +32,9 @@ export function onIdle(callback: VoidFunction, lView: LView) { * overridden/mocked in test environment and picked up by the runtime code. */ const _requestIdleCallback = () => - typeof requestIdleCallback !== 'undefined' ? requestIdleCallback : setTimeout; + typeof requestIdleCallback !== 'undefined' ? requestIdleCallback : setTimeout; const _cancelIdleCallback = () => - typeof requestIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout; + typeof requestIdleCallback !== 'undefined' ? cancelIdleCallback : clearTimeout; /** * Helper service to schedule `requestIdleCallback`s for batches of defer blocks, @@ -46,7 +46,7 @@ export class IdleScheduler { executingCallbacks = false; // Currently scheduled idle callback id. - idleId: number|null = null; + idleId: number | null = null; // Set of callbacks to be invoked next. current = new Set(); diff --git a/packages/core/src/defer/instructions.ts b/packages/core/src/defer/instructions.ts index c8e7c03803a1f..bb7fc42cc6aff 100644 --- a/packages/core/src/defer/instructions.ts +++ b/packages/core/src/defer/instructions.ts @@ -29,19 +29,67 @@ import {DirectiveDefList, PipeDefList} from '../render3/interfaces/definition'; import {TContainerNode, TNode} from '../render3/interfaces/node'; import {isDestroyed} from '../render3/interfaces/type_checks'; import {HEADER_OFFSET, INJECTOR, LView, PARENT, TVIEW, TView} from '../render3/interfaces/view'; -import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} from '../render3/state'; +import { + getCurrentTNode, + getLView, + getSelectedTNode, + getTView, + nextBindingIndex, +} from '../render3/state'; import {isPlatformBrowser} from '../render3/util/misc_utils'; -import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy} from '../render3/util/view_utils'; -import {addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer, shouldAddViewToDom} from '../render3/view_manipulation'; +import { + getConstant, + getTNode, + removeLViewOnDestroy, + storeLViewOnDestroy, +} from '../render3/util/view_utils'; +import { + addLViewToLContainer, + createAndRenderEmbeddedLView, + removeLViewFromLContainer, + shouldAddViewToDom, +} from '../render3/view_manipulation'; import {assertDefined, throwError} from '../util/assert'; import {performanceMarkFeature} from '../util/performance'; -import {invokeAllTriggerCleanupFns, invokeTriggerCleanupFns, storeTriggerCleanupFn} from './cleanup'; +import { + invokeAllTriggerCleanupFns, + invokeTriggerCleanupFns, + storeTriggerCleanupFn, +} from './cleanup'; import {onHover, onInteraction, onViewport, registerDomTrigger} from './dom_triggers'; import {onIdle} from './idle_scheduler'; -import {DEFER_BLOCK_STATE, DeferBlockBehavior, DeferBlockConfig, DeferBlockDependencyInterceptor, DeferBlockInternalState, DeferBlockState, DeferDependenciesLoadingState, DeferredLoadingBlockConfig, DeferredPlaceholderBlockConfig, DependencyResolverFn, LDeferBlockDetails, LOADING_AFTER_CLEANUP_FN, NEXT_DEFER_BLOCK_STATE, STATE_IS_FROZEN_UNTIL, TDeferBlockDetails, TriggerType} from './interfaces'; +import { + DEFER_BLOCK_STATE, + DeferBlockBehavior, + DeferBlockConfig, + DeferBlockDependencyInterceptor, + DeferBlockInternalState, + DeferBlockState, + DeferDependenciesLoadingState, + DeferredLoadingBlockConfig, + DeferredPlaceholderBlockConfig, + DependencyResolverFn, + LDeferBlockDetails, + LOADING_AFTER_CLEANUP_FN, + NEXT_DEFER_BLOCK_STATE, + STATE_IS_FROZEN_UNTIL, + TDeferBlockDetails, + TriggerType, +} from './interfaces'; import {onTimer, scheduleTimerTrigger} from './timer_scheduler'; -import {addDepsToRegistry, assertDeferredDependenciesLoaded, getLDeferBlockDetails, getLoadingBlockAfter, getMinimumDurationForState, getPrimaryBlockTNode, getTDeferBlockDetails, getTemplateIndexForState, setLDeferBlockDetails, setTDeferBlockDetails} from './utils'; +import { + addDepsToRegistry, + assertDeferredDependenciesLoaded, + getLDeferBlockDetails, + getLoadingBlockAfter, + getMinimumDurationForState, + getPrimaryBlockTNode, + getTDeferBlockDetails, + getTemplateIndexForState, + setLDeferBlockDetails, + setTDeferBlockDetails, +} from './utils'; /** * **INTERNAL**, avoid referencing it in application code. @@ -52,13 +100,14 @@ import {addDepsToRegistry, assertDeferredDependenciesLoaded, getLDeferBlockDetai * This token is only injected in devMode */ export const DEFER_BLOCK_DEPENDENCY_INTERCEPTOR = - new InjectionToken('DEFER_BLOCK_DEPENDENCY_INTERCEPTOR'); + new InjectionToken('DEFER_BLOCK_DEPENDENCY_INTERCEPTOR'); /** * **INTERNAL**, token used for configuring defer block behavior. */ -export const DEFER_BLOCK_CONFIG = - new InjectionToken(ngDevMode ? 'DEFER_BLOCK_CONFIG' : ''); +export const DEFER_BLOCK_CONFIG = new InjectionToken( + ngDevMode ? 'DEFER_BLOCK_CONFIG' : '', +); /** * Returns whether defer blocks should be triggered. @@ -81,23 +130,30 @@ function shouldTriggerDeferBlock(injector: Injector): boolean { * argument for the `ɵɵdefer` instruction, which references a timer-based * implementation. */ -let applyDeferBlockStateWithSchedulingImpl: (typeof applyDeferBlockState)|null = null; +let applyDeferBlockStateWithSchedulingImpl: typeof applyDeferBlockState | null = null; /** * Enables timer-related scheduling if `after` or `minimum` parameters are setup * on the `@loading` or `@placeholder` blocks. */ export function ɵɵdeferEnableTimerScheduling( - tView: TView, tDetails: TDeferBlockDetails, placeholderConfigIndex?: number|null, - loadingConfigIndex?: number|null) { + tView: TView, + tDetails: TDeferBlockDetails, + placeholderConfigIndex?: number | null, + loadingConfigIndex?: number | null, +) { const tViewConsts = tView.consts; if (placeholderConfigIndex != null) { - tDetails.placeholderBlockConfig = - getConstant(tViewConsts, placeholderConfigIndex); + tDetails.placeholderBlockConfig = getConstant( + tViewConsts, + placeholderConfigIndex, + ); } if (loadingConfigIndex != null) { - tDetails.loadingBlockConfig = - getConstant(tViewConsts, loadingConfigIndex); + tDetails.loadingBlockConfig = getConstant( + tViewConsts, + loadingConfigIndex, + ); } // Enable implementation that supports timer-based scheduling. @@ -125,11 +181,16 @@ export function ɵɵdeferEnableTimerScheduling( * @codeGenApi */ export function ɵɵdefer( - index: number, primaryTmplIndex: number, dependencyResolverFn?: DependencyResolverFn|null, - loadingTmplIndex?: number|null, placeholderTmplIndex?: number|null, - errorTmplIndex?: number|null, loadingConfigIndex?: number|null, - placeholderConfigIndex?: number|null, - enableTimerScheduling?: typeof ɵɵdeferEnableTimerScheduling) { + index: number, + primaryTmplIndex: number, + dependencyResolverFn?: DependencyResolverFn | null, + loadingTmplIndex?: number | null, + placeholderTmplIndex?: number | null, + errorTmplIndex?: number | null, + loadingConfigIndex?: number | null, + placeholderConfigIndex?: number | null, + enableTimerScheduling?: typeof ɵɵdeferEnableTimerScheduling, +) { const lView = getLView(); const tView = getTView(); const adjustedIndex = index + HEADER_OFFSET; @@ -163,20 +224,21 @@ export function ɵɵdefer( // Init instance-specific defer details and store it. const lDetails: LDeferBlockDetails = [ - null, // NEXT_DEFER_BLOCK_STATE - DeferBlockInternalState.Initial, // DEFER_BLOCK_STATE - null, // STATE_IS_FROZEN_UNTIL - null, // LOADING_AFTER_CLEANUP_FN - null, // TRIGGER_CLEANUP_FNS - null // PREFETCH_TRIGGER_CLEANUP_FNS + null, // NEXT_DEFER_BLOCK_STATE + DeferBlockInternalState.Initial, // DEFER_BLOCK_STATE + null, // STATE_IS_FROZEN_UNTIL + null, // LOADING_AFTER_CLEANUP_FN + null, // TRIGGER_CLEANUP_FNS + null, // PREFETCH_TRIGGER_CLEANUP_FNS ]; setLDeferBlockDetails(lView, adjustedIndex, lDetails); const cleanupTriggersFn = () => invokeAllTriggerCleanupFns(lDetails); // When defer block is triggered - unsubscribe from LView destroy cleanup. - storeTriggerCleanupFn( - TriggerType.Regular, lDetails, () => removeLViewOnDestroy(lView, cleanupTriggersFn)); + storeTriggerCleanupFn(TriggerType.Regular, lDetails, () => + removeLViewOnDestroy(lView, cleanupTriggersFn), + ); storeLViewOnDestroy(lView, cleanupTriggersFn); } @@ -190,7 +252,7 @@ export function ɵɵdeferWhen(rawValue: unknown) { if (bindingUpdated(lView, bindingIndex, rawValue)) { const prevConsumer = setActiveConsumer(null); try { - const value = Boolean(rawValue); // handle truthy or falsy values + const value = Boolean(rawValue); // handle truthy or falsy values const tNode = getSelectedTNode(); const lDetails = getLDeferBlockDetails(lView, tNode); const renderedState = lDetails[DEFER_BLOCK_STATE]; @@ -198,9 +260,10 @@ export function ɵɵdeferWhen(rawValue: unknown) { // If nothing is rendered yet, render a placeholder (if defined). renderPlaceholder(lView, tNode); } else if ( - value === true && - (renderedState === DeferBlockInternalState.Initial || - renderedState === DeferBlockState.Placeholder)) { + value === true && + (renderedState === DeferBlockInternalState.Initial || + renderedState === DeferBlockState.Placeholder) + ) { // The `when` condition has changed to `true`, trigger defer block loading // if the block is either in initial (nothing is rendered) or a placeholder // state. @@ -223,7 +286,7 @@ export function ɵɵdeferPrefetchWhen(rawValue: unknown) { if (bindingUpdated(lView, bindingIndex, rawValue)) { const prevConsumer = setActiveConsumer(null); try { - const value = Boolean(rawValue); // handle truthy or falsy values + const value = Boolean(rawValue); // handle truthy or falsy values const tView = lView[TVIEW]; const tNode = getSelectedTNode(); const tDetails = getTDeferBlockDetails(tView, tNode); @@ -273,7 +336,6 @@ export function ɵɵdeferOnImmediate() { triggerDeferBlock(lView, tNode); } - /** * Sets up logic to handle the `prefetch on immediate` deferred trigger. * @codeGenApi @@ -319,8 +381,14 @@ export function ɵɵdeferOnHover(triggerIndex: number, walkUpTimes?: number) { renderPlaceholder(lView, tNode); registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onHover, () => triggerDeferBlock(lView, tNode), - TriggerType.Regular); + lView, + tNode, + triggerIndex, + walkUpTimes, + onHover, + () => triggerDeferBlock(lView, tNode), + TriggerType.Regular, + ); } /** @@ -337,8 +405,14 @@ export function ɵɵdeferPrefetchOnHover(triggerIndex: number, walkUpTimes?: num if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) { registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onHover, - () => triggerPrefetching(tDetails, lView, tNode), TriggerType.Prefetch); + lView, + tNode, + triggerIndex, + walkUpTimes, + onHover, + () => triggerPrefetching(tDetails, lView, tNode), + TriggerType.Prefetch, + ); } } @@ -354,8 +428,14 @@ export function ɵɵdeferOnInteraction(triggerIndex: number, walkUpTimes?: numbe renderPlaceholder(lView, tNode); registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onInteraction, () => triggerDeferBlock(lView, tNode), - TriggerType.Regular); + lView, + tNode, + triggerIndex, + walkUpTimes, + onInteraction, + () => triggerDeferBlock(lView, tNode), + TriggerType.Regular, + ); } /** @@ -372,8 +452,14 @@ export function ɵɵdeferPrefetchOnInteraction(triggerIndex: number, walkUpTimes if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) { registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onInteraction, - () => triggerPrefetching(tDetails, lView, tNode), TriggerType.Prefetch); + lView, + tNode, + triggerIndex, + walkUpTimes, + onInteraction, + () => triggerPrefetching(tDetails, lView, tNode), + TriggerType.Prefetch, + ); } } @@ -389,8 +475,14 @@ export function ɵɵdeferOnViewport(triggerIndex: number, walkUpTimes?: number) renderPlaceholder(lView, tNode); registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onViewport, () => triggerDeferBlock(lView, tNode), - TriggerType.Regular); + lView, + tNode, + triggerIndex, + walkUpTimes, + onViewport, + () => triggerDeferBlock(lView, tNode), + TriggerType.Regular, + ); } /** @@ -407,8 +499,14 @@ export function ɵɵdeferPrefetchOnViewport(triggerIndex: number, walkUpTimes?: if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) { registerDomTrigger( - lView, tNode, triggerIndex, walkUpTimes, onViewport, - () => triggerPrefetching(tDetails, lView, tNode), TriggerType.Prefetch); + lView, + tNode, + triggerIndex, + walkUpTimes, + onViewport, + () => triggerPrefetching(tDetails, lView, tNode), + TriggerType.Prefetch, + ); } } @@ -418,7 +516,8 @@ export function ɵɵdeferPrefetchOnViewport(triggerIndex: number, walkUpTimes?: * Schedules triggering of a defer block for `on idle` and `on timer` conditions. */ function scheduleDelayedTrigger( - scheduleFn: (callback: VoidFunction, lView: LView) => VoidFunction) { + scheduleFn: (callback: VoidFunction, lView: LView) => VoidFunction, +) { const lView = getLView(); const tNode = getCurrentTNode()!; @@ -434,7 +533,8 @@ function scheduleDelayedTrigger( * @param scheduleFn A function that does the scheduling. */ function scheduleDelayedPrefetching( - scheduleFn: (callback: VoidFunction, lView: LView) => VoidFunction) { + scheduleFn: (callback: VoidFunction, lView: LView) => VoidFunction, +) { const lView = getLView(); const tNode = getCurrentTNode()!; const tView = lView[TVIEW]; @@ -461,8 +561,11 @@ function scheduleDelayedPrefetching( * between states via `DeferFixture.render` method. */ export function renderDeferBlockState( - newState: DeferBlockState, tNode: TNode, lContainer: LContainer, - skipTimerScheduling = false): void { + newState: DeferBlockState, + tNode: TNode, + lContainer: LContainer, + skipTimerScheduling = false, +): void { const hostLView = lContainer[PARENT]; const hostTView = hostLView[TVIEW]; @@ -479,23 +582,30 @@ export function renderDeferBlockState( const currentState = lDetails[DEFER_BLOCK_STATE]; - if (isValidStateChange(currentState, newState) && - isValidStateChange(lDetails[NEXT_DEFER_BLOCK_STATE] ?? -1, newState)) { + if ( + isValidStateChange(currentState, newState) && + isValidStateChange(lDetails[NEXT_DEFER_BLOCK_STATE] ?? -1, newState) + ) { const injector = hostLView[INJECTOR]!; const tDetails = getTDeferBlockDetails(hostTView, tNode); // Skips scheduling on the server since it can delay the server response. - const needsScheduling = !skipTimerScheduling && isPlatformBrowser(injector) && - (getLoadingBlockAfter(tDetails) !== null || - getMinimumDurationForState(tDetails, DeferBlockState.Loading) !== null || - getMinimumDurationForState(tDetails, DeferBlockState.Placeholder)); + const needsScheduling = + !skipTimerScheduling && + isPlatformBrowser(injector) && + (getLoadingBlockAfter(tDetails) !== null || + getMinimumDurationForState(tDetails, DeferBlockState.Loading) !== null || + getMinimumDurationForState(tDetails, DeferBlockState.Placeholder)); if (ngDevMode && needsScheduling) { assertDefined( - applyDeferBlockStateWithSchedulingImpl, 'Expected scheduling function to be defined'); + applyDeferBlockStateWithSchedulingImpl, + 'Expected scheduling function to be defined', + ); } - const applyStateFn = - needsScheduling ? applyDeferBlockStateWithSchedulingImpl! : applyDeferBlockState; + const applyStateFn = needsScheduling + ? applyDeferBlockStateWithSchedulingImpl! + : applyDeferBlockState; try { applyStateFn(newState, lDetails, lContainer, tNode, hostLView); } catch (error: unknown) { @@ -509,8 +619,10 @@ export function renderDeferBlockState( * created based on the `OutletInjector`. */ export function isRouterOutletInjector(currentInjector: Injector): boolean { - return (currentInjector instanceof ChainedInjector) && - (typeof (currentInjector.injector as any).__ngOutletInjector === 'function'); + return ( + currentInjector instanceof ChainedInjector && + typeof (currentInjector.injector as any).__ngOutletInjector === 'function' + ); } /** @@ -523,18 +635,23 @@ export function isRouterOutletInjector(currentInjector: Injector): boolean { * for a newly created `OutletInjector` instance. */ function createRouterOutletInjector( - parentOutletInjector: ChainedInjector, parentInjector: Injector) { + parentOutletInjector: ChainedInjector, + parentInjector: Injector, +) { const outletInjector = parentOutletInjector.injector as any; return outletInjector.__ngOutletInjector(parentInjector); } - /** * Applies changes to the DOM to reflect a given state. */ function applyDeferBlockState( - newState: DeferBlockState, lDetails: LDeferBlockDetails, lContainer: LContainer, tNode: TNode, - hostLView: LView) { + newState: DeferBlockState, + lDetails: LDeferBlockDetails, + lContainer: LContainer, + tNode: TNode, + hostLView: LView, +) { const stateTmplIndex = getTemplateIndexForState(newState, hostLView, tNode); if (stateTmplIndex !== null) { @@ -549,7 +666,7 @@ function applyDeferBlockState( removeLViewFromLContainer(lContainer, viewIndex); - let injector: Injector|undefined; + let injector: Injector | undefined; if (newState === DeferBlockState.Complete) { // When we render a defer block in completed state, there might be // newly loaded standalone components used within the block, which may @@ -568,13 +685,18 @@ function applyDeferBlockState( // with the `EnvironmentInjector` in Router's code, this special // handling can be removed. const isParentOutletInjector = isRouterOutletInjector(parentInjector); - const parentEnvInjector = - isParentOutletInjector ? parentInjector : parentInjector.get(EnvironmentInjector); - - injector = parentEnvInjector.get(CachedInjectorService) - .getOrCreateInjector( - tDetails, parentEnvInjector as EnvironmentInjector, providers, - ngDevMode ? 'DeferBlock Injector' : ''); + const parentEnvInjector = isParentOutletInjector + ? parentInjector + : parentInjector.get(EnvironmentInjector); + + injector = parentEnvInjector + .get(CachedInjectorService) + .getOrCreateInjector( + tDetails, + parentEnvInjector as EnvironmentInjector, + providers, + ngDevMode ? 'DeferBlock Injector' : '', + ); // Note: this is a continuation of the special case for Router's `OutletInjector`. // Since the `OutletInjector` handles `ActivatedRoute` and `ChildrenOutletContexts` @@ -587,10 +709,16 @@ function applyDeferBlockState( } } const dehydratedView = findMatchingDehydratedView(lContainer, activeBlockTNode.tView!.ssrId); - const embeddedLView = - createAndRenderEmbeddedLView(hostLView, activeBlockTNode, null, {dehydratedView, injector}); + const embeddedLView = createAndRenderEmbeddedLView(hostLView, activeBlockTNode, null, { + dehydratedView, + injector, + }); addLViewToLContainer( - lContainer, embeddedLView, viewIndex, shouldAddViewToDom(activeBlockTNode, dehydratedView)); + lContainer, + embeddedLView, + viewIndex, + shouldAddViewToDom(activeBlockTNode, dehydratedView), + ); markViewDirty(embeddedLView, NotificationSource.DeferBlockStateUpdate); } } @@ -602,8 +730,12 @@ function applyDeferBlockState( * `@placeholder` blocks. */ function applyDeferBlockStateWithScheduling( - newState: DeferBlockState, lDetails: LDeferBlockDetails, lContainer: LContainer, tNode: TNode, - hostLView: LView) { + newState: DeferBlockState, + lDetails: LDeferBlockDetails, + lContainer: LContainer, + tNode: TNode, + hostLView: LView, +) { const now = Date.now(); const hostTView = hostLView[TVIEW]; const tDetails = getTDeferBlockDetails(hostTView, tNode); @@ -617,8 +749,13 @@ function applyDeferBlockStateWithScheduling( // Trying to render loading, but it has an `after` config, // so schedule an update action after a timeout. lDetails[NEXT_DEFER_BLOCK_STATE] = newState; - const cleanupFn = - scheduleDeferBlockUpdate(loadingAfter, lDetails, tNode, lContainer, hostLView); + const cleanupFn = scheduleDeferBlockUpdate( + loadingAfter, + lDetails, + tNode, + lContainer, + hostLView, + ); lDetails[LOADING_AFTER_CLEANUP_FN] = cleanupFn; } else { // If we transition to a complete or an error state and there is a pending @@ -650,8 +787,12 @@ function applyDeferBlockStateWithScheduling( * Schedules an update operation after a specified timeout. */ function scheduleDeferBlockUpdate( - timeout: number, lDetails: LDeferBlockDetails, tNode: TNode, lContainer: LContainer, - hostLView: LView): VoidFunction { + timeout: number, + lDetails: LDeferBlockDetails, + tNode: TNode, + lContainer: LContainer, + hostLView: LView, +): VoidFunction { const callback = () => { const nextState = lDetails[NEXT_DEFER_BLOCK_STATE]; lDetails[STATE_IS_FROZEN_UNTIL] = null; @@ -673,7 +814,9 @@ function scheduleDeferBlockUpdate( * or an error state (represented as `3`). */ function isValidStateChange( - currentState: DeferBlockState|DeferBlockInternalState, newState: DeferBlockState): boolean { + currentState: DeferBlockState | DeferBlockInternalState, + newState: DeferBlockState, +): boolean { return currentState < newState; } @@ -696,7 +839,10 @@ export function triggerPrefetching(tDetails: TDeferBlockDetails, lView: LView, t * @param lView LView of a host view. */ export function triggerResourceLoading( - tDetails: TDeferBlockDetails, lView: LView, tNode: TNode): Promise { + tDetails: TDeferBlockDetails, + lView: LView, + tNode: TNode, +): Promise { const injector = lView[INJECTOR]!; const tView = lView[TVIEW]; @@ -720,8 +866,9 @@ export function triggerResourceLoading( if (ngDevMode) { // Check if dependency function interceptor is configured. - const deferDependencyInterceptor = - injector.get(DEFER_BLOCK_DEPENDENCY_INTERCEPTOR, null, {optional: true}); + const deferDependencyInterceptor = injector.get(DEFER_BLOCK_DEPENDENCY_INTERCEPTOR, null, { + optional: true, + }); if (deferDependencyInterceptor) { dependenciesFn = deferDependencyInterceptor.intercept(dependenciesFn); @@ -745,7 +892,7 @@ export function triggerResourceLoading( } // Start downloading of defer block dependencies. - tDetails.loadingPromise = Promise.allSettled(dependenciesFn()).then(results => { + tDetails.loadingPromise = Promise.allSettled(dependenciesFn()).then((results) => { let failed = false; const directiveDefs: DirectiveDefList = []; const pipeDefs: PipeDefList = []; @@ -779,11 +926,12 @@ export function triggerResourceLoading( if (tDetails.errorTmplIndex === null) { const templateLocation = getTemplateLocationDetails(lView); const error = new RuntimeError( - RuntimeErrorCode.DEFER_LOADING_FAILED, - ngDevMode && - 'Loading dependencies for `@defer` block failed, ' + - `but no \`@error\` block was configured${templateLocation}. ` + - 'Consider using the `@error` block to render an error state.'); + RuntimeErrorCode.DEFER_LOADING_FAILED, + ngDevMode && + 'Loading dependencies for `@defer` block failed, ' + + `but no \`@error\` block was configured${templateLocation}. ` + + 'Consider using the `@error` block to render an error state.', + ); handleError(lView, error); } } else { @@ -792,18 +940,22 @@ export function triggerResourceLoading( // Update directive and pipe registries to add newly downloaded dependencies. const primaryBlockTView = primaryBlockTNode.tView!; if (directiveDefs.length > 0) { - primaryBlockTView.directiveRegistry = - addDepsToRegistry(primaryBlockTView.directiveRegistry, directiveDefs); + primaryBlockTView.directiveRegistry = addDepsToRegistry( + primaryBlockTView.directiveRegistry, + directiveDefs, + ); // Extract providers from all NgModules imported by standalone components // used within this defer block. - const directiveTypes = directiveDefs.map(def => def.type); + const directiveTypes = directiveDefs.map((def) => def.type); const providers = internalImportProvidersFrom(false, ...directiveTypes); tDetails.providers = providers; } if (pipeDefs.length > 0) { - primaryBlockTView.pipeRegistry = - addDepsToRegistry(primaryBlockTView.pipeRegistry, pipeDefs); + primaryBlockTView.pipeRegistry = addDepsToRegistry( + primaryBlockTView.pipeRegistry, + pipeDefs, + ); } } }); @@ -826,10 +978,12 @@ function renderPlaceholder(lView: LView, tNode: TNode) { * @param tNode Represents defer block info shared across all instances. */ function renderDeferStateAfterResourceLoading( - tDetails: TDeferBlockDetails, tNode: TNode, lContainer: LContainer) { + tDetails: TDeferBlockDetails, + tNode: TNode, + lContainer: LContainer, +) { ngDevMode && - assertDefined( - tDetails.loadingPromise, 'Expected loading Promise to exist on this defer block'); + assertDefined(tDetails.loadingPromise, 'Expected loading Promise to exist on this defer block'); tDetails.loadingPromise!.then(() => { if (tDetails.loadingState === DeferDependenciesLoadingState.COMPLETE) { @@ -837,7 +991,6 @@ function renderDeferStateAfterResourceLoading( // Everything is loaded, show the primary block content renderDeferBlockState(DeferBlockState.Complete, tNode, lContainer); - } else if (tDetails.loadingState === DeferDependenciesLoadingState.FAILED) { renderDeferBlockState(DeferBlockState.Error, tNode, lContainer); } @@ -869,8 +1022,10 @@ function triggerDeferBlock(lView: LView, tNode: TNode) { triggerResourceLoading(tDetails, lView, tNode); // The `loadingState` might have changed to "loading". - if ((tDetails.loadingState as DeferDependenciesLoadingState) === - DeferDependenciesLoadingState.IN_PROGRESS) { + if ( + (tDetails.loadingState as DeferDependenciesLoadingState) === + DeferDependenciesLoadingState.IN_PROGRESS + ) { renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer); } break; diff --git a/packages/core/src/defer/interfaces.ts b/packages/core/src/defer/interfaces.ts index c47cbf74f7898..c8c2f27b50a55 100644 --- a/packages/core/src/defer/interfaces.ts +++ b/packages/core/src/defer/interfaces.ts @@ -54,10 +54,10 @@ export const MINIMUM_SLOT = 0; export const LOADING_AFTER_SLOT = 1; /** Configuration object for a loading block as it is stored in the component constants. */ -export type DeferredLoadingBlockConfig = [minimumTime: number|null, afterTime: number|null]; +export type DeferredLoadingBlockConfig = [minimumTime: number | null, afterTime: number | null]; /** Configuration object for a placeholder block as it is stored in the component constants. */ -export type DeferredPlaceholderBlockConfig = [minimumTime: number|null]; +export type DeferredPlaceholderBlockConfig = [minimumTime: number | null]; /** * Describes the data shared across all instances of a defer block. @@ -72,32 +72,32 @@ export interface TDeferBlockDetails { /** * Index in an LView and TData arrays where a template for the loading block can be found. */ - loadingTmplIndex: number|null; + loadingTmplIndex: number | null; /** * Extra configuration parameters (such as `after` and `minimum`) for the loading block. */ - loadingBlockConfig: DeferredLoadingBlockConfig|null; + loadingBlockConfig: DeferredLoadingBlockConfig | null; /** * Index in an LView and TData arrays where a template for the placeholder block can be found. */ - placeholderTmplIndex: number|null; + placeholderTmplIndex: number | null; /** * Extra configuration parameters (such as `after` and `minimum`) for the placeholder block. */ - placeholderBlockConfig: DeferredPlaceholderBlockConfig|null; + placeholderBlockConfig: DeferredPlaceholderBlockConfig | null; /** * Index in an LView and TData arrays where a template for the error block can be found. */ - errorTmplIndex: number|null; + errorTmplIndex: number | null; /** * Compiler-generated function that loads all dependencies for a defer block. */ - dependencyResolverFn: DependencyResolverFn|null; + dependencyResolverFn: DependencyResolverFn | null; /** * Keeps track of the current loading state of defer block dependencies. @@ -109,13 +109,13 @@ export interface TDeferBlockDetails { * are multiple instances of a defer block (e.g. if it was used inside of an *ngFor), * which all await the same set of dependencies. */ - loadingPromise: Promise|null; + loadingPromise: Promise | null; /** * List of providers collected from all NgModules that were imported by * standalone components used within this defer block. */ - providers: Provider[]|null; + providers: Provider[] | null; } /** @@ -171,35 +171,35 @@ export interface LDeferBlockDetails extends Array { /** * Currently rendered block state. */ - [DEFER_BLOCK_STATE]: DeferBlockState|DeferBlockInternalState; + [DEFER_BLOCK_STATE]: DeferBlockState | DeferBlockInternalState; /** * Block state that was requested when another state was rendered. */ - [NEXT_DEFER_BLOCK_STATE]: DeferBlockState|null; + [NEXT_DEFER_BLOCK_STATE]: DeferBlockState | null; /** * Timestamp indicating when the current state can be switched to * the next one, in case teh current state has `minimum` parameter. */ - [STATE_IS_FROZEN_UNTIL]: number|null; + [STATE_IS_FROZEN_UNTIL]: number | null; /** * Contains a reference to a cleanup function which cancels a timeout * when Angular waits before rendering loading state. This is used when * the loading block has the `after` parameter configured. */ - [LOADING_AFTER_CLEANUP_FN]: VoidFunction|null; + [LOADING_AFTER_CLEANUP_FN]: VoidFunction | null; /** * List of cleanup functions for regular triggers. */ - [TRIGGER_CLEANUP_FNS]: VoidFunction[]|null; + [TRIGGER_CLEANUP_FNS]: VoidFunction[] | null; /** * List of cleanup functions for prefetch triggers. */ - [PREFETCH_TRIGGER_CLEANUP_FNS]: VoidFunction[]|null; + [PREFETCH_TRIGGER_CLEANUP_FNS]: VoidFunction[] | null; } /** @@ -240,7 +240,7 @@ export interface DeferBlockDependencyInterceptor { /** * Invoked for each defer block when dependency loading function is accessed. */ - intercept(dependencyFn: DependencyResolverFn|null): DependencyResolverFn|null; + intercept(dependencyFn: DependencyResolverFn | null): DependencyResolverFn | null; /** * Allows to configure an interceptor function. diff --git a/packages/core/src/defer/timer_scheduler.ts b/packages/core/src/defer/timer_scheduler.ts index 327197725bb70..66566d561c127 100644 --- a/packages/core/src/defer/timer_scheduler.ts +++ b/packages/core/src/defer/timer_scheduler.ts @@ -43,23 +43,23 @@ export class TimerScheduler { executingCallbacks = false; // Currently scheduled `setTimeout` id. - timeoutId: number|null = null; + timeoutId: number | null = null; // When currently scheduled timer would fire. - invokeTimerAt: number|null = null; + invokeTimerAt: number | null = null; // List of callbacks to be invoked. // For each callback we also store a timestamp on when the callback // should be invoked. We store timestamps and callback functions // in a flat array to avoid creating new objects for each entry. // [timestamp1, callback1, timestamp2, callback2, ...] - current: Array = []; + current: Array = []; // List of callbacks collected while invoking current set of callbacks. // Those callbacks are added to the "current" queue at the end of // the current callback invocation. The shape of this list is the same // as the shape of the `current` list. - deferred: Array = []; + deferred: Array = []; add(delay: number, callback: VoidFunction) { const target = this.executingCallbacks ? this.deferred : this.current; @@ -81,7 +81,11 @@ export class TimerScheduler { } } - private addToQueue(target: Array, invokeAt: number, callback: VoidFunction) { + private addToQueue( + target: Array, + invokeAt: number, + callback: VoidFunction, + ) { let insertAtIndex = target.length; for (let i = 0; i < target.length; i += 2) { const invokeQueuedCallbackAt = target[i] as number; @@ -97,7 +101,7 @@ export class TimerScheduler { arrayInsert2(target, insertAtIndex, invokeAt, callback); } - private removeFromQueue(target: Array, callback: VoidFunction) { + private removeFromQueue(target: Array, callback: VoidFunction) { let index = -1; for (let i = 0; i < target.length; i += 2) { const queuedCallback = target[i + 1]; @@ -174,18 +178,20 @@ export class TimerScheduler { // average frame duration. This is needed for better // batching and to avoid kicking off excessive change // detection cycles. - const FRAME_DURATION_MS = 16; // 1000ms / 60fps + const FRAME_DURATION_MS = 16; // 1000ms / 60fps if (this.current.length > 0) { const now = Date.now(); // First element in the queue points at the timestamp // of the first (earliest) event. const invokeAt = this.current[0] as number; - if (this.timeoutId === null || - // Reschedule a timer in case a queue contains an item with - // an earlier timestamp and the delta is more than an average - // frame duration. - (this.invokeTimerAt && (this.invokeTimerAt - invokeAt > FRAME_DURATION_MS))) { + if ( + this.timeoutId === null || + // Reschedule a timer in case a queue contains an item with + // an earlier timestamp and the delta is more than an average + // frame duration. + (this.invokeTimerAt && this.invokeTimerAt - invokeAt > FRAME_DURATION_MS) + ) { // There was a timeout already, but an earlier event was added // into the queue. In this case we drop an old timer and setup // a new one with an updated (smaller) timeout. diff --git a/packages/core/src/defer/utils.ts b/packages/core/src/defer/utils.ts index 8ec90c120b208..5d8163a22871a 100644 --- a/packages/core/src/defer/utils.ts +++ b/packages/core/src/defer/utils.ts @@ -13,8 +13,14 @@ import {HEADER_OFFSET, LView, TVIEW, TView} from '../render3/interfaces/view'; import {getTNode} from '../render3/util/view_utils'; import {assertEqual, throwError} from '../util/assert'; -import {DeferBlockState, DeferDependenciesLoadingState, LDeferBlockDetails, LOADING_AFTER_SLOT, MINIMUM_SLOT, TDeferBlockDetails} from './interfaces'; - +import { + DeferBlockState, + DeferDependenciesLoadingState, + LDeferBlockDetails, + LOADING_AFTER_SLOT, + MINIMUM_SLOT, + TDeferBlockDetails, +} from './interfaces'; /** * Calculates a data slot index for defer block info (either static or @@ -36,7 +42,10 @@ export function getLDeferBlockDetails(lView: LView, tNode: TNode): LDeferBlockDe /** Stores a defer block instance state in LView. */ export function setLDeferBlockDetails( - lView: LView, deferBlockIndex: number, lDetails: LDeferBlockDetails) { + lView: LView, + deferBlockIndex: number, + lDetails: LDeferBlockDetails, +) { const tView = lView[TVIEW]; const slotIndex = getDeferBlockDataIndex(deferBlockIndex); ngDevMode && assertIndexInDeclRange(tView, slotIndex); @@ -52,14 +61,20 @@ export function getTDeferBlockDetails(tView: TView, tNode: TNode): TDeferBlockDe /** Stores a defer block static info in `TView.data`. */ export function setTDeferBlockDetails( - tView: TView, deferBlockIndex: number, deferBlockConfig: TDeferBlockDetails) { + tView: TView, + deferBlockIndex: number, + deferBlockConfig: TDeferBlockDetails, +) { const slotIndex = getDeferBlockDataIndex(deferBlockIndex); ngDevMode && assertIndexInDeclRange(tView, slotIndex); tView.data[slotIndex] = deferBlockConfig; } export function getTemplateIndexForState( - newState: DeferBlockState, hostLView: LView, tNode: TNode): number|null { + newState: DeferBlockState, + hostLView: LView, + tNode: TNode, +): number | null { const tView = hostLView[TVIEW]; const tDetails = getTDeferBlockDetails(tView, tNode); @@ -84,7 +99,9 @@ export function getTemplateIndexForState( * not specified - returns `null`. */ export function getMinimumDurationForState( - tDetails: TDeferBlockDetails, currentState: DeferBlockState): number|null { + tDetails: TDeferBlockDetails, + currentState: DeferBlockState, +): number | null { if (currentState === DeferBlockState.Placeholder) { return tDetails.placeholderBlockConfig?.[MINIMUM_SLOT] ?? null; } else if (currentState === DeferBlockState.Loading) { @@ -94,7 +111,7 @@ export function getMinimumDurationForState( } /** Retrieves the value of the `after` parameter on the @loading block. */ -export function getLoadingBlockAfter(tDetails: TDeferBlockDetails): number|null { +export function getLoadingBlockAfter(tDetails: TDeferBlockDetails): number | null { return tDetails.loadingBlockConfig?.[LOADING_AFTER_SLOT] ?? null; } @@ -102,7 +119,7 @@ export function getLoadingBlockAfter(tDetails: TDeferBlockDetails): number|null * Adds downloaded dependencies into a directive or a pipe registry, * making sure that a dependency doesn't yet exist in the registry. */ -export function addDepsToRegistry(currentDeps: T|null, newDeps: T): T { +export function addDepsToRegistry(currentDeps: T | null, newDeps: T): T { if (!currentDeps || currentDeps.length === 0) { return newDeps; } @@ -114,7 +131,7 @@ export function addDepsToRegistry(currentDeps: T|null // If `currentDeps` is the same length, there were no new deps and can // return the original array. - return (currentDeps.length === currentDepSet.size) ? currentDeps : Array.from(currentDepSet) as T; + return currentDeps.length === currentDepSet.size ? currentDeps : (Array.from(currentDepSet) as T); } /** Retrieves a TNode that represents main content of a defer block. */ @@ -130,8 +147,10 @@ export function getPrimaryBlockTNode(tView: TView, tDetails: TDeferBlockDetails) */ export function assertDeferredDependenciesLoaded(tDetails: TDeferBlockDetails) { assertEqual( - tDetails.loadingState, DeferDependenciesLoadingState.COMPLETE, - 'Expecting all deferred dependencies to be loaded.'); + tDetails.loadingState, + DeferDependenciesLoadingState.COMPLETE, + 'Expecting all deferred dependencies to be loaded.', + ); } /** @@ -141,6 +160,9 @@ export function assertDeferredDependenciesLoaded(tDetails: TDeferBlockDetails) { * that a primary template exists. All the other template options are optional. */ export function isTDeferBlockDetails(value: unknown): value is TDeferBlockDetails { - return value !== null && (typeof value === 'object') && - (typeof (value as TDeferBlockDetails).primaryTmplIndex === 'number'); + return ( + value !== null && + typeof value === 'object' && + typeof (value as TDeferBlockDetails).primaryTmplIndex === 'number' + ); } diff --git a/packages/core/src/di/contextual.ts b/packages/core/src/di/contextual.ts index 73fbdc325d5e6..2bc1211e6c974 100644 --- a/packages/core/src/di/contextual.ts +++ b/packages/core/src/di/contextual.ts @@ -7,7 +7,10 @@ */ import {RuntimeError, RuntimeErrorCode} from '../errors'; -import {InjectorProfilerContext, setInjectorProfilerContext} from '../render3/debug/injector_profiler'; +import { + InjectorProfilerContext, + setInjectorProfilerContext, +} from '../render3/debug/injector_profiler'; import {getInjectImplementation, setInjectImplementation} from './inject_switch'; import type {Injector} from './injector'; @@ -67,9 +70,10 @@ export function assertInInjectionContext(debugFn: Function): void { // from being retained in the bundle regardless of minification. if (!isInInjectionContext()) { throw new RuntimeError( - RuntimeErrorCode.MISSING_INJECTION_CONTEXT, - ngDevMode && - (debugFn.name + - '() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`')); + RuntimeErrorCode.MISSING_INJECTION_CONTEXT, + ngDevMode && + debugFn.name + + '() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`', + ); } } diff --git a/packages/core/src/di/create_injector.ts b/packages/core/src/di/create_injector.ts index d26401fcc9fdf..dd5ee1ce3c9d9 100644 --- a/packages/core/src/di/create_injector.ts +++ b/packages/core/src/di/create_injector.ts @@ -19,10 +19,17 @@ import {InjectorScope} from './scope'; * Create a new `Injector` which is configured using a `defType` of `InjectorType`s. */ export function createInjector( - defType: /* InjectorType */ any, parent: Injector|null = null, - additionalProviders: Array|null = null, name?: string): Injector { - const injector = - createInjectorWithoutInjectorInstances(defType, parent, additionalProviders, name); + defType: /* InjectorType */ any, + parent: Injector | null = null, + additionalProviders: Array | null = null, + name?: string, +): Injector { + const injector = createInjectorWithoutInjectorInstances( + defType, + parent, + additionalProviders, + name, + ); injector.resolveInjectorInitializers(); return injector; } @@ -33,13 +40,13 @@ export function createInjector( * should be resolved at a later point by calling `_resolveInjectorDefTypes`. */ export function createInjectorWithoutInjectorInstances( - defType: /* InjectorType */ any, parent: Injector|null = null, - additionalProviders: Array|null = null, name?: string, - scopes = new Set()): R3Injector { - const providers = [ - additionalProviders || EMPTY_ARRAY, - importProvidersFrom(defType), - ]; + defType: /* InjectorType */ any, + parent: Injector | null = null, + additionalProviders: Array | null = null, + name?: string, + scopes = new Set(), +): R3Injector { + const providers = [additionalProviders || EMPTY_ARRAY, importProvidersFrom(defType)]; name = name || (typeof defType === 'object' ? undefined : stringify(defType)); return new R3Injector(providers, parent || getNullInjector(), name || null, scopes); diff --git a/packages/core/src/di/forward_ref.ts b/packages/core/src/di/forward_ref.ts index d0ba224d4f6ef..ab5568e640a71 100644 --- a/packages/core/src/di/forward_ref.ts +++ b/packages/core/src/di/forward_ref.ts @@ -10,8 +10,6 @@ import {Type} from '../interface/type'; import {getClosureSafeProperty} from '../util/property'; import {stringify} from '../util/stringify'; - - /** * An interface that a function passed into `forwardRef` has to implement. * @@ -68,10 +66,10 @@ const __forward_ref__ = getClosureSafeProperty({__forward_ref__: getClosureSafeP */ export function forwardRef(forwardRefFn: ForwardRefFn): Type { (forwardRefFn).__forward_ref__ = forwardRef; - (forwardRefFn).toString = function() { + (forwardRefFn).toString = function () { return stringify(this()); }; - return (>forwardRefFn); + return >(forwardRefFn); } /** @@ -92,7 +90,10 @@ export function resolveForwardRef(type: T): T { } /** Checks whether a function is wrapped by a `forwardRef`. */ -export function isForwardRef(fn: any): fn is() => any { - return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) && - fn.__forward_ref__ === forwardRef; +export function isForwardRef(fn: any): fn is () => any { + return ( + typeof fn === 'function' && + fn.hasOwnProperty(__forward_ref__) && + fn.__forward_ref__ === forwardRef + ); } diff --git a/packages/core/src/di/host_tag_name_token.ts b/packages/core/src/di/host_tag_name_token.ts index a2366f9b84a34..2fbef5d4443cc 100644 --- a/packages/core/src/di/host_tag_name_token.ts +++ b/packages/core/src/di/host_tag_name_token.ts @@ -43,10 +43,11 @@ export const HOST_TAG_NAME = new InjectionToken(ngDevMode ? 'HOST_TAG_NA const tNode = getCurrentTNode(); if (tNode === null) { throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && - 'HOST_TAG_NAME can only be injected in directives and components ' + - 'during construction time (in a class constructor or as a class field initializer)'); + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && + 'HOST_TAG_NAME can only be injected in directives and components ' + + 'during construction time (in a class constructor or as a class field initializer)', + ); } if (tNode.type & TNodeType.Element) { return tNode.value; @@ -55,11 +56,13 @@ export const HOST_TAG_NAME = new InjectionToken(ngDevMode ? 'HOST_TAG_NA return null; } throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && - `HOST_TAG_NAME was used on ${ - getDevModeNodeName(tNode)} which doesn't have an underlying element in the DOM. ` + - `This is invalid, and so the dependency should be marked as optional.`); + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && + `HOST_TAG_NAME was used on ${getDevModeNodeName( + tNode, + )} which doesn't have an underlying element in the DOM. ` + + `This is invalid, and so the dependency should be marked as optional.`, + ); }; function getDevModeNodeName(tNode: TNode) { diff --git a/packages/core/src/di/index.ts b/packages/core/src/di/index.ts index 4650dc55d8b61..5aebe97050abf 100644 --- a/packages/core/src/di/index.ts +++ b/packages/core/src/di/index.ts @@ -15,18 +15,47 @@ export * from './metadata'; export {assertInInjectionContext, runInInjectionContext} from './contextual'; export {InjectFlags} from './interface/injector'; -export {ɵɵdefineInjectable, defineInjectable, ɵɵdefineInjector, InjectableType, InjectorType} from './interface/defs'; +export { + ɵɵdefineInjectable, + defineInjectable, + ɵɵdefineInjector, + InjectableType, + InjectorType, +} from './interface/defs'; export {forwardRef, resolveForwardRef, ForwardRefFn} from './forward_ref'; export {Injectable, InjectableDecorator, InjectableProvider} from './injectable'; export {Injector} from './injector'; export {EnvironmentInjector} from './r3_injector'; -export {importProvidersFrom, ImportProvidersSource, makeEnvironmentProviders} from './provider_collection'; +export { + importProvidersFrom, + ImportProvidersSource, + makeEnvironmentProviders, +} from './provider_collection'; export {ENVIRONMENT_INITIALIZER} from './initializer_token'; export {ProviderToken} from './provider_token'; export {ɵɵinject, inject, ɵɵinvalidFactoryDep} from './injector_compatibility'; export {InjectOptions} from './interface/injector'; export {INJECTOR} from './injector_token'; -export {ClassProvider, ModuleWithProviders, ClassSansProvider, ImportedNgModuleProviders, ConstructorProvider, EnvironmentProviders, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, Provider, StaticClassProvider, StaticClassSansProvider, StaticProvider, TypeProvider, ValueProvider, ValueSansProvider} from './interface/provider'; +export { + ClassProvider, + ModuleWithProviders, + ClassSansProvider, + ImportedNgModuleProviders, + ConstructorProvider, + EnvironmentProviders, + ConstructorSansProvider, + ExistingProvider, + ExistingSansProvider, + FactoryProvider, + FactorySansProvider, + Provider, + StaticClassProvider, + StaticClassSansProvider, + StaticProvider, + TypeProvider, + ValueProvider, + ValueSansProvider, +} from './interface/provider'; export {InjectionToken} from './injection_token'; export {HostAttributeToken} from './host_attribute_token'; export {HOST_TAG_NAME} from './host_tag_name_token'; diff --git a/packages/core/src/di/initializer_token.ts b/packages/core/src/di/initializer_token.ts index 04dafd00c5cab..0f93761dd6c6e 100644 --- a/packages/core/src/di/initializer_token.ts +++ b/packages/core/src/di/initializer_token.ts @@ -14,5 +14,6 @@ import {InjectionToken} from './injection_token'; * * @publicApi */ -export const ENVIRONMENT_INITIALIZER = - new InjectionToken void>>(ngDevMode ? 'ENVIRONMENT_INITIALIZER' : ''); +export const ENVIRONMENT_INITIALIZER = new InjectionToken void>>( + ngDevMode ? 'ENVIRONMENT_INITIALIZER' : '', +); diff --git a/packages/core/src/di/inject_switch.ts b/packages/core/src/di/inject_switch.ts index ce26489e4e80a..570293dce6731 100644 --- a/packages/core/src/di/inject_switch.ts +++ b/packages/core/src/di/inject_switch.ts @@ -14,7 +14,6 @@ import {getInjectableDef, ɵɵInjectableDeclaration} from './interface/defs'; import {InjectFlags} from './interface/injector'; import {ProviderToken} from './provider_token'; - /** * Current implementation of inject. * @@ -24,25 +23,24 @@ import {ProviderToken} from './provider_token'; * 1. `Injector` should not depend on ivy logic. * 2. To maintain tree shake-ability we don't want to bring in unnecessary code. */ -let _injectImplementation: ((token: ProviderToken, flags?: InjectFlags) => T | null)| - undefined; +let _injectImplementation: + | ((token: ProviderToken, flags?: InjectFlags) => T | null) + | undefined; export function getInjectImplementation() { return _injectImplementation; } - /** * Sets the current inject implementation. */ export function setInjectImplementation( - impl: ((token: ProviderToken, flags?: InjectFlags) => T | null)| - undefined): ((token: ProviderToken, flags?: InjectFlags) => T | null)|undefined { + impl: ((token: ProviderToken, flags?: InjectFlags) => T | null) | undefined, +): ((token: ProviderToken, flags?: InjectFlags) => T | null) | undefined { const previous = _injectImplementation; _injectImplementation = impl; return previous; } - /** * Injects `root` tokens in limp mode. * @@ -51,18 +49,21 @@ export function setInjectImplementation( * injectable definition. */ export function injectRootLimpMode( - token: ProviderToken, notFoundValue: T|undefined, flags: InjectFlags): T|null { - const injectableDef: ɵɵInjectableDeclaration|null = getInjectableDef(token); + token: ProviderToken, + notFoundValue: T | undefined, + flags: InjectFlags, +): T | null { + const injectableDef: ɵɵInjectableDeclaration | null = getInjectableDef(token); if (injectableDef && injectableDef.providedIn == 'root') { - return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() : - injectableDef.value; + return injectableDef.value === undefined + ? (injectableDef.value = injectableDef.factory()) + : injectableDef.value; } if (flags & InjectFlags.Optional) return null; if (notFoundValue !== undefined) return notFoundValue; throwProviderNotFoundError(token, 'Injector'); } - /** * Assert that `_injectImplementation` is not `fn`. * @@ -71,7 +72,8 @@ export function injectRootLimpMode( * @param fn Function which it should not equal to */ export function assertInjectImplementationNotEqual( - fn: ((token: ProviderToken, flags?: InjectFlags) => T | null)) { + fn: (token: ProviderToken, flags?: InjectFlags) => T | null, +) { ngDevMode && - assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion'); + assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion'); } diff --git a/packages/core/src/di/injectable.ts b/packages/core/src/di/injectable.ts index 90ec2260b70a3..565b6ebb21325 100644 --- a/packages/core/src/di/injectable.ts +++ b/packages/core/src/di/injectable.ts @@ -9,7 +9,14 @@ import {Type} from '../interface/type'; import {makeDecorator, TypeDecorator} from '../util/decorators'; -import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueSansProvider} from './interface/provider'; +import { + ClassSansProvider, + ConstructorSansProvider, + ExistingSansProvider, + FactorySansProvider, + StaticClassSansProvider, + ValueSansProvider, +} from './interface/provider'; import {compileInjectable} from './jit/injectable'; export {compileInjectable}; @@ -19,8 +26,13 @@ export {compileInjectable}; * * @publicApi */ -export type InjectableProvider = ValueSansProvider|ExistingSansProvider|StaticClassSansProvider| - ConstructorSansProvider|FactorySansProvider|ClassSansProvider; +export type InjectableProvider = + | ValueSansProvider + | ExistingSansProvider + | StaticClassSansProvider + | ConstructorSansProvider + | FactorySansProvider + | ClassSansProvider; /** * Type of the Injectable decorator / constructor function. @@ -48,11 +60,13 @@ export interface InjectableDecorator { * */ (): TypeDecorator; - (options?: {providedIn: Type|'root'|'platform'|'any'|null}& - InjectableProvider): TypeDecorator; - new(): Injectable; - new(options?: {providedIn: Type|'root'|'platform'|'any'|null}& - InjectableProvider): Injectable; + ( + options?: {providedIn: Type | 'root' | 'platform' | 'any' | null} & InjectableProvider, + ): TypeDecorator; + new (): Injectable; + new ( + options?: {providedIn: Type | 'root' | 'platform' | 'any' | null} & InjectableProvider, + ): Injectable; } /** @@ -79,7 +93,7 @@ export interface Injectable { * modules share one instance. This option is DEPRECATED. * */ - providedIn?: Type|'root'|'platform'|'any'|null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; } /** @@ -89,5 +103,9 @@ export interface Injectable { * @publicApi */ export const Injectable: InjectableDecorator = makeDecorator( - 'Injectable', undefined, undefined, undefined, - (type: Type, meta: Injectable) => compileInjectable(type as any, meta)); + 'Injectable', + undefined, + undefined, + undefined, + (type: Type, meta: Injectable) => compileInjectable(type as any, meta), +); diff --git a/packages/core/src/di/injection_token.ts b/packages/core/src/di/injection_token.ts index 822bb92c0cb40..4d92d55dea76e 100644 --- a/packages/core/src/di/injection_token.ts +++ b/packages/core/src/di/injection_token.ts @@ -72,13 +72,17 @@ export class InjectionToken { * it should but does not need to be unique * @param options Options for the token's usage, as described above */ - constructor(protected _desc: string, options?: { - providedIn?: Type|'root'|'platform'|'any'|null, factory: () => T - }) { + constructor( + protected _desc: string, + options?: { + providedIn?: Type | 'root' | 'platform' | 'any' | null; + factory: () => T; + }, + ) { this.ɵprov = undefined; if (typeof options == 'number') { (typeof ngDevMode === 'undefined' || ngDevMode) && - assertLessThan(options, 0, 'Only negative numbers are supported here'); + assertLessThan(options, 0, 'Only negative numbers are supported here'); // This is a special hack to assign __NG_ELEMENT_ID__ to this instance. // See `InjectorMarkers` (this as any).__NG_ELEMENT_ID__ = options; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index c165e6a291982..848aba0b0dcd1 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {createInjector} from './create_injector'; import {THROW_IF_NOT_FOUND, ɵɵinject} from './injector_compatibility'; import {InjectorMarkers} from './injector_marker'; @@ -43,7 +42,7 @@ import {ProviderToken} from './provider_token'; */ export abstract class Injector { static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; - static NULL: Injector = (/* @__PURE__ */ new NullInjector()); + static NULL: Injector = /* @__PURE__ */ new NullInjector(); /** * Internal note on the `options?: InjectOptions|InjectFlags` override of the `get` @@ -57,23 +56,33 @@ export abstract class Injector { * @returns The instance from the injector if defined, otherwise the `notFoundValue`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. */ - abstract get(token: ProviderToken, notFoundValue: undefined, options: InjectOptions&{ - optional?: false; - }): T; + abstract get( + token: ProviderToken, + notFoundValue: undefined, + options: InjectOptions & { + optional?: false; + }, + ): T; /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. */ - abstract get(token: ProviderToken, notFoundValue: null|undefined, options: InjectOptions): T - |null; + abstract get( + token: ProviderToken, + notFoundValue: null | undefined, + options: InjectOptions, + ): T | null; /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. */ - abstract get(token: ProviderToken, notFoundValue?: T, options?: InjectOptions|InjectFlags): - T; + abstract get( + token: ProviderToken, + notFoundValue?: T, + options?: InjectOptions | InjectFlags, + ): T; /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. @@ -104,15 +113,18 @@ export abstract class Injector { * @returns The new injector instance. * */ - static create(options: - {providers: Array, parent?: Injector, name?: string}): - Injector; - + static create(options: { + providers: Array; + parent?: Injector; + name?: string; + }): Injector; static create( - options: StaticProvider[]| - {providers: Array, parent?: Injector, name?: string}, - parent?: Injector): Injector { + options: + | StaticProvider[] + | {providers: Array; parent?: Injector; name?: string}, + parent?: Injector, + ): Injector { if (Array.isArray(options)) { return createInjector({name: ''}, parent, options, ''); } else { diff --git a/packages/core/src/di/injector_compatibility.ts b/packages/core/src/di/injector_compatibility.ts index 9fc5a562b1353..d0d01cccf3a74 100644 --- a/packages/core/src/di/injector_compatibility.ts +++ b/packages/core/src/di/injector_compatibility.ts @@ -16,11 +16,15 @@ import {stringify} from '../util/stringify'; import {resolveForwardRef} from './forward_ref'; import {getInjectImplementation, injectRootLimpMode} from './inject_switch'; import type {Injector} from './injector'; -import {DecoratorFlags, InjectFlags, InjectOptions, InternalInjectFlags} from './interface/injector'; +import { + DecoratorFlags, + InjectFlags, + InjectOptions, + InternalInjectFlags, +} from './interface/injector'; import {ProviderToken} from './provider_token'; import type {HostAttributeToken} from './host_attribute_token'; - const _THROW_IF_NOT_FOUND = {}; export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; @@ -43,32 +47,40 @@ export const SOURCE = '__source'; * - `null`: `inject` can be called but there is no injector (limp-mode). * - Injector instance: Use the injector for resolution. */ -let _currentInjector: Injector|undefined|null = undefined; +let _currentInjector: Injector | undefined | null = undefined; -export function getCurrentInjector(): Injector|undefined|null { +export function getCurrentInjector(): Injector | undefined | null { return _currentInjector; } -export function setCurrentInjector(injector: Injector|null|undefined): Injector|undefined|null { +export function setCurrentInjector( + injector: Injector | null | undefined, +): Injector | undefined | null { const former = _currentInjector; _currentInjector = injector; return former; } export function injectInjectorOnly(token: ProviderToken): T; -export function injectInjectorOnly(token: ProviderToken, flags?: InjectFlags): T|null; -export function injectInjectorOnly(token: ProviderToken, flags = InjectFlags.Default): T| - null { +export function injectInjectorOnly(token: ProviderToken, flags?: InjectFlags): T | null; +export function injectInjectorOnly( + token: ProviderToken, + flags = InjectFlags.Default, +): T | null { if (_currentInjector === undefined) { throw new RuntimeError( - RuntimeErrorCode.MISSING_INJECTION_CONTEXT, - ngDevMode && - `inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`runInInjectionContext\`.`); + RuntimeErrorCode.MISSING_INJECTION_CONTEXT, + ngDevMode && + `inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`runInInjectionContext\`.`, + ); } else if (_currentInjector === null) { return injectRootLimpMode(token, undefined, flags); } else { - const value = - _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags); + const value = _currentInjector.get( + token, + flags & InjectFlags.Optional ? null : undefined, + flags, + ); ngDevMode && emitInjectEvent(token as Type, value, flags); return value; } @@ -85,15 +97,21 @@ export function injectInjectorOnly(token: ProviderToken, flags = InjectFla * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm. */ export function ɵɵinject(token: ProviderToken): T; -export function ɵɵinject(token: ProviderToken, flags?: InjectFlags): T|null; +export function ɵɵinject(token: ProviderToken, flags?: InjectFlags): T | null; export function ɵɵinject(token: HostAttributeToken): string; -export function ɵɵinject(token: HostAttributeToken, flags?: InjectFlags): string|null; +export function ɵɵinject(token: HostAttributeToken, flags?: InjectFlags): string | null; export function ɵɵinject( - token: ProviderToken|HostAttributeToken, flags?: InjectFlags): string|null; + token: ProviderToken | HostAttributeToken, + flags?: InjectFlags, +): string | null; export function ɵɵinject( - token: ProviderToken|HostAttributeToken, flags = InjectFlags.Default): T|null { + token: ProviderToken | HostAttributeToken, + flags = InjectFlags.Default, +): T | null { return (getInjectImplementation() || injectInjectorOnly)( - resolveForwardRef(token as Type), flags); + resolveForwardRef(token as Type), + flags, + ); } /** @@ -107,14 +125,13 @@ export function ɵɵinject( */ export function ɵɵinvalidFactoryDep(index: number): never { throw new RuntimeError( - RuntimeErrorCode.INVALID_FACTORY_DEPENDENCY, - ngDevMode && - `This constructor is not compatible with Angular Dependency Injection because its dependency at index ${ - index} of the parameter list is invalid. + RuntimeErrorCode.INVALID_FACTORY_DEPENDENCY, + ngDevMode && + `This constructor is not compatible with Angular Dependency Injection because its dependency at index ${index} of the parameter list is invalid. This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator. -Please check that 1) the type for the parameter at index ${ - index} is correct and 2) the correct Angular decorators are defined for this class and its ancestors.`); +Please check that 1) the type for the parameter at index ${index} is correct and 2) the correct Angular decorators are defined for this class and its ancestors.`, + ); } /** @@ -135,7 +152,7 @@ export function inject(token: ProviderToken): T; * @publicApi * @deprecated prefer an options object instead of `InjectFlags` */ -export function inject(token: ProviderToken, flags?: InjectFlags): T|null; +export function inject(token: ProviderToken, flags?: InjectFlags): T | null; /** * @param token A token that represents a dependency that should be injected. * @param options Control how injection is executed. Options correspond to injection strategies @@ -146,7 +163,7 @@ export function inject(token: ProviderToken, flags?: InjectFlags): T|null; * * @publicApi */ -export function inject(token: ProviderToken, options: InjectOptions&{optional?: false}): T; +export function inject(token: ProviderToken, options: InjectOptions & {optional?: false}): T; /** * @param token A token that represents a dependency that should be injected. * @param options Control how injection is executed. Options correspond to injection strategies @@ -159,7 +176,7 @@ export function inject(token: ProviderToken, options: InjectOptions&{optio * * @publicApi */ -export function inject(token: ProviderToken, options: InjectOptions): T|null; +export function inject(token: ProviderToken, options: InjectOptions): T | null; /** * @param token A token that represents a static attribute on the host node that should be injected. * @returns Value of the attribute if it exists. @@ -175,7 +192,7 @@ export function inject(token: HostAttributeToken): string; * * @publicApi */ -export function inject(token: HostAttributeToken, options: {optional: true}): string|null; +export function inject(token: HostAttributeToken, options: {optional: true}): string | null; /** * @param token A token that represents a static attribute on the host node that should be injected. * @returns Value of the attribute if it exists. @@ -250,16 +267,18 @@ export function inject(token: HostAttributeToken, options: {optional: false}): s * @publicApi */ export function inject( - token: ProviderToken|HostAttributeToken, - flags: InjectFlags|InjectOptions = InjectFlags.Default) { + token: ProviderToken | HostAttributeToken, + flags: InjectFlags | InjectOptions = InjectFlags.Default, +) { // The `as any` here _shouldn't_ be necessary, but without it JSCompiler // throws a disambiguation error due to the multiple signatures. return ɵɵinject(token as any, convertToBitFlags(flags)); } // Converts object-based DI flags (`InjectOptions`) to bit flags (`InjectFlags`). -export function convertToBitFlags(flags: InjectOptions|InjectFlags|undefined): InjectFlags| - undefined { +export function convertToBitFlags( + flags: InjectOptions | InjectFlags | undefined, +): InjectFlags | undefined { if (typeof flags === 'undefined' || typeof flags === 'number') { return flags; } @@ -267,24 +286,25 @@ export function convertToBitFlags(flags: InjectOptions|InjectFlags|undefined): I // While TypeScript doesn't accept it without a cast, bitwise OR with false-y values in // JavaScript is a no-op. We can use that for a very codesize-efficient conversion from // `InjectOptions` to `InjectFlags`. - return (InternalInjectFlags.Default | // comment to force a line break in the formatter - ((flags.optional && InternalInjectFlags.Optional) as number) | - ((flags.host && InternalInjectFlags.Host) as number) | - ((flags.self && InternalInjectFlags.Self) as number) | - ((flags.skipSelf && InternalInjectFlags.SkipSelf) as number)) as InjectFlags; + return (InternalInjectFlags.Default | // comment to force a line break in the formatter + ((flags.optional && InternalInjectFlags.Optional) as number) | + ((flags.host && InternalInjectFlags.Host) as number) | + ((flags.self && InternalInjectFlags.Self) as number) | + ((flags.skipSelf && InternalInjectFlags.SkipSelf) as number)) as InjectFlags; } -export function injectArgs(types: (ProviderToken|any[])[]): any[] { +export function injectArgs(types: (ProviderToken | any[])[]): any[] { const args: any[] = []; for (let i = 0; i < types.length; i++) { const arg = resolveForwardRef(types[i]); if (Array.isArray(arg)) { if (arg.length === 0) { throw new RuntimeError( - RuntimeErrorCode.INVALID_DIFFER_INPUT, - ngDevMode && 'Arguments array must have arguments.'); + RuntimeErrorCode.INVALID_DIFFER_INPUT, + ngDevMode && 'Arguments array must have arguments.', + ); } - let type: Type|undefined = undefined; + let type: Type | undefined = undefined; let flags: InjectFlags = InjectFlags.Default; for (let j = 0; j < arg.length; j++) { @@ -320,7 +340,7 @@ export function injectArgs(types: (ProviderToken|any[])[]): any[] { * @param decorator Provided DI decorator. * @param flag InjectFlag that should be applied. */ -export function attachInjectFlag(decorator: any, flag: InternalInjectFlags|DecoratorFlags): any { +export function attachInjectFlag(decorator: any, flag: InternalInjectFlags | DecoratorFlags): any { decorator[DI_DECORATOR_FLAG] = flag; decorator.prototype[DI_DECORATOR_FLAG] = flag; return decorator; @@ -331,12 +351,16 @@ export function attachInjectFlag(decorator: any, flag: InternalInjectFlags|Decor * * @param token Token that may contain monkey-patched DI flags property. */ -export function getInjectFlag(token: any): number|undefined { +export function getInjectFlag(token: any): number | undefined { return token[DI_DECORATOR_FLAG]; } export function catchInjectorError( - e: any, token: any, injectorErrorName: string, source: string|null): never { + e: any, + token: any, + injectorErrorName: string, + source: string | null, +): never { const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH]; if (token[SOURCE]) { tokenPath.unshift(token[SOURCE]); @@ -348,7 +372,11 @@ export function catchInjectorError( } export function formatError( - text: string, obj: any, injectorErrorName: string, source: string|null = null): string { + text: string, + obj: any, + injectorErrorName: string, + source: string | null = null, +): string { text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.slice(2) : text; let context = stringify(obj); if (Array.isArray(obj)) { @@ -359,11 +387,14 @@ export function formatError( if (obj.hasOwnProperty(key)) { let value = obj[key]; parts.push( - key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value))); + key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)), + ); } } context = `{${parts.join(', ')}}`; } - return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${ - text.replace(NEW_LINE, '\n ')}`; + return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${text.replace( + NEW_LINE, + '\n ', + )}`; } diff --git a/packages/core/src/di/injector_marker.ts b/packages/core/src/di/injector_marker.ts index ca8ef57a29742..216c962d30b58 100644 --- a/packages/core/src/di/injector_marker.ts +++ b/packages/core/src/di/injector_marker.ts @@ -20,5 +20,5 @@ export const enum InjectorMarkers { /** * Marks that the current type is `Injector` */ - Injector = -1 + Injector = -1, } diff --git a/packages/core/src/di/injector_token.ts b/packages/core/src/di/injector_token.ts index b6f5f7a7040f8..30fd8634083ba 100644 --- a/packages/core/src/di/injector_token.ts +++ b/packages/core/src/di/injector_token.ts @@ -10,8 +10,6 @@ import {InjectionToken} from './injection_token'; import type {Injector} from './injector'; import {InjectorMarkers} from './injector_marker'; - - /** * An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors. * @@ -21,8 +19,8 @@ import {InjectorMarkers} from './injector_marker'; * @publicApi */ export const INJECTOR = new InjectionToken( - ngDevMode ? 'INJECTOR' : '', - // Disable tslint because this is const enum which gets inlined not top level prop access. - // tslint:disable-next-line: no-toplevel-property-access - InjectorMarkers.Injector as any, // Special value used by Ivy to identify `Injector`. + ngDevMode ? 'INJECTOR' : '', + // Disable tslint because this is const enum which gets inlined not top level prop access. + // tslint:disable-next-line: no-toplevel-property-access + InjectorMarkers.Injector as any, // Special value used by Ivy to identify `Injector`. ); diff --git a/packages/core/src/di/interface/defs.ts b/packages/core/src/di/interface/defs.ts index 37224294a8aef..e9df3443d13f1 100644 --- a/packages/core/src/di/interface/defs.ts +++ b/packages/core/src/di/interface/defs.ts @@ -9,9 +9,15 @@ import {Type} from '../../interface/type'; import {getClosureSafeProperty} from '../../util/property'; -import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, StaticClassProvider, ValueProvider} from './provider'; - - +import { + ClassProvider, + ConstructorProvider, + EnvironmentProviders, + ExistingProvider, + FactoryProvider, + StaticClassProvider, + ValueProvider, +} from './provider'; /** * Information about how a type or `InjectionToken` interfaces with the DI system. @@ -37,7 +43,7 @@ export interface ɵɵInjectableDeclaration { * - `null`, does not belong to any injector. Must be explicitly listed in the injector * `providers`. */ - providedIn: InjectorType|'root'|'platform'|'any'|'environment'|null; + providedIn: InjectorType | 'root' | 'platform' | 'any' | 'environment' | null; /** * The token to which this definition belongs. @@ -54,7 +60,7 @@ export interface ɵɵInjectableDeclaration { /** * In a case of no explicit injector, a location where the instance of the injectable is stored. */ - value: T|undefined; + value: T | undefined; } /** @@ -72,10 +78,19 @@ export interface ɵɵInjectableDeclaration { export interface ɵɵInjectorDef { // TODO(alxhub): Narrow down the type here once decorators properly change the return type of the // class they are decorating (to add the ɵprov property for example). - providers: (Type|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider| - StaticClassProvider|ClassProvider|EnvironmentProviders|any[])[]; + providers: ( + | Type + | ValueProvider + | ExistingProvider + | FactoryProvider + | ConstructorProvider + | StaticClassProvider + | ClassProvider + | EnvironmentProviders + | any[] + )[]; - imports: (InjectorType|InjectorTypeWithProviders)[]; + imports: (InjectorType | InjectorTypeWithProviders)[]; } /** @@ -118,11 +133,19 @@ export interface InjectorType extends Type { */ export interface InjectorTypeWithProviders { ngModule: InjectorType; - providers?: (Type|ValueProvider|ExistingProvider|FactoryProvider|ConstructorProvider| - StaticClassProvider|ClassProvider|EnvironmentProviders|any[])[]; + providers?: ( + | Type + | ValueProvider + | ExistingProvider + | FactoryProvider + | ConstructorProvider + | StaticClassProvider + | ClassProvider + | EnvironmentProviders + | any[] + )[]; } - /** * Construct an injectable definition which defines how a token will be constructed by the DI * system, and in which injectors (if any) it will be available. @@ -142,12 +165,13 @@ export interface InjectorTypeWithProviders { * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm. */ export function ɵɵdefineInjectable(opts: { - token: unknown, - providedIn?: Type|'root'|'platform'|'any'|'environment'|null, factory: () => T, + token: unknown; + providedIn?: Type | 'root' | 'platform' | 'any' | 'environment' | null; + factory: () => T; }): unknown { return { token: opts.token, - providedIn: opts.providedIn as any || null, + providedIn: (opts.providedIn as any) || null, factory: opts.factory, value: undefined, } as ɵɵInjectableDeclaration; @@ -177,7 +201,7 @@ export const defineInjectable = ɵɵdefineInjectable; * * @codeGenApi */ -export function ɵɵdefineInjector(options: {providers?: any[], imports?: any[]}): unknown { +export function ɵɵdefineInjector(options: {providers?: any[]; imports?: any[]}): unknown { return {providers: options.providers || [], imports: options.imports || []}; } @@ -187,7 +211,7 @@ export function ɵɵdefineInjector(options: {providers?: any[], imports?: any[]} * * @param type A type which may have its own (non-inherited) `ɵprov`. */ -export function getInjectableDef(type: any): ɵɵInjectableDeclaration|null { +export function getInjectableDef(type: any): ɵɵInjectableDeclaration | null { return getOwnDefinition(type, NG_PROV_DEF) || getOwnDefinition(type, NG_INJECTABLE_DEF); } @@ -199,7 +223,7 @@ export function isInjectable(type: any): boolean { * Return definition only if it is defined directly on `type` and is not inherited from a base * class of `type`. */ -function getOwnDefinition(type: any, field: string): ɵɵInjectableDeclaration|null { +function getOwnDefinition(type: any, field: string): ɵɵInjectableDeclaration | null { return type.hasOwnProperty(field) ? type[field] : null; } @@ -211,16 +235,15 @@ function getOwnDefinition(type: any, field: string): ɵɵInjectableDeclaratio * @deprecated Will be removed in a future version of Angular, where an error will occur in the * scenario if we find the `ɵprov` on an ancestor only. */ -export function getInheritedInjectableDef(type: any): ɵɵInjectableDeclaration|null { +export function getInheritedInjectableDef(type: any): ɵɵInjectableDeclaration | null { const def = type && (type[NG_PROV_DEF] || type[NG_INJECTABLE_DEF]); if (def) { ngDevMode && - console.warn( - `DEPRECATED: DI is instantiating a token "${ - type.name}" that inherits its @Injectable decorator but does not provide one itself.\n` + - `This will become an error in a future version of Angular. Please add @Injectable() to the "${ - type.name}" class.`); + console.warn( + `DEPRECATED: DI is instantiating a token "${type.name}" that inherits its @Injectable decorator but does not provide one itself.\n` + + `This will become an error in a future version of Angular. Please add @Injectable() to the "${type.name}" class.`, + ); return def; } else { return null; @@ -232,10 +255,10 @@ export function getInheritedInjectableDef(type: any): ɵɵInjectableDeclarati * * @param type type which may have an injector def (`ɵinj`) */ -export function getInjectorDef(type: any): ɵɵInjectorDef|null { - return type && (type.hasOwnProperty(NG_INJ_DEF) || type.hasOwnProperty(NG_INJECTOR_DEF)) ? - (type as any)[NG_INJ_DEF] : - null; +export function getInjectorDef(type: any): ɵɵInjectorDef | null { + return type && (type.hasOwnProperty(NG_INJ_DEF) || type.hasOwnProperty(NG_INJECTOR_DEF)) + ? (type as any)[NG_INJ_DEF] + : null; } export const NG_PROV_DEF = getClosureSafeProperty({ɵprov: getClosureSafeProperty}); diff --git a/packages/core/src/di/interface/injector.ts b/packages/core/src/di/interface/injector.ts index 240f178e804d3..b4e8f36a7cab4 100644 --- a/packages/core/src/di/interface/injector.ts +++ b/packages/core/src/di/interface/injector.ts @@ -6,14 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ - /** * Special flag indicating that a decorator is of type `Inject`. It's used to make `Inject` * decorator tree-shakable (so we don't have to rely on the `instanceof` checks). * Note: this flag is not included into the `InjectFlags` since it's an internal-only API. */ export const enum DecoratorFlags { - Inject = -1 + Inject = -1, } /** diff --git a/packages/core/src/di/interface/provider.ts b/packages/core/src/di/interface/provider.ts index d4a8df25b7d52..6b11e5107a33c 100644 --- a/packages/core/src/di/interface/provider.ts +++ b/packages/core/src/di/interface/provider.ts @@ -256,8 +256,12 @@ export interface FactoryProvider extends FactorySansProvider { * @publicApi */ export type StaticProvider = - ValueProvider|ExistingProvider|StaticClassProvider|ConstructorProvider|FactoryProvider|any[]; - + | ValueProvider + | ExistingProvider + | StaticClassProvider + | ConstructorProvider + | FactoryProvider + | any[]; /** * Configures the `Injector` to return an instance of `Type` when `Type' is used as the token. @@ -329,8 +333,14 @@ export interface ClassProvider extends ClassSansProvider { * * @publicApi */ -export type Provider = TypeProvider|ValueProvider|ClassProvider|ConstructorProvider| - ExistingProvider|FactoryProvider|any[]; +export type Provider = + | TypeProvider + | ValueProvider + | ClassProvider + | ConstructorProvider + | ExistingProvider + | FactoryProvider + | any[]; /** * Encapsulated `Provider`s that are only accepted during creation of an `EnvironmentInjector` (e.g. @@ -352,7 +362,7 @@ export type EnvironmentProviders = { }; export interface InternalEnvironmentProviders extends EnvironmentProviders { - ɵproviders: (Provider|EnvironmentProviders)[]; + ɵproviders: (Provider | EnvironmentProviders)[]; /** * If present, indicates that the `EnvironmentProviders` were derived from NgModule providers. @@ -363,8 +373,8 @@ export interface InternalEnvironmentProviders extends EnvironmentProviders { } export function isEnvironmentProviders( - value: Provider|EnvironmentProviders| - InternalEnvironmentProviders): value is InternalEnvironmentProviders { + value: Provider | EnvironmentProviders | InternalEnvironmentProviders, +): value is InternalEnvironmentProviders { return value && !!(value as InternalEnvironmentProviders).ɵproviders; } @@ -374,7 +384,6 @@ export function isEnvironmentProviders( */ export type ProcessProvidersFunction = (providers: Provider[]) => Provider[]; - /** * A wrapper around an NgModule that associates it with providers * Usage without a generic type is deprecated. @@ -383,7 +392,7 @@ export type ProcessProvidersFunction = (providers: Provider[]) => Provider[]; */ export interface ModuleWithProviders { ngModule: Type; - providers?: Array; + providers?: Array; } /** diff --git a/packages/core/src/di/internal_tokens.ts b/packages/core/src/di/internal_tokens.ts index f7a579a9a869d..f04474377fcf9 100644 --- a/packages/core/src/di/internal_tokens.ts +++ b/packages/core/src/di/internal_tokens.ts @@ -10,5 +10,6 @@ import {Type} from '../interface/type'; import {InjectionToken} from './injection_token'; -export const INJECTOR_DEF_TYPES = - new InjectionToken>>(ngDevMode ? 'INJECTOR_DEF_TYPES' : ''); +export const INJECTOR_DEF_TYPES = new InjectionToken>>( + ngDevMode ? 'INJECTOR_DEF_TYPES' : '', +); diff --git a/packages/core/src/di/jit/injectable.ts b/packages/core/src/di/jit/injectable.ts index 43cab19ab57ff..c02dbab8ea26c 100644 --- a/packages/core/src/di/jit/injectable.ts +++ b/packages/core/src/di/jit/injectable.ts @@ -6,20 +6,28 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, JitCompilerUsage, R3InjectableMetadataFacade} from '../../compiler/compiler_facade'; +import { + getCompilerFacade, + JitCompilerUsage, + R3InjectableMetadataFacade, +} from '../../compiler/compiler_facade'; import {Type} from '../../interface/type'; import {NG_FACTORY_DEF} from '../../render3/fields'; import {getClosureSafeProperty} from '../../util/property'; import {resolveForwardRef} from '../forward_ref'; import {Injectable} from '../injectable'; import {NG_PROV_DEF} from '../interface/defs'; -import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, ValueProvider, ValueSansProvider} from '../interface/provider'; +import { + ClassSansProvider, + ExistingSansProvider, + FactorySansProvider, + ValueProvider, + ValueSansProvider, +} from '../interface/provider'; import {angularCoreDiEnv} from './environment'; import {convertDependencies, reflectDependencies} from './util'; - - /** * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting * injectable def (`ɵprov`) onto the injectable type. @@ -33,10 +41,16 @@ export function compileInjectable(type: Type, meta?: Injectable): void { Object.defineProperty(type, NG_PROV_DEF, { get: () => { if (ngInjectableDef === null) { - const compiler = - getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'injectable', type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'injectable', + type, + }); ngInjectableDef = compiler.compileInjectable( - angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta)); + angularCoreDiEnv, + `ng:///${type.name}/ɵprov.js`, + getInjectableMetadata(type, meta), + ); } return ngInjectableDef; }, @@ -48,42 +62,47 @@ export function compileInjectable(type: Type, meta?: Injectable): void { Object.defineProperty(type, NG_FACTORY_DEF, { get: () => { if (ngFactoryDef === null) { - const compiler = - getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'injectable', type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'injectable', + type, + }); ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, { name: type.name, type, - typeArgumentCount: 0, // In JIT mode types are not available nor used. + typeArgumentCount: 0, // In JIT mode types are not available nor used. deps: reflectDependencies(type), - target: compiler.FactoryTarget.Injectable + target: compiler.FactoryTarget.Injectable, }); } return ngFactoryDef; }, // Leave this configurable so that the factories from directives or pipes can take precedence. - configurable: true + configurable: true, }); } } -type UseClassProvider = Injectable&ClassSansProvider&{deps?: any[]}; +type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]}; -const USE_VALUE = - getClosureSafeProperty({provide: String, useValue: getClosureSafeProperty}); +const USE_VALUE = getClosureSafeProperty({ + provide: String, + useValue: getClosureSafeProperty, +}); function isUseClassProvider(meta: Injectable): meta is UseClassProvider { return (meta as UseClassProvider).useClass !== undefined; } -function isUseValueProvider(meta: Injectable): meta is Injectable&ValueSansProvider { +function isUseValueProvider(meta: Injectable): meta is Injectable & ValueSansProvider { return USE_VALUE in meta; } -function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansProvider { +function isUseFactoryProvider(meta: Injectable): meta is Injectable & FactorySansProvider { return (meta as FactorySansProvider).useFactory !== undefined; } -function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider { +function isUseExistingProvider(meta: Injectable): meta is Injectable & ExistingSansProvider { return (meta as ExistingSansProvider).useExisting !== undefined; } diff --git a/packages/core/src/di/jit/util.ts b/packages/core/src/di/jit/util.ts index 5013e8e10a86d..cbbbe7c03646c 100644 --- a/packages/core/src/di/jit/util.ts +++ b/packages/core/src/di/jit/util.ts @@ -13,7 +13,7 @@ import {ReflectionCapabilities} from '../../reflection/reflection_capabilities'; import {Host, Inject, Optional, Self, SkipSelf} from '../metadata'; import {Attribute} from '../metadata_attr'; -let _reflect: ReflectionCapabilities|null = null; +let _reflect: ReflectionCapabilities | null = null; export function getReflect(): ReflectionCapabilities { return (_reflect = _reflect || new ReflectionCapabilities()); @@ -24,10 +24,10 @@ export function reflectDependencies(type: Type): R3DependencyMetadataFacade } export function convertDependencies(deps: any[]): R3DependencyMetadataFacade[] { - return deps.map(dep => reflectDependency(dep)); + return deps.map((dep) => reflectDependency(dep)); } -function reflectDependency(dep: any|any[]): R3DependencyMetadataFacade { +function reflectDependency(dep: any | any[]): R3DependencyMetadataFacade { const meta: R3DependencyMetadataFacade = { token: null, attribute: null, @@ -60,8 +60,9 @@ function reflectDependency(dep: any|any[]): R3DependencyMetadataFacade { } else if (param instanceof Attribute) { if (param.attributeName === undefined) { throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && `Attribute name must be defined.`); + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && `Attribute name must be defined.`, + ); } meta.attribute = param.attributeName; } else { diff --git a/packages/core/src/di/metadata.ts b/packages/core/src/di/metadata.ts index b8ef0f6a37909..e4c6f1a15c16c 100644 --- a/packages/core/src/di/metadata.ts +++ b/packages/core/src/di/metadata.ts @@ -11,7 +11,6 @@ import {makeParamDecorator} from '../util/decorators'; import {attachInjectFlag} from './injector_compatibility'; import {DecoratorFlags, InternalInjectFlags} from './interface/injector'; - /** * Type of the Inject decorator / constructor function. * @@ -36,7 +35,7 @@ export interface InjectDecorator { * */ (token: any): any; - new(token: any): Inject; + new (token: any): Inject; } /** @@ -58,9 +57,11 @@ export interface Inject { * @publicApi */ export const Inject: InjectDecorator = attachInjectFlag( - // Disable tslint because `DecoratorFlags` is a const enum which gets inlined. - // tslint:disable-next-line: no-toplevel-property-access - makeParamDecorator('Inject', (token: any) => ({token})), DecoratorFlags.Inject); + // Disable tslint because `DecoratorFlags` is a const enum which gets inlined. + makeParamDecorator('Inject', (token: any) => ({token})), + // tslint:disable-next-line: no-toplevel-property-access + DecoratorFlags.Inject, +); /** * Type of the Optional decorator / constructor function. @@ -86,7 +87,7 @@ export interface OptionalDecorator { * @see [Dependency Injection Guide](guide/di/dependency-injection. */ (): any; - new(): Optional; + new (): Optional; } /** @@ -103,9 +104,9 @@ export interface Optional {} * @publicApi */ export const Optional: OptionalDecorator = - // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. - // tslint:disable-next-line: no-toplevel-property-access - attachInjectFlag(makeParamDecorator('Optional'), InternalInjectFlags.Optional); + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Optional'), InternalInjectFlags.Optional); /** * Type of the Self decorator / constructor function. @@ -134,7 +135,7 @@ export interface SelfDecorator { * */ (): any; - new(): Self; + new (): Self; } /** @@ -151,10 +152,9 @@ export interface Self {} * @publicApi */ export const Self: SelfDecorator = - // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. - // tslint:disable-next-line: no-toplevel-property-access - attachInjectFlag(makeParamDecorator('Self'), InternalInjectFlags.Self); - + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Self'), InternalInjectFlags.Self); /** * Type of the `SkipSelf` decorator / constructor function. @@ -182,7 +182,7 @@ export interface SkipSelfDecorator { * */ (): any; - new(): SkipSelf; + new (): SkipSelf; } /** @@ -199,9 +199,9 @@ export interface SkipSelf {} * @publicApi */ export const SkipSelf: SkipSelfDecorator = - // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. - // tslint:disable-next-line: no-toplevel-property-access - attachInjectFlag(makeParamDecorator('SkipSelf'), InternalInjectFlags.SkipSelf); + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('SkipSelf'), InternalInjectFlags.SkipSelf); /** * Type of the `Host` decorator / constructor function. @@ -225,7 +225,7 @@ export interface HostDecorator { * Guide"](guide/di/di-in-action#optional). */ (): any; - new(): Host; + new (): Host; } /** @@ -242,6 +242,6 @@ export interface Host {} * @publicApi */ export const Host: HostDecorator = - // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. - // tslint:disable-next-line: no-toplevel-property-access - attachInjectFlag(makeParamDecorator('Host'), InternalInjectFlags.Host); + // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. + // tslint:disable-next-line: no-toplevel-property-access + attachInjectFlag(makeParamDecorator('Host'), InternalInjectFlags.Host); diff --git a/packages/core/src/di/metadata_attr.ts b/packages/core/src/di/metadata_attr.ts index dc7646db032db..0df826d3de900 100644 --- a/packages/core/src/di/metadata_attr.ts +++ b/packages/core/src/di/metadata_attr.ts @@ -9,7 +9,6 @@ import {ɵɵinjectAttribute} from '../render3/instructions/di_attr'; import {makeParamDecorator} from '../util/decorators'; - /** * Type of the Attribute decorator / constructor function. * @@ -38,7 +37,7 @@ export interface AttributeDecorator { * */ (name: string): any; - new(name: string): Attribute; + new (name: string): Attribute; } /** @@ -60,6 +59,9 @@ export interface Attribute { * @publicApi */ export const Attribute: AttributeDecorator = makeParamDecorator( - 'Attribute', - (attributeName?: string) => - ({attributeName, __NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName!)})); + 'Attribute', + (attributeName?: string) => ({ + attributeName, + __NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName!), + }), +); diff --git a/packages/core/src/di/provider_collection.ts b/packages/core/src/di/provider_collection.ts index 022844f54e2ee..e9fd8d319b4d4 100644 --- a/packages/core/src/di/provider_collection.ts +++ b/packages/core/src/di/provider_collection.ts @@ -21,15 +21,30 @@ import {resolveForwardRef} from './forward_ref'; import {ENVIRONMENT_INITIALIZER} from './initializer_token'; import {ɵɵinject as inject} from './injector_compatibility'; import {getInjectorDef, InjectorType, InjectorTypeWithProviders} from './interface/defs'; -import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, ImportedNgModuleProviders, InternalEnvironmentProviders, isEnvironmentProviders, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider'; +import { + ClassProvider, + ConstructorProvider, + EnvironmentProviders, + ExistingProvider, + FactoryProvider, + ImportedNgModuleProviders, + InternalEnvironmentProviders, + isEnvironmentProviders, + ModuleWithProviders, + Provider, + StaticClassProvider, + TypeProvider, + ValueProvider, +} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; /** * Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally * referenced in `@Component` in a component injector. */ -export function makeEnvironmentProviders(providers: (Provider|EnvironmentProviders)[]): - EnvironmentProviders { +export function makeEnvironmentProviders( + providers: (Provider | EnvironmentProviders)[], +): EnvironmentProviders { return { ɵproviders: providers, } as unknown as EnvironmentProviders; @@ -41,10 +56,14 @@ export function makeEnvironmentProviders(providers: (Provider|EnvironmentProvide * @publicApi */ export type ImportProvidersSource = - Type|ModuleWithProviders|Array; + | Type + | ModuleWithProviders + | Array; -type WalkProviderTreeVisitor = - (provider: SingleProvider, container: Type|InjectorType) => void; +type WalkProviderTreeVisitor = ( + provider: SingleProvider, + container: Type | InjectorType, +) => void; /** * Collects providers from all NgModules and standalone components, including transitively imported @@ -94,28 +113,32 @@ export function importProvidersFrom(...sources: ImportProvidersSource[]): Enviro } export function internalImportProvidersFrom( - checkForStandaloneCmp: boolean, ...sources: ImportProvidersSource[]): Provider[] { + checkForStandaloneCmp: boolean, + ...sources: ImportProvidersSource[] +): Provider[] { const providersOut: SingleProvider[] = []; - const dedup = new Set>(); // already seen types - let injectorTypesWithProviders: InjectorTypeWithProviders[]|undefined; + const dedup = new Set>(); // already seen types + let injectorTypesWithProviders: InjectorTypeWithProviders[] | undefined; const collectProviders: WalkProviderTreeVisitor = (provider) => { providersOut.push(provider); }; - deepForEach(sources, source => { + deepForEach(sources, (source) => { if ((typeof ngDevMode === 'undefined' || ngDevMode) && checkForStandaloneCmp) { const cmpDef = getComponentDef(source); if (cmpDef?.standalone) { throw new RuntimeError( - RuntimeErrorCode.IMPORT_PROVIDERS_FROM_STANDALONE, - `Importing providers supports NgModule or ModuleWithProviders but got a standalone component "${ - stringifyForError(source)}"`); + RuntimeErrorCode.IMPORT_PROVIDERS_FROM_STANDALONE, + `Importing providers supports NgModule or ModuleWithProviders but got a standalone component "${stringifyForError( + source, + )}"`, + ); } } // Narrow `source` to access the internal type analogue for `ModuleWithProviders`. - const internalSource = source as Type| InjectorTypeWithProviders; + const internalSource = source as Type | InjectorTypeWithProviders; if (walkProviderTree(internalSource, collectProviders, [], dedup)) { injectorTypesWithProviders ||= []; injectorTypesWithProviders.push(internalSource); @@ -134,22 +157,32 @@ export function internalImportProvidersFrom( * array. */ function processInjectorTypesWithProviders( - typesWithProviders: InjectorTypeWithProviders[], - visitor: WalkProviderTreeVisitor): void { + typesWithProviders: InjectorTypeWithProviders[], + visitor: WalkProviderTreeVisitor, +): void { for (let i = 0; i < typesWithProviders.length; i++) { const {ngModule, providers} = typesWithProviders[i]; - deepForEachProvider(providers! as Array, provider => { - ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule); - visitor(provider, ngModule); - }); + deepForEachProvider( + providers! as Array, + (provider) => { + ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule); + visitor(provider, ngModule); + }, + ); } } /** * Internal type for a single provider in a deep provider array. */ -export type SingleProvider = TypeProvider|ValueProvider|ClassProvider|ConstructorProvider| - ExistingProvider|FactoryProvider|StaticClassProvider; +export type SingleProvider = + | TypeProvider + | ValueProvider + | ClassProvider + | ConstructorProvider + | ExistingProvider + | FactoryProvider + | StaticClassProvider; /** * The logic visits an `InjectorType`, an `InjectorTypeWithProviders`, or a standalone @@ -161,15 +194,17 @@ export type SingleProvider = TypeProvider|ValueProvider|ClassProvider|Constructo * an injector definition are processed. (following View Engine semantics: see FW-1349) */ export function walkProviderTree( - container: Type|InjectorTypeWithProviders, visitor: WalkProviderTreeVisitor, - parents: Type[], - dedup: Set>): container is InjectorTypeWithProviders { + container: Type | InjectorTypeWithProviders, + visitor: WalkProviderTreeVisitor, + parents: Type[], + dedup: Set>, +): container is InjectorTypeWithProviders { container = resolveForwardRef(container); if (!container) return false; // The actual type which had the definition. Usually `container`, but may be an unwrapped type // from `InjectorTypeWithProviders`. - let defType: Type|null = null; + let defType: Type | null = null; let injDef = getInjectorDef(container); const cmpDef = !injDef && getComponentDef(container); @@ -179,8 +214,8 @@ export function walkProviderTree( // * A standalone directive or pipe that got pulled in from a standalone component's // dependencies. // Try to unwrap it as an `InjectorTypeWithProviders` first. - const ngModule: Type|undefined = - (container as InjectorTypeWithProviders).ngModule as Type| undefined; + const ngModule: Type | undefined = (container as InjectorTypeWithProviders) + .ngModule as Type | undefined; injDef = getInjectorDef(ngModule); if (injDef) { defType = ngModule!; @@ -213,7 +248,7 @@ export function walkProviderTree( if (cmpDef.dependencies) { const deps = - typeof cmpDef.dependencies === 'function' ? cmpDef.dependencies() : cmpDef.dependencies; + typeof cmpDef.dependencies === 'function' ? cmpDef.dependencies() : cmpDef.dependencies; for (const dep of deps) { walkProviderTree(dep, visitor, parents, dedup); } @@ -227,9 +262,9 @@ export function walkProviderTree( // Add it to the set of dedups. This way we can detect multiple imports of the same module dedup.add(defType); - let importTypesWithProviders: (InjectorTypeWithProviders[])|undefined; + let importTypesWithProviders: InjectorTypeWithProviders[] | undefined; try { - deepForEach(injDef.imports, imported => { + deepForEach(injDef.imports, (imported) => { if (walkProviderTree(imported, visitor, parents, dedup)) { importTypesWithProviders ||= []; // If the processed import is an injector type with providers, we store it in the @@ -267,15 +302,16 @@ export function walkProviderTree( // Provider to eagerly instantiate `defType` via `INJECTOR_INITIALIZER`. visitor( - {provide: ENVIRONMENT_INITIALIZER, useValue: () => inject(defType!), multi: true}, - defType); + {provide: ENVIRONMENT_INITIALIZER, useValue: () => inject(defType!), multi: true}, + defType, + ); } // Next, include providers listed on the definition itself. - const defProviders = injDef.providers as Array; + const defProviders = injDef.providers as Array; if (defProviders != null && !isDuplicate) { const injectorType = container as InjectorType; - deepForEachProvider(defProviders, provider => { + deepForEachProvider(defProviders, (provider) => { ngDevMode && validateProvider(provider as SingleProvider, defProviders, injectorType); visitor(provider, injectorType); }); @@ -286,29 +322,37 @@ export function walkProviderTree( } return ( - defType !== container && - (container as InjectorTypeWithProviders).providers !== undefined); + defType !== container && (container as InjectorTypeWithProviders).providers !== undefined + ); } function validateProvider( - provider: SingleProvider, providers: Array, - containerType: Type): void { - if (isTypeProvider(provider) || isValueProvider(provider) || isFactoryProvider(provider) || - isExistingProvider(provider)) { + provider: SingleProvider, + providers: Array, + containerType: Type, +): void { + if ( + isTypeProvider(provider) || + isValueProvider(provider) || + isFactoryProvider(provider) || + isExistingProvider(provider) + ) { return; } // Here we expect the provider to be a `useClass` provider (by elimination). const classRef = resolveForwardRef( - provider && ((provider as StaticClassProvider | ClassProvider).useClass || provider.provide)); + provider && ((provider as StaticClassProvider | ClassProvider).useClass || provider.provide), + ); if (!classRef) { throwInvalidProviderError(containerType, providers, provider); } } function deepForEachProvider( - providers: Array, - fn: (provider: SingleProvider) => void): void { + providers: Array, + fn: (provider: SingleProvider) => void, +): void { for (let provider of providers) { if (isEnvironmentProviders(provider)) { provider = provider.ɵproviders; @@ -321,8 +365,10 @@ function deepForEachProvider( } } -export const USE_VALUE = - getClosureSafeProperty({provide: String, useValue: getClosureSafeProperty}); +export const USE_VALUE = getClosureSafeProperty({ + provide: String, + useValue: getClosureSafeProperty, +}); export function isValueProvider(value: SingleProvider): value is ValueProvider { return value !== null && typeof value == 'object' && USE_VALUE in value; diff --git a/packages/core/src/di/provider_token.ts b/packages/core/src/di/provider_token.ts index 27d07dde63a94..8ef32a6475b53 100644 --- a/packages/core/src/di/provider_token.ts +++ b/packages/core/src/di/provider_token.ts @@ -16,4 +16,4 @@ import {InjectionToken} from './injection_token'; * * @publicApi */ -export type ProviderToken = Type|AbstractType|InjectionToken; +export type ProviderToken = Type | AbstractType | InjectionToken; diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 7b6203b088b96..6cb8d66733ed1 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -11,9 +11,19 @@ import '../util/ng_dev_mode'; import {RuntimeError, RuntimeErrorCode} from '../errors'; import {OnDestroy} from '../interface/lifecycle_hooks'; import {Type} from '../interface/type'; -import {emitInstanceCreatedByInjectorEvent, emitProviderConfiguredEvent, InjectorProfilerContext, runInInjectorProfilerContext, setInjectorProfilerContext} from '../render3/debug/injector_profiler'; +import { + emitInstanceCreatedByInjectorEvent, + emitProviderConfiguredEvent, + InjectorProfilerContext, + runInInjectorProfilerContext, + setInjectorProfilerContext, +} from '../render3/debug/injector_profiler'; import {FactoryFn, getFactoryDef} from '../render3/definition_factory'; -import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di'; +import { + throwCyclicDependencyError, + throwInvalidProviderError, + throwMixedMultiProviderError, +} from '../render3/errors_di'; import {NG_ENV_ID} from '../render3/fields'; import {newArray} from '../util/array_utils'; import {EMPTY_ARRAY} from '../util/empty'; @@ -24,14 +34,42 @@ import {ENVIRONMENT_INITIALIZER} from './initializer_token'; import {setInjectImplementation} from './inject_switch'; import {InjectionToken} from './injection_token'; import type {Injector} from './injector'; -import {catchInjectorError, convertToBitFlags, injectArgs, NG_TEMP_TOKEN_PATH, setCurrentInjector, THROW_IF_NOT_FOUND, ɵɵinject} from './injector_compatibility'; +import { + catchInjectorError, + convertToBitFlags, + injectArgs, + NG_TEMP_TOKEN_PATH, + setCurrentInjector, + THROW_IF_NOT_FOUND, + ɵɵinject, +} from './injector_compatibility'; import {INJECTOR} from './injector_token'; -import {getInheritedInjectableDef, getInjectableDef, InjectorType, ɵɵInjectableDeclaration} from './interface/defs'; +import { + getInheritedInjectableDef, + getInjectableDef, + InjectorType, + ɵɵInjectableDeclaration, +} from './interface/defs'; import {InjectFlags, InjectOptions} from './interface/injector'; -import {ClassProvider, ConstructorProvider, EnvironmentProviders, InternalEnvironmentProviders, isEnvironmentProviders, Provider, StaticClassProvider, TypeProvider} from './interface/provider'; +import { + ClassProvider, + ConstructorProvider, + EnvironmentProviders, + InternalEnvironmentProviders, + isEnvironmentProviders, + Provider, + StaticClassProvider, + TypeProvider, +} from './interface/provider'; import {INJECTOR_DEF_TYPES} from './internal_tokens'; import {NullInjector} from './null_injector'; -import {isExistingProvider, isFactoryProvider, isTypeProvider, isValueProvider, SingleProvider} from './provider_collection'; +import { + isExistingProvider, + isFactoryProvider, + isTypeProvider, + isValueProvider, + SingleProvider, +} from './provider_collection'; import {ProviderToken} from './provider_token'; import {INJECTOR_SCOPE, InjectorScope} from './scope'; import {setActiveConsumer} from '@angular/core/primitives/signals'; @@ -53,7 +91,7 @@ const CIRCULAR = {}; /** * A lazily initialized NullInjector. */ -let NULL_INJECTOR: Injector|undefined = undefined; +let NULL_INJECTOR: Injector | undefined = undefined; export function getNullInjector(): Injector { if (NULL_INJECTOR === undefined) { @@ -67,9 +105,9 @@ export function getNullInjector(): Injector { * current value. */ interface Record { - factory: (() => T)|undefined; - value: T|{}; - multi: any[]|undefined; + factory: (() => T) | undefined; + value: T | {}; + multi: any[] | undefined; } /** @@ -82,16 +120,23 @@ export abstract class EnvironmentInjector implements Injector { * @returns The instance from the injector if defined, otherwise the `notFoundValue`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. */ - abstract get(token: ProviderToken, notFoundValue: undefined, options: InjectOptions&{ - optional?: false; - }): T; + abstract get( + token: ProviderToken, + notFoundValue: undefined, + options: InjectOptions & { + optional?: false; + }, + ): T; /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`. */ - abstract get(token: ProviderToken, notFoundValue: null|undefined, options: InjectOptions): T - |null; + abstract get( + token: ProviderToken, + notFoundValue: null | undefined, + options: InjectOptions, + ): T | null; /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. @@ -138,7 +183,7 @@ export class R3Injector extends EnvironmentInjector { * - `null` value implies that we don't have the record. Used by tree-shakable injectors * to prevent further searches. */ - private records = new Map, Record|null>(); + private records = new Map, Record | null>(); /** * Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks. @@ -158,13 +203,16 @@ export class R3Injector extends EnvironmentInjector { private injectorDefTypes: Set>; constructor( - providers: Array, readonly parent: Injector, - readonly source: string|null, readonly scopes: Set) { + providers: Array, + readonly parent: Injector, + readonly source: string | null, + readonly scopes: Set, + ) { super(); // Start off by creating Records for every provider. - forEachSingleProvider( - providers as Array, - provider => this.processProvider(provider)); + forEachSingleProvider(providers as Array, (provider) => + this.processProvider(provider), + ); // Make sure the INJECTOR token provides this injector. this.records.set(INJECTOR, makeRecord(undefined, this)); @@ -176,7 +224,7 @@ export class R3Injector extends EnvironmentInjector { // Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide // any injectable scoped to APP_ROOT_SCOPE. - const record = this.records.get(INJECTOR_SCOPE) as Record; + const record = this.records.get(INJECTOR_SCOPE) as Record; if (record != null && typeof record.value === 'string') { this.scopes.add(record.value as InjectorScope); } @@ -229,7 +277,7 @@ export class R3Injector extends EnvironmentInjector { const previousInjector = setCurrentInjector(this); const previousInjectImplementation = setInjectImplementation(undefined); - let prevInjectContext: InjectorProfilerContext|undefined; + let prevInjectContext: InjectorProfilerContext | undefined; if (ngDevMode) { prevInjectContext = setInjectorProfilerContext({injector: this, token: null}); } @@ -244,8 +292,10 @@ export class R3Injector extends EnvironmentInjector { } override get( - token: ProviderToken, notFoundValue: any = THROW_IF_NOT_FOUND, - flags: InjectFlags|InjectOptions = InjectFlags.Default): T { + token: ProviderToken, + notFoundValue: any = THROW_IF_NOT_FOUND, + flags: InjectFlags | InjectOptions = InjectFlags.Default, + ): T { this.assertNotDestroyed(); if (token.hasOwnProperty(NG_ENV_ID)) { @@ -265,7 +315,7 @@ export class R3Injector extends EnvironmentInjector { // Check for the SkipSelf flag. if (!(flags & InjectFlags.SkipSelf)) { // SkipSelf isn't set, check if the record belongs to this injector. - let record: Record|undefined|null = this.records.get(token); + let record: Record | undefined | null = this.records.get(token); if (record === undefined) { // No record, but maybe the token is scoped to this injector. Look for an injectable // def with a scope matching this injector. @@ -297,13 +347,12 @@ export class R3Injector extends EnvironmentInjector { const nextInjector = !(flags & InjectFlags.Self) ? this.parent : getNullInjector(); // Set the notFoundValue based on the Optional flag - if optional is set and notFoundValue // is undefined, the value is null, otherwise it's the notFoundValue. - notFoundValue = (flags & InjectFlags.Optional) && notFoundValue === THROW_IF_NOT_FOUND ? - null : - notFoundValue; + notFoundValue = + flags & InjectFlags.Optional && notFoundValue === THROW_IF_NOT_FOUND ? null : notFoundValue; return nextInjector.get(token, notFoundValue); } catch (e: any) { if (e.name === 'NullInjectorError') { - const path: any[] = e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || []; + const path: any[] = (e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || []); path.unshift(stringify(token)); if (previousInjector) { // We still have a parent injector, keep throwing @@ -328,7 +377,7 @@ export class R3Injector extends EnvironmentInjector { const prevConsumer = setActiveConsumer(null); const previousInjector = setCurrentInjector(this); const previousInjectImplementation = setInjectImplementation(undefined); - let prevInjectContext: InjectorProfilerContext|undefined; + let prevInjectContext: InjectorProfilerContext | undefined; if (ngDevMode) { prevInjectContext = setInjectorProfilerContext({injector: this, token: null}); } @@ -337,11 +386,12 @@ export class R3Injector extends EnvironmentInjector { const initializers = this.get(ENVIRONMENT_INITIALIZER, EMPTY_ARRAY, InjectFlags.Self); if (ngDevMode && !Array.isArray(initializers)) { throw new RuntimeError( - RuntimeErrorCode.INVALID_MULTI_PROVIDER, - 'Unexpected type of the `ENVIRONMENT_INITIALIZER` token value ' + - `(expected an array, but got ${typeof initializers}). ` + - 'Please check that the `ENVIRONMENT_INITIALIZER` token is configured as a ' + - '`multi: true` provider.'); + RuntimeErrorCode.INVALID_MULTI_PROVIDER, + 'Unexpected type of the `ENVIRONMENT_INITIALIZER` token value ' + + `(expected an array, but got ${typeof initializers}). ` + + 'Please check that the `ENVIRONMENT_INITIALIZER` token is configured as a ' + + '`multi: true` provider.', + ); } for (const initializer of initializers) { initializer(); @@ -366,8 +416,9 @@ export class R3Injector extends EnvironmentInjector { assertNotDestroyed(): void { if (this._destroyed) { throw new RuntimeError( - RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED, - ngDevMode && 'Injector has already been destroyed.'); + RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED, + ngDevMode && 'Injector has already been destroyed.', + ); } } @@ -378,8 +429,9 @@ export class R3Injector extends EnvironmentInjector { // Determine the token from the provider. Either it's its own token, or has a {provide: ...} // property. provider = resolveForwardRef(provider); - let token: any = - isTypeProvider(provider) ? provider : resolveForwardRef(provider && provider.provide); + let token: any = isTypeProvider(provider) + ? provider + : resolveForwardRef(provider && provider.provide); // Construct a `Record` for the provider. const record = providerToRecord(provider); @@ -455,7 +507,7 @@ export class R3Injector extends EnvironmentInjector { } const providedIn = resolveForwardRef(def.providedIn); if (typeof providedIn === 'string') { - return providedIn === 'any' || (this.scopes.has(providedIn)); + return providedIn === 'any' || this.scopes.has(providedIn); } else { return this.injectorDefTypes.has(providedIn); } @@ -482,8 +534,9 @@ function injectableDefOrInjectorDefFactory(token: ProviderToken): FactoryFn // If it's missing that, it's an error. if (token instanceof InjectionToken) { throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && `Token ${stringify(token)} is missing a ɵprov definition.`); + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && `Token ${stringify(token)} is missing a ɵprov definition.`, + ); } // Undecorated types can sometimes be created if they have no constructor arguments. @@ -500,10 +553,12 @@ function getUndecoratedInjectableFactory(token: Function) { const paramLength = token.length; if (paramLength > 0) { throw new RuntimeError( - RuntimeErrorCode.INVALID_INJECTION_TOKEN, - ngDevMode && - `Can't resolve all parameters for ${stringify(token)}: (${ - newArray(paramLength, '?').join(', ')}).`); + RuntimeErrorCode.INVALID_INJECTION_TOKEN, + ngDevMode && + `Can't resolve all parameters for ${stringify(token)}: (${newArray(paramLength, '?').join( + ', ', + )}).`, + ); } // The constructor function appears to have no parameters. @@ -523,7 +578,7 @@ function providerToRecord(provider: SingleProvider): Record { if (isValueProvider(provider)) { return makeRecord(undefined, provider.useValue); } else { - const factory: (() => any)|undefined = providerToFactory(provider); + const factory: (() => any) | undefined = providerToFactory(provider); return makeRecord(factory, NOT_YET); } } @@ -534,8 +589,11 @@ function providerToRecord(provider: SingleProvider): Record { * @param provider provider to convert to factory */ export function providerToFactory( - provider: SingleProvider, ngModuleType?: InjectorType, providers?: any[]): () => any { - let factory: (() => any)|undefined = undefined; + provider: SingleProvider, + ngModuleType?: InjectorType, + providers?: any[], +): () => any { + let factory: (() => any) | undefined = undefined; if (ngDevMode && isEnvironmentProviders(provider)) { throwInvalidProviderError(undefined, providers, provider); } @@ -552,13 +610,14 @@ export function providerToFactory( factory = () => ɵɵinject(resolveForwardRef(provider.useExisting)); } else { const classRef = resolveForwardRef( - provider && - ((provider as StaticClassProvider | ClassProvider).useClass || provider.provide)); + provider && + ((provider as StaticClassProvider | ClassProvider).useClass || provider.provide), + ); if (ngDevMode && !classRef) { throwInvalidProviderError(ngModuleType, providers, provider); } if (hasDeps(provider)) { - factory = () => new (classRef)(...injectArgs(provider.deps)); + factory = () => new classRef(...injectArgs(provider.deps)); } else { return getFactoryDef(classRef) || injectableDefOrInjectorDefFactory(classRef); } @@ -568,7 +627,10 @@ export function providerToFactory( } function makeRecord( - factory: (() => T)|undefined, value: T|{}, multi: boolean = false): Record { + factory: (() => T) | undefined, + value: T | {}, + multi: boolean = false, +): Record { return { factory: factory, value: value, @@ -576,23 +638,30 @@ function makeRecord( }; } -function hasDeps(value: ClassProvider|ConstructorProvider| - StaticClassProvider): value is ClassProvider&{deps: any[]} { +function hasDeps( + value: ClassProvider | ConstructorProvider | StaticClassProvider, +): value is ClassProvider & {deps: any[]} { return !!(value as any).deps; } function hasOnDestroy(value: any): value is OnDestroy { - return value !== null && typeof value === 'object' && - typeof (value as OnDestroy).ngOnDestroy === 'function'; + return ( + value !== null && + typeof value === 'object' && + typeof (value as OnDestroy).ngOnDestroy === 'function' + ); } function couldBeInjectableType(value: any): value is ProviderToken { - return (typeof value === 'function') || - (typeof value === 'object' && value instanceof InjectionToken); + return ( + typeof value === 'function' || (typeof value === 'object' && value instanceof InjectionToken) + ); } function forEachSingleProvider( - providers: Array, fn: (provider: SingleProvider) => void): void { + providers: Array, + fn: (provider: SingleProvider) => void, +): void { for (const provider of providers) { if (Array.isArray(provider)) { forEachSingleProvider(provider, fn); diff --git a/packages/core/src/di/scope.ts b/packages/core/src/di/scope.ts index 2c4d375771a93..0742e4f61c2ff 100644 --- a/packages/core/src/di/scope.ts +++ b/packages/core/src/di/scope.ts @@ -8,13 +8,13 @@ import {InjectionToken} from './injection_token'; - -export type InjectorScope = 'root'|'platform'|'environment'; +export type InjectorScope = 'root' | 'platform' | 'environment'; /** * An internal token whose presence in an injector indicates that the injector should treat itself * as a root scoped injector when processing requests for unknown tokens which may indicate * they are provided in the root scope. */ -export const INJECTOR_SCOPE = - new InjectionToken(ngDevMode ? 'Set Injector scope.' : ''); +export const INJECTOR_SCOPE = new InjectionToken( + ngDevMode ? 'Set Injector scope.' : '', +); diff --git a/packages/core/src/error_handler.ts b/packages/core/src/error_handler.ts index 3c7b45b2651ca..bb0004182ffaa 100644 --- a/packages/core/src/error_handler.ts +++ b/packages/core/src/error_handler.ts @@ -50,7 +50,7 @@ export class ErrorHandler { } /** @internal */ - _findOriginalError(error: any): Error|null { + _findOriginalError(error: any): Error | null { let e = error && getOriginalError(error); while (e && getOriginalError(e)) { e = getOriginalError(e); @@ -67,10 +67,12 @@ export class ErrorHandler { * is calling `ErrorHandler.handleError` outside of the Angular zone. */ export const INTERNAL_APPLICATION_ERROR_HANDLER = new InjectionToken<(e: any) => void>( - (typeof ngDevMode === 'undefined' || ngDevMode) ? 'internal error handler' : '', { - providedIn: 'root', - factory: () => { - const userErrorHandler = inject(ErrorHandler); - return userErrorHandler.handleError.bind(this); - } - }); + typeof ngDevMode === 'undefined' || ngDevMode ? 'internal error handler' : '', + { + providedIn: 'root', + factory: () => { + const userErrorHandler = inject(ErrorHandler); + return userErrorHandler.handleError.bind(this); + }, + }, +); diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index a0391e9fae726..6617d10243c4d 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -133,7 +133,6 @@ export const enum RuntimeErrorCode { RUNTIME_DEPS_ORPHAN_COMPONENT = 1001, } - /** * Class that represents a runtime error. * Formats and outputs the error message in a consistent way. @@ -151,7 +150,10 @@ export const enum RuntimeErrorCode { * logic. */ export class RuntimeError extends Error { - constructor(public code: T, message: null|false|string) { + constructor( + public code: T, + message: null | false | string, + ) { super(formatRuntimeError(code, message)); } } @@ -161,7 +163,9 @@ export class RuntimeError extends Error { * See additional info on the `message` argument type in the `RuntimeError` class description. */ export function formatRuntimeError( - code: T, message: null|false|string): string { + code: T, + message: null | false | string, +): string { // Error code might be a negative number, which is a special marker that instructs the logic to // generate a link to the error details page on angular.io. // We also prepend `0` to non-compile-time errors. @@ -172,8 +176,7 @@ export function formatRuntimeError( if (ngDevMode && code < 0) { const addPeriodSeparator = !errorMessage.match(/[.,;!?\n]$/); const separator = addPeriodSeparator ? '.' : ''; - errorMessage = - `${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`; + errorMessage = `${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`; } return errorMessage; } diff --git a/packages/core/src/event_emitter.ts b/packages/core/src/event_emitter.ts index 53788a7efb8f6..00d556bd2b8d2 100644 --- a/packages/core/src/event_emitter.ts +++ b/packages/core/src/event_emitter.ts @@ -77,7 +77,7 @@ export interface EventEmitter extends Subject, OutputRef { * @param [isAsync=false] When true, deliver events asynchronously. * */ - new(isAsync?: boolean): EventEmitter; + new (isAsync?: boolean): EventEmitter; /** * Emits an event containing a given value. @@ -92,8 +92,11 @@ export interface EventEmitter extends Subject, OutputRef { * @param complete When supplied, a custom handler for a completion notification from this * emitter. */ - subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): - Subscription; + subscribe( + next?: (value: T) => void, + error?: (error: any) => void, + complete?: () => void, + ): Subscription; /** * Registers handlers for events emitted by this instance. * @param observerOrNext When supplied, a custom handler for emitted events, or an observer @@ -106,8 +109,8 @@ export interface EventEmitter extends Subject, OutputRef { } class EventEmitter_ extends Subject implements OutputRef { - __isAsync: boolean; // tslint:disable-line - destroyRef: DestroyRef|undefined = undefined; + __isAsync: boolean; // tslint:disable-line + destroyRef: DestroyRef | undefined = undefined; constructor(isAsync: boolean = false) { super(); @@ -173,6 +176,7 @@ function _wrapInTimeout(fn: (value: unknown) => any) { * @publicApi */ export const EventEmitter: { - new (isAsync?: boolean): EventEmitter; new(isAsync?: boolean): EventEmitter; + new (isAsync?: boolean): EventEmitter; + new (isAsync?: boolean): EventEmitter; readonly prototype: EventEmitter; } = EventEmitter_ as any; diff --git a/packages/core/src/hydration/annotate.ts b/packages/core/src/hydration/annotate.ts index 76cac96156d58..5db324e6da608 100644 --- a/packages/core/src/hydration/annotate.ts +++ b/packages/core/src/hydration/annotate.ts @@ -16,20 +16,62 @@ import {getComponentDef} from '../render3/definition'; import {CONTAINER_HEADER_OFFSET, LContainer} from '../render3/interfaces/container'; import {isTNodeShape, TNode, TNodeType} from '../render3/interfaces/node'; import {RElement} from '../render3/interfaces/renderer_dom'; -import {hasI18n, isComponentHost, isLContainer, isProjectionTNode, isRootView} from '../render3/interfaces/type_checks'; -import {CONTEXT, HEADER_OFFSET, HOST, LView, PARENT, RENDERER, TView, TVIEW, TViewType} from '../render3/interfaces/view'; +import { + hasI18n, + isComponentHost, + isLContainer, + isProjectionTNode, + isRootView, +} from '../render3/interfaces/type_checks'; +import { + CONTEXT, + HEADER_OFFSET, + HOST, + LView, + PARENT, + RENDERER, + TView, + TVIEW, + TViewType, +} from '../render3/interfaces/view'; import {unwrapLView, unwrapRNode} from '../render3/util/view_utils'; import {TransferState} from '../transfer_state'; import {unsupportedProjectionOfDomNodes} from './error_handling'; -import {collectDomEventsInfo, EVENT_REPLAY_ENABLED_DEFAULT, setJSActionAttribute} from './event_replay'; -import {getOrComputeI18nChildren, isI18nHydrationEnabled, isI18nHydrationSupportEnabled, trySerializeI18nBlock} from './i18n'; -import {CONTAINERS, DISCONNECTED_NODES, ELEMENT_CONTAINERS, I18N_DATA, MULTIPLIER, NODES, NUM_ROOT_NODES, SerializedContainerView, SerializedView, TEMPLATE_ID, TEMPLATES} from './interfaces'; +import { + collectDomEventsInfo, + EVENT_REPLAY_ENABLED_DEFAULT, + setJSActionAttribute, +} from './event_replay'; +import { + getOrComputeI18nChildren, + isI18nHydrationEnabled, + isI18nHydrationSupportEnabled, + trySerializeI18nBlock, +} from './i18n'; +import { + CONTAINERS, + DISCONNECTED_NODES, + ELEMENT_CONTAINERS, + I18N_DATA, + MULTIPLIER, + NODES, + NUM_ROOT_NODES, + SerializedContainerView, + SerializedView, + TEMPLATE_ID, + TEMPLATES, +} from './interfaces'; import {calcPathForNode, isDisconnectedNode} from './node_lookup_utils'; import {isInSkipHydrationBlock, SKIP_HYDRATION_ATTR_NAME} from './skip_hydration'; import {IS_EVENT_REPLAY_ENABLED} from './tokens'; -import {getLNodeForHydration, NGH_ATTR_NAME, NGH_DATA_KEY, processTextNodeBeforeSerialization, TextNodeMarker} from './utils'; - +import { + getLNodeForHydration, + NGH_ATTR_NAME, + NGH_DATA_KEY, + processTextNodeBeforeSerialization, + TextNodeMarker, +} from './utils'; /** * A collection that tracks all serialized views (`ngh` DOM annotations) @@ -87,7 +129,7 @@ export interface HydrationContext { serializedViewCollection: SerializedViewCollection; corruptedTextNodes: Map; isI18nHydrationEnabled: boolean; - i18nChildren: Map|null>; + i18nChildren: Map | null>; eventTypesToReplay: Set; shouldReplayEvents: boolean; } @@ -96,7 +138,7 @@ export interface HydrationContext { * Computes the number of root nodes in a given view * (or child nodes in a given container if a tNode is provided). */ -function calcNumRootNodes(tView: TView, lView: LView, tNode: TNode|null): number { +function calcNumRootNodes(tView: TView, lView: LView, tNode: TNode | null): number { const rootNodes: unknown[] = []; collectNativeNodes(tView, lView, tNode, rootNodes); return rootNodes.length; @@ -111,12 +153,14 @@ function calcNumRootNodesInLContainer(lContainer: LContainer): number { return rootNodes.length; } - /** * Annotates root level component's LView for hydration, * see `annotateHostElementForHydration` for additional information. */ -function annotateComponentLViewForHydration(lView: LView, context: HydrationContext): number|null { +function annotateComponentLViewForHydration( + lView: LView, + context: HydrationContext, +): number | null { const hostElement = lView[HOST]; // Root elements might also be annotated with the `ngSkipHydration` attribute, // check if it's present before starting the serialization process. @@ -219,7 +263,9 @@ export function annotateForHydration(appRef: ApplicationRef, doc: Document) { * @returns an array of the `SerializedView` objects */ function serializeLContainer( - lContainer: LContainer, context: HydrationContext): SerializedContainerView[] { + lContainer: LContainer, + context: HydrationContext, +): SerializedContainerView[] { const views: SerializedContainerView[] = []; let lastViewAsString = ''; @@ -228,7 +274,7 @@ function serializeLContainer( let template: string; let numRootNodes: number; - let serializedView: SerializedContainerView|undefined; + let serializedView: SerializedContainerView | undefined; if (isRootView(childLView)) { // If this is a root view, get an LView for the underlying component, @@ -301,7 +347,11 @@ function serializeLContainer( * current serialized view. */ function appendSerializedNodePath( - ngh: SerializedView, tNode: TNode, lView: LView, excludedParentNodes: Set|null) { + ngh: SerializedView, + tNode: TNode, + lView: LView, + excludedParentNodes: Set | null, +) { const noOffsetIndex = tNode.index - HEADER_OFFSET; ngh[NODES] ??= {}; ngh[NODES][noOffsetIndex] = calcPathForNode(tNode, lView, excludedParentNodes); @@ -333,9 +383,9 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView const ngh: SerializedView = {}; const tView = lView[TVIEW]; const i18nChildren = getOrComputeI18nChildren(tView, context); - const nativeElementsToEventTypes = context.shouldReplayEvents ? - collectDomEventsInfo(tView, lView, context.eventTypesToReplay) : - null; + const nativeElementsToEventTypes = context.shouldReplayEvents + ? collectDomEventsInfo(tView, lView, context.eventTypesToReplay) + : null; // Iterate over DOM element references in an LView. for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) { const tNode = tView.data[i] as TNode; @@ -391,8 +441,10 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView // appears at projection location), skip annotations for this content // since all DOM nodes in this projection were handled while processing // a parent lView, which contains those nodes. - if (!isProjectionTNode(projectionHeadTNode) && - !isInSkipHydrationBlock(projectionHeadTNode)) { + if ( + !isProjectionTNode(projectionHeadTNode) && + !isInSkipHydrationBlock(projectionHeadTNode) + ) { if (isDisconnectedNode(projectionHeadTNode, lView)) { // Check whether this node is connected, since we may have a TNode // in the data structure as a projection segment head, but the @@ -429,7 +481,7 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView } // Serialize views within this LContainer. - const hostNode = lView[i][HOST]!; // host node of this container + const hostNode = lView[i][HOST]!; // host node of this container // LView[i][HOST] can be of 2 different types: // - either a DOM node @@ -464,7 +516,7 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView // not be able to find an anchor. In this case, use full path instead. let nextTNode = tNode.next; // Skip over all `` slots in a row. - while (nextTNode !== null && (nextTNode.type & TNodeType.Projection)) { + while (nextTNode !== null && nextTNode.type & TNodeType.Projection) { nextTNode = nextTNode.next; } if (nextTNode && !isInSkipHydrationBlock(nextTNode)) { @@ -495,11 +547,17 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView * connection to identify the location of a node. */ function conditionallyAnnotateNodePath( - ngh: SerializedView, tNode: TNode, lView: LView, - excludedParentNodes: Set|null) { + ngh: SerializedView, + tNode: TNode, + lView: LView, + excludedParentNodes: Set | null, +) { // Handle case #1 described above. - if (tNode.projectionNext && tNode.projectionNext !== tNode.next && - !isInSkipHydrationBlock(tNode.projectionNext)) { + if ( + tNode.projectionNext && + tNode.projectionNext !== tNode.next && + !isInSkipHydrationBlock(tNode.projectionNext) + ) { appendSerializedNodePath(ngh, tNode.projectionNext, lView, excludedParentNodes); } @@ -507,8 +565,12 @@ function conditionallyAnnotateNodePath( // Note: we only do that for the first node (i.e. when `tNode.prev === null`), // the rest of the nodes would rely on the current node location, so no extra // annotation is needed. - if (tNode.prev === null && tNode.parent !== null && isDisconnectedNode(tNode.parent, lView) && - !isDisconnectedNode(tNode, lView)) { + if ( + tNode.prev === null && + tNode.parent !== null && + isDisconnectedNode(tNode.parent, lView) && + !isDisconnectedNode(tNode, lView) + ) { appendSerializedNodePath(ngh, tNode, lView, excludedParentNodes); } } @@ -519,9 +581,9 @@ function conditionallyAnnotateNodePath( */ function componentUsesShadowDomEncapsulation(lView: LView): boolean { const instance = lView[CONTEXT]; - return instance?.constructor ? - getComponentDef(instance.constructor)?.encapsulation === ViewEncapsulation.ShadowDom : - false; + return instance?.constructor + ? getComponentDef(instance.constructor)?.encapsulation === ViewEncapsulation.ShadowDom + : false; } /** @@ -538,10 +600,15 @@ function componentUsesShadowDomEncapsulation(lView: LView): boolean { * or `null` when a given component can not be serialized. */ function annotateHostElementForHydration( - element: RElement, lView: LView, context: HydrationContext): number|null { + element: RElement, + lView: LView, + context: HydrationContext, +): number | null { const renderer = lView[RENDERER]; - if ((hasI18n(lView) && !isI18nHydrationSupportEnabled()) || - componentUsesShadowDomEncapsulation(lView)) { + if ( + (hasI18n(lView) && !isI18nHydrationSupportEnabled()) || + componentUsesShadowDomEncapsulation(lView) + ) { // Attach the skip hydration attribute if this component: // - either has i18n blocks, since hydrating such blocks is not yet supported // - or uses ShadowDom view encapsulation, since Domino doesn't support @@ -567,7 +634,9 @@ function annotateHostElementForHydration( * @param doc The document */ function insertCorruptedTextNodeMarkers( - corruptedTextNodes: Map, doc: Document) { + corruptedTextNodes: Map, + doc: Document, +) { for (const [textNode, marker] of corruptedTextNodes) { textNode.after(doc.createComment(marker)); } diff --git a/packages/core/src/hydration/api.ts b/packages/core/src/hydration/api.ts index c401ac3bac9c0..4660017aa0f62 100644 --- a/packages/core/src/hydration/api.ts +++ b/packages/core/src/hydration/api.ts @@ -8,7 +8,13 @@ import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref'; import {Console} from '../console'; -import {ENVIRONMENT_INITIALIZER, EnvironmentProviders, Injector, makeEnvironmentProviders, Provider} from '../di'; +import { + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + Injector, + makeEnvironmentProviders, + Provider, +} from '../di'; import {inject} from '../di/injector_compatibility'; import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../errors'; import {enableLocateOrCreateContainerRefImpl} from '../linker/view_container_ref'; @@ -25,8 +31,17 @@ import {performanceMarkFeature} from '../util/performance'; import {NgZone} from '../zone'; import {cleanupDehydratedViews} from './cleanup'; -import {enableClaimDehydratedIcuCaseImpl, enablePrepareI18nBlockForHydrationImpl, isI18nHydrationEnabled, setIsI18nHydrationSupportEnabled} from './i18n'; -import {IS_HYDRATION_DOM_REUSE_ENABLED, IS_I18N_HYDRATION_ENABLED, PRESERVE_HOST_CONTENT} from './tokens'; +import { + enableClaimDehydratedIcuCaseImpl, + enablePrepareI18nBlockForHydrationImpl, + isI18nHydrationEnabled, + setIsI18nHydrationSupportEnabled, +} from './i18n'; +import { + IS_HYDRATION_DOM_REUSE_ENABLED, + IS_I18N_HYDRATION_ENABLED, + PRESERVE_HOST_CONTENT, +} from './tokens'; import {enableRetrieveHydrationInfoImpl, NGH_DATA_KEY, SSR_CONTENT_INTEGRITY_MARKER} from './utils'; import {enableFindMatchingDehydratedViewImpl} from './views'; @@ -96,15 +111,15 @@ function enableI18nHydrationRuntimeSupport() { */ function printHydrationStats(injector: Injector) { const console = injector.get(Console); - const message = `Angular hydrated ${ngDevMode!.hydratedComponents} component(s) ` + - `and ${ngDevMode!.hydratedNodes} node(s), ` + - `${ngDevMode!.componentsSkippedHydration} component(s) were skipped. ` + - `Learn more at https://angular.io/guide/hydration.`; + const message = + `Angular hydrated ${ngDevMode!.hydratedComponents} component(s) ` + + `and ${ngDevMode!.hydratedNodes} node(s), ` + + `${ngDevMode!.componentsSkippedHydration} component(s) were skipped. ` + + `Learn more at https://angular.io/guide/hydration.`; // tslint:disable-next-line:no-console console.log(message); } - /** * Returns a Promise that is resolved when an application becomes stable. */ @@ -149,15 +164,16 @@ export function withDomHydration(): EnvironmentProviders { // hydration annotations. Otherwise, keep hydration disabled. const transferState = inject(TransferState, {optional: true}); isEnabled = !!transferState?.get(NGH_DATA_KEY, null); - if (!isEnabled && (typeof ngDevMode !== 'undefined' && ngDevMode)) { + if (!isEnabled && typeof ngDevMode !== 'undefined' && ngDevMode) { const console = inject(Console); const message = formatRuntimeError( - RuntimeErrorCode.MISSING_HYDRATION_ANNOTATIONS, - 'Angular hydration was requested on the client, but there was no ' + - 'serialized information present in the server response, ' + - 'thus hydration was not enabled. ' + - 'Make sure the `provideClientHydration()` is included into the list ' + - 'of providers in the server part of the application configuration.'); + RuntimeErrorCode.MISSING_HYDRATION_ANNOTATIONS, + 'Angular hydration was requested on the client, but there was no ' + + 'serialized information present in the server response, ' + + 'thus hydration was not enabled. ' + + 'Make sure the `provideClientHydration()` is included into the list ' + + 'of providers in the server part of the application configuration.', + ); // tslint:disable-next-line:no-console console.warn(message); } @@ -195,7 +211,7 @@ export function withDomHydration(): EnvironmentProviders { // On a server, an application is rendered from scratch, // so the host content needs to be empty. return isPlatformBrowser() && inject(IS_HYDRATION_DOM_REUSE_ENABLED); - } + }, }, { provide: APP_BOOTSTRAP_LISTENER, @@ -219,10 +235,10 @@ export function withDomHydration(): EnvironmentProviders { }); }; } - return () => {}; // noop + return () => {}; // noop }, multi: true, - } + }, ]); } @@ -254,10 +270,9 @@ export function withI18nSupport(): Provider[] { */ function logWarningOnStableTimedout(time: number, console: Console): void { const message = - `Angular hydration expected the ApplicationRef.isStable() to emit \`true\`, but it ` + - `didn't happen within ${ - time}ms. Angular hydration logic depends on the application becoming stable ` + - `as a signal to complete hydration process.`; + `Angular hydration expected the ApplicationRef.isStable() to emit \`true\`, but it ` + + `didn't happen within ${time}ms. Angular hydration logic depends on the application becoming stable ` + + `as a signal to complete hydration process.`; console.warn(formatRuntimeError(RuntimeErrorCode.HYDRATION_STABLE_TIMEDOUT, message)); } @@ -274,21 +289,25 @@ function logWarningOnStableTimedout(time: number, console: Console): void { */ function verifySsrContentsIntegrity(): void { const doc = getDocument(); - let hydrationMarker: Node|undefined; + let hydrationMarker: Node | undefined; for (const node of doc.body.childNodes) { - if (node.nodeType === Node.COMMENT_NODE && - node.textContent?.trim() === SSR_CONTENT_INTEGRITY_MARKER) { + if ( + node.nodeType === Node.COMMENT_NODE && + node.textContent?.trim() === SSR_CONTENT_INTEGRITY_MARKER + ) { hydrationMarker = node; break; } } if (!hydrationMarker) { throw new RuntimeError( - RuntimeErrorCode.MISSING_SSR_CONTENT_INTEGRITY_MARKER, - typeof ngDevMode !== 'undefined' && ngDevMode && - 'Angular hydration logic detected that HTML content of this page was modified after it ' + - 'was produced during server side rendering. Make sure that there are no optimizations ' + - 'that remove comment nodes from HTML enabled on your CDN. Angular hydration ' + - 'relies on HTML produced by the server, including whitespaces and comment nodes.'); + RuntimeErrorCode.MISSING_SSR_CONTENT_INTEGRITY_MARKER, + typeof ngDevMode !== 'undefined' && + ngDevMode && + 'Angular hydration logic detected that HTML content of this page was modified after it ' + + 'was produced during server side rendering. Make sure that there are no optimizations ' + + 'that remove comment nodes from HTML enabled on your CDN. Angular hydration ' + + 'relies on HTML produced by the server, including whitespaces and comment nodes.', + ); } } diff --git a/packages/core/src/hydration/cleanup.ts b/packages/core/src/hydration/cleanup.ts index 4cb85dea78bc6..d0b0664ef4eef 100644 --- a/packages/core/src/hydration/cleanup.ts +++ b/packages/core/src/hydration/cleanup.ts @@ -7,11 +7,23 @@ */ import {ApplicationRef} from '../application/application_ref'; -import {CONTAINER_HEADER_OFFSET, DEHYDRATED_VIEWS, LContainer} from '../render3/interfaces/container'; +import { + CONTAINER_HEADER_OFFSET, + DEHYDRATED_VIEWS, + LContainer, +} from '../render3/interfaces/container'; import {Renderer} from '../render3/interfaces/renderer'; import {RNode} from '../render3/interfaces/renderer_dom'; import {isLContainer, isLView} from '../render3/interfaces/type_checks'; -import {HEADER_OFFSET, HOST, HYDRATION, LView, PARENT, RENDERER, TVIEW} from '../render3/interfaces/view'; +import { + HEADER_OFFSET, + HOST, + HYDRATION, + LView, + PARENT, + RENDERER, + TVIEW, +} from '../render3/interfaces/view'; import {nativeRemoveNode} from '../render3/node_manipulation'; import {EMPTY_ARRAY} from '../util/empty'; diff --git a/packages/core/src/hydration/compression.ts b/packages/core/src/hydration/compression.ts index b61d146cf556a..991bf75b9afb3 100644 --- a/packages/core/src/hydration/compression.ts +++ b/packages/core/src/hydration/compression.ts @@ -15,8 +15,9 @@ import {NodeNavigationStep, REFERENCE_NODE_BODY, REFERENCE_NODE_HOST} from './in * - the `b` char which indicates that the lookup should start from the `document.body` * - the `h` char to start lookup from the component host node (`lView[HOST]`) */ -const REF_EXTRACTOR_REGEXP = - new RegExp(`^(\\d+)*(${REFERENCE_NODE_BODY}|${REFERENCE_NODE_HOST})*(.*)`); +const REF_EXTRACTOR_REGEXP = new RegExp( + `^(\\d+)*(${REFERENCE_NODE_BODY}|${REFERENCE_NODE_HOST})*(.*)`, +); /** * Helper function that takes a reference node location and a set of navigation steps @@ -27,7 +28,7 @@ const REF_EXTRACTOR_REGEXP = * 'nextSibling'], the function returns: `bf2n`. */ export function compressNodeLocation(referenceNode: string, path: NodeNavigationStep[]): string { - const result: Array = [referenceNode]; + const result: Array = [referenceNode]; for (const segment of path) { const lastIdx = result.length - 1; if (lastIdx > 0 && result[lastIdx - 1] === segment) { @@ -57,13 +58,14 @@ export function compressNodeLocation(referenceNode: string, path: NodeNavigation * This information is later consumed by the code that navigates the DOM to find * a given node by its location. */ -export function decompressNodeLocation(path: string): - [string|number, ...(number | NodeNavigationStep)[]] { +export function decompressNodeLocation( + path: string, +): [string | number, ...(number | NodeNavigationStep)[]] { const matches = path.match(REF_EXTRACTOR_REGEXP)!; const [_, refNodeId, refNodeName, rest] = matches; // If a reference node is represented by an index, transform it to a number. const ref = refNodeId ? parseInt(refNodeId, 10) : refNodeName; - const steps: (number|NodeNavigationStep)[] = []; + const steps: (number | NodeNavigationStep)[] = []; // Match all segments in a path. for (const [_, step, count] of rest.matchAll(/(f|n)(\d*)/g)) { const repeat = parseInt(count, 10) || 1; diff --git a/packages/core/src/hydration/error_handling.ts b/packages/core/src/hydration/error_handling.ts index e75079d49ff4e..a53824544957d 100644 --- a/packages/core/src/hydration/error_handling.ts +++ b/packages/core/src/hydration/error_handling.ts @@ -51,12 +51,19 @@ function getFriendlyStringFromTNodeType(tNodeType: TNodeType): string { * Validates that provided nodes match during the hydration process. */ export function validateMatchingNode( - node: RNode|null, nodeType: number, tagName: string|null, lView: LView, tNode: TNode, - isViewContainerAnchor = false): void { - if (!node || - ((node as Node).nodeType !== nodeType || - ((node as Node).nodeType === Node.ELEMENT_NODE && - (node as HTMLElement).tagName.toLowerCase() !== tagName?.toLowerCase()))) { + node: RNode | null, + nodeType: number, + tagName: string | null, + lView: LView, + tNode: TNode, + isViewContainerAnchor = false, +): void { + if ( + !node || + (node as Node).nodeType !== nodeType || + ((node as Node).nodeType === Node.ELEMENT_NODE && + (node as HTMLElement).tagName.toLowerCase() !== tagName?.toLowerCase()) + ) { const expectedNode = shortRNodeDescription(nodeType, tagName, null); let header = `During hydration Angular expected ${expectedNode} but `; @@ -76,8 +83,10 @@ export function validateMatchingNode( markRNodeAsHavingHydrationMismatch(componentHostElement, expectedDom); } else { const actualNode = shortRNodeDescription( - (node as Node).nodeType, (node as HTMLElement).tagName ?? null, - (node as HTMLElement).textContent ?? null); + (node as Node).nodeType, + (node as HTMLElement).tagName ?? null, + (node as HTMLElement).textContent ?? null, + ); header += `found ${actualNode}.\n\n`; const actualDom = describeDomFromNode(node); @@ -97,7 +106,7 @@ export function validateMatchingNode( /** * Validates that a given node has sibling nodes */ -export function validateSiblingNodeExists(node: RNode|null): void { +export function validateSiblingNodeExists(node: RNode | null): void { validateNodeExists(node); if (!node!.nextSibling) { const header = 'During hydration Angular expected more sibling nodes to be present.\n\n'; @@ -115,10 +124,13 @@ export function validateSiblingNodeExists(node: RNode|null): void { * Validates that a node exists or throws */ export function validateNodeExists( - node: RNode|null, lView: LView|null = null, tNode: TNode|null = null): void { + node: RNode | null, + lView: LView | null = null, + tNode: TNode | null = null, +): void { if (!node) { const header = - 'During hydration, Angular expected an element to be present at this location.\n\n'; + 'During hydration, Angular expected an element to be present at this location.\n\n'; let expected = ''; let footer = ''; if (lView !== null && tNode !== null) { @@ -130,7 +142,9 @@ export function validateNodeExists( } throw new RuntimeError( - RuntimeErrorCode.HYDRATION_MISSING_NODE, `${header}${expected}\n\n${footer}`); + RuntimeErrorCode.HYDRATION_MISSING_NODE, + `${header}${expected}\n\n${footer}`, + ); } } @@ -155,15 +169,15 @@ export function nodeNotFoundError(lView: LView, tNode: TNode): Error { * @param path the path to the node */ export function nodeNotFoundAtPathError(host: Node, path: string): Error { - const header = `During hydration Angular was unable to locate a node ` + - `using the "${path}" path, starting from the ${describeRNode(host)} node.\n\n`; + const header = + `During hydration Angular was unable to locate a node ` + + `using the "${path}" path, starting from the ${describeRNode(host)} node.\n\n`; const footer = getHydrationErrorFooter(); markRNodeAsHavingHydrationMismatch(host); throw new RuntimeError(RuntimeErrorCode.HYDRATION_MISSING_NODE, header + footer); } - /** * Builds the hydration error message in the case that dom nodes are created outside of * the Angular context and are being used as projected nodes @@ -173,11 +187,12 @@ export function nodeNotFoundAtPathError(host: Node, path: string): Error { * @returns an error */ export function unsupportedProjectionOfDomNodes(rNode: RNode): Error { - const header = 'During serialization, Angular detected DOM nodes ' + - 'that were created outside of Angular context and provided as projectable nodes ' + - '(likely via `ViewContainerRef.createComponent` or `createComponent` APIs). ' + - 'Hydration is not supported for such cases, consider refactoring the code to avoid ' + - 'this pattern or using `ngSkipHydration` on the host element of the component.\n\n'; + const header = + 'During serialization, Angular detected DOM nodes ' + + 'that were created outside of Angular context and provided as projectable nodes ' + + '(likely via `ViewContainerRef.createComponent` or `createComponent` APIs). ' + + 'Hydration is not supported for such cases, consider refactoring the code to avoid ' + + 'this pattern or using `ngSkipHydration` on the host element of the component.\n\n'; const actual = `${describeDomFromNode(rNode)}\n\n`; const message = header + actual + getHydrationAttributeNote(); return new RuntimeError(RuntimeErrorCode.UNSUPPORTED_PROJECTION_DOM_NODES, message); @@ -191,9 +206,10 @@ export function unsupportedProjectionOfDomNodes(rNode: RNode): Error { * @returns an error */ export function invalidSkipHydrationHost(rNode: RNode): Error { - const header = 'The `ngSkipHydration` flag is applied on a node ' + - 'that doesn\'t act as a component host. Hydration can be ' + - 'skipped only on per-component basis.\n\n'; + const header = + 'The `ngSkipHydration` flag is applied on a node ' + + "that doesn't act as a component host. Hydration can be " + + 'skipped only on per-component basis.\n\n'; const actual = `${describeDomFromNode(rNode)}\n\n`; const footer = 'Please move the `ngSkipHydration` attribute to the component host element.\n\n'; const message = header + actual + footer; @@ -211,7 +227,7 @@ export function invalidSkipHydrationHost(rNode: RNode): Error { function stringifyTNodeAttrs(tNode: TNode): string { const results = []; if (tNode.attrs) { - for (let i = 0; i < tNode.attrs.length;) { + for (let i = 0; i < tNode.attrs.length; ) { const attrName = tNode.attrs[i++]; // Once we reach the first flag, we know that the list of // attributes is over. @@ -366,7 +382,10 @@ function describeDomFromNode(node: RNode): string { * @returns string */ function shortRNodeDescription( - nodeType: number, tagName: string|null, textContent: string|null): string { + nodeType: number, + tagName: string | null, + textContent: string | null, +): string { switch (nodeType) { case Node.ELEMENT_NODE: return `<${tagName!.toLowerCase()}>`; @@ -380,7 +399,6 @@ function shortRNodeDescription( } } - /** * Builds the footer hydration error message * @@ -389,19 +407,23 @@ function shortRNodeDescription( */ function getHydrationErrorFooter(componentClassName?: string): string { const componentInfo = componentClassName ? `the "${componentClassName}"` : 'corresponding'; - return `To fix this problem:\n` + - ` * check ${componentInfo} component for hydration-related issues\n` + - ` * check to see if your template has valid HTML structure\n` + - ` * or skip hydration by adding the \`ngSkipHydration\` attribute ` + - `to its host node in a template\n\n`; + return ( + `To fix this problem:\n` + + ` * check ${componentInfo} component for hydration-related issues\n` + + ` * check to see if your template has valid HTML structure\n` + + ` * or skip hydration by adding the \`ngSkipHydration\` attribute ` + + `to its host node in a template\n\n` + ); } /** * An attribute related note for hydration errors */ function getHydrationAttributeNote(): string { - return 'Note: attributes are only displayed to better represent the DOM' + - ' but have no effect on hydration mismatches.\n\n'; + return ( + 'Note: attributes are only displayed to better represent the DOM' + + ' but have no effect on hydration mismatches.\n\n' + ); } // Node string utility functions @@ -423,7 +445,7 @@ function stripNewlines(input: string): string { * @param maxLength a maximum length in characters * @returns string */ -function shorten(input: string|null, maxLength = 50): string { +function shorten(input: string | null, maxLength = 50): string { if (!input) { return ''; } diff --git a/packages/core/src/hydration/event_replay.ts b/packages/core/src/hydration/event_replay.ts index 6157e25cec9f6..41be1d1d6c44b 100644 --- a/packages/core/src/hydration/event_replay.ts +++ b/packages/core/src/hydration/event_replay.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {Dispatcher, EventContract, EventInfoWrapper, registerDispatcher} from '@angular/core/primitives/event-dispatch'; +import { + Dispatcher, + EventContract, + EventInfoWrapper, + registerDispatcher, +} from '@angular/core/primitives/event-dispatch'; import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref'; import {APP_ID} from '../application/application_tokens'; @@ -64,10 +69,10 @@ export function withEventReplay(): Provider[] { }); }; } - return () => {}; // noop for the server code + return () => {}; // noop for the server code }, multi: true, - } + }, ]; } @@ -76,14 +81,17 @@ export function withEventReplay(): Provider[] { * LView. Maps collected events to a corresponding DOM element (an element is used as a key). */ export function collectDomEventsInfo( - tView: TView, lView: LView, eventTypesToReplay: Set): Map { + tView: TView, + lView: LView, + eventTypesToReplay: Set, +): Map { const events = new Map(); const lCleanup = lView[CLEANUP]; const tCleanup = tView.cleanup; if (!tCleanup || !lCleanup) { return events; } - for (let i = 0; i < tCleanup.length;) { + for (let i = 0; i < tCleanup.length; ) { const firstParam = tCleanup[i++]; const secondParam = tCleanup[i++]; if (typeof firstParam !== 'string') { @@ -92,7 +100,7 @@ export function collectDomEventsInfo( const name: string = firstParam; eventTypesToReplay.add(name); const listenerElement = unwrapRNode(lView[secondParam]) as any as Element; - i++; // move the cursor to the next position (location of the listener idx) + i++; // move the cursor to the next position (location of the listener idx) const useCaptureOrIndx = tCleanup[i++]; // if useCaptureOrIndx is boolean then report it as is. // if useCaptureOrIndx is positive number then it in unsubscribe method @@ -111,11 +119,14 @@ export function collectDomEventsInfo( } export function setJSActionAttribute( - tNode: TNode, rNode: RNode, nativeElementToEvents: Map) { + tNode: TNode, + rNode: RNode, + nativeElementToEvents: Map, +) { if (tNode.type & TNodeType.Element) { const nativeElement = unwrapRNode(rNode) as Element; const events = nativeElementToEvents.get(nativeElement) ?? []; - const parts = events.map(event => `${event}:`); + const parts = events.map((event) => `${event}:`); if (parts.length > 0) { nativeElement.setAttribute('jsaction', parts.join(';')); } @@ -126,7 +137,7 @@ export function setJSActionAttribute( * Registers a function that should be invoked to replay events. */ function setEventReplayer(dispatcher: Dispatcher) { - dispatcher.setEventReplayer(queue => { + dispatcher.setEventReplayer((queue) => { for (const event of queue) { handleEvent(event); } @@ -136,7 +147,7 @@ function setEventReplayer(dispatcher: Dispatcher) { /** * Finds an LView that a given DOM element belongs to. */ -function getLViewByElement(target: HTMLElement): LView|null { +function getLViewByElement(target: HTMLElement): LView | null { let lView = readLView(target); if (lView) { return lView; @@ -145,7 +156,7 @@ function getLViewByElement(target: HTMLElement): LView|null { // traverse upwards up the DOM to find the nearest element that // has already been monkey patched with data. let parent = target as HTMLElement; - while (parent = parent.parentNode as HTMLElement) { + while ((parent = parent.parentNode as HTMLElement)) { lView = readLView(parent); if (lView) { // To prevent additional lookups, monkey-patch LView id onto this DOM node. @@ -176,21 +187,25 @@ function handleEvent(event: EventInfoWrapper) { } } -type Listener = ((value: Event) => unknown)|(() => unknown); +type Listener = ((value: Event) => unknown) | (() => unknown); function getEventListeners( - tView: TView, lView: LView, nativeElement: Element, eventName: string): Listener[] { + tView: TView, + lView: LView, + nativeElement: Element, + eventName: string, +): Listener[] { const listeners: Listener[] = []; const lCleanup = lView[CLEANUP]; const tCleanup = tView.cleanup; if (tCleanup && lCleanup) { - for (let i = 0; i < tCleanup.length;) { + for (let i = 0; i < tCleanup.length; ) { const storedEventName = tCleanup[i++]; const nativeElementIndex = tCleanup[i++]; if (typeof storedEventName === 'string') { const listenerElement = unwrapRNode(lView[nativeElementIndex]) as any as Element; const listener: Listener = lCleanup[tCleanup[i++]]; - i++; // increment to the next position; + i++; // increment to the next position; if (listenerElement === nativeElement && eventName === storedEventName) { listeners.push(listener); } diff --git a/packages/core/src/hydration/i18n.ts b/packages/core/src/hydration/i18n.ts index 014b965f8b72a..4519007c023b2 100644 --- a/packages/core/src/hydration/i18n.ts +++ b/packages/core/src/hydration/i18n.ts @@ -21,7 +21,12 @@ import type {HydrationContext} from './annotate'; import {DehydratedIcuData, DehydratedView, I18N_DATA} from './interfaces'; import {locateNextRNode, tryLocateRNodeByPath} from './node_lookup_utils'; import {IS_I18N_HYDRATION_ENABLED} from './tokens'; -import {getNgContainerSize, initDisconnectedNodes, isSerializedElementContainer, processTextNodeBeforeSerialization} from './utils'; +import { + getNgContainerSize, + initDisconnectedNodes, + isSerializedElementContainer, + processTextNodeBeforeSerialization, +} from './utils'; let _isI18nHydrationSupportEnabled = false; @@ -47,7 +52,11 @@ export function isI18nHydrationSupportEnabled() { * @param subTemplateIndex sub-template index, or -1 for the main template */ export function prepareI18nBlockForHydration( - lView: LView, index: number, parentTNode: TNode|null, subTemplateIndex: number): void { + lView: LView, + index: number, + parentTNode: TNode | null, + subTemplateIndex: number, +): void { _prepareI18nBlockForHydrationImpl(lView, index, parentTNode, subTemplateIndex); } @@ -67,8 +76,10 @@ export function isI18nHydrationEnabled(injector?: Injector) { * Since i18n blocks don't introduce a parent TNode, this is necessary * in order to determine which indices in a LView are translated. */ -export function getOrComputeI18nChildren(tView: TView, context: HydrationContext): Set| - null { +export function getOrComputeI18nChildren( + tView: TView, + context: HydrationContext, +): Set | null { let i18nChildren = context.i18nChildren.get(tView); if (i18nChildren === undefined) { i18nChildren = collectI18nChildren(tView); @@ -77,7 +88,7 @@ export function getOrComputeI18nChildren(tView: TView, context: HydrationContext return i18nChildren; } -function collectI18nChildren(tView: TView): Set|null { +function collectI18nChildren(tView: TView): Set | null { const children = new Set(); function collectI18nViews(node: I18nNode) { @@ -129,7 +140,10 @@ function collectI18nChildren(tView: TView): Set|null { * @returns the i18n data, or null if there is no relevant data */ export function trySerializeI18nBlock( - lView: LView, index: number, context: HydrationContext): Array|null { + lView: LView, + index: number, + context: HydrationContext, +): Array | null { if (!context.isI18nHydrationEnabled) { return null; } @@ -141,12 +155,16 @@ export function trySerializeI18nBlock( } const caseQueue: number[] = []; - tI18n.ast.forEach(node => serializeI18nBlock(lView, caseQueue, context, node)); + tI18n.ast.forEach((node) => serializeI18nBlock(lView, caseQueue, context, node)); return caseQueue.length > 0 ? caseQueue : null; } function serializeI18nBlock( - lView: LView, caseQueue: number[], context: HydrationContext, node: I18nNode) { + lView: LView, + caseQueue: number[], + context: HydrationContext, + node: I18nNode, +) { switch (node.kind) { case I18nNodeKind.TEXT: const rNode = unwrapRNode(lView[node.index]!); @@ -155,7 +173,7 @@ function serializeI18nBlock( case I18nNodeKind.ELEMENT: case I18nNodeKind.PLACEHOLDER: - node.children.forEach(node => serializeI18nBlock(lView, caseQueue, context, node)); + node.children.forEach((node) => serializeI18nBlock(lView, caseQueue, context, node)); break; case I18nNodeKind.ICU: @@ -165,7 +183,7 @@ function serializeI18nBlock( // need to invert it to get the proper value. const caseIdx = currentCase < 0 ? ~currentCase : currentCase; caseQueue.push(caseIdx); - node.cases[caseIdx].forEach(node => serializeI18nBlock(lView, caseQueue, context, node)); + node.cases[caseIdx].forEach((node) => serializeI18nBlock(lView, caseQueue, context, node)); } break; } @@ -177,7 +195,7 @@ function serializeI18nBlock( interface I18nHydrationContext { hydrationInfo: DehydratedView; lView: LView; - i18nNodes: Map; + i18nNodes: Map; disconnectedNodes: Set; caseQueue: number[]; dehydratedIcuData: Map; @@ -188,7 +206,7 @@ interface I18nHydrationContext { */ interface I18nHydrationState { // The current node - currentNode: Node|null; + currentNode: Node | null; /** * Whether the tree should be connected. @@ -202,7 +220,7 @@ interface I18nHydrationState { isConnected: boolean; } -function setCurrentNode(state: I18nHydrationState, node: Node|null) { +function setCurrentNode(state: I18nHydrationState, node: Node | null) { state.currentNode = node; } @@ -211,7 +229,10 @@ function setCurrentNode(state: I18nHydrationState, node: Node|null) { * AST node. */ function appendI18nNodeToCollection( - context: I18nHydrationContext, state: I18nHydrationState, astNode: I18nNode) { + context: I18nHydrationContext, + state: I18nHydrationState, + astNode: I18nNode, +) { const noOffsetIndex = astNode.index - HEADER_OFFSET; const {disconnectedNodes} = context; const currentNode = state.currentNode; @@ -252,12 +273,16 @@ function skipSiblingNodes(state: I18nHydrationState, skip: number) { /** * Fork the given state into a new state for hydrating children. */ -function forkHydrationState(state: I18nHydrationState, nextNode: Node|null) { +function forkHydrationState(state: I18nHydrationState, nextNode: Node | null) { return {currentNode: nextNode, isConnected: state.isConnected}; } function prepareI18nBlockForHydrationImpl( - lView: LView, index: number, parentTNode: TNode|null, subTemplateIndex: number) { + lView: LView, + index: number, + parentTNode: TNode | null, + subTemplateIndex: number, +) { if (!isI18nHydrationSupportEnabled()) { return; } @@ -270,8 +295,7 @@ function prepareI18nBlockForHydrationImpl( const tView = lView[TVIEW]; const tI18n = tView.data[index] as TI18n; ngDevMode && - assertDefined( - tI18n, 'Expected i18n data to be present in a given TView slot during hydration'); + assertDefined(tI18n, 'Expected i18n data to be present in a given TView slot during hydration'); function findHydrationRoot() { if (isRootTemplateMessage(subTemplateIndex)) { @@ -284,7 +308,7 @@ function prepareI18nBlockForHydrationImpl( // If this i18n block is attached to an , then we want to begin // hydrating directly with the RNode. Otherwise, for a TNode with a physical DOM // element, we want to recurse into the first child and begin there. - return (parentTNode!.type & TNodeType.ElementContainer) ? rootNode : rootNode.firstChild; + return parentTNode!.type & TNodeType.ElementContainer ? rootNode : rootNode.firstChild; } // This is a nested template in an i18n block. In this case, the entire view @@ -297,14 +321,18 @@ function prepareI18nBlockForHydrationImpl( ngDevMode && assertDefined(currentNode, 'Expected root i18n node during hydration'); const disconnectedNodes = initDisconnectedNodes(hydrationInfo) ?? new Set(); - const i18nNodes = hydrationInfo.i18nNodes ??= new Map(); + const i18nNodes = (hydrationInfo.i18nNodes ??= new Map()); const caseQueue = hydrationInfo.data[I18N_DATA]?.[index - HEADER_OFFSET] ?? []; - const dehydratedIcuData = hydrationInfo.dehydratedIcuData ??= - new Map(); + const dehydratedIcuData = (hydrationInfo.dehydratedIcuData ??= new Map< + number, + DehydratedIcuData + >()); collectI18nNodesFromDom( - {hydrationInfo, lView, i18nNodes, disconnectedNodes, caseQueue, dehydratedIcuData}, - {currentNode, isConnected: true}, tI18n.ast); + {hydrationInfo, lView, i18nNodes, disconnectedNodes, caseQueue, dehydratedIcuData}, + {currentNode, isConnected: true}, + tI18n.ast, + ); // Nodes from inactive ICU cases should be considered disconnected. We track them above // because they aren't (and shouldn't be) serialized. Since we may mutate or create a @@ -313,14 +341,20 @@ function prepareI18nBlockForHydrationImpl( } function collectI18nNodesFromDom( - context: I18nHydrationContext, state: I18nHydrationState, nodeOrNodes: I18nNode|I18nNode[]) { + context: I18nHydrationContext, + state: I18nHydrationState, + nodeOrNodes: I18nNode | I18nNode[], +) { if (Array.isArray(nodeOrNodes)) { for (const node of nodeOrNodes) { // If the node is being projected elsewhere, we need to temporarily // branch the state to that location to continue hydration. // Otherwise, we continue hydration from the current location. - const targetNode = - tryLocateRNodeByPath(context.hydrationInfo, context.lView, node.index - HEADER_OFFSET); + const targetNode = tryLocateRNodeByPath( + context.hydrationInfo, + context.lView, + node.index - HEADER_OFFSET, + ); const nextState = targetNode ? forkHydrationState(state, targetNode as Node) : state; collectI18nNodesFromDom(context, nextState, node); } @@ -336,8 +370,10 @@ function collectI18nNodesFromDom( case I18nNodeKind.ELEMENT: { // Recurse into the current element's children... collectI18nNodesFromDom( - context, forkHydrationState(state, state.currentNode?.firstChild ?? null), - nodeOrNodes.children); + context, + forkHydrationState(state, state.currentNode?.firstChild ?? null), + nodeOrNodes.children, + ); // And claim the parent element itself. const currentNode = appendI18nNodeToCollection(context, state, nodeOrNodes); @@ -370,8 +406,10 @@ function collectI18nNodesFromDom( // Non-container elements represent an actual node in the DOM, so we // need to continue hydration with the children, and claim the node. collectI18nNodesFromDom( - context, forkHydrationState(state, state.currentNode?.firstChild ?? null), - nodeOrNodes.children); + context, + forkHydrationState(state, state.currentNode?.firstChild ?? null), + nodeOrNodes.children, + ); setCurrentNode(state, currentNode?.nextSibling ?? null); // Elements can also be the anchor of a view container, so there may @@ -387,9 +425,11 @@ function collectI18nNodesFromDom( case I18nPlaceholderType.SUBTEMPLATE: { ngDevMode && - assertNotEqual( - containerSize, null, - 'Expected a container size while hydrating i18n subtemplate'); + assertNotEqual( + containerSize, + null, + 'Expected a container size while hydrating i18n subtemplate', + ); // Hydration expects to find the head of the template. appendI18nNodeToCollection(context, state, nodeOrNodes); @@ -414,7 +454,10 @@ function collectI18nNodesFromDom( // so that we correctly populate disconnected nodes. for (let i = 0; i < nodeOrNodes.cases.length; i++) { collectI18nNodesFromDom( - context, i === selectedCase ? state : childState, nodeOrNodes.cases[i]); + context, + i === selectedCase ? state : childState, + nodeOrNodes.cases[i], + ); } if (selectedCase !== null) { @@ -481,7 +524,10 @@ export function cleanupI18nHydrationData(lView: LView) { } function cleanupDehydratedIcuData( - renderer: Renderer, i18nNodes: Map, dehydratedIcuData: DehydratedIcuData) { + renderer: Renderer, + i18nNodes: Map, + dehydratedIcuData: DehydratedIcuData, +) { for (const node of dehydratedIcuData.node.cases[dehydratedIcuData.case]) { const rNode = i18nNodes.get(node.index - HEADER_OFFSET); if (rNode) { diff --git a/packages/core/src/hydration/interfaces.ts b/packages/core/src/hydration/interfaces.ts index ebc445cde79a9..dedd988cfadad 100644 --- a/packages/core/src/hydration/interfaces.ts +++ b/packages/core/src/hydration/interfaces.ts @@ -9,7 +9,6 @@ import type {I18nICUNode} from '../render3/interfaces/i18n'; import {RNode} from '../render3/interfaces/renderer_dom'; - /** Encodes that the node lookup should start from the host node of this component. */ export const REFERENCE_NODE_HOST = 'h'; @@ -34,7 +33,7 @@ export const TEMPLATES = 't'; export const CONTAINERS = 'c'; export const MULTIPLIER = 'x'; export const NUM_ROOT_NODES = 'r'; -export const TEMPLATE_ID = 'i'; // as it's also an "id" +export const TEMPLATE_ID = 'i'; // as it's also an "id" export const NODES = 'n'; export const DISCONNECTED_NODES = 'd'; export const I18N_DATA = 'l'; @@ -155,13 +154,13 @@ export interface DehydratedView { * in invoking corresponding DOM actions (attaching DOM nodes action is * skipped when we hydrate, since nodes are already in the DOM). */ - firstChild: RNode|null; + firstChild: RNode | null; /** * Stores references to first nodes in DOM segments that * represent either an or a view container. */ - segmentHeads?: {[index: number]: RNode|null}; + segmentHeads?: {[index: number]: RNode | null}; /** * An instance of a Set that represents nodes disconnected from @@ -174,7 +173,7 @@ export interface DehydratedView { * If the value is `null`, it means that there were no disconnected * nodes detected in this view at serialization time. */ - disconnectedNodes?: Set|null; + disconnectedNodes?: Set | null; /** * A mapping from a view to the first child to begin claiming nodes. @@ -182,7 +181,7 @@ export interface DehydratedView { * This mapping is generated by an i18n block, and is the source of * truth for the nodes inside of it. */ - i18nNodes?: Map; + i18nNodes?: Map; /** * A mapping from the index of an ICU node to dehydrated data for it. diff --git a/packages/core/src/hydration/node_lookup_utils.ts b/packages/core/src/hydration/node_lookup_utils.ts index 4e525edd8acdc..12254e7648685 100644 --- a/packages/core/src/hydration/node_lookup_utils.ts +++ b/packages/core/src/hydration/node_lookup_utils.ts @@ -8,7 +8,13 @@ import {TNode, TNodeType} from '../render3/interfaces/node'; import {RElement, RNode} from '../render3/interfaces/renderer_dom'; -import {DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, HOST, LView, TView} from '../render3/interfaces/view'; +import { + DECLARATION_COMPONENT_VIEW, + HEADER_OFFSET, + HOST, + LView, + TView, +} from '../render3/interfaces/view'; import {getFirstNativeNode} from '../render3/node_manipulation'; import {ɵɵresolveBody} from '../render3/util/misc_utils'; import {renderStringify} from '../render3/util/stringify_utils'; @@ -16,11 +22,20 @@ import {getNativeByTNode, unwrapRNode} from '../render3/util/view_utils'; import {assertDefined} from '../util/assert'; import {compressNodeLocation, decompressNodeLocation} from './compression'; -import {nodeNotFoundAtPathError, nodeNotFoundError, validateSiblingNodeExists} from './error_handling'; -import {DehydratedView, NodeNavigationStep, NODES, REFERENCE_NODE_BODY, REFERENCE_NODE_HOST} from './interfaces'; +import { + nodeNotFoundAtPathError, + nodeNotFoundError, + validateSiblingNodeExists, +} from './error_handling'; +import { + DehydratedView, + NodeNavigationStep, + NODES, + REFERENCE_NODE_BODY, + REFERENCE_NODE_HOST, +} from './interfaces'; import {calcSerializedContainerSize, getSegmentHead} from './utils'; - /** Whether current TNode is a first node in an . */ function isFirstElementInNgContainer(tNode: TNode): boolean { return !tNode.prev && tNode.parent?.type === TNodeType.ElementContainer; @@ -39,8 +54,11 @@ function getNoOffsetIndex(tNode: TNode): number { * only use internal data structures and state to compute this information. */ export function isDisconnectedNode(tNode: TNode, lView: LView) { - return !(tNode.type & TNodeType.Projection) && !!lView[tNode.index] && - !(unwrapRNode(lView[tNode.index]) as Node)?.isConnected; + return ( + !(tNode.type & TNodeType.Projection) && + !!lView[tNode.index] && + !(unwrapRNode(lView[tNode.index]) as Node)?.isConnected + ); } /** @@ -51,7 +69,9 @@ export function isDisconnectedNode(tNode: TNode, lView: LView) { * @returns an RNode that corresponds to the instruction index */ export function locateI18nRNodeByIndex( - hydrationInfo: DehydratedView, noOffsetIndex: number): T|null|undefined { + hydrationInfo: DehydratedView, + noOffsetIndex: number, +): T | null | undefined { const i18nNodes = hydrationInfo.i18nNodes; if (i18nNodes) { return i18nNodes.get(noOffsetIndex) as T | null | undefined; @@ -68,7 +88,10 @@ export function locateI18nRNodeByIndex( * @returns an RNode that corresponds to the instruction index or null if no path exists */ export function tryLocateRNodeByPath( - hydrationInfo: DehydratedView, lView: LView, noOffsetIndex: number): RNode|null { + hydrationInfo: DehydratedView, + lView: LView, + noOffsetIndex: number, +): RNode | null { const nodes = hydrationInfo.data[NODES]; const path = nodes?.[noOffsetIndex]; return path ? locateRNodeByPath(path, lView) : null; @@ -84,7 +107,11 @@ export function tryLocateRNodeByPath( * @returns an RNode that represents a given tNode */ export function locateNextRNode( - hydrationInfo: DehydratedView, tView: TView, lView: LView, tNode: TNode): T|null { + hydrationInfo: DehydratedView, + tView: TView, + lView: LView, + tNode: TNode, +): T | null { const noOffsetIndex = getNoOffsetIndex(tNode); let native = locateI18nRNodeByIndex(hydrationInfo, noOffsetIndex); @@ -102,10 +129,11 @@ export function locateNextRNode( const previousTNodeParent = tNode.prev === null; const previousTNode = (tNode.prev ?? tNode.parent)!; ngDevMode && - assertDefined( - previousTNode, - 'Unexpected state: current TNode does not have a connection ' + - 'to the previous node or a parent node.'); + assertDefined( + previousTNode, + 'Unexpected state: current TNode does not have a connection ' + + 'to the previous node or a parent node.', + ); if (isFirstElementInNgContainer(tNode)) { const noOffsetParentIndex = getNoOffsetIndex(tNode.parent!); native = getSegmentHead(hydrationInfo, noOffsetParentIndex); @@ -122,8 +150,10 @@ export function locateNextRNode( const noOffsetPrevSiblingIndex = getNoOffsetIndex(previousTNode); const segmentHead = getSegmentHead(hydrationInfo, noOffsetPrevSiblingIndex); if (previousTNode.type === TNodeType.Element && segmentHead) { - const numRootNodesToSkip = - calcSerializedContainerSize(hydrationInfo, noOffsetPrevSiblingIndex); + const numRootNodesToSkip = calcSerializedContainerSize( + hydrationInfo, + noOffsetPrevSiblingIndex, + ); // `+1` stands for an anchor comment node after all the views in this container. const nodesToSkip = numRootNodesToSkip + 1; // First node after this segment. @@ -141,7 +171,7 @@ export function locateNextRNode( /** * Skips over a specified number of nodes and returns the next sibling node after that. */ -export function siblingAfter(skip: number, from: RNode): T|null { +export function siblingAfter(skip: number, from: RNode): T | null { let currentNode = from; for (let i = 0; i < skip; i++) { ngDevMode && validateSiblingNodeExists(currentNode); @@ -155,7 +185,7 @@ export function siblingAfter(skip: number, from: RNode): T|null * (in terms of `nextSibling` and `firstChild` navigations). Used in error * messages in dev mode. */ -function stringifyNavigationInstructions(instructions: (number|NodeNavigationStep)[]): string { +function stringifyNavigationInstructions(instructions: (number | NodeNavigationStep)[]): string { const container = []; for (let i = 0; i < instructions.length; i += 2) { const step = instructions[i]; @@ -171,7 +201,7 @@ function stringifyNavigationInstructions(instructions: (number|NodeNavigationSte * Helper function that navigates from a starting point node (the `from` node) * using provided set of navigation instructions (within `path` argument). */ -function navigateToNode(from: Node, instructions: (number|NodeNavigationStep)[]): RNode { +function navigateToNode(from: Node, instructions: (number | NodeNavigationStep)[]): RNode { let node = from; for (let i = 0; i < instructions.length; i += 2) { const step = instructions[i]; @@ -207,7 +237,8 @@ function locateRNodeByPath(path: string, lView: LView): RNode { ref = lView[DECLARATION_COMPONENT_VIEW][HOST] as unknown as Element; } else if (referenceNode === REFERENCE_NODE_BODY) { ref = ɵɵresolveBody( - lView[DECLARATION_COMPONENT_VIEW][HOST] as RElement & {ownerDocument: Document}); + lView[DECLARATION_COMPONENT_VIEW][HOST] as RElement & {ownerDocument: Document}, + ); } else { const parentElementId = Number(referenceNode); ref = unwrapRNode((lView as any)[parentElementId + HEADER_OFFSET]) as Element; @@ -222,7 +253,7 @@ function locateRNodeByPath(path: string, lView: LView): RNode { * tree. That is, we should be able to get from `start` to `finish` purely by using `.firstChild` * and `.nextSibling` operations. */ -export function navigateBetween(start: Node, finish: Node): NodeNavigationStep[]|null { +export function navigateBetween(start: Node, finish: Node): NodeNavigationStep[] | null { if (start === finish) { return []; } else if (start.parentElement == null || finish.parentElement == null) { @@ -252,9 +283,9 @@ export function navigateBetween(start: Node, finish: Node): NodeNavigationStep[] * Calculates a path between 2 sibling nodes (generates a number of `NextSibling` navigations). * Returns `null` if no such path exists between the given nodes. */ -function navigateBetweenSiblings(start: Node, finish: Node): NodeNavigationStep[]|null { +function navigateBetweenSiblings(start: Node, finish: Node): NodeNavigationStep[] | null { const nav: NodeNavigationStep[] = []; - let node: Node|null = null; + let node: Node | null = null; for (node = start; node != null && node !== finish; node = node.nextSibling) { nav.push(NodeNavigationStep.NextSibling); } @@ -272,7 +303,7 @@ function navigateBetweenSiblings(start: Node, finish: Node): NodeNavigationStep[ * - the `to` node is a node that the runtime logic would be looking up, * using the path generated by this function. */ -export function calcPathBetween(from: Node, to: Node, fromNodeName: string): string|null { +export function calcPathBetween(from: Node, to: Node, fromNodeName: string): string | null { const path = navigateBetween(from, to); return path === null ? null : compressNodeLocation(fromNodeName, path); } @@ -282,9 +313,12 @@ export function calcPathBetween(from: Node, to: Node, fromNodeName: string): str * instructions needs to be generated for a TNode. */ export function calcPathForNode( - tNode: TNode, lView: LView, excludedParentNodes: Set|null): string { + tNode: TNode, + lView: LView, + excludedParentNodes: Set | null, +): string { let parentTNode = tNode.parent; - let parentIndex: number|string; + let parentIndex: number | string; let parentRNode: RNode; let referenceNodeName: string; @@ -300,8 +334,9 @@ export function calcPathForNode( // because it belongs to an i18n block, which requires paths which aren't // relative to other views in an i18n block. while ( - parentTNode !== null && - (isDisconnectedNode(parentTNode, lView) || (excludedParentNodes?.has(parentTNode.index)))) { + parentTNode !== null && + (isDisconnectedNode(parentTNode, lView) || excludedParentNodes?.has(parentTNode.index)) + ) { parentTNode = parentTNode.parent; } @@ -331,7 +366,7 @@ export function calcPathForNode( rNode = firstRNode; } } - let path: string|null = calcPathBetween(parentRNode as Node, rNode as Node, referenceNodeName); + let path: string | null = calcPathBetween(parentRNode as Node, rNode as Node, referenceNodeName); if (path === null && parentRNode !== rNode) { // Searching for a path between elements within a host node failed. // Trying to find a path to an element starting from the `document.body` instead. diff --git a/packages/core/src/hydration/skip_hydration.ts b/packages/core/src/hydration/skip_hydration.ts index c131ff4860b69..184dfdb6544ff 100644 --- a/packages/core/src/hydration/skip_hydration.ts +++ b/packages/core/src/hydration/skip_hydration.ts @@ -61,7 +61,7 @@ export function isInSkipHydrationBlock(tNode: TNode): boolean { if (hasInSkipHydrationBlockFlag(tNode)) { return true; } - let currentTNode: TNode|null = tNode.parent; + let currentTNode: TNode | null = tNode.parent; while (currentTNode) { if (hasInSkipHydrationBlockFlag(tNode) || hasSkipHydrationAttrOnTNode(currentTNode)) { return true; diff --git a/packages/core/src/hydration/tokens.ts b/packages/core/src/hydration/tokens.ts index 42e7999699a92..7dad4fc0cbce9 100644 --- a/packages/core/src/hydration/tokens.ts +++ b/packages/core/src/hydration/tokens.ts @@ -13,7 +13,8 @@ import {InjectionToken} from '../di/injection_token'; * during hydration is enabled. */ export const IS_HYDRATION_DOM_REUSE_ENABLED = new InjectionToken( - (typeof ngDevMode === 'undefined' || !!ngDevMode) ? 'IS_HYDRATION_DOM_REUSE_ENABLED' : ''); + typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_HYDRATION_DOM_REUSE_ENABLED' : '', +); // By default (in client rendering mode), we remove all the contents // of the host element and render an application after that. @@ -24,21 +25,25 @@ export const PRESERVE_HOST_CONTENT_DEFAULT = false; * retained during the bootstrap. */ export const PRESERVE_HOST_CONTENT = new InjectionToken( - (typeof ngDevMode === 'undefined' || !!ngDevMode) ? 'PRESERVE_HOST_CONTENT' : '', { - providedIn: 'root', - factory: () => PRESERVE_HOST_CONTENT_DEFAULT, - }); + typeof ngDevMode === 'undefined' || !!ngDevMode ? 'PRESERVE_HOST_CONTENT' : '', + { + providedIn: 'root', + factory: () => PRESERVE_HOST_CONTENT_DEFAULT, + }, +); /** * Internal token that indicates whether hydration support for i18n * is enabled. */ export const IS_I18N_HYDRATION_ENABLED = new InjectionToken( - (typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_I18N_HYDRATION_ENABLED' : '')); + typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_I18N_HYDRATION_ENABLED' : '', +); /** * Internal token that indicates whether event replay support for SSR * is enabled. */ export const IS_EVENT_REPLAY_ENABLED = new InjectionToken( - (typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_EVENT_REPLAY_ENABLED' : '')); + typeof ngDevMode === 'undefined' || !!ngDevMode ? 'IS_EVENT_REPLAY_ENABLED' : '', +); diff --git a/packages/core/src/hydration/utils.ts b/packages/core/src/hydration/utils.ts index 46c7ef20383ef..66a3ae210d977 100644 --- a/packages/core/src/hydration/utils.ts +++ b/packages/core/src/hydration/utils.ts @@ -18,7 +18,17 @@ import {makeStateKey, TransferState} from '../transfer_state'; import {assertDefined} from '../util/assert'; import type {HydrationContext} from './annotate'; -import {CONTAINERS, DehydratedView, DISCONNECTED_NODES, ELEMENT_CONTAINERS, MULTIPLIER, NUM_ROOT_NODES, SerializedContainerView, SerializedElementContainers, SerializedView,} from './interfaces'; +import { + CONTAINERS, + DehydratedView, + DISCONNECTED_NODES, + ELEMENT_CONTAINERS, + MULTIPLIER, + NUM_ROOT_NODES, + SerializedContainerView, + SerializedElementContainers, + SerializedView, +} from './interfaces'; /** * The name of the key used in the TransferState collection, @@ -75,10 +85,10 @@ export const enum TextNodeMarker { let _retrieveHydrationInfoImpl: typeof retrieveHydrationInfoImpl = () => null; export function retrieveHydrationInfoImpl( - rNode: RElement, - injector: Injector, - isRootView = false, - ): DehydratedView|null { + rNode: RElement, + injector: Injector, + isRootView = false, +): DehydratedView | null { let nghAttrValue = rNode.getAttribute(NGH_ATTR_NAME); if (nghAttrValue == null) return null; @@ -171,10 +181,10 @@ export function enableRetrieveHydrationInfoImpl() { * and accessing a corresponding slot in TransferState storage. */ export function retrieveHydrationInfo( - rNode: RElement, - injector: Injector, - isRootView = false, - ): DehydratedView|null { + rNode: RElement, + injector: Injector, + isRootView = false, +): DehydratedView | null { return _retrieveHydrationInfoImpl(rNode, injector, isRootView); } @@ -184,7 +194,7 @@ export function retrieveHydrationInfo( * - an LContainer for cases when component acts as a ViewContainerRef anchor * - `null` in case of an embedded view */ -export function getLNodeForHydration(viewRef: ViewRef): LView|LContainer|null { +export function getLNodeForHydration(viewRef: ViewRef): LView | LContainer | null { // Reading an internal field from `ViewRef` instance. let lView = (viewRef as any)._lView as LView; const tView = lView[TVIEW]; @@ -202,7 +212,7 @@ export function getLNodeForHydration(viewRef: ViewRef): LView|LContainer|null { return lView; } -function getTextNodeContent(node: Node): string|undefined { +function getTextNodeContent(node: Node): string | undefined { return node.textContent?.replace(/\s/gm, ''); } @@ -221,7 +231,7 @@ export function processTextNodeMarkersBeforeHydration(node: HTMLElement) { acceptNode(node) { const content = getTextNodeContent(node); const isTextNodeMarker = - content === TextNodeMarker.EmptyNode || content === TextNodeMarker.Separator; + content === TextNodeMarker.EmptyNode || content === TextNodeMarker.Separator; return isTextNodeMarker ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, }); @@ -255,13 +265,15 @@ export enum HydrationStatus { } // clang-format off -export type HydrationInfo = { - status: HydrationStatus.Hydrated|HydrationStatus.Skipped; -}|{ - status: HydrationStatus.Mismatched; - actualNodeDetails: string|null; - expectedNodeDetails: string|null -}; +export type HydrationInfo = + | { + status: HydrationStatus.Hydrated | HydrationStatus.Skipped; + } + | { + status: HydrationStatus.Mismatched; + actualNodeDetails: string | null; + expectedNodeDetails: string | null; + }; // clang-format on const HYDRATION_INFO_KEY = '__ngDebugHydrationInfo__'; @@ -274,7 +286,7 @@ function patchHydrationInfo(node: RNode, info: HydrationInfo) { (node as HydratedNode)[HYDRATION_INFO_KEY] = info; } -export function readHydrationInfo(node: RNode): HydrationInfo|null { +export function readHydrationInfo(node: RNode): HydrationInfo | null { return (node as HydratedNode)[HYDRATION_INFO_KEY] ?? null; } @@ -286,8 +298,8 @@ export function readHydrationInfo(node: RNode): HydrationInfo|null { export function markRNodeAsClaimedByHydration(node: RNode, checkIfAlreadyClaimed = true) { if (!ngDevMode) { throw new Error( - 'Calling `markRNodeAsClaimedByHydration` in prod mode ' + - 'is not supported and likely a mistake.', + 'Calling `markRNodeAsClaimedByHydration` in prod mode ' + + 'is not supported and likely a mistake.', ); } if (checkIfAlreadyClaimed && isRNodeClaimedForHydration(node)) { @@ -300,8 +312,8 @@ export function markRNodeAsClaimedByHydration(node: RNode, checkIfAlreadyClaimed export function markRNodeAsSkippedByHydration(node: RNode) { if (!ngDevMode) { throw new Error( - 'Calling `markRNodeAsSkippedByHydration` in prod mode ' + - 'is not supported and likely a mistake.', + 'Calling `markRNodeAsSkippedByHydration` in prod mode ' + + 'is not supported and likely a mistake.', ); } patchHydrationInfo(node, {status: HydrationStatus.Skipped}); @@ -309,14 +321,14 @@ export function markRNodeAsSkippedByHydration(node: RNode) { } export function markRNodeAsHavingHydrationMismatch( - node: RNode, - expectedNodeDetails: string|null = null, - actualNodeDetails: string|null = null, + node: RNode, + expectedNodeDetails: string | null = null, + actualNodeDetails: string | null = null, ) { if (!ngDevMode) { throw new Error( - 'Calling `markRNodeAsMismatchedByHydration` in prod mode ' + - 'is not supported and likely a mistake.', + 'Calling `markRNodeAsMismatchedByHydration` in prod mode ' + + 'is not supported and likely a mistake.', ); } @@ -341,15 +353,15 @@ export function isRNodeClaimedForHydration(node: RNode): boolean { } export function setSegmentHead( - hydrationInfo: DehydratedView, - index: number, - node: RNode|null, - ): void { + hydrationInfo: DehydratedView, + index: number, + node: RNode | null, +): void { hydrationInfo.segmentHeads ??= {}; hydrationInfo.segmentHeads[index] = node; } -export function getSegmentHead(hydrationInfo: DehydratedView, index: number): RNode|null { +export function getSegmentHead(hydrationInfo: DehydratedView, index: number): RNode | null { return hydrationInfo.segmentHeads?.[index] ?? null; } @@ -360,7 +372,7 @@ export function getSegmentHead(hydrationInfo: DehydratedView, index: number): RN * container (in case this `` was also used as a view * container host node, e.g. ). */ -export function getNgContainerSize(hydrationInfo: DehydratedView, index: number): number|null { +export function getNgContainerSize(hydrationInfo: DehydratedView, index: number): number | null { const data = hydrationInfo.data; let size = data[ELEMENT_CONTAINERS]?.[index] ?? null; // If there is no serialized information available in the `ELEMENT_CONTAINERS` slot, @@ -374,14 +386,16 @@ export function getNgContainerSize(hydrationInfo: DehydratedView, index: number) } export function isSerializedElementContainer( - hydrationInfo: DehydratedView, index: number): boolean { + hydrationInfo: DehydratedView, + index: number, +): boolean { return hydrationInfo.data[ELEMENT_CONTAINERS]?.[index] !== undefined; } export function getSerializedContainerViews( - hydrationInfo: DehydratedView, - index: number, - ): SerializedContainerView[]|null { + hydrationInfo: DehydratedView, + index: number, +): SerializedContainerView[] | null { return hydrationInfo.data[CONTAINERS]?.[index] ?? null; } @@ -402,7 +416,7 @@ export function calcSerializedContainerSize(hydrationInfo: DehydratedView, index * Attempt to initialize the `disconnectedNodes` field of the given * `DehydratedView`. Returns the initialized value. */ -export function initDisconnectedNodes(hydrationInfo: DehydratedView): Set|null { +export function initDisconnectedNodes(hydrationInfo: DehydratedView): Set | null { // Check if we are processing disconnected info for the first time. if (typeof hydrationInfo.disconnectedNodes === 'undefined') { const nodeIds = hydrationInfo.data[DISCONNECTED_NODES]; diff --git a/packages/core/src/hydration/views.ts b/packages/core/src/hydration/views.ts index ad440dbb08c34..33a2eb16ade0e 100644 --- a/packages/core/src/hydration/views.ts +++ b/packages/core/src/hydration/views.ts @@ -10,18 +10,24 @@ import {DEHYDRATED_VIEWS, LContainer} from '../render3/interfaces/container'; import {RNode} from '../render3/interfaces/renderer_dom'; import {removeDehydratedViews} from './cleanup'; -import {DehydratedContainerView, MULTIPLIER, NUM_ROOT_NODES, SerializedContainerView, TEMPLATE_ID} from './interfaces'; +import { + DehydratedContainerView, + MULTIPLIER, + NUM_ROOT_NODES, + SerializedContainerView, + TEMPLATE_ID, +} from './interfaces'; import {siblingAfter} from './node_lookup_utils'; - /** * Given a current DOM node and a serialized information about the views * in a container, walks over the DOM structure, collecting the list of * dehydrated views. */ export function locateDehydratedViewsInContainer( - currentRNode: RNode, - serializedViews: SerializedContainerView[]): [RNode, DehydratedContainerView[]] { + currentRNode: RNode, + serializedViews: SerializedContainerView[], +): [RNode, DehydratedContainerView[]] { const dehydratedViews: DehydratedContainerView[] = []; for (const serializedView of serializedViews) { // Repeats a view multiple times as needed, based on the serialized information @@ -65,7 +71,9 @@ let _findMatchingDehydratedViewImpl: typeof findMatchingDehydratedViewImpl = () * in this container. */ function findMatchingDehydratedViewImpl( - lContainer: LContainer, template: string|null): DehydratedContainerView|null { + lContainer: LContainer, + template: string | null, +): DehydratedContainerView | null { const views = lContainer[DEHYDRATED_VIEWS]; if (!template || views === null || views.length === 0) { return null; @@ -92,6 +100,8 @@ export function enableFindMatchingDehydratedViewImpl() { } export function findMatchingDehydratedView( - lContainer: LContainer, template: string|null): DehydratedContainerView|null { + lContainer: LContainer, + template: string | null, +): DehydratedContainerView | null { return _findMatchingDehydratedViewImpl(lContainer, template); } diff --git a/packages/core/src/i18n/locale_data_api.ts b/packages/core/src/i18n/locale_data_api.ts index 31edd75bd4b70..3d1ecc0ff1465 100644 --- a/packages/core/src/i18n/locale_data_api.ts +++ b/packages/core/src/i18n/locale_data_api.ts @@ -22,7 +22,7 @@ let LOCALE_DATA: {[localeId: string]: any} = {}; * * The signature `registerLocaleData(data: any, extraData?: any)` is deprecated since v5.1 */ -export function registerLocaleData(data: any, localeId?: string|any, extraData?: any): void { +export function registerLocaleData(data: any, localeId?: string | any, extraData?: any): void { if (typeof localeId !== 'string') { extraData = localeId; localeId = data[LocaleDataIndex.LocaleId]; @@ -64,8 +64,9 @@ export function findLocaleData(locale: string): any { } throw new RuntimeError( - RuntimeErrorCode.MISSING_LOCALE_DATA, - ngDevMode && `Missing locale data for the locale "${locale}".`); + RuntimeErrorCode.MISSING_LOCALE_DATA, + ngDevMode && `Missing locale data for the locale "${locale}".`, + ); } /** @@ -77,7 +78,7 @@ export function findLocaleData(locale: string): any { * @returns The code of the default currency for the given locale. * */ -export function getLocaleCurrencyCode(locale: string): string|null { +export function getLocaleCurrencyCode(locale: string): string | null { const data = findLocaleData(locale); return data[LocaleDataIndex.CurrencyCode] || null; } @@ -95,16 +96,17 @@ export function getLocalePluralCase(locale: string): (value: number) => number { return data[LocaleDataIndex.PluralCase]; } - - /** * Helper function to get the given `normalizedLocale` from `LOCALE_DATA` * or from the global `ng.common.locale`. */ export function getLocaleData(normalizedLocale: string): any { if (!(normalizedLocale in LOCALE_DATA)) { - LOCALE_DATA[normalizedLocale] = global.ng && global.ng.common && global.ng.common.locales && - global.ng.common.locales[normalizedLocale]; + LOCALE_DATA[normalizedLocale] = + global.ng && + global.ng.common && + global.ng.common.locales && + global.ng.common.locales[normalizedLocale]; } return LOCALE_DATA[normalizedLocale]; } @@ -141,7 +143,7 @@ export enum LocaleDataIndex { Currencies, Directionality, PluralCase, - ExtraData + ExtraData, } /** @@ -150,7 +152,7 @@ export enum LocaleDataIndex { export const enum ExtraLocaleDataIndex { ExtraDayPeriodFormats = 0, ExtraDayPeriodStandalone, - ExtraDayPeriodsRules + ExtraDayPeriodsRules, } /** @@ -159,7 +161,7 @@ export const enum ExtraLocaleDataIndex { export const enum CurrencyIndex { Symbol = 0, SymbolNarrow, - NbOfDigits + NbOfDigits, } /** diff --git a/packages/core/src/i18n/localization.ts b/packages/core/src/i18n/localization.ts index 7cd40545368f3..d4c49a2e84246 100644 --- a/packages/core/src/i18n/localization.ts +++ b/packages/core/src/i18n/localization.ts @@ -16,7 +16,7 @@ const pluralMapping = ['zero', 'one', 'two', 'few', 'many']; export function getPluralCase(value: string, locale: string): string { const plural = getLocalePluralCase(locale)(parseInt(value, 10)); const result = pluralMapping[plural]; - return (result !== undefined) ? result : 'other'; + return result !== undefined ? result : 'other'; } /** diff --git a/packages/core/src/i18n/tokens.ts b/packages/core/src/i18n/tokens.ts index 009ead56d1ce5..0f743907643d4 100644 --- a/packages/core/src/i18n/tokens.ts +++ b/packages/core/src/i18n/tokens.ts @@ -21,8 +21,12 @@ declare const $localize: {locale?: string}; * * Ivy enabled: use `$localize.locale` */ export function getGlobalLocale(): string { - if (typeof ngI18nClosureMode !== 'undefined' && ngI18nClosureMode && - typeof goog !== 'undefined' && goog.LOCALE !== 'en') { + if ( + typeof ngI18nClosureMode !== 'undefined' && + ngI18nClosureMode && + typeof goog !== 'undefined' && + goog.LOCALE !== 'en' + ) { // * The default `goog.LOCALE` value is `en`, while Angular used `en-US`. // * In order to preserve backwards compatibility, we use Angular default value over // Closure Compiler's one. @@ -66,7 +70,7 @@ export function getGlobalLocale(): string { export const LOCALE_ID: InjectionToken = new InjectionToken(ngDevMode ? 'LocaleId' : '', { providedIn: 'root', factory: () => - inject(LOCALE_ID, InjectFlags.Optional | InjectFlags.SkipSelf) || getGlobalLocale(), + inject(LOCALE_ID, InjectFlags.Optional | InjectFlags.SkipSelf) || getGlobalLocale(), }); /** @@ -107,11 +111,13 @@ export const LOCALE_ID: InjectionToken = new InjectionToken(ngDevMode ? * * @publicApi */ -export const DEFAULT_CURRENCY_CODE = - new InjectionToken(ngDevMode ? 'DefaultCurrencyCode' : '', { - providedIn: 'root', - factory: () => USD_CURRENCY_CODE, - }); +export const DEFAULT_CURRENCY_CODE = new InjectionToken( + ngDevMode ? 'DefaultCurrencyCode' : '', + { + providedIn: 'root', + factory: () => USD_CURRENCY_CODE, + }, +); /** * Use this token at bootstrap to provide the content of your translation file (`xtb`, @@ -160,8 +166,9 @@ export const TRANSLATIONS = new InjectionToken(ngDevMode ? 'Translations * * @publicApi */ -export const TRANSLATIONS_FORMAT = - new InjectionToken(ngDevMode ? 'TranslationsFormat' : ''); +export const TRANSLATIONS_FORMAT = new InjectionToken( + ngDevMode ? 'TranslationsFormat' : '', +); /** * Use this enum at bootstrap as an option of `bootstrapModule` to define the strategy diff --git a/packages/core/src/image_performance_warning.ts b/packages/core/src/image_performance_warning.ts index e79ce2a69ed1e..ab4a783f12926 100644 --- a/packages/core/src/image_performance_warning.ts +++ b/packages/core/src/image_performance_warning.ts @@ -25,15 +25,17 @@ const OVERSIZED_IMAGE_TOLERANCE = 1200; @Injectable({providedIn: 'root'}) export class ImagePerformanceWarning implements OnDestroy { // Map of full image URLs -> original `ngSrc` values. - private window: Window|null = null; - private observer: PerformanceObserver|null = null; + private window: Window | null = null; + private observer: PerformanceObserver | null = null; private options: ImageConfig = inject(IMAGE_CONFIG); private ngZone = inject(NgZone); private lcpImageUrl?: string; public start() { - if (typeof PerformanceObserver === 'undefined' || - (this.options?.disableImageSizeWarning && this.options?.disableImageLazyLoadWarning)) { + if ( + typeof PerformanceObserver === 'undefined' || + (this.options?.disableImageSizeWarning && this.options?.disableImageLazyLoadWarning) + ) { return; } this.observer = this.initPerformanceObserver(); @@ -67,7 +69,7 @@ export class ImagePerformanceWarning implements OnDestroy { this.observer?.disconnect(); } - private initPerformanceObserver(): PerformanceObserver|null { + private initPerformanceObserver(): PerformanceObserver | null { if (typeof PerformanceObserver === 'undefined') { return null; } @@ -94,8 +96,9 @@ export class ImagePerformanceWarning implements OnDestroy { private scanImages(): void { const images = getDocument().querySelectorAll('img'); - let lcpElementFound, lcpElementLoadedCorrectly = false; - images.forEach(image => { + let lcpElementFound, + lcpElementLoadedCorrectly = false; + images.forEach((image) => { if (!this.options?.disableImageSizeWarning) { for (const image of images) { // Image elements using the NgOptimizedImage directive are excluded, @@ -118,8 +121,12 @@ export class ImagePerformanceWarning implements OnDestroy { } } }); - if (lcpElementFound && !lcpElementLoadedCorrectly && this.lcpImageUrl && - !this.options?.disableImageLazyLoadWarning) { + if ( + lcpElementFound && + !lcpElementLoadedCorrectly && + this.lcpImageUrl && + !this.options?.disableImageLazyLoadWarning + ) { logLazyLCPWarning(this.lcpImageUrl); } } @@ -154,29 +161,35 @@ export class ImagePerformanceWarning implements OnDestroy { const recommendedWidth = this.window.devicePixelRatio * renderedWidth; const recommendedHeight = this.window.devicePixelRatio * renderedHeight; - const oversizedWidth = (intrinsicWidth - recommendedWidth) >= OVERSIZED_IMAGE_TOLERANCE; - const oversizedHeight = (intrinsicHeight - recommendedHeight) >= OVERSIZED_IMAGE_TOLERANCE; + const oversizedWidth = intrinsicWidth - recommendedWidth >= OVERSIZED_IMAGE_TOLERANCE; + const oversizedHeight = intrinsicHeight - recommendedHeight >= OVERSIZED_IMAGE_TOLERANCE; return oversizedWidth || oversizedHeight; } } function logLazyLCPWarning(src: string) { - console.warn(formatRuntimeError( + console.warn( + formatRuntimeError( RuntimeErrorCode.IMAGE_PERFORMANCE_WARNING, `An image with src ${src} is the Largest Contentful Paint (LCP) element ` + - `but was given a "loading" value of "lazy", which can negatively impact ` + - `application loading performance. This warning can be addressed by ` + - `changing the loading value of the LCP image to "eager", or by using the ` + - `NgOptimizedImage directive's prioritization utilities. For more ` + - `information about addressing or disabling this warning, see ` + - `https://angular.io/errors/NG0913`)); + `but was given a "loading" value of "lazy", which can negatively impact ` + + `application loading performance. This warning can be addressed by ` + + `changing the loading value of the LCP image to "eager", or by using the ` + + `NgOptimizedImage directive's prioritization utilities. For more ` + + `information about addressing or disabling this warning, see ` + + `https://angular.io/errors/NG0913`, + ), + ); } function logOversizedImageWarning(src: string) { - console.warn(formatRuntimeError( + console.warn( + formatRuntimeError( RuntimeErrorCode.IMAGE_PERFORMANCE_WARNING, `An image with src ${src} has intrinsic file dimensions much larger than its ` + - `rendered size. This can negatively impact application loading performance. ` + - `For more information about addressing or disabling this warning, see ` + - `https://angular.io/errors/NG0913`)); + `rendered size. This can negatively impact application loading performance. ` + + `For more information about addressing or disabling this warning, see ` + + `https://angular.io/errors/NG0913`, + ), + ); } diff --git a/packages/core/src/interface/lifecycle_hooks.ts b/packages/core/src/interface/lifecycle_hooks.ts index 5c0d828a1d0d3..ceb1da66799d4 100644 --- a/packages/core/src/interface/lifecycle_hooks.ts +++ b/packages/core/src/interface/lifecycle_hooks.ts @@ -7,7 +7,6 @@ */ import {SimpleChanges} from './simple_change'; - /** * @description * A lifecycle hook that is called when any data-bound property of a directive changes. diff --git a/packages/core/src/interface/simple_change.ts b/packages/core/src/interface/simple_change.ts index 76c608dc5583d..f257ce2e77e41 100644 --- a/packages/core/src/interface/simple_change.ts +++ b/packages/core/src/interface/simple_change.ts @@ -16,7 +16,11 @@ * @publicApi */ export class SimpleChange { - constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {} + constructor( + public previousValue: any, + public currentValue: any, + public firstChange: boolean, + ) {} /** * Check whether the new value is the first value assigned. */ diff --git a/packages/core/src/interface/type.ts b/packages/core/src/interface/type.ts index cbacc039ed4af..1ef3e958af7ae 100644 --- a/packages/core/src/interface/type.ts +++ b/packages/core/src/interface/type.ts @@ -35,7 +35,7 @@ export interface AbstractType extends Function { } export interface Type extends Function { - new(...args: any[]): T; + new (...args: any[]): T; } export type Mutable = { @@ -68,5 +68,5 @@ export type Mutable = { * ``` */ export type Writable = { - -readonly[K in keyof T]: T[K]; + -readonly [K in keyof T]: T[K]; }; diff --git a/packages/core/src/linker.ts b/packages/core/src/linker.ts index 15773e41fa109..b0c82954d1bb2 100644 --- a/packages/core/src/linker.ts +++ b/packages/core/src/linker.ts @@ -7,7 +7,13 @@ */ // Public API for compiler -export {Compiler, COMPILER_OPTIONS, CompilerFactory, CompilerOptions, ModuleWithComponentFactories} from './linker/compiler'; +export { + Compiler, + COMPILER_OPTIONS, + CompilerFactory, + CompilerOptions, + ModuleWithComponentFactories, +} from './linker/compiler'; export {ComponentFactory, ComponentRef} from './linker/component_factory'; export {ComponentFactoryResolver} from './linker/component_factory_resolver'; export {DestroyRef} from './linker/destroy_ref'; diff --git a/packages/core/src/linker/compiler.ts b/packages/core/src/linker/compiler.ts index be50666626186..55624b8433d92 100644 --- a/packages/core/src/linker/compiler.ts +++ b/packages/core/src/linker/compiler.ts @@ -30,8 +30,9 @@ import {NgModuleFactory} from './ng_module_factory'; */ export class ModuleWithComponentFactories { constructor( - public ngModuleFactory: NgModuleFactory, - public componentFactories: ComponentFactory[]) {} + public ngModuleFactory: NgModuleFactory, + public componentFactories: ComponentFactory[], + ) {} } /** @@ -71,21 +72,23 @@ export class Compiler { compileModuleAndAllComponentsSync(moduleType: Type): ModuleWithComponentFactories { const ngModuleFactory = this.compileModuleSync(moduleType); const moduleDef = getNgModuleDef(moduleType)!; - const componentFactories = - maybeUnwrapFn(moduleDef.declarations) - .reduce((factories: ComponentFactory[], declaration: Type) => { - const componentDef = getComponentDef(declaration); - componentDef && factories.push(new ComponentFactoryR3(componentDef)); - return factories; - }, [] as ComponentFactory[]); + const componentFactories = maybeUnwrapFn(moduleDef.declarations).reduce( + (factories: ComponentFactory[], declaration: Type) => { + const componentDef = getComponentDef(declaration); + componentDef && factories.push(new ComponentFactoryR3(componentDef)); + return factories; + }, + [] as ComponentFactory[], + ); return new ModuleWithComponentFactories(ngModuleFactory, componentFactories); } /** * Same as {@link #compileModuleAsync} but also creates ComponentFactories for all components. */ - compileModuleAndAllComponentsAsync(moduleType: Type): - Promise> { + compileModuleAndAllComponentsAsync( + moduleType: Type, + ): Promise> { return Promise.resolve(this.compileModuleAndAllComponentsSync(moduleType)); } @@ -102,7 +105,7 @@ export class Compiler { /** * Returns the id for a given NgModule, if one is defined and known to the compiler. */ - getModuleId(moduleType: Type): string|undefined { + getModuleId(moduleType: Type): string | undefined { return undefined; } } @@ -113,9 +116,9 @@ export class Compiler { * @publicApi */ export type CompilerOptions = { - defaultEncapsulation?: ViewEncapsulation, - providers?: StaticProvider[], - preserveWhitespaces?: boolean, + defaultEncapsulation?: ViewEncapsulation; + providers?: StaticProvider[]; + preserveWhitespaces?: boolean; }; /** @@ -123,8 +126,9 @@ export type CompilerOptions = { * * @publicApi */ -export const COMPILER_OPTIONS = - new InjectionToken(ngDevMode ? 'compilerOptions' : ''); +export const COMPILER_OPTIONS = new InjectionToken( + ngDevMode ? 'compilerOptions' : '', +); /** * A factory for creating a Compiler diff --git a/packages/core/src/linker/component_factory.ts b/packages/core/src/linker/component_factory.ts index 22f179a08574b..557bc9b0d25d9 100644 --- a/packages/core/src/linker/component_factory.ts +++ b/packages/core/src/linker/component_factory.ts @@ -105,18 +105,21 @@ export abstract class ComponentFactory { * The inputs of the component. */ abstract get inputs(): { - propName: string, - templateName: string, - transform?: (value: any) => any, + propName: string; + templateName: string; + transform?: (value: any) => any; }[]; /** * The outputs of the component. */ - abstract get outputs(): {propName: string, templateName: string}[]; + abstract get outputs(): {propName: string; templateName: string}[]; /** * Creates a new component. */ abstract create( - injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, - environmentInjector?: EnvironmentInjector|NgModuleRef): ComponentRef; + injector: Injector, + projectableNodes?: any[][], + rootSelectorOrNode?: string | any, + environmentInjector?: EnvironmentInjector | NgModuleRef, + ): ComponentRef; } diff --git a/packages/core/src/linker/component_factory_resolver.ts b/packages/core/src/linker/component_factory_resolver.ts index 2e9eeaff63d75..61a95cbbf1546 100644 --- a/packages/core/src/linker/component_factory_resolver.ts +++ b/packages/core/src/linker/component_factory_resolver.ts @@ -23,9 +23,8 @@ export function getComponent(error: Error): Type { return (error as any)[ERROR_COMPONENT]; } - class _NullComponentFactoryResolver implements ComponentFactoryResolver { - resolveComponentFactory(component: {new(...args: any[]): T}): ComponentFactory { + resolveComponentFactory(component: {new (...args: any[]): T}): ComponentFactory { throw noComponentFactoryError(component); } } @@ -46,7 +45,7 @@ class _NullComponentFactoryResolver implements ComponentFactoryResolver { * Component class can be used directly. */ export abstract class ComponentFactoryResolver { - static NULL: ComponentFactoryResolver = (/* @__PURE__ */ new _NullComponentFactoryResolver()); + static NULL: ComponentFactoryResolver = /* @__PURE__ */ new _NullComponentFactoryResolver(); /** * Retrieves the factory object that creates a component of the given type. * @param component The component type. diff --git a/packages/core/src/linker/element_ref.ts b/packages/core/src/linker/element_ref.ts index a54cf23ea7f8c..02bb6476f04cc 100644 --- a/packages/core/src/linker/element_ref.ts +++ b/packages/core/src/linker/element_ref.ts @@ -77,6 +77,6 @@ export class ElementRef { * @param value value to unwrap * @returns `nativeElement` if `ElementRef` otherwise returns value as is. */ -export function unwrapElementRef(value: T|ElementRef): T|R { +export function unwrapElementRef(value: T | ElementRef): T | R { return value instanceof ElementRef ? value.nativeElement : value; } diff --git a/packages/core/src/linker/ng_module_factory.ts b/packages/core/src/linker/ng_module_factory.ts index 89ade4482aac1..917e608e26bf3 100644 --- a/packages/core/src/linker/ng_module_factory.ts +++ b/packages/core/src/linker/ng_module_factory.ts @@ -12,7 +12,6 @@ import {Type} from '../interface/type'; import {ComponentFactoryResolver} from './component_factory_resolver'; - /** * Represents an instance of an `NgModule` created by an `NgModuleFactory`. * Provides access to the `NgModule` instance and related objects. @@ -71,5 +70,5 @@ export interface InternalNgModuleRef extends NgModuleRef { */ export abstract class NgModuleFactory { abstract get moduleType(): Type; - abstract create(parentInjector: Injector|null): NgModuleRef; + abstract create(parentInjector: Injector | null): NgModuleRef; } diff --git a/packages/core/src/linker/ng_module_factory_loader.ts b/packages/core/src/linker/ng_module_factory_loader.ts index 35b35824aaf8d..d344b9e5610d4 100644 --- a/packages/core/src/linker/ng_module_factory_loader.ts +++ b/packages/core/src/linker/ng_module_factory_loader.ts @@ -37,8 +37,6 @@ export function getNgModuleById(id: string): Type { return type; } -function noModuleError( - id: string, - ): Error { +function noModuleError(id: string): Error { return new Error(`No module with ID ${id} loaded`); } diff --git a/packages/core/src/linker/ng_module_registration.ts b/packages/core/src/linker/ng_module_registration.ts index 080a7c9b0073a..4dac46a022c9c 100644 --- a/packages/core/src/linker/ng_module_registration.ts +++ b/packages/core/src/linker/ng_module_registration.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {Type} from '../interface/type'; import {NgModuleType} from '../metadata/ng_module_def'; import {getNgModuleDef} from '../render3/definition'; @@ -24,10 +23,11 @@ const modules = new Map(); */ let checkForDuplicateNgModules = true; -function assertSameOrNotExisting(id: string, type: Type|null, incoming: Type): void { +function assertSameOrNotExisting(id: string, type: Type | null, incoming: Type): void { if (type && type !== incoming && checkForDuplicateNgModules) { throw new Error( - `Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`); + `Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`, + ); } } @@ -51,7 +51,7 @@ export function clearModulesForTest(): void { modules.clear(); } -export function getRegisteredNgModuleType(id: string): NgModuleType|undefined { +export function getRegisteredNgModuleType(id: string): NgModuleType | undefined { return modules.get(id); } diff --git a/packages/core/src/linker/query_list.ts b/packages/core/src/linker/query_list.ts index bda36dbb907eb..e4f61e6179a2c 100644 --- a/packages/core/src/linker/query_list.ts +++ b/packages/core/src/linker/query_list.ts @@ -48,7 +48,7 @@ export class QueryList implements Iterable { private _onDirty?: () => void = undefined; private _results: Array = []; private _changesDetected: boolean = false; - private _changes: EventEmitter>|undefined = undefined; + private _changes: EventEmitter> | undefined = undefined; readonly length: number = 0; readonly first: T = undefined!; @@ -58,7 +58,7 @@ export class QueryList implements Iterable { * Returns `Observable` of `QueryList` notifying the subscriber of changes. */ get changes(): Observable { - return this._changes ??= new EventEmitter(); + return (this._changes ??= new EventEmitter()); } /** @@ -78,7 +78,7 @@ export class QueryList implements Iterable { /** * Returns the QueryList entry at `index`. */ - get(index: number): T|undefined { + get(index: number): T | undefined { return this._results[index]; } @@ -104,7 +104,7 @@ export class QueryList implements Iterable { * See * [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) */ - find(fn: (item: T, index: number, array: T[]) => boolean): T|undefined { + find(fn: (item: T, index: number, array: T[]) => boolean): T | undefined { return this._results.find(fn); } @@ -155,10 +155,10 @@ export class QueryList implements Iterable { * function) to detect if the lists are different. If the function is not provided, elements * are compared as is (without any pre-processing). */ - reset(resultsTree: Array, identityAccessor?: (value: T) => unknown): void { + reset(resultsTree: Array, identityAccessor?: (value: T) => unknown): void { (this as {dirty: boolean}).dirty = false; const newResultFlat = flatten(resultsTree); - if (this._changesDetected = !arrayEquals(this._results, newResultFlat, identityAccessor)) { + if ((this._changesDetected = !arrayEquals(this._results, newResultFlat, identityAccessor))) { this._results = newResultFlat; (this as Writable).length = newResultFlat.length; (this as Writable).last = newResultFlat[this.length - 1]; diff --git a/packages/core/src/linker/template_ref.ts b/packages/core/src/linker/template_ref.ts index af9acc2c7b681..ea92ff8d67064 100644 --- a/packages/core/src/linker/template_ref.ts +++ b/packages/core/src/linker/template_ref.ts @@ -69,8 +69,10 @@ export abstract class TemplateRef { * @internal */ abstract createEmbeddedViewImpl( - context: C, injector?: Injector, - dehydratedView?: DehydratedContainerView|null): EmbeddedViewRef; + context: C, + injector?: Injector, + dehydratedView?: DehydratedContainerView | null, + ): EmbeddedViewRef; /** * Returns an `ssrId` associated with a TView, which was used to @@ -78,13 +80,13 @@ export abstract class TemplateRef { * * @internal */ - abstract get ssrId(): string|null; + abstract get ssrId(): string | null; /** * @internal * @nocollapse */ - static __NG_ELEMENT_ID__: () => TemplateRef| null = injectTemplateRef; + static __NG_ELEMENT_ID__: () => TemplateRef | null = injectTemplateRef; } const ViewEngineTemplateRef = TemplateRef; @@ -93,8 +95,10 @@ const ViewEngineTemplateRef = TemplateRef; // in g3 depends on them being separate. const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef { constructor( - private _declarationLView: LView, private _declarationTContainer: TContainerNode, - public override elementRef: ElementRef) { + private _declarationLView: LView, + private _declarationTContainer: TContainerNode, + public override elementRef: ElementRef, + ) { super(); } @@ -104,7 +108,7 @@ const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef { * * @internal */ - override get ssrId(): string|null { + override get ssrId(): string | null { return this._declarationTContainer.tView?.ssrId || null; } @@ -116,11 +120,16 @@ const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef { * @internal */ override createEmbeddedViewImpl( - context: T, injector?: Injector, - dehydratedView?: DehydratedContainerView): EmbeddedViewRef { + context: T, + injector?: Injector, + dehydratedView?: DehydratedContainerView, + ): EmbeddedViewRef { const embeddedLView = createAndRenderEmbeddedLView( - this._declarationLView, this._declarationTContainer, context, - {embeddedViewInjector: injector, dehydratedView}); + this._declarationLView, + this._declarationTContainer, + context, + {embeddedViewInjector: injector, dehydratedView}, + ); return new R3_ViewRef(embeddedLView); } }; @@ -130,7 +139,7 @@ const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef { * * @returns The TemplateRef instance to use */ -export function injectTemplateRef(): TemplateRef|null { +export function injectTemplateRef(): TemplateRef | null { return createTemplateRef(getCurrentTNode()!, getLView()); } @@ -141,11 +150,14 @@ export function injectTemplateRef(): TemplateRef|null { * @param hostLView The `LView` to which the node belongs * @returns The TemplateRef instance or null if we can't create a TemplateRef on a given node type */ -export function createTemplateRef(hostTNode: TNode, hostLView: LView): TemplateRef|null { +export function createTemplateRef(hostTNode: TNode, hostLView: LView): TemplateRef | null { if (hostTNode.type & TNodeType.Container) { ngDevMode && assertDefined(hostTNode.tView, 'TView must be allocated'); return new R3TemplateRef( - hostLView, hostTNode as TContainerNode, createElementRef(hostTNode, hostLView)); + hostLView, + hostTNode as TContainerNode, + createElementRef(hostTNode, hostLView), + ); } return null; } diff --git a/packages/core/src/linker/view_container_ref.ts b/packages/core/src/linker/view_container_ref.ts index 648991e523247..e619e71834d15 100644 --- a/packages/core/src/linker/view_container_ref.ts +++ b/packages/core/src/linker/view_container_ref.ts @@ -11,7 +11,11 @@ import {EnvironmentInjector} from '../di/r3_injector'; import {validateMatchingNode} from '../hydration/error_handling'; import {CONTAINERS} from '../hydration/interfaces'; import {hasInSkipHydrationBlockFlag, isInSkipHydrationBlock} from '../hydration/skip_hydration'; -import {getSegmentHead, isDisconnectedNode, markRNodeAsClaimedByHydration} from '../hydration/utils'; +import { + getSegmentHead, + isDisconnectedNode, + markRNodeAsClaimedByHydration, +} from '../hydration/utils'; import {findMatchingDehydratedView, locateDehydratedViewsInContainer} from '../hydration/views'; import {isType, Type} from '../interface/type'; import {assertNodeInjector} from '../render3/assert'; @@ -19,21 +23,58 @@ import {ComponentFactory as R3ComponentFactory} from '../render3/component_ref'; import {getComponentDef} from '../render3/definition'; import {getParentInjectorLocation, NodeInjector} from '../render3/di'; import {addToViewTree, createLContainer} from '../render3/instructions/shared'; -import {CONTAINER_HEADER_OFFSET, DEHYDRATED_VIEWS, LContainer, NATIVE, VIEW_REFS} from '../render3/interfaces/container'; +import { + CONTAINER_HEADER_OFFSET, + DEHYDRATED_VIEWS, + LContainer, + NATIVE, + VIEW_REFS, +} from '../render3/interfaces/container'; import {NodeInjectorOffset} from '../render3/interfaces/injector'; -import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeType} from '../render3/interfaces/node'; +import { + TContainerNode, + TDirectiveHostNode, + TElementContainerNode, + TElementNode, + TNode, + TNodeType, +} from '../render3/interfaces/node'; import {RComment, RNode} from '../render3/interfaces/renderer_dom'; import {isLContainer} from '../render3/interfaces/type_checks'; -import {HEADER_OFFSET, HYDRATION, LView, PARENT, RENDERER, T_HOST, TVIEW} from '../render3/interfaces/view'; +import { + HEADER_OFFSET, + HYDRATION, + LView, + PARENT, + RENDERER, + T_HOST, + TVIEW, +} from '../render3/interfaces/view'; import {assertTNodeType} from '../render3/node_assert'; -import {destroyLView, detachView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from '../render3/node_manipulation'; +import { + destroyLView, + detachView, + nativeInsertBefore, + nativeNextSibling, + nativeParentNode, +} from '../render3/node_manipulation'; import {getCurrentTNode, getLView} from '../render3/state'; -import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from '../render3/util/injector_utils'; +import { + getParentInjectorIndex, + getParentInjectorView, + hasParentInjector, +} from '../render3/util/injector_utils'; import {getNativeByTNode, unwrapRNode, viewAttachedToContainer} from '../render3/util/view_utils'; import {addLViewToLContainer, shouldAddViewToDom} from '../render3/view_manipulation'; import {ViewRef as R3ViewRef} from '../render3/view_ref'; import {addToArray, removeFromArray} from '../util/array_utils'; -import {assertDefined, assertEqual, assertGreaterThan, assertLessThan, throwError} from '../util/assert'; +import { + assertDefined, + assertEqual, + assertGreaterThan, + assertLessThan, + throwError, +} from '../util/assert'; import {ComponentFactory, ComponentRef} from './component_factory'; import {createElementRef, ElementRef} from './element_ref'; @@ -124,7 +165,7 @@ export abstract class ViewContainerRef { * @param index The 0-based index of the view to retrieve. * @returns The `ViewRef` instance, or null if the index is out of range. */ - abstract get(index: number): ViewRef|null; + abstract get(index: number): ViewRef | null; /** * Reports how many views are currently attached to this container. @@ -145,10 +186,14 @@ export abstract class ViewContainerRef { * * @returns The `ViewRef` instance for the newly created view. */ - abstract createEmbeddedView(templateRef: TemplateRef, context?: C, options?: { - index?: number, - injector?: Injector - }): EmbeddedViewRef; + abstract createEmbeddedView( + templateRef: TemplateRef, + context?: C, + options?: { + index?: number; + injector?: Injector; + }, + ): EmbeddedViewRef; /** * Instantiates an embedded view and inserts it @@ -161,8 +206,11 @@ export abstract class ViewContainerRef { * * @returns The `ViewRef` instance for the newly created view. */ - abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): - EmbeddedViewRef; + abstract createEmbeddedView( + templateRef: TemplateRef, + context?: C, + index?: number, + ): EmbeddedViewRef; /** * Instantiates a single component and inserts its host view into this container. @@ -184,13 +232,16 @@ export abstract class ViewContainerRef { * * @returns The new `ComponentRef` which contains the component instance and the host view. */ - abstract createComponent(componentType: Type, options?: { - index?: number, - injector?: Injector, - ngModuleRef?: NgModuleRef, - environmentInjector?: EnvironmentInjector|NgModuleRef, - projectableNodes?: Node[][], - }): ComponentRef; + abstract createComponent( + componentType: Type, + options?: { + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + environmentInjector?: EnvironmentInjector | NgModuleRef; + projectableNodes?: Node[][]; + }, + ): ComponentRef; /** * Instantiates a single component and inserts its host view into this container. @@ -211,9 +262,12 @@ export abstract class ViewContainerRef { * Component class directly. */ abstract createComponent( - componentFactory: ComponentFactory, index?: number, injector?: Injector, - projectableNodes?: any[][], - environmentInjector?: EnvironmentInjector|NgModuleRef): ComponentRef; + componentFactory: ComponentFactory, + index?: number, + injector?: Injector, + projectableNodes?: any[][], + environmentInjector?: EnvironmentInjector | NgModuleRef, + ): ComponentRef; /** * Inserts a view into this container. @@ -254,7 +308,7 @@ export abstract class ViewContainerRef { * @param index The 0-based index of the view to detach. * If not specified, the last view in the container is detached. */ - abstract detach(index?: number): ViewRef|null; + abstract detach(index?: number): ViewRef | null; /** * @internal @@ -280,9 +334,10 @@ const VE_ViewContainerRef = ViewContainerRef; // for that lands, this can be cleaned up. const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { constructor( - private _lContainer: LContainer, - private _hostTNode: TElementNode|TContainerNode|TElementContainerNode, - private _hostLView: LView) { + private _lContainer: LContainer, + private _hostTNode: TElementNode | TContainerNode | TElementContainerNode, + private _hostLView: LView, + ) { super(); } @@ -301,8 +356,9 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { const parentView = getParentInjectorView(parentLocation, this._hostLView); const injectorIndex = getParentInjectorIndex(parentLocation); ngDevMode && assertNodeInjector(parentView, injectorIndex); - const parentTNode = - parentView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TElementNode; + const parentTNode = parentView[TVIEW].data[ + injectorIndex + NodeInjectorOffset.TNODE + ] as TElementNode; return new NodeInjector(parentTNode, parentView); } else { return new NodeInjector(null, this._hostLView); @@ -315,27 +371,40 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { } } - override get(index: number): ViewRef|null { + override get(index: number): ViewRef | null { const viewRefs = getViewRefs(this._lContainer); - return viewRefs !== null && viewRefs[index] || null; + return (viewRefs !== null && viewRefs[index]) || null; } override get length(): number { return this._lContainer.length - CONTAINER_HEADER_OFFSET; } - override createEmbeddedView(templateRef: TemplateRef, context?: C, options?: { - index?: number, - injector?: Injector - }): EmbeddedViewRef; - override createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): - EmbeddedViewRef; - override createEmbeddedView(templateRef: TemplateRef, context?: C, indexOrOptions?: number|{ + override createEmbeddedView( + templateRef: TemplateRef, + context?: C, + options?: { + index?: number; + injector?: Injector; + }, + ): EmbeddedViewRef; + override createEmbeddedView( + templateRef: TemplateRef, + context?: C, index?: number, - injector?: Injector - }): EmbeddedViewRef { - let index: number|undefined; - let injector: Injector|undefined; + ): EmbeddedViewRef; + override createEmbeddedView( + templateRef: TemplateRef, + context?: C, + indexOrOptions?: + | number + | { + index?: number; + injector?: Injector; + }, + ): EmbeddedViewRef { + let index: number | undefined; + let injector: Injector | undefined; if (typeof indexOrOptions === 'number') { index = indexOrOptions; @@ -345,39 +414,54 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { } const dehydratedView = findMatchingDehydratedView(this._lContainer, templateRef.ssrId); - const viewRef = - templateRef.createEmbeddedViewImpl(context || {}, injector, dehydratedView); + const viewRef = templateRef.createEmbeddedViewImpl( + context || {}, + injector, + dehydratedView, + ); this.insertImpl(viewRef, index, shouldAddViewToDom(this._hostTNode, dehydratedView)); return viewRef; } - override createComponent(componentType: Type, options?: { - index?: number, - injector?: Injector, - projectableNodes?: Node[][], - ngModuleRef?: NgModuleRef, - }): ComponentRef; + override createComponent( + componentType: Type, + options?: { + index?: number; + injector?: Injector; + projectableNodes?: Node[][]; + ngModuleRef?: NgModuleRef; + }, + ): ComponentRef; /** * @deprecated Angular no longer requires component factories to dynamically create components. * Use different signature of the `createComponent` method, which allows passing * Component class directly. */ override createComponent( - componentFactory: ComponentFactory, index?: number|undefined, - injector?: Injector|undefined, projectableNodes?: any[][]|undefined, - environmentInjector?: EnvironmentInjector|NgModuleRef|undefined): ComponentRef; + componentFactory: ComponentFactory, + index?: number | undefined, + injector?: Injector | undefined, + projectableNodes?: any[][] | undefined, + environmentInjector?: EnvironmentInjector | NgModuleRef | undefined, + ): ComponentRef; override createComponent( - componentFactoryOrType: ComponentFactory|Type, indexOrOptions?: number|undefined|{ - index?: number, - injector?: Injector, - ngModuleRef?: NgModuleRef, - environmentInjector?: EnvironmentInjector|NgModuleRef, - projectableNodes?: Node[][], - }, - injector?: Injector|undefined, projectableNodes?: any[][]|undefined, - environmentInjector?: EnvironmentInjector|NgModuleRef|undefined): ComponentRef { + componentFactoryOrType: ComponentFactory | Type, + indexOrOptions?: + | number + | undefined + | { + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + environmentInjector?: EnvironmentInjector | NgModuleRef; + projectableNodes?: Node[][]; + }, + injector?: Injector | undefined, + projectableNodes?: any[][] | undefined, + environmentInjector?: EnvironmentInjector | NgModuleRef | undefined, + ): ComponentRef { const isComponentFactory = componentFactoryOrType && !isType(componentFactoryOrType); - let index: number|undefined; + let index: number | undefined; // This function supports 2 signatures and we need to handle options correctly for both: // 1. When first argument is a Component type. This signature also requires extra @@ -387,37 +471,43 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { if (isComponentFactory) { if (ngDevMode) { assertEqual( - typeof indexOrOptions !== 'object', true, - 'It looks like Component factory was provided as the first argument ' + - 'and an options object as the second argument. This combination of arguments ' + - 'is incompatible. You can either change the first argument to provide Component ' + - 'type or change the second argument to be a number (representing an index at ' + - 'which to insert the new component\'s host view into this container)'); + typeof indexOrOptions !== 'object', + true, + 'It looks like Component factory was provided as the first argument ' + + 'and an options object as the second argument. This combination of arguments ' + + 'is incompatible. You can either change the first argument to provide Component ' + + 'type or change the second argument to be a number (representing an index at ' + + "which to insert the new component's host view into this container)", + ); } index = indexOrOptions as number | undefined; } else { if (ngDevMode) { assertDefined( - getComponentDef(componentFactoryOrType), - `Provided Component class doesn't contain Component definition. ` + - `Please check whether provided class has @Component decorator.`); + getComponentDef(componentFactoryOrType), + `Provided Component class doesn't contain Component definition. ` + + `Please check whether provided class has @Component decorator.`, + ); assertEqual( - typeof indexOrOptions !== 'number', true, - 'It looks like Component type was provided as the first argument ' + - 'and a number (representing an index at which to insert the new component\'s ' + - 'host view into this container as the second argument. This combination of arguments ' + - 'is incompatible. Please use an object as the second argument instead.'); + typeof indexOrOptions !== 'number', + true, + 'It looks like Component type was provided as the first argument ' + + "and a number (representing an index at which to insert the new component's " + + 'host view into this container as the second argument. This combination of arguments ' + + 'is incompatible. Please use an object as the second argument instead.', + ); } const options = (indexOrOptions || {}) as { - index?: number, - injector?: Injector, - ngModuleRef?: NgModuleRef, - environmentInjector?: EnvironmentInjector | NgModuleRef, - projectableNodes?: Node[][], + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + environmentInjector?: EnvironmentInjector | NgModuleRef; + projectableNodes?: Node[][]; }; if (ngDevMode && options.environmentInjector && options.ngModuleRef) { throwError( - `Cannot pass both environmentInjector and ngModuleRef options to createComponent().`); + `Cannot pass both environmentInjector and ngModuleRef options to createComponent().`, + ); } index = options.index; injector = options.injector; @@ -425,9 +515,9 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { environmentInjector = options.environmentInjector || options.ngModuleRef; } - const componentFactory: ComponentFactory = isComponentFactory ? - componentFactoryOrType as ComponentFactory: - new R3ComponentFactory(getComponentDef(componentFactoryOrType)!); + const componentFactory: ComponentFactory = isComponentFactory + ? (componentFactoryOrType as ComponentFactory) + : new R3ComponentFactory(getComponentDef(componentFactoryOrType)!); const contextInjector = injector || this.parentInjector; // If an `NgModuleRef` is not provided explicitly, try retrieving it from the DI tree. @@ -462,10 +552,17 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { const componentDef = getComponentDef(componentFactory.componentType ?? {}); const dehydratedView = findMatchingDehydratedView(this._lContainer, componentDef?.id ?? null); const rNode = dehydratedView?.firstChild ?? null; - const componentRef = - componentFactory.create(contextInjector, projectableNodes, rNode, environmentInjector); + const componentRef = componentFactory.create( + contextInjector, + projectableNodes, + rNode, + environmentInjector, + ); this.insertImpl( - componentRef.hostView, index, shouldAddViewToDom(this._hostTNode, dehydratedView)); + componentRef.hostView, + index, + shouldAddViewToDom(this._hostTNode, dehydratedView), + ); return componentRef; } @@ -494,15 +591,19 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { } else { const prevLContainer = lView[PARENT] as LContainer; ngDevMode && - assertEqual( - isLContainer(prevLContainer), true, - 'An attached view should have its PARENT point to a container.'); - + assertEqual( + isLContainer(prevLContainer), + true, + 'An attached view should have its PARENT point to a container.', + ); // We need to re-create a R3ViewContainerRef instance since those are not stored on // LView (nor anywhere else). const prevVCRef = new R3ViewContainerRef( - prevLContainer, prevLContainer[T_HOST] as TDirectiveHostNode, prevLContainer[PARENT]); + prevLContainer, + prevLContainer[T_HOST] as TDirectiveHostNode, + prevLContainer[PARENT], + ); prevVCRef.detach(prevVCRef.indexOf(viewRef)); } @@ -548,12 +649,12 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { } } - override detach(index?: number): ViewRef|null { + override detach(index?: number): ViewRef | null { const adjustedIdx = this._adjustIndex(index, -1); const view = detachView(this._lContainer, adjustedIdx); const wasDetached = - view && removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx) != null; + view && removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx) != null; return wasDetached ? new R3ViewRef(view!) : null; } @@ -570,7 +671,7 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { } }; -function getViewRefs(lContainer: LContainer): ViewRef[]|null { +function getViewRefs(lContainer: LContainer): ViewRef[] | null { return lContainer[VIEW_REFS] as ViewRef[]; } @@ -586,8 +687,9 @@ function getOrCreateViewRefs(lContainer: LContainer): ViewRef[] { * @returns The ViewContainerRef instance to use */ export function createContainerRef( - hostTNode: TElementNode|TContainerNode|TElementContainerNode, - hostLView: LView): ViewContainerRef { + hostTNode: TElementNode | TContainerNode | TElementContainerNode, + hostLView: LView, +): ViewContainerRef { ngDevMode && assertTNodeType(hostTNode, TNodeType.AnyContainer | TNodeType.AnyRNode); let lContainer: LContainer; @@ -623,13 +725,18 @@ function insertAnchorNode(hostLView: LView, hostTNode: TNode): RComment { const hostNative = getNativeByTNode(hostTNode, hostLView)!; const parentOfHostNative = nativeParentNode(renderer, hostNative); nativeInsertBefore( - renderer, parentOfHostNative!, commentNode, nativeNextSibling(renderer, hostNative), false); + renderer, + parentOfHostNative!, + commentNode, + nativeNextSibling(renderer, hostNative), + false, + ); return commentNode; } let _locateOrCreateAnchorNode = createAnchorNode; let _populateDehydratedViewsInLContainer: typeof populateDehydratedViewsInLContainerImpl = () => - false; // noop by default + false; // noop by default /** * Looks up dehydrated views that belong to a given LContainer and populates @@ -645,7 +752,10 @@ let _populateDehydratedViewsInLContainer: typeof populateDehydratedViewsInLConta * in a skip hydration section. */ export function populateDehydratedViewsInLContainer( - lContainer: LContainer, tNode: TNode, hostLView: LView): boolean { + lContainer: LContainer, + tNode: TNode, + hostLView: LView, +): boolean { return _populateDehydratedViewsInLContainer(lContainer, tNode, hostLView); } @@ -654,7 +764,11 @@ export function populateDehydratedViewsInLContainer( * assigned to the `lContainer[NATIVE]` slot. */ function createAnchorNode( - lContainer: LContainer, hostLView: LView, hostTNode: TNode, slotValue: any) { + lContainer: LContainer, + hostLView: LView, + hostTNode: TNode, + slotValue: any, +) { // We already have a native element (anchor) set, return. if (lContainer[NATIVE]) return; @@ -681,7 +795,10 @@ function createAnchorNode( * in a skip hydration section. */ function populateDehydratedViewsInLContainerImpl( - lContainer: LContainer, tNode: TNode, hostLView: LView): boolean { + lContainer: LContainer, + tNode: TNode, + hostLView: LView, +): boolean { // We already have a native element (anchor) set and the process // of finding dehydrated views happened (so the `lContainer[DEHYDRATED_VIEWS]` // is not null), exit early. @@ -691,8 +808,10 @@ function populateDehydratedViewsInLContainerImpl( const hydrationInfo = hostLView[HYDRATION]; const noOffsetIndex = tNode.index - HEADER_OFFSET; - const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock(tNode) || - isDisconnectedNode(hydrationInfo, noOffsetIndex); + const isNodeCreationMode = + !hydrationInfo || + isInSkipHydrationBlock(tNode) || + isDisconnectedNode(hydrationInfo, noOffsetIndex); // Regular creation mode. if (isNodeCreationMode) { @@ -700,17 +819,20 @@ function populateDehydratedViewsInLContainerImpl( } // Hydration mode, looking up an anchor node and dehydrated views in DOM. - const currentRNode: RNode|null = getSegmentHead(hydrationInfo, noOffsetIndex); + const currentRNode: RNode | null = getSegmentHead(hydrationInfo, noOffsetIndex); const serializedViews = hydrationInfo.data[CONTAINERS]?.[noOffsetIndex]; ngDevMode && - assertDefined( - serializedViews, - 'Unexpected state: no hydration info available for a given TNode, ' + - 'which represents a view container.'); + assertDefined( + serializedViews, + 'Unexpected state: no hydration info available for a given TNode, ' + + 'which represents a view container.', + ); - const [commentNode, dehydratedViews] = - locateDehydratedViewsInContainer(currentRNode!, serializedViews!); + const [commentNode, dehydratedViews] = locateDehydratedViewsInContainer( + currentRNode!, + serializedViews!, + ); if (ngDevMode) { validateMatchingNode(commentNode, Node.COMMENT_NODE, null, hostLView, tNode, true); @@ -729,7 +851,11 @@ function populateDehydratedViewsInLContainerImpl( } function locateOrCreateAnchorNode( - lContainer: LContainer, hostLView: LView, hostTNode: TNode, slotValue: any): void { + lContainer: LContainer, + hostLView: LView, + hostTNode: TNode, + slotValue: any, +): void { if (!_populateDehydratedViewsInLContainer(lContainer, hostTNode, hostLView)) { // Populating dehydrated views operation returned `false`, which indicates // that the logic was running in client-only mode, this an anchor comment diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 6725f7a6e6948..f0109aa75638a 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -11,11 +11,44 @@ * to be used by the decorator versions of these annotations. */ - export {Attribute, AttributeDecorator} from './di/metadata_attr'; -export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './interface/lifecycle_hooks'; -export {ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di'; -export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostBindingDecorator, HostListener, HostListenerDecorator, Input, InputDecorator, Output, OutputDecorator, Pipe, PipeDecorator} from './metadata/directives'; +export { + AfterContentChecked, + AfterContentInit, + AfterViewChecked, + AfterViewInit, + DoCheck, + OnChanges, + OnDestroy, + OnInit, +} from './interface/lifecycle_hooks'; +export { + ContentChild, + ContentChildDecorator, + ContentChildren, + ContentChildrenDecorator, + Query, + ViewChild, + ViewChildDecorator, + ViewChildren, + ViewChildrenDecorator, +} from './metadata/di'; +export { + Component, + ComponentDecorator, + Directive, + DirectiveDecorator, + HostBinding, + HostBindingDecorator, + HostListener, + HostListenerDecorator, + Input, + InputDecorator, + Output, + OutputDecorator, + Pipe, + PipeDecorator, +} from './metadata/directives'; export {DoBootstrap} from './metadata/do_bootstrap'; export {NgModule, NgModuleDecorator} from './metadata/ng_module'; export {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from './metadata/schema'; diff --git a/packages/core/src/metadata/di.ts b/packages/core/src/metadata/di.ts index 24b72e0a111be..54f4a7c7fd9fc 100644 --- a/packages/core/src/metadata/di.ts +++ b/packages/core/src/metadata/di.ts @@ -35,10 +35,9 @@ export interface AttributeDecorator { * @publicApi */ (name: string): any; - new(name: string): Attribute; + new (name: string): Attribute; } - /** * Type of the Attribute metadata. * @@ -84,7 +83,6 @@ export interface Query { // explicitly set. export const emitDistinctChangesOnlyDefaultValue = true; - /** * Base class for query metadata. * @@ -165,13 +163,18 @@ export interface ContentChildrenDecorator { * * @Annotation */ - (selector: ProviderToken|Function|string, opts?: { - descendants?: boolean, - emitDistinctChangesOnly?: boolean, - read?: any, - }): any; - new(selector: ProviderToken|Function|string, - opts?: {descendants?: boolean, emitDistinctChangesOnly?: boolean, read?: any}): Query; + ( + selector: ProviderToken | Function | string, + opts?: { + descendants?: boolean; + emitDistinctChangesOnly?: boolean; + read?: any; + }, + ): any; + new ( + selector: ProviderToken | Function | string, + opts?: {descendants?: boolean; emitDistinctChangesOnly?: boolean; read?: any}, + ): Query; } /** @@ -191,15 +194,17 @@ export type ContentChildren = Query; * @publicApi */ export const ContentChildren: ContentChildrenDecorator = makePropDecorator( - 'ContentChildren', (selector?: any, opts: any = {}) => ({ - selector, - first: false, - isViewQuery: false, - descendants: false, - emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, - ...opts - }), - Query); + 'ContentChildren', + (selector?: any, opts: any = {}) => ({ + selector, + first: false, + isViewQuery: false, + descendants: false, + emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, + ...opts, + }), + Query, +); /** * Type of the ContentChild decorator / constructor function. @@ -266,10 +271,14 @@ export interface ContentChildDecorator { * * @Annotation */ - (selector: ProviderToken|Function|string, - opts?: {descendants?: boolean, read?: any, static?: boolean}): any; - new(selector: ProviderToken|Function|string, - opts?: {descendants?: boolean, read?: any, static?: boolean}): ContentChild; + ( + selector: ProviderToken | Function | string, + opts?: {descendants?: boolean; read?: any; static?: boolean}, + ): any; + new ( + selector: ProviderToken | Function | string, + opts?: {descendants?: boolean; read?: any; static?: boolean}, + ): ContentChild; } /** @@ -288,10 +297,16 @@ export type ContentChild = Query; * @publicApi */ export const ContentChild: ContentChildDecorator = makePropDecorator( - 'ContentChild', - (selector?: any, opts: any = {}) => - ({selector, first: true, isViewQuery: false, descendants: true, ...opts}), - Query); + 'ContentChild', + (selector?: any, opts: any = {}) => ({ + selector, + first: true, + isViewQuery: false, + descendants: true, + ...opts, + }), + Query, +); /** * Type of the ViewChildren decorator / constructor function. @@ -352,10 +367,14 @@ export interface ViewChildrenDecorator { * * @Annotation */ - (selector: ProviderToken|Function|string, - opts?: {read?: any, emitDistinctChangesOnly?: boolean}): any; - new(selector: ProviderToken|Function|string, - opts?: {read?: any, emitDistinctChangesOnly?: boolean}): ViewChildren; + ( + selector: ProviderToken | Function | string, + opts?: {read?: any; emitDistinctChangesOnly?: boolean}, + ): any; + new ( + selector: ProviderToken | Function | string, + opts?: {read?: any; emitDistinctChangesOnly?: boolean}, + ): ViewChildren; } /** @@ -372,15 +391,17 @@ export type ViewChildren = Query; * @publicApi */ export const ViewChildren: ViewChildrenDecorator = makePropDecorator( - 'ViewChildren', (selector?: any, opts: any = {}) => ({ - selector, - first: false, - isViewQuery: true, - descendants: true, - emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, - ...opts - }), - Query); + 'ViewChildren', + (selector?: any, opts: any = {}) => ({ + selector, + first: false, + isViewQuery: true, + descendants: true, + emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, + ...opts, + }), + Query, +); /** * Type of the ViewChild decorator / constructor function. @@ -443,9 +464,14 @@ export interface ViewChildDecorator { * * @Annotation */ - (selector: ProviderToken|Function|string, opts?: {read?: any, static?: boolean}): any; - new(selector: ProviderToken|Function|string, - opts?: {read?: any, static?: boolean}): ViewChild; + ( + selector: ProviderToken | Function | string, + opts?: {read?: any; static?: boolean}, + ): any; + new ( + selector: ProviderToken | Function | string, + opts?: {read?: any; static?: boolean}, + ): ViewChild; } /** @@ -462,7 +488,13 @@ export type ViewChild = Query; * @publicApi */ export const ViewChild: ViewChildDecorator = makePropDecorator( - 'ViewChild', - (selector: any, opts: any) => - ({selector, first: true, isViewQuery: true, descendants: true, ...opts}), - Query); + 'ViewChild', + (selector: any, opts: any) => ({ + selector, + first: true, + isViewQuery: true, + descendants: true, + ...opts, + }), + Query, +); diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index f5afb6ef217a5..cd6b9cd12d03c 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -16,8 +16,6 @@ import {makeDecorator, makePropDecorator, TypeDecorator} from '../util/decorator import {SchemaMetadata} from './schema'; import {ViewEncapsulation} from './view'; - - /** * Type of the Directive decorator / constructor function. * @publicApi @@ -105,7 +103,7 @@ export interface DirectiveDecorator { /** * See the `Directive` decorator. */ - new(obj?: Directive): Directive; + new (obj?: Directive): Directive; } /** @@ -182,12 +180,15 @@ export interface Directive { * ``` * */ - inputs?: ({ - name: string, - alias?: string, - required?: boolean, - transform?: (value: any) => any, - }|string)[]; + inputs?: ( + | { + name: string; + alias?: string; + required?: boolean; + transform?: (value: any) => any; + } + | string + )[]; /** * Enumerates the set of event-bound output properties. @@ -359,11 +360,14 @@ export interface Directive { * defines an input named `menuDisabled`, you can alias this to `disabled` by adding * `'menuDisabled: disabled'` as an entry to `inputs`. */ - hostDirectives?: (Type|{ - directive: Type, - inputs?: string[], - outputs?: string[], - })[]; + hostDirectives?: ( + | Type + | { + directive: Type; + inputs?: string[]; + outputs?: string[]; + } + )[]; } /** @@ -372,8 +376,12 @@ export interface Directive { * @publicApi */ export const Directive: DirectiveDecorator = makeDecorator( - 'Directive', (dir: Directive = {}) => dir, undefined, undefined, - (type: Type, meta: Directive) => compileDirective(type, meta)); + 'Directive', + (dir: Directive = {}) => dir, + undefined, + undefined, + (type: Type, meta: Directive) => compileDirective(type, meta), +); /** * Component decorator interface @@ -522,7 +530,7 @@ export interface ComponentDecorator { /** * See the `Component` decorator. */ - new(obj: Component): Component; + new (obj: Component): Component; } /** @@ -588,7 +596,7 @@ export interface Component extends Directive { * One or more inline CSS stylesheets to use * in this component. */ - styles?: string|string[]; + styles?: string | string[]; /** * One or more animation `trigger()` calls, containing @@ -649,7 +657,7 @@ export interface Component extends Directive { * More information about standalone components, directives, and pipes can be found in [this * guide](guide/components/importing). */ - imports?: (Type|ReadonlyArray)[]; + imports?: (Type | ReadonlyArray)[]; /** * The `deferredImports` property specifies a standalone component's template dependencies, @@ -660,7 +668,7 @@ export interface Component extends Directive { * Note: this is an internal-only field, use regular `@Component.imports` field instead. * @internal */ - deferredImports?: (Type|ReadonlyArray)[]; + deferredImports?: (Type | ReadonlyArray)[]; /** * The set of schemas that declare elements to be allowed in a standalone component. Elements and @@ -682,8 +690,12 @@ export interface Component extends Directive { * @publicApi */ export const Component: ComponentDecorator = makeDecorator( - 'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), - Directive, undefined, (type: Type, meta: Component) => compileComponent(type, meta)); + 'Component', + (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), + Directive, + undefined, + (type: Type, meta: Component) => compileComponent(type, meta), +); /** * Type of the Pipe decorator / constructor function. @@ -717,7 +729,7 @@ export interface PipeDecorator { /** * See the `Pipe` decorator. */ - new(obj: Pipe): Pipe; + new (obj: Pipe): Pipe; } /** @@ -760,9 +772,12 @@ export interface Pipe { * @publicApi */ export const Pipe: PipeDecorator = makeDecorator( - 'Pipe', (p: Pipe) => ({pure: true, ...p}), undefined, undefined, - (type: Type, meta: Pipe) => compilePipe(type, meta)); - + 'Pipe', + (p: Pipe) => ({pure: true, ...p}), + undefined, + undefined, + (type: Type, meta: Pipe) => compilePipe(type, meta), +); /** * @publicApi @@ -820,8 +835,8 @@ export interface InputDecorator { * @see [Input properties](guide/components/inputs) * @see [Output properties](guide/components/outputs) */ - (arg?: string|Input): any; - new(arg?: string|Input): any; + (arg?: string | Input): any; + new (arg?: string | Input): any; } /** @@ -864,13 +879,15 @@ export interface Input { * @Annotation * @publicApi */ -export const Input: InputDecorator = - makePropDecorator('Input', (arg?: string|{alias?: string, required?: boolean}) => { - if (!arg) { - return {}; - } - return typeof arg === 'string' ? {alias: arg} : arg; - }); +export const Input: InputDecorator = makePropDecorator( + 'Input', + (arg?: string | {alias?: string; required?: boolean}) => { + if (!arg) { + return {}; + } + return typeof arg === 'string' ? {alias: arg} : arg; + }, +); /** * Type of the Output decorator / constructor function. @@ -896,7 +913,7 @@ export interface OutputDecorator { * */ (alias?: string): any; - new(alias?: string): any; + new (alias?: string): any; } /** @@ -917,8 +934,6 @@ export interface Output { */ export const Output: OutputDecorator = makePropDecorator('Output', (alias?: string) => ({alias})); - - /** * Type of the HostBinding decorator / constructor function. * @@ -967,7 +982,7 @@ export interface HostBindingDecorator { * */ (hostPropertyName?: string): any; - new(hostPropertyName?: string): any; + new (hostPropertyName?: string): any; } /** @@ -990,9 +1005,10 @@ export interface HostBinding { * @Annotation * @publicApi */ -export const HostBinding: HostBindingDecorator = - makePropDecorator('HostBinding', (hostPropertyName?: string) => ({hostPropertyName})); - +export const HostBinding: HostBindingDecorator = makePropDecorator( + 'HostBinding', + (hostPropertyName?: string) => ({hostPropertyName}), +); /** * Type of the HostListener decorator / constructor function. @@ -1064,7 +1080,7 @@ export interface HostListenerDecorator { * */ (eventName: string, args?: string[]): any; - new(eventName: string, args?: string[]): any; + new (eventName: string, args?: string[]): any; } /** @@ -1087,5 +1103,7 @@ export interface HostListener { * @Annotation * @publicApi */ -export const HostListener: HostListenerDecorator = - makePropDecorator('HostListener', (eventName?: string, args?: string[]) => ({eventName, args})); +export const HostListener: HostListenerDecorator = makePropDecorator( + 'HostListener', + (eventName?: string, args?: string[]) => ({eventName, args}), +); diff --git a/packages/core/src/metadata/do_bootstrap.ts b/packages/core/src/metadata/do_bootstrap.ts index d9b1ce22637d9..9c33eb53bd251 100644 --- a/packages/core/src/metadata/do_bootstrap.ts +++ b/packages/core/src/metadata/do_bootstrap.ts @@ -8,7 +8,6 @@ import {ApplicationRef} from '../application/application_ref'; - /** * @description * Hook for manual bootstrapping of the application instead of using `bootstrap` array in @NgModule diff --git a/packages/core/src/metadata/ng_module.ts b/packages/core/src/metadata/ng_module.ts index b3c6310d67716..abef6c4acabd1 100644 --- a/packages/core/src/metadata/ng_module.ts +++ b/packages/core/src/metadata/ng_module.ts @@ -12,7 +12,6 @@ import {SchemaMetadata} from '../metadata/schema'; import {compileNgModule} from '../render3/jit/module'; import {makeDecorator, TypeDecorator} from '../util/decorators'; - /** * Type of the NgModule decorator / constructor function. * @@ -23,7 +22,7 @@ export interface NgModuleDecorator { * Decorator that marks a class as an NgModule and supplies configuration metadata. */ (obj?: NgModule): TypeDecorator; - new(obj?: NgModule): NgModule; + new (obj?: NgModule): NgModule; } /** @@ -79,7 +78,7 @@ export interface NgModule { * } * ``` */ - providers?: Array; + providers?: Array; /** * The set of components, directives, and pipes (declarables @@ -107,7 +106,7 @@ export interface NgModule { * } * ``` */ - declarations?: Array|any[]>; + declarations?: Array | any[]>; /** * The set of NgModules whose exported declarables @@ -136,7 +135,7 @@ export interface NgModule { * ``` * */ - imports?: Array|ModuleWithProviders<{}>|any[]>; + imports?: Array | ModuleWithProviders<{}> | any[]>; /** * The set of components, directives, and pipes declared in this @@ -168,12 +167,12 @@ export interface NgModule { * } * ``` */ - exports?: Array|any[]>; + exports?: Array | any[]>; /** * The set of components that are bootstrapped when this module is bootstrapped. */ - bootstrap?: Array|any[]>; + bootstrap?: Array | any[]>; /** * The set of schemas that declare elements to be allowed in the NgModule. @@ -185,7 +184,7 @@ export interface NgModule { * @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; + schemas?: Array; /** * A name or path that uniquely identifies this NgModule in `getNgModuleById`. @@ -206,16 +205,20 @@ export interface NgModule { * @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, meta: NgModule) => compileNgModule(type, meta)); + '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, meta: NgModule) => compileNgModule(type, meta), +); diff --git a/packages/core/src/metadata/ng_module_def.ts b/packages/core/src/metadata/ng_module_def.ts index 25a1370e4bb64..13ba8da789547 100644 --- a/packages/core/src/metadata/ng_module_def.ts +++ b/packages/core/src/metadata/ng_module_def.ts @@ -10,7 +10,6 @@ import {Type} from '../interface/type'; import {SchemaMetadata} from './schema'; - export interface NgModuleType extends Type { ɵmod: NgModuleDef; } @@ -25,9 +24,9 @@ export interface NgModuleType extends Type { * to module A's compilation scope when module A imports B). */ export interface NgModuleTransitiveScopes { - compilation: {directives: Set; pipes: Set;}; - exported: {directives: Set; pipes: Set;}; - schemas: SchemaMetadata[]|null; + compilation: {directives: Set; pipes: Set}; + exported: {directives: Set; pipes: Set}; + schemas: SchemaMetadata[] | null; } /** @@ -49,30 +48,30 @@ export interface NgModuleDef { * * @see {NgModuleScopeInfoFromDecorator} This field is only used in global compilation mode. In local compilation mode the bootstrap info is computed and added in runtime. */ - bootstrap: Type[]|(() => Type[]); + bootstrap: Type[] | (() => Type[]); /** List of components, directives, and pipes declared by this module. */ - declarations: Type[]|(() => Type[]); + declarations: Type[] | (() => Type[]); /** List of modules or `ModuleWithProviders` imported by this module. */ - imports: Type[]|(() => Type[]); + imports: Type[] | (() => Type[]); /** * List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this * module. */ - exports: Type[]|(() => Type[]); + exports: Type[] | (() => Type[]); /** * Cached value of computed `transitiveCompileScopes` for this module. * * This should never be read directly, but accessed via `transitiveScopesFor`. */ - transitiveCompileScopes: NgModuleTransitiveScopes|null; + transitiveCompileScopes: NgModuleTransitiveScopes | null; /** The set of schemas that declare elements to be allowed in the NgModule. */ - schemas: SchemaMetadata[]|null; + schemas: SchemaMetadata[] | null; /** Unique ID for the module with which it should be registered. */ - id: string|null; + id: string | null; } diff --git a/packages/core/src/metadata/resource_loading.ts b/packages/core/src/metadata/resource_loading.ts index c8c77f2d95bd5..d1c091c0482ba 100644 --- a/packages/core/src/metadata/resource_loading.ts +++ b/packages/core/src/metadata/resource_loading.ts @@ -10,7 +10,6 @@ import {Type} from '../interface/type'; import {Component} from './directives'; - /** * Used to resolve resource URLs on `@Component` when used with JIT compilation. * @@ -44,7 +43,8 @@ import {Component} from './directives'; * contents of the resolved URL. Browser's `fetch()` method is a good default implementation. */ export function resolveComponentResources( - resourceResolver: (url: string) => (Promise}>)): Promise { + resourceResolver: (url: string) => Promise}>, +): Promise { // Store all promises which are fetching the resources. const componentResolved: Promise[] = []; @@ -54,7 +54,7 @@ export function resolveComponentResources( let promise = urlMap.get(url); if (!promise) { const resp = resourceResolver(url); - urlMap.set(url, promise = resp.then(unwrapResponse)); + urlMap.set(url, (promise = resp.then(unwrapResponse))); } return promise; } @@ -62,36 +62,43 @@ export function resolveComponentResources( componentResourceResolutionQueue.forEach((component: Component, type: Type) => { const promises: Promise[] = []; if (component.templateUrl) { - promises.push(cachedResourceResolve(component.templateUrl).then((template) => { - component.template = template; - })); + promises.push( + cachedResourceResolve(component.templateUrl).then((template) => { + component.template = template; + }), + ); } const styles = - typeof component.styles === 'string' ? [component.styles] : (component.styles || []); + typeof component.styles === 'string' ? [component.styles] : component.styles || []; component.styles = styles; if (component.styleUrl && component.styleUrls?.length) { throw new Error( - '@Component cannot define both `styleUrl` and `styleUrls`. ' + - 'Use `styleUrl` if the component has one stylesheet, or `styleUrls` if it has multiple'); + '@Component cannot define both `styleUrl` and `styleUrls`. ' + + 'Use `styleUrl` if the component has one stylesheet, or `styleUrls` if it has multiple', + ); } else if (component.styleUrls?.length) { const styleOffset = component.styles.length; const styleUrls = component.styleUrls; component.styleUrls.forEach((styleUrl, index) => { - styles.push(''); // pre-allocate array. - promises.push(cachedResourceResolve(styleUrl).then((style) => { - styles[styleOffset + index] = style; - styleUrls.splice(styleUrls.indexOf(styleUrl), 1); - if (styleUrls.length == 0) { - component.styleUrls = undefined; - } - })); + styles.push(''); // pre-allocate array. + promises.push( + cachedResourceResolve(styleUrl).then((style) => { + styles[styleOffset + index] = style; + styleUrls.splice(styleUrls.indexOf(styleUrl), 1); + if (styleUrls.length == 0) { + component.styleUrls = undefined; + } + }), + ); }); } else if (component.styleUrl) { - promises.push(cachedResourceResolve(component.styleUrl).then((style) => { - styles.push(style); - component.styleUrl = undefined; - })); + promises.push( + cachedResourceResolve(component.styleUrl).then((style) => { + styles.push(style); + component.styleUrl = undefined; + }), + ); } const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type)); @@ -119,8 +126,10 @@ export function isComponentDefPendingResolution(type: Type): boolean { export function componentNeedsResolution(component: Component): boolean { return !!( - (component.templateUrl && !component.hasOwnProperty('template')) || - (component.styleUrls && component.styleUrls.length) || component.styleUrl); + (component.templateUrl && !component.hasOwnProperty('template')) || + (component.styleUrls && component.styleUrls.length) || + component.styleUrl + ); } export function clearResolutionOfComponentResourcesQueue(): Map, Component> { const old = componentResourceResolutionQueue; @@ -138,7 +147,7 @@ export function isComponentResourceResolutionQueueEmpty() { return componentResourceResolutionQueue.size === 0; } -function unwrapResponse(response: string|{text(): Promise}): string|Promise { +function unwrapResponse(response: string | {text(): Promise}): string | Promise { return typeof response == 'string' ? response : response.text(); } diff --git a/packages/core/src/metadata/schema.ts b/packages/core/src/metadata/schema.ts index a1cf63aed10de..0c11dd4505a60 100644 --- a/packages/core/src/metadata/schema.ts +++ b/packages/core/src/metadata/schema.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - /** * A schema definition associated with an NgModule. * @@ -31,7 +30,7 @@ export interface SchemaMetadata { * @publicApi */ export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { - name: 'custom-elements' + name: 'custom-elements', }; /** @@ -44,5 +43,5 @@ export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { * @publicApi */ export const NO_ERRORS_SCHEMA: SchemaMetadata = { - name: 'no-errors-schema' + name: 'no-errors-schema', }; diff --git a/packages/core/src/metadata/view.ts b/packages/core/src/metadata/view.ts index 04838345b191c..fa5ab885918e5 100644 --- a/packages/core/src/metadata/view.ts +++ b/packages/core/src/metadata/view.ts @@ -46,5 +46,5 @@ export enum ViewEncapsulation { * a ShadowRoot for the component's host element which is then used to encapsulate * all the Component's styling. */ - ShadowDom = 3 + ShadowDom = 3, } diff --git a/packages/core/src/platform/platform.ts b/packages/core/src/platform/platform.ts index 244d50fd554a3..f6aac1eced3af 100644 --- a/packages/core/src/platform/platform.ts +++ b/packages/core/src/platform/platform.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {publishDefaultGlobalUtils, publishSignalConfiguration} from '../application/application_ref'; +import { + publishDefaultGlobalUtils, + publishSignalConfiguration, +} from '../application/application_ref'; import {PLATFORM_INITIALIZER} from '../application/application_tokens'; import {InjectionToken, Injector, StaticProvider} from '../di'; import {INJECTOR_SCOPE} from '../di/scope'; @@ -14,14 +17,15 @@ import {RuntimeError, RuntimeErrorCode} from '../errors'; import {PLATFORM_DESTROY_LISTENERS, PlatformRef} from './platform_ref'; -let _platformInjector: Injector|null = null; +let _platformInjector: Injector | null = null; /** * Internal token to indicate whether having multiple bootstrapped platform should be allowed (only * one bootstrapped platform is allowed by default). This token helps to support SSR scenarios. */ -export const ALLOW_MULTIPLE_PLATFORMS = - new InjectionToken(ngDevMode ? 'AllowMultipleToken' : ''); +export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken( + ngDevMode ? 'AllowMultipleToken' : '', +); /** * Creates a platform. @@ -32,9 +36,9 @@ export const ALLOW_MULTIPLE_PLATFORMS = export function createPlatform(injector: Injector): PlatformRef { if (_platformInjector && !_platformInjector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { throw new RuntimeError( - RuntimeErrorCode.MULTIPLE_PLATFORMS, - ngDevMode && - 'There can be only one platform. Destroy the previous one to create a new one.'); + RuntimeErrorCode.MULTIPLE_PLATFORMS, + ngDevMode && 'There can be only one platform. Destroy the previous one to create a new one.', + ); } publishDefaultGlobalUtils(); publishSignalConfiguration(); @@ -56,15 +60,20 @@ export function createPlatform(injector: Injector): PlatformRef { * @publicApi */ export function createPlatformFactory( - parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef)|null, name: string, - providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) => PlatformRef { + parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null, + name: string, + providers: StaticProvider[] = [], +): (extraProviders?: StaticProvider[]) => PlatformRef { const desc = `Platform: ${name}`; const marker = new InjectionToken(desc); return (extraProviders: StaticProvider[] = []) => { let platform = getPlatform(); if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { - const platformProviders: StaticProvider[] = - [...providers, ...extraProviders, {provide: marker, useValue: true}]; + const platformProviders: StaticProvider[] = [ + ...providers, + ...extraProviders, + {provide: marker, useValue: true}, + ]; if (parentPlatformFactory) { parentPlatformFactory(platformProviders); } else { @@ -84,8 +93,8 @@ function createPlatformInjector(providers: StaticProvider[] = [], name?: string) name, providers: [ {provide: INJECTOR_SCOPE, useValue: 'platform'}, - {provide: PLATFORM_DESTROY_LISTENERS, useValue: new Set([() => _platformInjector = null])}, - ...providers + {provide: PLATFORM_DESTROY_LISTENERS, useValue: new Set([() => (_platformInjector = null)])}, + ...providers, ], }); } @@ -102,11 +111,14 @@ export function assertPlatform(requiredToken: any): PlatformRef { throw new RuntimeError(RuntimeErrorCode.PLATFORM_NOT_FOUND, ngDevMode && 'No platform exists!'); } - if ((typeof ngDevMode === 'undefined' || ngDevMode) && - !platform.injector.get(requiredToken, null)) { + if ( + (typeof ngDevMode === 'undefined' || ngDevMode) && + !platform.injector.get(requiredToken, null) + ) { throw new RuntimeError( - RuntimeErrorCode.MULTIPLE_PLATFORMS, - 'A platform with a different configuration has been created. Please destroy it first.'); + RuntimeErrorCode.MULTIPLE_PLATFORMS, + 'A platform with a different configuration has been created. Please destroy it first.', + ); } return platform; @@ -117,7 +129,7 @@ export function assertPlatform(requiredToken: any): PlatformRef { * * @publicApi */ -export function getPlatform(): PlatformRef|null { +export function getPlatform(): PlatformRef | null { return _platformInjector?.get(PlatformRef) ?? null; } diff --git a/packages/core/src/platform/platform_core_providers.ts b/packages/core/src/platform/platform_core_providers.ts index 75bc072fecf60..04220af429c67 100644 --- a/packages/core/src/platform/platform_core_providers.ts +++ b/packages/core/src/platform/platform_core_providers.ts @@ -16,5 +16,5 @@ import {PlatformRef} from './platform_ref'; * * @publicApi */ -export const platformCore: (extraProviders?: StaticProvider[]|undefined) => PlatformRef = - createPlatformFactory(null, 'core', []); +export const platformCore: (extraProviders?: StaticProvider[] | undefined) => PlatformRef = + createPlatformFactory(null, 'core', []); diff --git a/packages/core/src/platform/platform_ref.ts b/packages/core/src/platform/platform_ref.ts index 06735ab8446f2..37cf8724f9d59 100644 --- a/packages/core/src/platform/platform_ref.ts +++ b/packages/core/src/platform/platform_ref.ts @@ -8,8 +8,18 @@ import {ApplicationInitStatus} from '../application/application_init'; import {compileNgModuleFactory} from '../application/application_ngmodule_factory_compiler'; -import {_callAndReportToErrorHandler, ApplicationRef, BootstrapOptions, optionsReducer, remove} from '../application/application_ref'; -import {getNgZoneOptions, internalProvideZoneChangeDetection, PROVIDED_NG_ZONE} from '../change_detection/scheduling/ng_zone_scheduling'; +import { + _callAndReportToErrorHandler, + ApplicationRef, + BootstrapOptions, + optionsReducer, + remove, +} from '../application/application_ref'; +import { + getNgZoneOptions, + internalProvideZoneChangeDetection, + PROVIDED_NG_ZONE, +} from '../change_detection/scheduling/ng_zone_scheduling'; import {Injectable, InjectionToken, Injector} from '../di'; import {ErrorHandler} from '../error_handler'; import {RuntimeError, RuntimeErrorCode} from '../errors'; @@ -23,15 +33,15 @@ import {createNgModuleRefWithProviders} from '../render3/ng_module_ref'; import {stringify} from '../util/stringify'; import {getNgZone} from '../zone/ng_zone'; - /** * Internal token that allows to register extra callbacks that should be invoked during the * `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the * `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the * entire class tree-shakeable. */ -export const PLATFORM_DESTROY_LISTENERS = - new InjectionToken>(ngDevMode ? 'PlatformDestroyListeners' : ''); +export const PLATFORM_DESTROY_LISTENERS = new InjectionToken>( + ngDevMode ? 'PlatformDestroyListeners' : '', +); /** * The Angular platform is the entry point for Angular on a web page. @@ -57,16 +67,21 @@ export class PlatformRef { * @deprecated Passing NgModule factories as the `PlatformRef.bootstrapModuleFactory` function * argument is deprecated. Use the `PlatformRef.bootstrapModule` API instead. */ - bootstrapModuleFactory(moduleFactory: NgModuleFactory, options?: BootstrapOptions): - Promise> { + bootstrapModuleFactory( + moduleFactory: NgModuleFactory, + options?: BootstrapOptions, + ): Promise> { // Note: We need to create the NgZone _before_ we instantiate the module, // as instantiating the module creates some providers eagerly. // So we create a mini parent injector that just contains the new NgZone and // pass that as parent to the NgModuleFactory. - const ngZone = getNgZone(options?.ngZone, getNgZoneOptions({ - eventCoalescing: options?.ngZoneEventCoalescing, - runCoalescing: options?.ngZoneRunCoalescing - })); + const ngZone = getNgZone( + options?.ngZone, + getNgZoneOptions({ + eventCoalescing: options?.ngZoneEventCoalescing, + runCoalescing: options?.ngZoneRunCoalescing, + }), + ); // Note: Create ngZoneInjector within ngZone.run so that all of the instantiated services are // created within the Angular zone // Do not try to replace ngZone.run with ApplicationRef#run because ApplicationRef would then be @@ -74,30 +89,33 @@ export class PlatformRef { return ngZone.run(() => { const ignoreChangesOutsideZone = options?.ignoreChangesOutsideZone; const moduleRef = createNgModuleRefWithProviders( - moduleFactory.moduleType, - this.injector, - internalProvideZoneChangeDetection( - {ngZoneFactory: () => ngZone, ignoreChangesOutsideZone}), + moduleFactory.moduleType, + this.injector, + internalProvideZoneChangeDetection({ngZoneFactory: () => ngZone, ignoreChangesOutsideZone}), ); - if ((typeof ngDevMode === 'undefined' || ngDevMode) && - moduleRef.injector.get(PROVIDED_NG_ZONE, null) !== null) { + if ( + (typeof ngDevMode === 'undefined' || ngDevMode) && + moduleRef.injector.get(PROVIDED_NG_ZONE, null) !== null + ) { throw new RuntimeError( - RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, - '`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.'); + RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, + '`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.', + ); } const exceptionHandler = moduleRef.injector.get(ErrorHandler, null); if ((typeof ngDevMode === 'undefined' || ngDevMode) && exceptionHandler === null) { throw new RuntimeError( - RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, - 'No ErrorHandler. Is platform module (BrowserModule) included?'); + RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, + 'No ErrorHandler. Is platform module (BrowserModule) included?', + ); } ngZone.runOutsideAngular(() => { const subscription = ngZone.onError.subscribe({ next: (error: any) => { exceptionHandler!.handleError(error); - } + }, }); moduleRef.onDestroy(() => { remove(this._modules, moduleRef); @@ -135,27 +153,31 @@ export class PlatformRef { * */ bootstrapModule( - moduleType: Type, - compilerOptions: (CompilerOptions&BootstrapOptions)| - Array = []): Promise> { + moduleType: Type, + compilerOptions: + | (CompilerOptions & BootstrapOptions) + | Array = [], + ): Promise> { const options = optionsReducer({}, compilerOptions); - return compileNgModuleFactory(this.injector, options, moduleType) - .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options)); + return compileNgModuleFactory(this.injector, options, moduleType).then((moduleFactory) => + this.bootstrapModuleFactory(moduleFactory, options), + ); } private _moduleDoBootstrap(moduleRef: InternalNgModuleRef): void { const appRef = moduleRef.injector.get(ApplicationRef); if (moduleRef._bootstrapComponents.length > 0) { - moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f)); + moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f)); } else if (moduleRef.instance.ngDoBootstrap) { moduleRef.instance.ngDoBootstrap(appRef); } else { throw new RuntimeError( - RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND, - ngDevMode && - `The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` + - `but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` + - `Please define one of these.`); + RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND, + ngDevMode && + `The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` + + `but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` + + `Please define one of these.`, + ); } this._modules.push(moduleRef); } @@ -182,15 +204,16 @@ export class PlatformRef { destroy() { if (this._destroyed) { throw new RuntimeError( - RuntimeErrorCode.PLATFORM_ALREADY_DESTROYED, - ngDevMode && 'The platform has already been destroyed!'); + RuntimeErrorCode.PLATFORM_ALREADY_DESTROYED, + ngDevMode && 'The platform has already been destroyed!', + ); } - this._modules.slice().forEach(module => module.destroy()); - this._destroyListeners.forEach(listener => listener()); + this._modules.slice().forEach((module) => module.destroy()); + this._destroyListeners.forEach((listener) => listener()); const destroyListeners = this._injector.get(PLATFORM_DESTROY_LISTENERS, null); if (destroyListeners) { - destroyListeners.forEach(listener => listener()); + destroyListeners.forEach((listener) => listener()); destroyListeners.clear(); } diff --git a/packages/core/src/r3_symbols.ts b/packages/core/src/r3_symbols.ts index 90ccc0bf7ebf1..c4051acf00251 100644 --- a/packages/core/src/r3_symbols.ts +++ b/packages/core/src/r3_symbols.ts @@ -25,13 +25,15 @@ export {ɵɵinject} from './di/injector_compatibility'; export {ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵInjectableDeclaration} from './di/interface/defs'; export {NgModuleDef} from './metadata/ng_module_def'; export {ɵɵdefineNgModule} from './render3/definition'; -export {ɵɵFactoryDeclaration, ɵɵInjectorDeclaration, ɵɵNgModuleDeclaration} from './render3/interfaces/public_definitions'; +export { + ɵɵFactoryDeclaration, + ɵɵInjectorDeclaration, + ɵɵNgModuleDeclaration, +} from './render3/interfaces/public_definitions'; export {setClassMetadata, setClassMetadataAsync} from './render3/metadata'; export {NgModuleFactory} from './render3/ng_module_ref'; export {noSideEffects as ɵnoSideEffects} from './util/closure'; - - /** * The existence of this constant (in this particular file) informs the Angular compiler that the * current program is actually @angular/core, which needs to be compiled specially. diff --git a/packages/core/src/reflection/reflection_capabilities.ts b/packages/core/src/reflection/reflection_capabilities.ts index f6e7c014551b3..1d989e44f4882 100644 --- a/packages/core/src/reflection/reflection_capabilities.ts +++ b/packages/core/src/reflection/reflection_capabilities.ts @@ -13,8 +13,6 @@ import {global} from '../util/global'; import {PlatformReflectionCapabilities} from './platform_reflection_capabilities'; - - /* * ######################### * Attention: These Regular expressions have to hold even if the code is minified! @@ -47,7 +45,7 @@ import {PlatformReflectionCapabilities} from './platform_reflection_capabilities * More details can be found in: https://github.com/angular/angular/issues/38453. */ export const ES5_DELEGATE_CTOR = - /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*(arguments|(?:[^()]+\(\[\],)?[^()]+\(arguments\).*)\)/; + /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*(arguments|(?:[^()]+\(\[\],)?[^()]+\(arguments\).*)\)/; /** Regular expression that detects ES2015 classes which extend from other classes. */ export const ES2015_INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{/; /** @@ -55,13 +53,13 @@ export const ES2015_INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{ * have an explicit constructor defined. */ export const ES2015_INHERITED_CLASS_WITH_CTOR = - /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(/; + /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(/; /** * Regular expression that detects ES2015 classes which extend from other classes * and inherit a constructor. */ export const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR = - /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/; + /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/; /** * Determine whether a stringified type is a class which delegates its constructor @@ -72,9 +70,11 @@ export const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR = * an initialized instance property. */ export function isDelegateCtor(typeStr: string): boolean { - return ES5_DELEGATE_CTOR.test(typeStr) || - ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR.test(typeStr) || - (ES2015_INHERITED_CLASS.test(typeStr) && !ES2015_INHERITED_CLASS_WITH_CTOR.test(typeStr)); + return ( + ES5_DELEGATE_CTOR.test(typeStr) || + ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR.test(typeStr) || + (ES2015_INHERITED_CLASS.test(typeStr) && !ES2015_INHERITED_CLASS_WITH_CTOR.test(typeStr)) + ); } export class ReflectionCapabilities implements PlatformReflectionCapabilities { @@ -116,7 +116,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { return result; } - private _ownParameters(type: Type, parentCtor: any): any[][]|null { + private _ownParameters(type: Type, parentCtor: any): any[][] | null { const typeStr = type.toString(); // If we have no decorators, we only have function.length as metadata. // In that case, to detect whether a child class declared an own constructor or not, @@ -140,18 +140,20 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { // Newer tsickle uses a function closure // Retain the non-function case for compatibility with older tsickle const ctorParameters = - typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams; + typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams; const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type); const paramAnnotations = ctorParameters.map( - (ctorParam: any) => - ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators)); + (ctorParam: any) => ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators), + ); return this._zipTypesAndAnnotations(paramTypes, paramAnnotations); } // API for metadata created by invoking the decorators. const paramAnnotations = type.hasOwnProperty(PARAMETERS) && (type as any)[PARAMETERS]; - const paramTypes = this._reflect && this._reflect.getOwnMetadata && - this._reflect.getOwnMetadata('design:paramtypes', type); + const paramTypes = + this._reflect && + this._reflect.getOwnMetadata && + this._reflect.getOwnMetadata('design:paramtypes', type); if (paramTypes || paramAnnotations) { return this._zipTypesAndAnnotations(paramTypes, paramAnnotations); } @@ -177,7 +179,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { return parameters || []; } - private _ownAnnotations(typeOrFunc: Type, parentCtor: any): any[]|null { + private _ownAnnotations(typeOrFunc: Type, parentCtor: any): any[] | null { // Prefer the direct API. if ((typeOrFunc).annotations && (typeOrFunc).annotations !== parentCtor.annotations) { let annotations = (typeOrFunc).annotations; @@ -209,10 +211,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { return parentAnnotations.concat(ownAnnotations); } - private _ownPropMetadata(typeOrFunc: any, parentCtor: any): {[key: string]: any[]}|null { + private _ownPropMetadata(typeOrFunc: any, parentCtor: any): {[key: string]: any[]} | null { // Prefer the direct API. - if ((typeOrFunc).propMetadata && - (typeOrFunc).propMetadata !== parentCtor.propMetadata) { + if ( + (typeOrFunc).propMetadata && + (typeOrFunc).propMetadata !== parentCtor.propMetadata + ) { let propMetadata = (typeOrFunc).propMetadata; if (typeof propMetadata === 'function' && propMetadata.propMetadata) { propMetadata = propMetadata.propMetadata; @@ -221,11 +225,13 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { } // API of tsickle for lowering decorators to properties on the class. - if ((typeOrFunc).propDecorators && - (typeOrFunc).propDecorators !== parentCtor.propDecorators) { + if ( + (typeOrFunc).propDecorators && + (typeOrFunc).propDecorators !== parentCtor.propDecorators + ) { const propDecorators = (typeOrFunc).propDecorators; const propMetadata = <{[key: string]: any[]}>{}; - Object.keys(propDecorators).forEach(prop => { + Object.keys(propDecorators).forEach((prop) => { propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]); }); return propMetadata; @@ -280,7 +286,7 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[] if (!decoratorInvocations) { return []; } - return decoratorInvocations.map(decoratorInvocation => { + return decoratorInvocations.map((decoratorInvocation) => { const decoratorType = decoratorInvocation.type; const annotationCls = decoratorType.annotationCls; const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : []; diff --git a/packages/core/src/render/api.ts b/packages/core/src/render/api.ts index f473b061a64ec..11945267ed509 100644 --- a/packages/core/src/render/api.ts +++ b/packages/core/src/render/api.ts @@ -13,7 +13,6 @@ import {getComponentLViewByIndex} from '../render3/util/view_utils'; import {RendererStyleFlags2, RendererType2} from './api_flags'; - /** * Creates and initializes a custom renderer that implements the `Renderer2` base class. * @@ -26,7 +25,7 @@ export abstract class RendererFactory2 { * @param type The base class to implement. * @returns The new custom renderer instance. */ - abstract createRenderer(hostElement: any, type: RendererType2|null): Renderer2; + abstract createRenderer(hostElement: any, type: RendererType2 | null): Renderer2; /** * A callback invoked when rendering has begun. */ @@ -42,7 +41,6 @@ export abstract class RendererFactory2 { abstract whenRenderingDone?(): Promise; } - /** * Extend this base class to implement custom rendering. By default, Angular * renders a template into DOM. You can use custom rendering to intercept @@ -76,7 +74,7 @@ export abstract class Renderer2 { * @param namespace The namespace for the new element. * @returns The new element. */ - abstract createElement(name: string, namespace?: string|null): any; + abstract createElement(name: string, namespace?: string | null): any; /** * Implement this callback to add a comment to the DOM of the host element. * @param value The comment text. @@ -94,7 +92,7 @@ export abstract class Renderer2 { * If null or undefined, the view engine won't call it. * This is used as a performance optimization for production mode. */ - destroyNode: ((node: any) => void)|null = null; + destroyNode: ((node: any) => void) | null = null; /** * Appends a child to a given parent node in the host element DOM. * @param parent The parent node. @@ -132,7 +130,7 @@ export abstract class Renderer2 { * content projection via `` elements. * @returns The root element. */ - abstract selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): any; + abstract selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): any; /** * Implement this callback to get the parent of a given node * in the host element's DOM. @@ -157,7 +155,7 @@ export abstract class Renderer2 { * @param value The new value. * @param namespace The namespace. */ - abstract setAttribute(el: any, name: string, value: string, namespace?: string|null): void; + abstract setAttribute(el: any, name: string, value: string, namespace?: string | null): void; /** * Implement this callback to remove an attribute from an element in the DOM. @@ -165,7 +163,7 @@ export abstract class Renderer2 { * @param name The attribute name. * @param namespace The namespace. */ - abstract removeAttribute(el: any, name: string, namespace?: string|null): void; + abstract removeAttribute(el: any, name: string, namespace?: string | null): void; /** * Implement this callback to add a class to an element in the DOM. * @param el The element. @@ -222,8 +220,10 @@ export abstract class Renderer2 { * @returns An "unlisten" function for disposing of this handler. */ abstract listen( - target: 'window'|'document'|'body'|any, eventName: string, - callback: (event: any) => boolean | void): () => void; + target: 'window' | 'document' | 'body' | any, + eventName: string, + callback: (event: any) => boolean | void, + ): () => void; /** * @internal diff --git a/packages/core/src/render/api_flags.ts b/packages/core/src/render/api_flags.ts index 66cdd62cf1ed0..5aa6d4207bf11 100644 --- a/packages/core/src/render/api_flags.ts +++ b/packages/core/src/render/api_flags.ts @@ -8,7 +8,6 @@ import {ViewEncapsulation} from '../metadata/view'; - /** * Used by `RendererFactory2` to associate custom rendering data and styles * with a rendering implementation. @@ -42,7 +41,6 @@ export interface RendererType2 { data: {[kind: string]: any}; } - /** * Flags for renderer-specific style modifiers. * @publicApi @@ -58,5 +56,5 @@ export enum RendererStyleFlags2 { /** * Marks a style as using dash case naming (this-is-dash-case). */ - DashCase = 1 << 1 + DashCase = 1 << 1, } diff --git a/packages/core/src/render3/after_render_hooks.ts b/packages/core/src/render3/after_render_hooks.ts index 6104f196371c4..5b0037969f0e7 100644 --- a/packages/core/src/render3/after_render_hooks.ts +++ b/packages/core/src/render3/after_render_hooks.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionScheduler, NotificationSource} from '../change_detection/scheduling/zoneless_scheduling'; +import { + ChangeDetectionScheduler, + NotificationSource, +} from '../change_detection/scheduling/zoneless_scheduling'; import {assertInInjectionContext, Injector, runInInjectionContext, ɵɵdefineInjectable} from '../di'; import {inject} from '../di/injector_compatibility'; import {ErrorHandler} from '../error_handler'; @@ -138,7 +141,7 @@ export interface InternalAfterNextRenderOptions { /** `AfterRenderRef` that does nothing. */ const NOOP_AFTER_RENDER_REF: AfterRenderRef = { - destroy() {} + destroy() {}, }; /** @@ -157,7 +160,9 @@ const NOOP_AFTER_RENDER_REF: AfterRenderRef = { * to be tree-shaken, and the framework shouldn't need much of the behavior. */ export function internalAfterNextRender( - callback: VoidFunction, options?: InternalAfterNextRenderOptions) { + callback: VoidFunction, + options?: InternalAfterNextRenderOptions, +) { const injector = options?.injector ?? inject(Injector); // Similarly to the public `afterNextRender` function, an internal one @@ -218,10 +223,11 @@ export function internalAfterNextRender( */ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions): AfterRenderRef { ngDevMode && - assertNotInReactiveContext( - afterRender, - 'Call `afterRender` outside of a reactive context. For example, schedule the render ' + - 'callback inside the component constructor`.'); + assertNotInReactiveContext( + afterRender, + 'Call `afterRender` outside of a reactive context. For example, schedule the render ' + + 'callback inside the component constructor`.', + ); !options && assertInInjectionContext(afterRender); const injector = options?.injector ?? inject(Injector); @@ -235,7 +241,8 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions const afterRenderEventManager = injector.get(AfterRenderEventManager); // Lazily initialize the handler implementation, if necessary. This is so that it can be // tree-shaken if `afterRender` and `afterNextRender` aren't used. - const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl(); + const callbackHandler = (afterRenderEventManager.handler ??= + new AfterRenderCallbackHandlerImpl()); const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite; const destroy = () => { callbackHandler.unregister(instance); @@ -298,7 +305,9 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions * @developerPreview */ export function afterNextRender( - callback: VoidFunction, options?: AfterRenderOptions): AfterRenderRef { + callback: VoidFunction, + options?: AfterRenderOptions, +): AfterRenderRef { !options && assertInInjectionContext(afterNextRender); const injector = options?.injector ?? inject(Injector); @@ -311,17 +320,22 @@ export function afterNextRender( const afterRenderEventManager = injector.get(AfterRenderEventManager); // Lazily initialize the handler implementation, if necessary. This is so that it can be // tree-shaken if `afterRender` and `afterNextRender` aren't used. - const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl(); + const callbackHandler = (afterRenderEventManager.handler ??= + new AfterRenderCallbackHandlerImpl()); const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite; const destroy = () => { callbackHandler.unregister(instance); unregisterFn(); }; const unregisterFn = injector.get(DestroyRef).onDestroy(destroy); - const instance = runInInjectionContext(injector, () => new AfterRenderCallback(phase, () => { - destroy(); - callback(); - })); + const instance = runInInjectionContext( + injector, + () => + new AfterRenderCallback(phase, () => { + destroy(); + callback(); + }), + ); callbackHandler.register(instance); return {destroy}; @@ -333,7 +347,10 @@ export function afterNextRender( class AfterRenderCallback { private errorHandler = inject(ErrorHandler, {optional: true}); - constructor(readonly phase: AfterRenderPhase, private callbackFn: VoidFunction) { + constructor( + readonly phase: AfterRenderPhase, + private callbackFn: VoidFunction, + ) { // Registering a callback will notify the scheduler. inject(ChangeDetectionScheduler, {optional: true})?.notify(NotificationSource.NewRenderHook); } @@ -428,7 +445,7 @@ class AfterRenderCallbackHandlerImpl implements AfterRenderCallbackHandler { */ export class AfterRenderEventManager { /* @internal */ - handler: AfterRenderCallbackHandler|null = null; + handler: AfterRenderCallbackHandler | null = null; /* @internal */ internalCallbacks: VoidFunction[] = []; diff --git a/packages/core/src/render3/apply_value_input_field.ts b/packages/core/src/render3/apply_value_input_field.ts index 7c493b0c422db..1bfabd10ab304 100644 --- a/packages/core/src/render3/apply_value_input_field.ts +++ b/packages/core/src/render3/apply_value_input_field.ts @@ -6,12 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ - import {InputSignalNode} from '../authoring/input/input_signal_node'; export function applyValueToInputField( - instance: T, inputSignalNode: null|InputSignalNode, privateName: string, - value: unknown) { + instance: T, + inputSignalNode: null | InputSignalNode, + privateName: string, + value: unknown, +) { if (inputSignalNode !== null) { inputSignalNode.applyValueToInputSignal(inputSignalNode, value); } else { diff --git a/packages/core/src/render3/assert.ts b/packages/core/src/render3/assert.ts index 26dfb38ffadc0..63abb415bfe0f 100644 --- a/packages/core/src/render3/assert.ts +++ b/packages/core/src/render3/assert.ts @@ -16,12 +16,20 @@ import {TIcu} from './interfaces/i18n'; import {NodeInjectorOffset} from './interfaces/injector'; import {TNode} from './interfaces/node'; import {isLContainer, isLView} from './interfaces/type_checks'; -import {DECLARATION_COMPONENT_VIEW, FLAGS, HEADER_OFFSET, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view'; +import { + DECLARATION_COMPONENT_VIEW, + FLAGS, + HEADER_OFFSET, + LView, + LViewFlags, + T_HOST, + TVIEW, + TView, +} from './interfaces/view'; // [Assert functions do not constraint type when they are guarded by a truthy // expression.](https://github.com/microsoft/TypeScript/issues/37295) - export function assertTNodeForLView(tNode: TNode, lView: LView) { assertTNodeForTView(tNode, lView[TVIEW]); } @@ -44,7 +52,6 @@ export function assertTNode(tNode: TNode) { } } - export function assertTIcu(tIcu: TIcu) { assertDefined(tIcu, 'Expected TIcu to be defined'); if (!(typeof tIcu.currentCaseLViewIndex === 'number')) { @@ -53,16 +60,18 @@ export function assertTIcu(tIcu: TIcu) { } export function assertComponentType( - actual: any, - msg: string = 'Type passed in is not ComponentType, it does not have \'ɵcmp\' property.') { + actual: any, + msg: string = "Type passed in is not ComponentType, it does not have 'ɵcmp' property.", +) { if (!getComponentDef(actual)) { throwError(msg); } } export function assertNgModuleType( - actual: any, - msg: string = 'Type passed in is not NgModuleType, it does not have \'ɵmod\' property.') { + actual: any, + msg: string = "Type passed in is not NgModuleType, it does not have 'ɵmod' property.", +) { if (!getNgModuleDef(actual)) { throwError(msg); } @@ -72,7 +81,7 @@ export function assertCurrentTNodeIsParent(isParent: boolean) { assertEqual(isParent, true, 'currentTNode should be a parent'); } -export function assertHasParent(tNode: TNode|null) { +export function assertHasParent(tNode: TNode | null) { assertDefined(tNode, 'currentTNode should exist!'); assertDefined(tNode!.parent, 'currentTNode should have a parent'); } @@ -82,7 +91,7 @@ export function assertLContainer(value: any): asserts value is LContainer { assertEqual(isLContainer(value), true, 'Expecting LContainer'); } -export function assertLViewOrUndefined(value: any): asserts value is LView|null|undefined { +export function assertLViewOrUndefined(value: any): asserts value is LView | null | undefined { value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null'); } @@ -93,12 +102,18 @@ export function assertLView(value: any): asserts value is LView { export function assertFirstCreatePass(tView: TView, errMessage?: string) { assertEqual( - tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.'); + tView.firstCreatePass, + true, + errMessage || 'Should only be called in first create pass.', + ); } export function assertFirstUpdatePass(tView: TView, errMessage?: string) { assertEqual( - tView.firstUpdatePass, true, errMessage || 'Should only be called in first update pass.'); + tView.firstUpdatePass, + true, + errMessage || 'Should only be called in first update pass.', + ); } /** @@ -108,7 +123,8 @@ export function assertFirstUpdatePass(tView: TView, errMessage?: string) { export function assertDirectiveDef(obj: any): asserts obj is DirectiveDef { if (obj.type === undefined || obj.selectors == undefined || obj.inputs === undefined) { throwError( - `Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`); + `Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`, + ); } } @@ -130,15 +146,17 @@ export function assertBetween(lower: number, upper: number, index: number) { export function assertProjectionSlots(lView: LView, errMessage?: string) { assertDefined(lView[DECLARATION_COMPONENT_VIEW], 'Component views should exist.'); assertDefined( - lView[DECLARATION_COMPONENT_VIEW][T_HOST]!.projection, - errMessage || - 'Components with projection nodes () must have projection slots defined.'); + lView[DECLARATION_COMPONENT_VIEW][T_HOST]!.projection, + errMessage || + 'Components with projection nodes () must have projection slots defined.', + ); } -export function assertParentView(lView: LView|null, errMessage?: string) { +export function assertParentView(lView: LView | null, errMessage?: string) { assertDefined( - lView, - errMessage || 'Component views should always have a parent view (component\'s host view)'); + lView, + errMessage || "Component views should always have a parent view (component's host view)", + ); } export function assertNoDuplicateDirectives(directives: DirectiveDef[]): void { @@ -152,9 +170,10 @@ export function assertNoDuplicateDirectives(directives: DirectiveDef[]) for (const current of directives) { if (seenDirectives.has(current)) { throw new RuntimeError( - RuntimeErrorCode.DUPLICATE_DIRECTIVE, - `Directive ${current.type.name} matches multiple times on the same element. ` + - `Directives can only match an element once.`); + RuntimeErrorCode.DUPLICATE_DIRECTIVE, + `Directive ${current.type.name} matches multiple times on the same element. ` + + `Directives can only match an element once.`, + ); } seenDirectives.add(current); } @@ -179,6 +198,7 @@ export function assertNodeInjector(lView: LView, injectorIndex: number) { assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter'); assertNumber( - lView[injectorIndex + NodeInjectorOffset.PARENT], - 'injectorIndex should point to parent injector'); + lView[injectorIndex + NodeInjectorOffset.PARENT], + 'injectorIndex should point to parent injector', + ); } diff --git a/packages/core/src/render3/bindings.ts b/packages/core/src/render3/bindings.ts index 719f2a2131073..9fe63c1f30c73 100644 --- a/packages/core/src/render3/bindings.ts +++ b/packages/core/src/render3/bindings.ts @@ -14,19 +14,17 @@ import {LView} from './interfaces/view'; import {isInCheckNoChangesMode} from './state'; import {NO_CHANGE} from './tokens'; - // TODO(misko): consider inlining /** Updates binding and returns the value. */ export function updateBinding(lView: LView, bindingIndex: number, value: any): any { - return lView[bindingIndex] = value; + return (lView[bindingIndex] = value); } - /** Gets the current binding value. */ export function getBinding(lView: LView, bindingIndex: number): any { ngDevMode && assertIndexInRange(lView, bindingIndex); ngDevMode && - assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.'); + assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.'); return lView[bindingIndex]; } @@ -46,7 +44,7 @@ export function getBinding(lView: LView, bindingIndex: number): any { export function bindingUpdated(lView: LView, bindingIndex: number, value: any): boolean { ngDevMode && assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.'); ngDevMode && - assertLessThan(bindingIndex, lView.length, `Slot should have been initialized to NO_CHANGE`); + assertLessThan(bindingIndex, lView.length, `Slot should have been initialized to NO_CHANGE`); const oldValue = lView[bindingIndex]; if (Object.is(oldValue, value)) { @@ -57,10 +55,19 @@ export function bindingUpdated(lView: LView, bindingIndex: number, value: any): // (before the change detection was run). const oldValueToCompare = oldValue !== NO_CHANGE ? oldValue : undefined; if (!devModeEqual(oldValueToCompare, value)) { - const details = - getExpressionChangedErrorDetails(lView, bindingIndex, oldValueToCompare, value); + const details = getExpressionChangedErrorDetails( + lView, + bindingIndex, + oldValueToCompare, + value, + ); throwErrorIfNoChangesMode( - oldValue === NO_CHANGE, details.oldValue, details.newValue, details.propName, lView); + oldValue === NO_CHANGE, + details.oldValue, + details.newValue, + details.propName, + lView, + ); } // There was a change, but the `devModeEqual` decided that the change is exempt from an error. // For this reason we exit as if no change. The early exit is needed to prevent the changed @@ -81,14 +88,25 @@ export function bindingUpdated2(lView: LView, bindingIndex: number, exp1: any, e /** Updates 3 bindings if changed, then returns whether any was updated. */ export function bindingUpdated3( - lView: LView, bindingIndex: number, exp1: any, exp2: any, exp3: any): boolean { + lView: LView, + bindingIndex: number, + exp1: any, + exp2: any, + exp3: any, +): boolean { const different = bindingUpdated2(lView, bindingIndex, exp1, exp2); return bindingUpdated(lView, bindingIndex + 2, exp3) || different; } /** Updates 4 bindings if changed, then returns whether any was updated. */ export function bindingUpdated4( - lView: LView, bindingIndex: number, exp1: any, exp2: any, exp3: any, exp4: any): boolean { + lView: LView, + bindingIndex: number, + exp1: any, + exp2: any, + exp3: any, + exp4: any, +): boolean { const different = bindingUpdated2(lView, bindingIndex, exp1, exp2); return bindingUpdated2(lView, bindingIndex + 2, exp3, exp4) || different; } diff --git a/packages/core/src/render3/collect_native_nodes.ts b/packages/core/src/render3/collect_native_nodes.ts index 7a264ea2b1de8..8e5ee0e2be50d 100644 --- a/packages/core/src/render3/collect_native_nodes.ts +++ b/packages/core/src/render3/collect_native_nodes.ts @@ -17,15 +17,19 @@ import {assertTNodeType} from './node_assert'; import {getProjectionNodes} from './node_manipulation'; import {getLViewParent, unwrapRNode} from './util/view_utils'; - export function collectNativeNodes( - tView: TView, lView: LView, tNode: TNode|null, result: any[], - isProjection: boolean = false): any[] { + tView: TView, + lView: LView, + tNode: TNode | null, + result: any[], + isProjection: boolean = false, +): any[] { while (tNode !== null) { ngDevMode && - assertTNodeType( - tNode, - TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu); + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu, + ); const lNode = lView[tNode.index]; if (lNode !== null) { @@ -44,8 +48,8 @@ export function collectNativeNodes( collectNativeNodes(tView, lView, tNode.child, result); } else if (tNodeType & TNodeType.Icu) { const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); - let rNode: RNode|null; - while (rNode = nextRNode()) { + let rNode: RNode | null; + while ((rNode = nextRNode())) { result.push(rNode); } } else if (tNodeType & TNodeType.Projection) { diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index ae5e3429a026a..583cfc9b92f1b 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -74,18 +74,25 @@ import {assertComponentDef} from './errors'; * * @publicApi */ -export function createComponent(component: Type, options: { - environmentInjector: EnvironmentInjector, - hostElement?: Element, - elementInjector?: Injector, - projectableNodes?: Node[][], -}): ComponentRef { +export function createComponent( + component: Type, + options: { + environmentInjector: EnvironmentInjector; + hostElement?: Element; + elementInjector?: Injector; + projectableNodes?: Node[][]; + }, +): ComponentRef { ngDevMode && assertComponentDef(component); const componentDef = getComponentDef(component)!; const elementInjector = options.elementInjector || getNullInjector(); const factory = new ComponentFactory(componentDef); return factory.create( - elementInjector, options.projectableNodes, options.hostElement, options.environmentInjector); + elementInjector, + options.projectableNodes, + options.hostElement, + options.environmentInjector, + ); } /** @@ -107,14 +114,14 @@ export interface ComponentMirror { * The inputs of the component. */ get inputs(): ReadonlyArray<{ - readonly propName: string, - readonly templateName: string, - readonly transform?: (value: any) => any, + readonly propName: string; + readonly templateName: string; + readonly transform?: (value: any) => any; }>; /** * The outputs of the component. */ - get outputs(): ReadonlyArray<{readonly propName: string, readonly templateName: string}>; + get outputs(): ReadonlyArray<{readonly propName: string; readonly templateName: string}>; /** * Selector for all elements in the component. */ @@ -170,7 +177,7 @@ export interface ComponentMirror { * * @publicApi */ -export function reflectComponentType(component: Type): ComponentMirror|null { +export function reflectComponentType(component: Type): ComponentMirror | null { const componentDef = getComponentDef(component); if (!componentDef) return null; @@ -183,13 +190,13 @@ export function reflectComponentType(component: Type): ComponentMirror| return factory.componentType; }, get inputs(): ReadonlyArray<{ - propName: string, - templateName: string, - transform?: (value: any) => any, + propName: string; + templateName: string; + transform?: (value: any) => any; }> { return factory.inputs; }, - get outputs(): ReadonlyArray<{propName: string, templateName: string}> { + get outputs(): ReadonlyArray<{propName: string; templateName: string}> { return factory.outputs; }, get ngContentSelectors(): ReadonlyArray { diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 933cc8c0c023f..ace80e5e02303 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -9,7 +9,10 @@ import {setActiveConsumer} from '@angular/core/primitives/signals'; import {ChangeDetectorRef} from '../change_detection/change_detector_ref'; -import {ChangeDetectionScheduler, NotificationSource} from '../change_detection/scheduling/zoneless_scheduling'; +import { + ChangeDetectionScheduler, + NotificationSource, +} from '../change_detection/scheduling/zoneless_scheduling'; import {Injector} from '../di/injector'; import {convertToBitFlags} from '../di/injector_compatibility'; import {InjectFlags, InjectOptions} from '../di/interface/injector'; @@ -19,7 +22,10 @@ import {RuntimeError, RuntimeErrorCode} from '../errors'; import {DehydratedView} from '../hydration/interfaces'; import {retrieveHydrationInfo} from '../hydration/utils'; import {Type} from '../interface/type'; -import {ComponentFactory as AbstractComponentFactory, ComponentRef as AbstractComponentRef} from '../linker/component_factory'; +import { + ComponentFactory as AbstractComponentFactory, + ComponentRef as AbstractComponentRef, +} from '../linker/component_factory'; import {ComponentFactoryResolver as AbstractComponentFactoryResolver} from '../linker/component_factory_resolver'; import {createElementRef, ElementRef} from '../linker/element_ref'; import {NgModuleRef} from '../linker/ng_module_factory'; @@ -38,16 +44,47 @@ import {registerPostOrderHooks} from './hooks'; import {reportUnknownPropertyError} from './instructions/element_validation'; import {markViewDirty} from './instructions/mark_view_dirty'; import {renderView} from './instructions/render'; -import {addToViewTree, createLView, createTView, executeContentQueries, getOrCreateComponentTView, getOrCreateTNode, initializeDirectives, invokeDirectivesHostBindings, locateHostElement, markAsComponentHost, setInputsForProperty} from './instructions/shared'; +import { + addToViewTree, + createLView, + createTView, + executeContentQueries, + getOrCreateComponentTView, + getOrCreateTNode, + initializeDirectives, + invokeDirectivesHostBindings, + locateHostElement, + markAsComponentHost, + setInputsForProperty, +} from './instructions/shared'; import {ComponentDef, DirectiveDef, HostDirectiveDefs} from './interfaces/definition'; import {InputFlags} from './interfaces/input_flags'; -import {NodeInputBindings, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node'; +import { + NodeInputBindings, + TContainerNode, + TElementContainerNode, + TElementNode, + TNode, + TNodeType, +} from './interfaces/node'; import {Renderer} from './interfaces/renderer'; import {RElement, RNode} from './interfaces/renderer_dom'; -import {CONTEXT, HEADER_OFFSET, INJECTOR, LView, LViewEnvironment, LViewFlags, TVIEW, TViewType} from './interfaces/view'; +import { + CONTEXT, + HEADER_OFFSET, + INJECTOR, + LView, + LViewEnvironment, + LViewFlags, + TVIEW, + TViewType, +} from './interfaces/view'; import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; import {createElementNode, setupStaticAttributes, writeDirectClass} from './node_manipulation'; -import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher'; +import { + extractAttrsAndClassesFromSelector, + stringifyCSSSelectorList, +} from './node_selector_matcher'; import {enterView, getCurrentTNode, getLView, leaveView} from './state'; import {computeStaticStyling} from './styling/static_styling'; import {mergeHostAttrs, setUpAttributes} from './util/attrs_utils'; @@ -70,9 +107,10 @@ export class ComponentFactoryResolver extends AbstractComponentFactoryResolver { } } -function toRefArray(map: {[P in keyof T]?: string|[minifiedName: string, flags: InputFlags]}): - {propName: string; templateName: string}[] { - const array: {propName: string; templateName: string;}[] = []; +function toRefArray(map: { + [P in keyof T]?: string | [minifiedName: string, flags: InputFlags]; +}): {propName: string; templateName: string}[] { + const array: {propName: string; templateName: string}[] = []; for (const publicName in map) { if (!map.hasOwnProperty(publicName)) { continue; @@ -91,9 +129,9 @@ function toRefArray(map: {[P in keyof T]?: string|[minifiedName: string, flag return array; } -function getNamespace(elementName: string): string|null { +function getNamespace(elementName: string): string | null { const name = elementName.toLowerCase(); - return name === 'svg' ? SVG_NAMESPACE : (name === 'math' ? MATH_ML_NAMESPACE : null); + return name === 'svg' ? SVG_NAMESPACE : name === 'math' ? MATH_ML_NAMESPACE : null; } /** @@ -101,15 +139,23 @@ function getNamespace(elementName: string): string|null { * injector. Used primarily when creating components or embedded views dynamically. */ export class ChainedInjector implements Injector { - constructor(public injector: Injector, public parentInjector: Injector) {} + constructor( + public injector: Injector, + public parentInjector: Injector, + ) {} - get(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags|InjectOptions): T { + get(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags | InjectOptions): T { flags = convertToBitFlags(flags); - const value = this.injector.get( - token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, flags); + const value = this.injector.get( + token, + NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, + flags, + ); - if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR || - notFoundValue === (NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as unknown as T)) { + if ( + value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR || + notFoundValue === (NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as unknown as T) + ) { // Return the value from the root element injector when // - it provides it // (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) @@ -132,16 +178,16 @@ export class ComponentFactory extends AbstractComponentFactory { isBoundToModule: boolean; override get inputs(): { - propName: string, - templateName: string, - transform?: (value: any) => any, + propName: string; + templateName: string; + transform?: (value: any) => any; }[] { const componentDef = this.componentDef; const inputTransforms = componentDef.inputTransforms; const refArray = toRefArray(componentDef.inputs) as { - propName: string, - templateName: string, - transform?: (value: any) => any, + propName: string; + templateName: string; + transform?: (value: any) => any; }[]; if (inputTransforms !== null) { @@ -155,7 +201,7 @@ export class ComponentFactory extends AbstractComponentFactory { return refArray; } - override get outputs(): {propName: string; templateName: string;}[] { + override get outputs(): {propName: string; templateName: string}[] { return toRefArray(this.componentDef.outputs); } @@ -163,57 +209,69 @@ export class ComponentFactory extends AbstractComponentFactory { * @param componentDef The component definition. * @param ngModule The NgModuleRef to which the factory is bound. */ - constructor(private componentDef: ComponentDef, private ngModule?: NgModuleRef) { + constructor( + private componentDef: ComponentDef, + private ngModule?: NgModuleRef, + ) { super(); this.componentType = componentDef.type; this.selector = stringifyCSSSelectorList(componentDef.selectors); - this.ngContentSelectors = - componentDef.ngContentSelectors ? componentDef.ngContentSelectors : []; + this.ngContentSelectors = componentDef.ngContentSelectors + ? componentDef.ngContentSelectors + : []; this.isBoundToModule = !!ngModule; } override create( - injector: Injector, projectableNodes?: any[][]|undefined, rootSelectorOrNode?: any, - environmentInjector?: NgModuleRef|EnvironmentInjector| - undefined): AbstractComponentRef { + injector: Injector, + projectableNodes?: any[][] | undefined, + rootSelectorOrNode?: any, + environmentInjector?: NgModuleRef | EnvironmentInjector | undefined, + ): AbstractComponentRef { const prevConsumer = setActiveConsumer(null); try { // Check if the component is orphan - if (ngDevMode && (typeof ngJitMode === 'undefined' || ngJitMode) && - this.componentDef.debugInfo?.forbidOrphanRendering) { + if ( + ngDevMode && + (typeof ngJitMode === 'undefined' || ngJitMode) && + this.componentDef.debugInfo?.forbidOrphanRendering + ) { if (depsTracker.isOrphanComponent(this.componentType)) { throw new RuntimeError( - RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT, - `Orphan component found! Trying to render the component ${ - debugStringifyTypeForError( - this.componentType)} without first loading the NgModule that declares it. It is recommended to make this component standalone in order to avoid this error. If this is not possible now, import the component's NgModule in the appropriate NgModule, or the standalone component in which you are trying to render this component. If this is a lazy import, load the NgModule lazily as well and use its module injector.`); + RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT, + `Orphan component found! Trying to render the component ${debugStringifyTypeForError( + this.componentType, + )} without first loading the NgModule that declares it. It is recommended to make this component standalone in order to avoid this error. If this is not possible now, import the component's NgModule in the appropriate NgModule, or the standalone component in which you are trying to render this component. If this is a lazy import, load the NgModule lazily as well and use its module injector.`, + ); } } environmentInjector = environmentInjector || this.ngModule; - let realEnvironmentInjector = environmentInjector instanceof EnvironmentInjector ? - environmentInjector : - environmentInjector?.injector; + let realEnvironmentInjector = + environmentInjector instanceof EnvironmentInjector + ? environmentInjector + : environmentInjector?.injector; if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) { realEnvironmentInjector = - this.componentDef.getStandaloneInjector(realEnvironmentInjector) || - realEnvironmentInjector; + this.componentDef.getStandaloneInjector(realEnvironmentInjector) || + realEnvironmentInjector; } - const rootViewInjector = realEnvironmentInjector ? - new ChainedInjector(injector, realEnvironmentInjector) : - injector; + const rootViewInjector = realEnvironmentInjector + ? new ChainedInjector(injector, realEnvironmentInjector) + : injector; const rendererFactory = rootViewInjector.get(RendererFactory2, null); if (rendererFactory === null) { throw new RuntimeError( - RuntimeErrorCode.RENDERER_NOT_FOUND, - ngDevMode && - 'Angular was not able to inject a renderer (RendererFactory2). ' + - 'Likely this is due to a broken DI hierarchy. ' + - 'Make sure that any injector used to create this component has a correct parent.'); + RuntimeErrorCode.RENDERER_NOT_FOUND, + ngDevMode && + 'Angular was not able to inject a renderer (RendererFactory2). ' + + 'Likely this is due to a broken DI hierarchy. ' + + 'Make sure that any injector used to create this component has a correct parent.', + ); } const sanitizer = rootViewInjector.get(Sanitizer, null); @@ -233,11 +291,15 @@ export class ComponentFactory extends AbstractComponentFactory { // Determine a tag name used for creating host elements when this component is created // dynamically. Default to 'div' if this component did not specify any tag name in its // selector. - const elementName = this.componentDef.selectors[0][0] as string || 'div'; - const hostRNode = rootSelectorOrNode ? - locateHostElement( - hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation, rootViewInjector) : - createElementNode(hostRenderer, elementName, getNamespace(elementName)); + const elementName = (this.componentDef.selectors[0][0] as string) || 'div'; + const hostRNode = rootSelectorOrNode + ? locateHostElement( + hostRenderer, + rootSelectorOrNode, + this.componentDef.encapsulation, + rootViewInjector, + ) + : createElementNode(hostRenderer, elementName, getNamespace(elementName)); let rootFlags = LViewFlags.IsRoot; if (this.componentDef.signals) { @@ -246,17 +308,38 @@ export class ComponentFactory extends AbstractComponentFactory { rootFlags |= LViewFlags.CheckAlways; } - let hydrationInfo: DehydratedView|null = null; + let hydrationInfo: DehydratedView | null = null; if (hostRNode !== null) { hydrationInfo = retrieveHydrationInfo(hostRNode, rootViewInjector, true /* isRootView */); } // Create the root view. Uses empty TView and ContentTemplate. - const rootTView = - createTView(TViewType.Root, null, null, 1, 0, null, null, null, null, null, null); + const rootTView = createTView( + TViewType.Root, + null, + null, + 1, + 0, + null, + null, + null, + null, + null, + null, + ); const rootLView = createLView( - null, rootTView, null, rootFlags, null, null, environment, hostRenderer, rootViewInjector, - null, hydrationInfo); + null, + rootTView, + null, + rootFlags, + null, + null, + environment, + hostRenderer, + rootViewInjector, + null, + hydrationInfo, + ); // rootView is the parent when bootstrapping // TODO(misko): it looks like we are entering view here but we don't really need to as @@ -271,13 +354,16 @@ export class ComponentFactory extends AbstractComponentFactory { try { const rootComponentDef = this.componentDef; let rootDirectives: DirectiveDef[]; - let hostDirectiveDefs: HostDirectiveDefs|null = null; + let hostDirectiveDefs: HostDirectiveDefs | null = null; if (rootComponentDef.findHostDirectiveDefs) { rootDirectives = []; hostDirectiveDefs = new Map(); rootComponentDef.findHostDirectiveDefs( - rootComponentDef, rootDirectives, hostDirectiveDefs); + rootComponentDef, + rootDirectives, + hostDirectiveDefs, + ); rootDirectives.push(rootComponentDef); ngDevMode && assertNoDuplicateDirectives(rootDirectives); } else { @@ -286,8 +372,14 @@ export class ComponentFactory extends AbstractComponentFactory { const hostTNode = createRootComponentTNode(rootLView, hostRNode); const componentView = createRootComponentView( - hostTNode, hostRNode, rootComponentDef, rootDirectives, rootLView, environment, - hostRenderer); + hostTNode, + hostRNode, + rootComponentDef, + rootDirectives, + rootLView, + environment, + hostRenderer, + ); tElementNode = getTNode(rootTView, HEADER_OFFSET) as TElementNode; @@ -305,16 +397,25 @@ export class ComponentFactory extends AbstractComponentFactory { // TODO: should LifecycleHooksFeature and other host features be generated by the compiler // and executed here? Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref component = createRootComponent( - componentView, rootComponentDef, rootDirectives, hostDirectiveDefs, rootLView, - [LifecycleHooksFeature]); + componentView, + rootComponentDef, + rootDirectives, + hostDirectiveDefs, + rootLView, + [LifecycleHooksFeature], + ); renderView(rootTView, rootLView, null); } finally { leaveView(); } return new ComponentRef( - this.componentType, component, createElementRef(tElementNode, rootLView), rootLView, - tElementNode); + this.componentType, + component, + createElementRef(tElementNode, rootLView), + rootLView, + tElementNode, + ); } finally { setActiveConsumer(prevConsumer); } @@ -334,30 +435,36 @@ export class ComponentRef extends AbstractComponentRef { override hostView: ViewRef; override changeDetectorRef: ChangeDetectorRef; override componentType: Type; - private previousInputValues: Map|null = null; + private previousInputValues: Map | null = null; constructor( - componentType: Type, instance: T, public location: ElementRef, private _rootLView: LView, - private _tNode: TElementNode|TContainerNode|TElementContainerNode) { + componentType: Type, + instance: T, + public location: ElementRef, + private _rootLView: LView, + private _tNode: TElementNode | TContainerNode | TElementContainerNode, + ) { super(); this.instance = instance; this.hostView = this.changeDetectorRef = new ViewRef( - _rootLView, - undefined, /* _cdRefInjectingView */ - false, /* notifyErrorHandler */ + _rootLView, + undefined /* _cdRefInjectingView */, + false /* notifyErrorHandler */, ); this.componentType = componentType; } override setInput(name: string, value: unknown): void { const inputData = this._tNode.inputs; - let dataValue: NodeInputBindings[typeof name]|undefined; + let dataValue: NodeInputBindings[typeof name] | undefined; if (inputData !== null && (dataValue = inputData[name])) { this.previousInputValues ??= new Map(); // Do not set the input if it is the same as the last value // This behavior matches `bindingUpdated` when binding inputs in templates. - if (this.previousInputValues.has(name) && - Object.is(this.previousInputValues.get(name), value)) { + if ( + this.previousInputValues.has(name) && + Object.is(this.previousInputValues.get(name), value) + ) { return; } @@ -369,10 +476,8 @@ export class ComponentRef extends AbstractComponentRef { } else { if (ngDevMode) { const cmpNameForError = stringifyForError(this.componentType); - let message = - `Can't set value of the '${name}' input on the '${cmpNameForError}' component. `; - message += `Make sure that the '${ - name}' property is annotated with @Input() or a mapped @Input('${name}') exists.`; + let message = `Can't set value of the '${name}' input on the '${cmpNameForError}' component. `; + message += `Make sure that the '${name}' property is annotated with @Input() or a mapped @Input('${name}') exists.`; reportUnknownPropertyError(message); } } @@ -392,7 +497,7 @@ export class ComponentRef extends AbstractComponentRef { } /** Represents a HostFeature function. */ -type HostFeature = ((component: T, componentDef: ComponentDef) => void); +type HostFeature = (component: T, componentDef: ComponentDef) => void; /** Creates a TNode that can be used to instantiate a root component. */ function createRootComponentTNode(lView: LView, rNode: RNode): TElementNode { @@ -420,15 +525,20 @@ function createRootComponentTNode(lView: LView, rNode: RNode): TElementNode { * @returns Component view created */ function createRootComponentView( - tNode: TElementNode, hostRNode: RElement|null, rootComponentDef: ComponentDef, - rootDirectives: DirectiveDef[], rootView: LView, environment: LViewEnvironment, - hostRenderer: Renderer): LView { + tNode: TElementNode, + hostRNode: RElement | null, + rootComponentDef: ComponentDef, + rootDirectives: DirectiveDef[], + rootView: LView, + environment: LViewEnvironment, + hostRenderer: Renderer, +): LView { const tView = rootView[TVIEW]; applyRootComponentStyling(rootDirectives, tNode, hostRNode, hostRenderer); // Hydration info is on the host element and needs to be retrieved // and passed to the component LView. - let hydrationInfo: DehydratedView|null = null; + let hydrationInfo: DehydratedView | null = null; if (hostRNode !== null) { hydrationInfo = retrieveHydrationInfo(hostRNode, rootView[INJECTOR]!); } @@ -440,8 +550,18 @@ function createRootComponentView( lViewFlags = LViewFlags.Dirty; } const componentView = createLView( - rootView, getOrCreateComponentTView(rootComponentDef), null, lViewFlags, - rootView[tNode.index], tNode, environment, viewRenderer, null, null, hydrationInfo); + rootView, + getOrCreateComponentTView(rootComponentDef), + null, + lViewFlags, + rootView[tNode.index], + tNode, + environment, + viewRenderer, + null, + null, + hydrationInfo, + ); if (tView.firstCreatePass) { markAsComponentHost(tView, tNode, rootDirectives.length - 1); @@ -450,13 +570,16 @@ function createRootComponentView( addToViewTree(rootView, componentView); // Store component view at node index, with node as the HOST - return rootView[tNode.index] = componentView; + return (rootView[tNode.index] = componentView); } /** Sets up the styling information on a root component. */ function applyRootComponentStyling( - rootDirectives: DirectiveDef[], tNode: TElementNode, rNode: RElement|null, - hostRenderer: Renderer): void { + rootDirectives: DirectiveDef[], + tNode: TElementNode, + rNode: RElement | null, + hostRenderer: Renderer, +): void { for (const def of rootDirectives) { tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs); } @@ -475,9 +598,13 @@ function applyRootComponentStyling( * renderComponent() and ViewContainerRef.createComponent(). */ function createRootComponent( - componentView: LView, rootComponentDef: ComponentDef, rootDirectives: DirectiveDef[], - hostDirectiveDefs: HostDirectiveDefs|null, rootLView: LView, - hostFeatures: HostFeature[]|null): any { + componentView: LView, + rootComponentDef: ComponentDef, + rootDirectives: DirectiveDef[], + hostDirectiveDefs: HostDirectiveDefs | null, + rootLView: LView, + hostFeatures: HostFeature[] | null, +): any { const rootTNode = getCurrentTNode() as TElementNode; ngDevMode && assertDefined(rootTNode, 'tNode should have been already created'); const tView = rootLView[TVIEW]; @@ -500,9 +627,13 @@ function createRootComponent( // We're guaranteed for the `componentOffset` to be positive here // since a root component always matches a component def. ngDevMode && - assertGreaterThan(rootTNode.componentOffset, -1, 'componentOffset must be great than -1'); + assertGreaterThan(rootTNode.componentOffset, -1, 'componentOffset must be great than -1'); const component = getNodeInjectable( - rootLView, tView, rootTNode.directiveStart + rootTNode.componentOffset, rootTNode); + rootLView, + tView, + rootTNode.directiveStart + rootTNode.componentOffset, + rootTNode, + ); componentView[CONTEXT] = rootLView[CONTEXT] = component; if (hostFeatures !== null) { @@ -520,8 +651,11 @@ function createRootComponent( /** Sets the static attributes on a root component. */ function setRootNodeAttributes( - hostRenderer: Renderer2, componentDef: ComponentDef, hostRNode: RElement, - rootSelectorOrNode: any) { + hostRenderer: Renderer2, + componentDef: ComponentDef, + hostRNode: RElement, + rootSelectorOrNode: any, +) { if (rootSelectorOrNode) { // The placeholder will be replaced with the actual version at build time. setUpAttributes(hostRenderer, hostRNode, ['ng-version', '0.0.0-PLACEHOLDER']); @@ -541,8 +675,11 @@ function setRootNodeAttributes( /** Projects the `projectableNodes` that were specified when creating a root component. */ function projectNodes( - tNode: TElementNode, ngContentSelectors: string[], projectableNodes: any[][]) { - const projection: (TNode|RNode[]|null)[] = tNode.projection = []; + tNode: TElementNode, + ngContentSelectors: string[], + projectableNodes: any[][], +) { + const projection: (TNode | RNode[] | null)[] = (tNode.projection = []); for (let i = 0; i < ngContentSelectors.length; i++) { const nodesforSlot = projectableNodes[i]; // Projectable nodes can be passed as array of arrays or an array of iterables (ngUpgrade diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index 58c9d1a7b29c5..60306a76e23fa 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -19,8 +19,6 @@ import {isLView} from './interfaces/type_checks'; import {CONTEXT, HEADER_OFFSET, HOST, ID, LView, TVIEW} from './interfaces/view'; import {getComponentLViewByIndex, unwrapRNode} from './util/view_utils'; - - /** * Returns the matching `LContext` data for a given DOM node, directive or component instance. * @@ -41,7 +39,7 @@ import {getComponentLViewByIndex, unwrapRNode} from './util/view_utils'; * * @param target Component, Directive or DOM Node. */ -export function getLContext(target: any): LContext|null { +export function getLContext(target: any): LContext | null { let mpValue = readPatchedData(target); if (mpValue) { // only when it's an array is it considered an LView instance @@ -50,7 +48,7 @@ export function getLContext(target: any): LContext|null { const lView: LView = mpValue!; let nodeIndex: number; let component: any = undefined; - let directives: any[]|null|undefined = undefined; + let directives: any[] | null | undefined = undefined; if (isComponentInstance(target)) { nodeIndex = findViaComponent(lView, target); @@ -77,9 +75,10 @@ export function getLContext(target: any): LContext|null { // with different target values then the missing target data will be filled in. const native = unwrapRNode(lView[nodeIndex]); const existingCtx = readPatchedData(native); - const context: LContext = (existingCtx && !Array.isArray(existingCtx)) ? - existingCtx : - createLContext(lView, nodeIndex, native); + const context: LContext = + existingCtx && !Array.isArray(existingCtx) + ? existingCtx + : createLContext(lView, nodeIndex, native); // only when the component has been discovered then update the monkey-patch if (component && context.component === undefined) { @@ -105,10 +104,10 @@ export function getLContext(target: any): LContext|null { // if the context is not found then we need to traverse upwards up the DOM // to find the nearest element that has already been monkey patched with data let parent = rElement as any; - while (parent = parent.parentNode) { + while ((parent = parent.parentNode)) { const parentContext = readPatchedData(parent); if (parentContext) { - const lView = Array.isArray(parentContext) ? parentContext as LView : parentContext.lView; + const lView = Array.isArray(parentContext) ? (parentContext as LView) : parentContext.lView; // the edge of the app was also reached here through another means // (maybe because the DOM was changed manually). @@ -177,7 +176,7 @@ export function attachLViewId(target: any, data: LView) { * Returns the monkey-patch value data present on the target (which could be * a component, directive or a DOM node). */ -export function readLView(target: any): LView|null { +export function readLView(target: any): LView | null { const data = readPatchedData(target); if (isLView(data)) { return data; @@ -189,7 +188,7 @@ export function readLView(target: any): LView|null { * Assigns the given data to the given target (which could be a component, * directive or DOM node instance) using monkey-patching. */ -export function attachPatchData(target: any, data: LView|LContext) { +export function attachPatchData(target: any, data: LView | LContext) { ngDevMode && assertDefined(target, 'Target expected'); // Only attach the ID of the view in order to avoid memory leaks (see #41047). We only do this // for `LView`, because we have control over when an `LView` is created and destroyed, whereas @@ -206,13 +205,13 @@ export function attachPatchData(target: any, data: LView|LContext) { * Returns the monkey-patch value data present on the target (which could be * a component, directive or a DOM node). */ -export function readPatchedData(target: any): LView|LContext|null { +export function readPatchedData(target: any): LView | LContext | null { ngDevMode && assertDefined(target, 'Target expected'); const data = target[MONKEY_PATCH_KEY_NAME]; - return (typeof data === 'number') ? getLViewById(data) : data || null; + return typeof data === 'number' ? getLViewById(data) : data || null; } -export function readPatchedLView(target: any): LView|null { +export function readPatchedLView(target: any): LView | null { const value = readPatchedData(target); if (value) { return (isLView(value) ? value : value.lView) as LView; @@ -245,7 +244,7 @@ function findViaNativeElement(lView: LView, target: RElement): number { /** * Locates the next tNode (child, sibling or parent). */ -function traverseNextElement(tNode: TNode): TNode|null { +function traverseNextElement(tNode: TNode): TNode | null { if (tNode.child) { return tNode.child; } else if (tNode.next) { @@ -317,7 +316,7 @@ function findViaDirective(lView: LView, directiveInstance: {}): number { * @param nodeIndex The node index * @param lView The target view data */ -export function getDirectivesAtNodeIndex(nodeIndex: number, lView: LView): any[]|null { +export function getDirectivesAtNodeIndex(nodeIndex: number, lView: LView): any[] | null { const tNode = lView[TVIEW].data[nodeIndex] as TNode; if (tNode.directiveStart === 0) return EMPTY_ARRAY; const results: any[] = []; @@ -330,7 +329,7 @@ export function getDirectivesAtNodeIndex(nodeIndex: number, lView: LView): any[] return results; } -export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|null { +export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {} | null { const tNode = lView[TVIEW].data[nodeIndex] as TNode; const {directiveStart, componentOffset} = tNode; return componentOffset > -1 ? lView[directiveStart + componentOffset] : null; @@ -340,7 +339,7 @@ export function getComponentAtNodeIndex(nodeIndex: number, lView: LView): {}|nul * Returns a map of local references (local reference name => element or directive instance) that * exist on a given element. */ -export function discoverLocalRefs(lView: LView, nodeIndex: number): {[key: string]: any}|null { +export function discoverLocalRefs(lView: LView, nodeIndex: number): {[key: string]: any} | null { const tNode = lView[TVIEW].data[nodeIndex] as TNode; if (tNode && tNode.localNames) { const result: {[key: string]: any} = {}; diff --git a/packages/core/src/render3/debug/framework_injector_profiler.ts b/packages/core/src/render3/debug/framework_injector_profiler.ts index d5253741bf8cb..8efdfe247cd65 100644 --- a/packages/core/src/render3/debug/framework_injector_profiler.ts +++ b/packages/core/src/render3/debug/framework_injector_profiler.ts @@ -16,7 +16,15 @@ import {getNodeInjectorLView, getNodeInjectorTNode, NodeInjector} from '../di'; import {TNode} from '../interfaces/node'; import {LView} from '../interfaces/view'; -import {InjectedService, InjectorCreatedInstance, InjectorProfilerContext, InjectorProfilerEvent, InjectorProfilerEventType, ProviderRecord, setInjectorProfiler} from './injector_profiler'; +import { + InjectedService, + InjectorCreatedInstance, + InjectorProfilerContext, + InjectorProfilerEvent, + InjectorProfilerEventType, + ProviderRecord, + setInjectorProfiler, +} from './injector_profiler'; /** * These are the data structures that our framework injector profiler will fill with data in order @@ -54,15 +62,19 @@ import {InjectedService, InjectorCreatedInstance, InjectorProfilerContext, Injec * */ class DIDebugData { - resolverToTokenToDependencies = - new WeakMap, InjectedService[]>>(); - resolverToProviders = new WeakMap(); + resolverToTokenToDependencies = new WeakMap< + Injector | LView, + WeakMap, InjectedService[]> + >(); + resolverToProviders = new WeakMap(); standaloneInjectorToComponent = new WeakMap>(); reset() { - this.resolverToTokenToDependencies = - new WeakMap, InjectedService[]>>(); - this.resolverToProviders = new WeakMap(); + this.resolverToTokenToDependencies = new WeakMap< + Injector | LView, + WeakMap, InjectedService[]> + >(); + this.resolverToProviders = new WeakMap(); this.standaloneInjectorToComponent = new WeakMap>(); } } @@ -87,8 +99,9 @@ export function getFrameworkDIDebugData(): DIDebugData { */ export function setupFrameworkInjectorProfiler(): void { frameworkDIDebugData.reset(); - setInjectorProfiler( - (injectorProfilerEvent) => handleInjectorProfilerEvent(injectorProfilerEvent)); + setInjectorProfiler((injectorProfilerEvent) => + handleInjectorProfilerEvent(injectorProfilerEvent), + ); } function handleInjectorProfilerEvent(injectorProfilerEvent: InjectorProfilerEvent): void { @@ -157,7 +170,7 @@ function handleInjectEvent(context: InjectorProfilerContext, data: InjectedServi * @param injector * @returns {lView: LView, tNode: TNode}|undefined */ -function getNodeInjectorContext(injector: Injector): {lView: LView, tNode: TNode}|undefined { +function getNodeInjectorContext(injector: Injector): {lView: LView; tNode: TNode} | undefined { if (!(injector instanceof NodeInjector)) { throwError('getNodeInjectorContext must be called with a NodeInjector'); } @@ -183,7 +196,9 @@ function getNodeInjectorContext(injector: Injector): {lView: LView, tNode: TNode * */ function handleInstanceCreatedByInjectorEvent( - context: InjectorProfilerContext, data: InjectorCreatedInstance): void { + context: InjectorProfilerContext, + data: InjectorCreatedInstance, +): void { const {value} = data; if (getDIResolver(context.injector) === null) { @@ -192,7 +207,7 @@ function handleInstanceCreatedByInjectorEvent( // if our value is an instance of a standalone component, map the injector of that standalone // component to the component class. Otherwise, this event is a noop. - let standaloneComponent: Type|undefined = undefined; + let standaloneComponent: Type | undefined = undefined; if (typeof value === 'object') { standaloneComponent = value?.constructor as Type; } @@ -200,8 +215,11 @@ function handleInstanceCreatedByInjectorEvent( return; } - const environmentInjector: EnvironmentInjector|null = - context.injector.get(EnvironmentInjector, null, {optional: true}); + const environmentInjector: EnvironmentInjector | null = context.injector.get( + EnvironmentInjector, + null, + {optional: true}, + ); // Standalone components should have an environment injector. If one cannot be // found we may be in a test case for low level functionality that did not explicitly // setup this injector. In those cases, we simply ignore this event. @@ -237,10 +255,12 @@ function isStandaloneComponent(value: Type): boolean { * */ function handleProviderConfiguredEvent( - context: InjectorProfilerContext, data: ProviderRecord): void { + context: InjectorProfilerContext, + data: ProviderRecord, +): void { const {resolverToProviders} = frameworkDIDebugData; - let diResolver: Injector|TNode; + let diResolver: Injector | TNode; if (context?.injector instanceof NodeInjector) { diResolver = getNodeInjectorTNode(context.injector) as TNode; } else { @@ -258,8 +278,8 @@ function handleProviderConfiguredEvent( resolverToProviders.get(diResolver)!.push(data); } -function getDIResolver(injector: Injector|undefined): Injector|LView|null { - let diResolver: Injector|LView|null = null; +function getDIResolver(injector: Injector | undefined): Injector | LView | null { + let diResolver: Injector | LView | null = null; if (injector === undefined) { return diResolver; @@ -286,6 +306,8 @@ function getDIResolver(injector: Injector|undefined): Injector|LView|null { // https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-canbeheldweakly function canBeHeldWeakly(value: any): boolean { // we check for value !== null here because typeof null === 'object - return value !== null && - (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol'); + return ( + value !== null && + (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol') + ); } diff --git a/packages/core/src/render3/debug/injector_profiler.ts b/packages/core/src/render3/debug/injector_profiler.ts index d47db92a7349d..410cd30e0a876 100644 --- a/packages/core/src/render3/debug/injector_profiler.ts +++ b/packages/core/src/render3/debug/injector_profiler.ts @@ -34,7 +34,7 @@ export const enum InjectorProfilerEventType { /** * Emits when an injector configures a provider. */ - ProviderConfigured + ProviderConfigured, } /** @@ -53,10 +53,9 @@ export interface InjectorProfilerContext { * - Example: if ModuleA --provides--> ServiceA --injects--> ServiceB * then inject(ServiceB) in ServiceA has ServiceA as a construction context */ - token: Type|null; + token: Type | null; } - export interface InjectedServiceEvent { type: InjectorProfilerEventType.Inject; context: InjectorProfilerContext; @@ -80,7 +79,9 @@ export interface ProviderConfiguredEvent { */ export type InjectorProfilerEvent = - InjectedServiceEvent|InjectorCreatedInstanceEvent|ProviderConfiguredEvent; + | InjectedServiceEvent + | InjectorCreatedInstanceEvent + | ProviderConfiguredEvent; /** * An object that contains information about a provider that has been configured @@ -91,7 +92,7 @@ export interface ProviderRecord { /** * DI token that this provider is configuring */ - token: Type|InjectionToken; + token: Type | InjectionToken; /** * Determines if provider is configured as view provider. @@ -127,7 +128,7 @@ export interface InjectedService { /** * DI token of the Service that is injected */ - token?: Type|InjectionToken; + token?: Type | InjectionToken; /** * Value of the injected service @@ -137,7 +138,7 @@ export interface InjectedService { /** * Flags that this service was injected with */ - flags?: InternalInjectFlags|InjectFlags|InjectOptions; + flags?: InternalInjectFlags | InjectFlags | InjectOptions; /** * Injector that this service was provided in. @@ -147,7 +148,7 @@ export interface InjectedService { /** * In NodeInjectors, the LView and TNode that serviced this injection. */ - injectedIn?: {lView: LView, tNode: TNode}; + injectedIn?: {lView: LView; tNode: TNode}; } export interface InjectorProfiler { @@ -168,7 +169,7 @@ export function setInjectorProfilerContext(context: InjectorProfilerContext) { return previous; } -let injectorProfilerCallback: InjectorProfiler|null = null; +let injectorProfilerCallback: InjectorProfiler | null = null; /** * Sets the callback function which will be invoked during certain DI events within the @@ -180,7 +181,7 @@ let injectorProfilerCallback: InjectorProfiler|null = null; * * @param profiler function provided by the caller or null value to disable profiling. */ -export const setInjectorProfiler = (injectorProfiler: InjectorProfiler|null) => { +export const setInjectorProfiler = (injectorProfiler: InjectorProfiler | null) => { !ngDevMode && throwError('setInjectorProfiler should never be called in production mode'); injectorProfilerCallback = injectorProfiler; }; @@ -205,7 +206,9 @@ function injectorProfiler(event: InjectorProfilerEvent): void { * @param eventProvider A provider object */ export function emitProviderConfiguredEvent( - eventProvider: SingleProvider, isViewProvider: boolean = false): void { + eventProvider: SingleProvider, + isViewProvider: boolean = false, +): void { !ngDevMode && throwError('Injector profiler should never be called in production mode'); let token; @@ -228,13 +231,13 @@ export function emitProviderConfiguredEvent( // as `ɵprov`. In this case, we want to emit the provider that is attached to the token, not the // token itself. if (eventProvider instanceof InjectionToken) { - provider = eventProvider.ɵprov as FactoryProvider || eventProvider; + provider = (eventProvider.ɵprov as FactoryProvider) || eventProvider; } injectorProfiler({ type: InjectorProfilerEventType.ProviderConfigured, context: getInjectorProfilerContext(), - providerRecord: {token, provider, isViewProvider} + providerRecord: {token, provider, isViewProvider}, }); } @@ -250,7 +253,7 @@ export function emitInstanceCreatedByInjectorEvent(instance: unknown): void { injectorProfiler({ type: InjectorProfilerEventType.InstanceCreatedByInjector, context: getInjectorProfilerContext(), - instance: {value: instance} + instance: {value: instance}, }); } @@ -265,14 +268,17 @@ export function emitInjectEvent(token: Type, value: unknown, flags: Inj injectorProfiler({ type: InjectorProfilerEventType.Inject, context: getInjectorProfilerContext(), - service: {token, value, flags} + service: {token, value, flags}, }); } export function runInInjectorProfilerContext( - injector: Injector, token: Type, callback: () => void): void { + injector: Injector, + token: Type, + callback: () => void, +): void { !ngDevMode && - throwError('runInInjectorProfilerContext should never be called in production mode'); + throwError('runInInjectorProfilerContext should never be called in production mode'); const prevInjectContext = setInjectorProfilerContext({injector, token}); try { diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index fae41a9578e9a..2abf25d871eee 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -18,7 +18,22 @@ import {initNgDevMode} from '../util/ng_dev_mode'; import {stringify} from '../util/stringify'; import {NG_COMP_DEF, NG_DIR_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; -import type {ComponentDef, ComponentDefFeature, ComponentTemplate, ContentQueriesFunction, DependencyTypeList, DirectiveDef, DirectiveDefFeature, DirectiveDefListOrFactory, HostBindingsFunction, InputTransformFunction, PipeDef, PipeDefListOrFactory, TypeOrFactory, ViewQueriesFunction} from './interfaces/definition'; +import type { + ComponentDef, + ComponentDefFeature, + ComponentTemplate, + ContentQueriesFunction, + DependencyTypeList, + DirectiveDef, + DirectiveDefFeature, + DirectiveDefListOrFactory, + HostBindingsFunction, + InputTransformFunction, + PipeDef, + PipeDefListOrFactory, + TypeOrFactory, + ViewQueriesFunction, +} from './interfaces/definition'; import {InputFlags} from './interfaces/input_flags'; import type {TAttributes, TConstantsOrFactory} from './interfaces/node'; import {CssSelectorList} from './interfaces/projection'; @@ -83,12 +98,16 @@ import {stringifyCSSSelectorList} from './node_selector_matcher'; * inconsistent behavior in that it uses declared names rather than minified or public. */ type DirectiveInputs = { - [P in keyof T]?: - // Basic case. Mapping minified name to public name. - string| - // Complex input when there are flags, or differing public name and declared name, or there - // is a transform. Such inputs are not as common, so the array form is only generated then. - [flags: InputFlags, publicName: string, declaredName?: string, transform?: InputTransformFunction] + [P in keyof T]?: // Basic case. Mapping minified name to public name. + | string + // Complex input when there are flags, or differing public name and declared name, or there + // is a transform. Such inputs are not as common, so the array form is only generated then. + | [ + flags: InputFlags, + publicName: string, + declaredName?: string, + transform?: InputTransformFunction, + ]; }; interface DirectiveDefinition { @@ -178,7 +197,7 @@ interface DirectiveDefinition { * Additional set of instructions specific to view query processing. This could be seen as a * set of instructions to be inserted into the template function. */ - viewQuery?: ViewQueriesFunction|null; + viewQuery?: ViewQueriesFunction | null; /** * Defines the name that can be used in the template to assign this directive to a variable. @@ -298,7 +317,7 @@ interface ComponentDefinition extends Omit, 'features' /** * The set of schemas that declare elements to be allowed in the component's template. */ - schemas?: SchemaMetadata[]|null; + schemas?: SchemaMetadata[] | null; } /** @@ -317,8 +336,9 @@ interface ComponentDefinition extends Omit, 'features' * ``` * @codeGenApi */ -export function ɵɵdefineComponent(componentDefinition: ComponentDefinition): - Mutable, keyof ComponentDef> { +export function ɵɵdefineComponent( + componentDefinition: ComponentDefinition, +): Mutable, keyof ComponentDef> { return noSideEffects(() => { // Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent. // See the `initNgDevMode` docstring for more information. @@ -333,9 +353,9 @@ export function ɵɵdefineComponent(componentDefinition: ComponentDefinition< consts: componentDefinition.consts || null, ngContentSelectors: componentDefinition.ngContentSelectors, onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush, - directiveDefs: null!, // assigned in noSideEffects - pipeDefs: null!, // assigned in noSideEffects - dependencies: baseDef.standalone && componentDefinition.dependencies || null, + directiveDefs: null!, // assigned in noSideEffects + pipeDefs: null!, // assigned in noSideEffects + dependencies: (baseDef.standalone && componentDefinition.dependencies) || null, getStandaloneInjector: null, signals: componentDefinition.signals ?? false, data: componentDefinition.data || {}, @@ -357,11 +377,11 @@ export function ɵɵdefineComponent(componentDefinition: ComponentDefinition< }); } -export function extractDirectiveDef(type: Type): DirectiveDef|ComponentDef|null { +export function extractDirectiveDef(type: Type): DirectiveDef | ComponentDef | null { return getComponentDef(type) || getDirectiveDef(type); } -function nonNull(value: T|null): value is T { +function nonNull(value: T | null): value is T { return value !== null; } @@ -475,16 +495,18 @@ export function ɵɵdefineNgModule(def: { * */ -function parseAndConvertBindingsForDefinition(obj: DirectiveDefinition['outputs']| - undefined): Record; function parseAndConvertBindingsForDefinition( - obj: DirectiveInputs|undefined, declaredInputs: Record): - Record; + obj: DirectiveDefinition['outputs'] | undefined, +): Record; +function parseAndConvertBindingsForDefinition( + obj: DirectiveInputs | undefined, + declaredInputs: Record, +): Record; function parseAndConvertBindingsForDefinition( - obj: undefined|DirectiveInputs|DirectiveDefinition['outputs'], - declaredInputs?: Record): - Record { + obj: undefined | DirectiveInputs | DirectiveDefinition['outputs'], + declaredInputs?: Record, +): Record { if (obj == null) return EMPTY_OBJ as any; const newLookup: any = {}; for (const minifiedKey in obj) { @@ -497,7 +519,7 @@ function parseAndConvertBindingsForDefinition( if (Array.isArray(value)) { inputFlags = value[0]; publicName = value[1]; - declaredName = value[2] ?? publicName; // declared name might not be set to save bytes. + declaredName = value[2] ?? publicName; // declared name might not be set to save bytes. } else { publicName = value; declaredName = value; @@ -507,7 +529,7 @@ function parseAndConvertBindingsForDefinition( if (declaredInputs) { // Perf note: An array is only allocated for the input if there are flags. newLookup[publicName] = - inputFlags !== InputFlags.None ? [minifiedKey, inputFlags] : minifiedKey; + inputFlags !== InputFlags.None ? [minifiedKey, inputFlags] : minifiedKey; declaredInputs[publicName] = declaredName as string; } else { newLookup[publicName] = minifiedKey; @@ -533,8 +555,9 @@ function parseAndConvertBindingsForDefinition( * * @codeGenApi */ -export function ɵɵdefineDirective(directiveDefinition: DirectiveDefinition): - Mutable, keyof DirectiveDef> { +export function ɵɵdefineDirective( + directiveDefinition: DirectiveDefinition, +): Mutable, keyof DirectiveDef> { return noSideEffects(() => { const def = getNgDirectiveDef(directiveDefinition); initFeatures(def); @@ -574,14 +597,14 @@ export function ɵɵdefinePipe(pipeDef: { */ standalone?: boolean; }): unknown { - return (>{ + return >{ type: pipeDef.type, name: pipeDef.name, factory: null, pure: pipeDef.pure !== false, standalone: pipeDef.standalone === true, - onDestroy: pipeDef.type.prototype.ngOnDestroy || null - }); + onDestroy: pipeDef.type.prototype.ngOnDestroy || null, + }; } /** @@ -590,15 +613,15 @@ export function ɵɵdefinePipe(pipeDef: { * explicit. This would require some sort of migration strategy. */ -export function getComponentDef(type: any): ComponentDef|null { +export function getComponentDef(type: any): ComponentDef | null { return type[NG_COMP_DEF] || null; } -export function getDirectiveDef(type: any): DirectiveDef|null { +export function getDirectiveDef(type: any): DirectiveDef | null { return type[NG_DIR_DEF] || null; } -export function getPipeDef(type: any): PipeDef|null { +export function getPipeDef(type: any): PipeDef | null { return type[NG_PIPE_DEF] || null; } @@ -616,8 +639,8 @@ export function isStandalone(type: Type): boolean { } export function getNgModuleDef(type: any, throwNotFound: true): NgModuleDef; -export function getNgModuleDef(type: any): NgModuleDef|null; -export function getNgModuleDef(type: any, throwNotFound?: boolean): NgModuleDef|null { +export function getNgModuleDef(type: any): NgModuleDef | null; +export function getNgModuleDef(type: any, throwNotFound?: boolean): NgModuleDef | null { const ngModuleDef = type[NG_MOD_DEF] || null; if (!ngModuleDef && throwNotFound === true) { throw new Error(`Type ${stringify(type)} does not have 'ɵmod' property.`); @@ -625,8 +648,9 @@ export function getNgModuleDef(type: any, throwNotFound?: boolean): NgModuleD return ngModuleDef; } -function getNgDirectiveDef(directiveDefinition: DirectiveDefinition): - Mutable, keyof DirectiveDef> { +function getNgDirectiveDef( + directiveDefinition: DirectiveDefinition, +): Mutable, keyof DirectiveDef> { const declaredInputs: Record = {}; return { @@ -655,28 +679,36 @@ function getNgDirectiveDef(directiveDefinition: DirectiveDefinition): }; } -function initFeatures(definition:|Mutable, keyof DirectiveDef>| - Mutable, keyof ComponentDef>): void { +function initFeatures( + definition: + | Mutable, keyof DirectiveDef> + | Mutable, keyof ComponentDef>, +): void { definition.features?.forEach((fn) => fn(definition)); } export function extractDefListOrFactory( - dependencies: TypeOrFactory|undefined, - pipeDef: false): DirectiveDefListOrFactory|null; + dependencies: TypeOrFactory | undefined, + pipeDef: false, +): DirectiveDefListOrFactory | null; export function extractDefListOrFactory( - dependencies: TypeOrFactory|undefined, pipeDef: true): PipeDefListOrFactory| - null; + dependencies: TypeOrFactory | undefined, + pipeDef: true, +): PipeDefListOrFactory | null; export function extractDefListOrFactory( - dependencies: TypeOrFactory|undefined, pipeDef: boolean): unknown { + dependencies: TypeOrFactory | undefined, + pipeDef: boolean, +): unknown { if (!dependencies) { return null; } const defExtractor = pipeDef ? getPipeDef : extractDirectiveDef; - return () => (typeof dependencies === 'function' ? dependencies() : dependencies) - .map(dep => defExtractor(dep)) - .filter(nonNull); + return () => + (typeof dependencies === 'function' ? dependencies() : dependencies) + .map((dep) => defExtractor(dep)) + .filter(nonNull); } /** @@ -723,7 +755,7 @@ function getComponentId(componentDef: ComponentDef): string { ].join('|'); for (const char of hashSelectors) { - hash = Math.imul(31, hash) + char.charCodeAt(0) << 0; + hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0; } // Force positive number hash. @@ -736,13 +768,16 @@ function getComponentId(componentDef: ComponentDef): string { if (GENERATED_COMP_IDS.has(compId)) { const previousCompDefType = GENERATED_COMP_IDS.get(compId)!; if (previousCompDefType !== componentDef.type) { - console.warn(formatRuntimeError( + console.warn( + formatRuntimeError( RuntimeErrorCode.COMPONENT_ID_COLLISION, `Component ID generation collision detected. Components '${ - previousCompDefType.name}' and '${componentDef.type.name}' with selector '${ - stringifyCSSSelectorList( - componentDef - .selectors)}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`)); + previousCompDefType.name + }' and '${componentDef.type.name}' with selector '${stringifyCSSSelectorList( + componentDef.selectors, + )}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`, + ), + ); } } else { GENERATED_COMP_IDS.set(compId, componentDef.type); diff --git a/packages/core/src/render3/definition_factory.ts b/packages/core/src/render3/definition_factory.ts index 381445c9e307a..e5f6b2e4d55c4 100644 --- a/packages/core/src/render3/definition_factory.ts +++ b/packages/core/src/render3/definition_factory.ts @@ -10,7 +10,6 @@ import {Type} from '../interface/type'; import {stringify} from '../util/stringify'; import {NG_FACTORY_DEF} from './fields'; - /** * Definition of what a factory function should look like. */ @@ -27,10 +26,9 @@ export type FactoryFn = { (t?: undefined): T; }; - export function getFactoryDef(type: any, throwNotFound: true): FactoryFn; -export function getFactoryDef(type: any): FactoryFn|null; -export function getFactoryDef(type: any, throwNotFound?: boolean): FactoryFn|null { +export function getFactoryDef(type: any): FactoryFn | null; +export function getFactoryDef(type: any, throwNotFound?: boolean): FactoryFn | null { const hasFactoryDef = type.hasOwnProperty(NG_FACTORY_DEF); if (!hasFactoryDef && throwNotFound === true && ngDevMode) { throw new Error(`Type ${stringify(type)} does not have 'ɵfac' property.`); diff --git a/packages/core/src/render3/deps_tracker/api.ts b/packages/core/src/render3/deps_tracker/api.ts index 9d28e2d40ef73..b08087b0027c4 100644 --- a/packages/core/src/render3/deps_tracker/api.ts +++ b/packages/core/src/render3/deps_tracker/api.ts @@ -8,14 +8,20 @@ import {Type} from '../../interface/type'; import {NgModuleType} from '../../metadata/ng_module_def'; -import {ComponentType, DependencyTypeList, DirectiveType, NgModuleScopeInfoFromDecorator, PipeType} from '../interfaces/definition'; +import { + ComponentType, + DependencyTypeList, + DirectiveType, + NgModuleScopeInfoFromDecorator, + PipeType, +} from '../interfaces/definition'; /** * Represents the set of dependencies of a type in a certain context. */ interface ScopeData { pipes: Set>; - directives: Set|ComponentType|Type>; + directives: Set | ComponentType | Type>; /** * If true it indicates that calculating this scope somehow was not successful. The consumers @@ -75,8 +81,10 @@ export interface DepsTrackerApi { * The implementation is expected to use some caching mechanism in order to optimize the resources * needed to do this computation. */ - getComponentDependencies(cmp: ComponentType, rawImports?: (Type|(() => Type))[]): - ComponentDependencies; + getComponentDependencies( + cmp: ComponentType, + rawImports?: (Type | (() => Type))[], + ): ComponentDependencies; /** * Registers an NgModule into the tracker with the given scope info. @@ -115,8 +123,9 @@ export interface DepsTrackerApi { * `clearScopeCacheFor` method. */ getStandaloneComponentScope( - type: ComponentType, - rawImports: (Type|(() => Type))[]): StandaloneComponentScope; + type: ComponentType, + rawImports: (Type | (() => Type))[], + ): StandaloneComponentScope; /** * Checks if the NgModule declaring the component is not loaded into the browser yet. Always diff --git a/packages/core/src/render3/deps_tracker/deps_tracker.ts b/packages/core/src/render3/deps_tracker/deps_tracker.ts index 7bfb51fe38837..dbac3b90aa5ef 100644 --- a/packages/core/src/render3/deps_tracker/deps_tracker.ts +++ b/packages/core/src/render3/deps_tracker/deps_tracker.ts @@ -12,11 +12,20 @@ import {Type} from '../../interface/type'; import {NgModuleType} from '../../metadata/ng_module_def'; import {flatten} from '../../util/array_utils'; import {getComponentDef, getNgModuleDef, isStandalone} from '../definition'; -import {ComponentType, NgModuleScopeInfoFromDecorator, RawScopeInfoFromDecorator} from '../interfaces/definition'; +import { + ComponentType, + NgModuleScopeInfoFromDecorator, + RawScopeInfoFromDecorator, +} from '../interfaces/definition'; import {isComponent, isDirective, isNgModule, isPipe, verifyStandaloneImport} from '../jit/util'; import {maybeUnwrapFn} from '../util/misc_utils'; -import {ComponentDependencies, DepsTrackerApi, NgModuleScope, StandaloneComponentScope} from './api'; +import { + ComponentDependencies, + DepsTrackerApi, + NgModuleScope, + StandaloneComponentScope, +} from './api'; /** * Indicates whether to use the runtime dependency tracker for scope calculation in JIT compilation. @@ -61,14 +70,17 @@ class DepsTracker implements DepsTrackerApi { } /** @override */ - getComponentDependencies(type: ComponentType, rawImports?: RawScopeInfoFromDecorator[]): - ComponentDependencies { + getComponentDependencies( + type: ComponentType, + rawImports?: RawScopeInfoFromDecorator[], + ): ComponentDependencies { this.resolveNgModulesDecls(); const def = getComponentDef(type); if (def === null) { throw new Error( - `Attempting to get component dependencies for a type that is not a component: ${type}`); + `Attempting to get component dependencies for a type that is not a component: ${type}`, + ); } if (def.standalone) { @@ -83,7 +95,7 @@ class DepsTracker implements DepsTrackerApi { ...scope.compilation.directives, ...scope.compilation.pipes, ...scope.compilation.ngModules, - ] + ], }; } else { if (!this.ownerNgModule.has(type)) { @@ -99,10 +111,7 @@ class DepsTracker implements DepsTrackerApi { } return { - dependencies: [ - ...scope.compilation.directives, - ...scope.compilation.pipes, - ], + dependencies: [...scope.compilation.directives, ...scope.compilation.pipes], }; } } @@ -164,8 +173,9 @@ class DepsTracker implements DepsTrackerApi { } else { // The standalone thing is neither a component nor a directive nor a pipe ... (what?) throw new RuntimeError( - RuntimeErrorCode.RUNTIME_DEPS_INVALID_IMPORTED_TYPE, - 'The standalone imported type is neither a component nor a directive nor a pipe'); + RuntimeErrorCode.RUNTIME_DEPS_INVALID_IMPORTED_TYPE, + 'The standalone imported type is neither a component nor a directive nor a pipe', + ); } } else { // The import is neither a module nor a module-with-providers nor a standalone thing. This @@ -223,8 +233,10 @@ class DepsTracker implements DepsTrackerApi { } /** @override */ - getStandaloneComponentScope(type: ComponentType, rawImports?: RawScopeInfoFromDecorator[]): - StandaloneComponentScope { + getStandaloneComponentScope( + type: ComponentType, + rawImports?: RawScopeInfoFromDecorator[], + ): StandaloneComponentScope { if (this.standaloneComponentsScopeCache.has(type)) { return this.standaloneComponentsScopeCache.get(type)!; } @@ -236,8 +248,9 @@ class DepsTracker implements DepsTrackerApi { } private computeStandaloneComponentScope( - type: ComponentType, - rawImports?: RawScopeInfoFromDecorator[]): StandaloneComponentScope { + type: ComponentType, + rawImports?: RawScopeInfoFromDecorator[], + ): StandaloneComponentScope { const ans: StandaloneComponentScope = { compilation: { // Standalone components are always able to self-reference. diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index ddcf50f55227f..b052dd307a330 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -18,25 +18,60 @@ import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert'; import {noSideEffects} from '../util/closure'; import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert'; -import {emitInstanceCreatedByInjectorEvent, InjectorProfilerContext, runInInjectorProfilerContext, setInjectorProfilerContext} from './debug/injector_profiler'; +import { + emitInstanceCreatedByInjectorEvent, + InjectorProfilerContext, + runInInjectorProfilerContext, + setInjectorProfilerContext, +} from './debug/injector_profiler'; import {getFactoryDef} from './definition_factory'; import {throwCyclicDependencyError, throwProviderNotFoundError} from './errors_di'; import {NG_ELEMENT_ID, NG_FACTORY_DEF} from './fields'; import {registerPreOrderHooks} from './hooks'; -import { AttributeMarker } from './interfaces/attribute_marker'; +import {AttributeMarker} from './interfaces/attribute_marker'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; -import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, NodeInjectorOffset, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector'; -import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node'; +import { + isFactory, + NO_PARENT_INJECTOR, + NodeInjectorFactory, + NodeInjectorOffset, + RelativeInjectorLocation, + RelativeInjectorLocationFlags, +} from './interfaces/injector'; +import { + TContainerNode, + TDirectiveHostNode, + TElementContainerNode, + TElementNode, + TNode, + TNodeProviderIndexes, + TNodeType, +} from './interfaces/node'; import {isComponentDef, isComponentHost} from './interfaces/type_checks'; -import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, EMBEDDED_VIEW_INJECTOR, FLAGS, INJECTOR, LView, LViewFlags, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view'; +import { + DECLARATION_COMPONENT_VIEW, + DECLARATION_VIEW, + EMBEDDED_VIEW_INJECTOR, + FLAGS, + INJECTOR, + LView, + LViewFlags, + T_HOST, + TData, + TVIEW, + TView, + TViewType, +} from './interfaces/view'; import {assertTNodeType} from './node_assert'; import {enterDI, getCurrentTNode, getLView, leaveDI} from './state'; import {isNameOnlyAttributeMarker} from './util/attrs_utils'; -import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils'; +import { + getParentInjectorIndex, + getParentInjectorView, + hasParentInjector, +} from './util/injector_utils'; import {stringifyForError} from './util/stringify_utils'; - - /** * Defines if the call to `inject` should include `viewProviders` in its resolution. * @@ -111,9 +146,12 @@ const NOT_FOUND = {}; * @param type The directive token to register */ export function bloomAdd( - injectorIndex: number, tView: TView, type: ProviderToken|string): void { + injectorIndex: number, + tView: TView, + type: ProviderToken | string, +): void { ngDevMode && assertEqual(tView.firstCreatePass, true, 'expected firstCreatePass to be true'); - let id: number|undefined; + let id: number | undefined; if (typeof type === 'string') { id = type.charCodeAt(0) || 0; } else if (type.hasOwnProperty(NG_ELEMENT_ID)) { @@ -149,7 +187,9 @@ export function bloomAdd( * @returns Node injector */ export function getOrCreateNodeInjectorForNode( - tNode: TElementNode|TContainerNode|TElementContainerNode, lView: LView): number { + tNode: TElementNode | TContainerNode | TElementContainerNode, + lView: LView, +): number { const existingInjectorIndex = getInjectorIndex(tNode, lView); if (existingInjectorIndex !== -1) { return existingInjectorIndex; @@ -158,8 +198,8 @@ export function getOrCreateNodeInjectorForNode( const tView = lView[TVIEW]; if (tView.firstCreatePass) { tNode.injectorIndex = lView.length; - insertBloom(tView.data, tNode); // foundation for node bloom - insertBloom(lView, null); // foundation for cumulative bloom + insertBloom(tView.data, tNode); // foundation for node bloom + insertBloom(lView, null); // foundation for cumulative bloom insertBloom(tView.blueprint, null); } @@ -183,19 +223,20 @@ export function getOrCreateNodeInjectorForNode( return injectorIndex; } -function insertBloom(arr: any[], footer: TNode|null): void { +function insertBloom(arr: any[], footer: TNode | null): void { arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer); } - export function getInjectorIndex(tNode: TNode, lView: LView): number { - if (tNode.injectorIndex === -1 || - // If the injector index is the same as its parent's injector index, then the index has been - // copied down from the parent node. No injector has been created yet on this node. - (tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) || - // After the first template pass, the injector index might exist but the parent values - // might not have been calculated yet for this instance - lView[tNode.injectorIndex + NodeInjectorOffset.PARENT] === null) { + if ( + tNode.injectorIndex === -1 || + // If the injector index is the same as its parent's injector index, then the index has been + // copied down from the parent node. No injector has been created yet on this node. + (tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) || + // After the first template pass, the injector index might exist but the parent values + // might not have been calculated yet for this instance + lView[tNode.injectorIndex + NodeInjectorOffset.PARENT] === null + ) { return -1; } else { ngDevMode && assertIndexInRange(lView, tNode.injectorIndex); @@ -214,15 +255,15 @@ export function getParentInjectorLocation(tNode: TNode, lView: LView): RelativeI if (tNode.parent && tNode.parent.injectorIndex !== -1) { // If we have a parent `TNode` and there is an injector associated with it we are done, because // the parent injector is within the current `LView`. - return tNode.parent.injectorIndex as RelativeInjectorLocation; // ViewOffset is 0 + return tNode.parent.injectorIndex as RelativeInjectorLocation; // ViewOffset is 0 } // When parent injector location is computed it may be outside of the current view. (ie it could // be pointing to a declared parent location). This variable stores number of declaration parents // we need to walk up in order to find the parent injector location. let declarationViewOffset = 0; - let parentTNode: TNode|null = null; - let lViewCursor: LView|null = lView; + let parentTNode: TNode | null = null; + let lViewCursor: LView | null = lView; // The parent injector is not in the current `LView`. We will have to walk the declared parent // `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent @@ -243,8 +284,8 @@ export function getParentInjectorLocation(tNode: TNode, lView: LView): RelativeI if (parentTNode.injectorIndex !== -1) { // We found a NodeInjector which points to something. return (parentTNode.injectorIndex | - (declarationViewOffset << RelativeInjectorLocationFlags.ViewOffsetShift)) as - RelativeInjectorLocation; + (declarationViewOffset << + RelativeInjectorLocationFlags.ViewOffsetShift)) as RelativeInjectorLocation; } } return NO_PARENT_INJECTOR; @@ -257,7 +298,10 @@ export function getParentInjectorLocation(tNode: TNode, lView: LView): RelativeI * @param token The type or the injection token to be made public */ export function diPublicInInjector( - injectorIndex: number, tView: TView, token: ProviderToken): void { + injectorIndex: number, + tView: TView, + token: ProviderToken, +): void { bloomAdd(injectorIndex, tView, token); } @@ -292,7 +336,7 @@ export function diPublicInInjector( * * @publicApi */ -export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|null { +export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string | null { ngDevMode && assertTNodeType(tNode, TNodeType.AnyContainer | TNodeType.AnyRNode); ngDevMode && assertDefined(tNode, 'expecting tNode'); if (attrNameToInject === 'class') { @@ -335,10 +379,12 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str return null; } - function notFoundValueOrThrow( - notFoundValue: T|null, token: ProviderToken, flags: InjectFlags): T|null { - if ((flags & InjectFlags.Optional) || notFoundValue !== undefined) { + notFoundValue: T | null, + token: ProviderToken, + flags: InjectFlags, +): T | null { + if (flags & InjectFlags.Optional || notFoundValue !== undefined) { return notFoundValue; } else { throwProviderNotFoundError(token, 'NodeInjector'); @@ -355,8 +401,12 @@ function notFoundValueOrThrow( * @returns the value from the injector or throws an exception */ function lookupTokenUsingModuleInjector( - lView: LView, token: ProviderToken, flags: InjectFlags, notFoundValue?: any): T|null { - if ((flags & InjectFlags.Optional) && notFoundValue === undefined) { + lView: LView, + token: ProviderToken, + flags: InjectFlags, + notFoundValue?: any, +): T | null { + if (flags & InjectFlags.Optional && notFoundValue === undefined) { // This must be set or the NullInjector will throw for optional deps notFoundValue = null; } @@ -397,17 +447,28 @@ function lookupTokenUsingModuleInjector( * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ export function getOrCreateInjectable( - tNode: TDirectiveHostNode|null, lView: LView, token: ProviderToken, - flags: InjectFlags = InjectFlags.Default, notFoundValue?: any): T|null { + tNode: TDirectiveHostNode | null, + lView: LView, + token: ProviderToken, + flags: InjectFlags = InjectFlags.Default, + notFoundValue?: any, +): T | null { if (tNode !== null) { // If the view or any of its ancestors have an embedded // view injector, we have to look it up there first. - if (lView[FLAGS] & LViewFlags.HasEmbeddedViewInjector && - // The token must be present on the current node injector when the `Self` - // flag is set, so the lookup on embedded view injector(s) can be skipped. - !(flags & InjectFlags.Self)) { - const embeddedInjectorValue = - lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, NOT_FOUND); + if ( + lView[FLAGS] & LViewFlags.HasEmbeddedViewInjector && + // The token must be present on the current node injector when the `Self` + // flag is set, so the lookup on embedded view injector(s) can be skipped. + !(flags & InjectFlags.Self) + ) { + const embeddedInjectorValue = lookupTokenUsingEmbeddedInjector( + tNode, + lView, + token, + flags, + NOT_FOUND, + ); if (embeddedInjectorValue !== NOT_FOUND) { return embeddedInjectorValue; } @@ -435,8 +496,12 @@ export function getOrCreateInjectable( * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ function lookupTokenUsingNodeInjector( - tNode: TDirectiveHostNode, lView: LView, token: ProviderToken, flags: InjectFlags, - notFoundValue?: any) { + tNode: TDirectiveHostNode, + lView: LView, + token: ProviderToken, + flags: InjectFlags, + notFoundValue?: any, +) { const bloomHash = bloomHashBitOrFactory(token); // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef // so just call the factory function to create it. @@ -444,23 +509,25 @@ function lookupTokenUsingNodeInjector( if (!enterDI(lView, tNode, flags)) { // Failed to enter DI, try module injector instead. If a token is injected with the @Host // flag, the module injector is not searched for that token in Ivy. - return (flags & InjectFlags.Host) ? - notFoundValueOrThrow(notFoundValue, token, flags) : - lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue); + return flags & InjectFlags.Host + ? notFoundValueOrThrow(notFoundValue, token, flags) + : lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue); } try { let value: unknown; if (ngDevMode) { runInInjectorProfilerContext( - new NodeInjector(getCurrentTNode() as TElementNode, getLView()), token as Type, - () => { - value = bloomHash(flags); - - if (value != null) { - emitInstanceCreatedByInjectorEvent(value); - } - }); + new NodeInjector(getCurrentTNode() as TElementNode, getLView()), + token as Type, + () => { + value = bloomHash(flags); + + if (value != null) { + emitInstanceCreatedByInjectorEvent(value); + } + }, + ); } else { value = bloomHash(flags); } @@ -477,17 +544,19 @@ function lookupTokenUsingNodeInjector( // A reference to the previous injector TView that was found while climbing the element // injector tree. This is used to know if viewProviders can be accessed on the current // injector. - let previousTView: TView|null = null; + let previousTView: TView | null = null; let injectorIndex = getInjectorIndex(tNode, lView); let parentLocation = NO_PARENT_INJECTOR; - let hostTElementNode: TNode|null = - flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null; + let hostTElementNode: TNode | null = + flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null; // If we should skip this injector, or if there is no injector on this node, start by // searching the parent injector. if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) { - parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) : - lView[injectorIndex + NodeInjectorOffset.PARENT]; + parentLocation = + injectorIndex === -1 + ? getParentInjectorLocation(tNode, lView) + : lView[injectorIndex + NodeInjectorOffset.PARENT]; if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) { injectorIndex = -1; @@ -506,23 +575,32 @@ function lookupTokenUsingNodeInjector( // Check the current injector. If it matches, see if it contains token. const tView = lView[TVIEW]; ngDevMode && - assertTNodeForLView(tView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode, lView); + assertTNodeForLView(tView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode, lView); if (bloomHasToken(bloomHash, injectorIndex, tView.data)) { // At this point, we have an injector which *may* contain the token, so we step through // the providers and directives associated with the injector's corresponding node to get // the instance. - const instance: T|{}|null = searchTokensOnInjector( - injectorIndex, lView, token, previousTView, flags, hostTElementNode); + const instance: T | {} | null = searchTokensOnInjector( + injectorIndex, + lView, + token, + previousTView, + flags, + hostTElementNode, + ); if (instance !== NOT_FOUND) { return instance; } } parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT]; - if (parentLocation !== NO_PARENT_INJECTOR && - shouldSearchParent( - flags, - lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] === hostTElementNode) && - bloomHasToken(bloomHash, injectorIndex, lView)) { + if ( + parentLocation !== NO_PARENT_INJECTOR && + shouldSearchParent( + flags, + lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] === hostTElementNode, + ) && + bloomHasToken(bloomHash, injectorIndex, lView) + ) { // The def wasn't found anywhere on this node, so it was a false positive. // Traverse up the tree and continue searching. previousTView = tView; @@ -541,35 +619,46 @@ function lookupTokenUsingNodeInjector( } function searchTokensOnInjector( - injectorIndex: number, lView: LView, token: ProviderToken, previousTView: TView|null, - flags: InjectFlags, hostTElementNode: TNode|null) { + injectorIndex: number, + lView: LView, + token: ProviderToken, + previousTView: TView | null, + flags: InjectFlags, + hostTElementNode: TNode | null, +) { const currentTView = lView[TVIEW]; const tNode = currentTView.data[injectorIndex + NodeInjectorOffset.TNODE] as TNode; // First, we need to determine if view providers can be accessed by the starting element. // There are two possibilities - const canAccessViewProviders = previousTView == null ? - // 1) This is the first invocation `previousTView == null` which means that we are at the - // `TNode` of where injector is starting to look. In such a case the only time we are allowed - // to look into the ViewProviders is if: - // - we are on a component - // - AND the injector set `includeViewProviders` to true (implying that the token can see - // ViewProviders because it is the Component or a Service which itself was declared in - // ViewProviders) - (isComponentHost(tNode) && includeViewProviders) : - // 2) `previousTView != null` which means that we are now walking across the parent nodes. - // In such a case we are only allowed to look into the ViewProviders if: - // - We just crossed from child View to Parent View `previousTView != currentTView` - // - AND the parent TNode is an Element. - // This means that we just came from the Component's View and therefore are allowed to see - // into the ViewProviders. - (previousTView != currentTView && ((tNode.type & TNodeType.AnyRNode) !== 0)); + const canAccessViewProviders = + previousTView == null + ? // 1) This is the first invocation `previousTView == null` which means that we are at the + // `TNode` of where injector is starting to look. In such a case the only time we are allowed + // to look into the ViewProviders is if: + // - we are on a component + // - AND the injector set `includeViewProviders` to true (implying that the token can see + // ViewProviders because it is the Component or a Service which itself was declared in + // ViewProviders) + isComponentHost(tNode) && includeViewProviders + : // 2) `previousTView != null` which means that we are now walking across the parent nodes. + // In such a case we are only allowed to look into the ViewProviders if: + // - We just crossed from child View to Parent View `previousTView != currentTView` + // - AND the parent TNode is an Element. + // This means that we just came from the Component's View and therefore are allowed to see + // into the ViewProviders. + previousTView != currentTView && (tNode.type & TNodeType.AnyRNode) !== 0; // This special case happens when there is a @host on the inject and when we are searching // on the host element node. - const isHostSpecialCase = (flags & InjectFlags.Host) && hostTElementNode === tNode; + const isHostSpecialCase = flags & InjectFlags.Host && hostTElementNode === tNode; const injectableIdx = locateDirectiveOrProvider( - tNode, currentTView, token, canAccessViewProviders, isHostSpecialCase); + tNode, + currentTView, + token, + canAccessViewProviders, + isHostSpecialCase, + ); if (injectableIdx !== null) { return getNodeInjectable(lView, currentTView, injectableIdx, tNode as TElementNode); } else { @@ -588,8 +677,12 @@ function searchTokensOnInjector( * @returns Index of a found directive or provider, or null when none found. */ export function locateDirectiveOrProvider( - tNode: TNode, tView: TView, token: ProviderToken|string, canAccessViewProviders: boolean, - isHostSpecialCase: boolean|number): number|null { + tNode: TNode, + tView: TView, + token: ProviderToken | string, + canAccessViewProviders: boolean, + isHostSpecialCase: boolean | number, +): number | null { const nodeProviderIndexes = tNode.providerIndexes; const tInjectables = tView.data; @@ -597,15 +690,18 @@ export function locateDirectiveOrProvider( const directivesStart = tNode.directiveStart; const directiveEnd = tNode.directiveEnd; const cptViewProvidersCount = - nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; - const startingIndex = - canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount; + nodeProviderIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; + const startingIndex = canAccessViewProviders + ? injectablesStart + : injectablesStart + cptViewProvidersCount; // When the host special case applies, only the viewProviders and the component are visible const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd; for (let i = startingIndex; i < endIndex; i++) { - const providerTokenOrDef = tInjectables[i] as ProviderToken| DirectiveDef| string; - if (i < directivesStart && token === providerTokenOrDef || - i >= directivesStart && (providerTokenOrDef as DirectiveDef).type === token) { + const providerTokenOrDef = tInjectables[i] as ProviderToken | DirectiveDef | string; + if ( + (i < directivesStart && token === providerTokenOrDef) || + (i >= directivesStart && (providerTokenOrDef as DirectiveDef).type === token) + ) { return i; } } @@ -626,7 +722,11 @@ export function locateDirectiveOrProvider( * instantiates the `injectable` and caches the value. */ export function getNodeInjectable( - lView: LView, tView: TView, index: number, tNode: TDirectiveHostNode): any { + lView: LView, + tView: TView, + index: number, + tNode: TDirectiveHostNode, +): any { let value = lView[index]; const tData = tView.data; if (isFactory(value)) { @@ -637,25 +737,28 @@ export function getNodeInjectable( const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders); factory.resolving = true; - let prevInjectContext: InjectorProfilerContext|undefined; + let prevInjectContext: InjectorProfilerContext | undefined; if (ngDevMode) { // tData indexes mirror the concrete instances in its corresponding LView. // lView[index] here is either the injectable instace itself or a factory, // therefore tData[index] is the constructor of that injectable or a // definition object that contains the constructor in a `.type` field. const token = - (tData[index] as (DirectiveDef| ComponentDef)).type || tData[index]; + (tData[index] as DirectiveDef | ComponentDef).type || tData[index]; const injector = new NodeInjector(tNode, lView); prevInjectContext = setInjectorProfilerContext({injector, token}); } - const previousInjectImplementation = - factory.injectImpl ? setInjectImplementation(factory.injectImpl) : null; + const previousInjectImplementation = factory.injectImpl + ? setInjectImplementation(factory.injectImpl) + : null; const success = enterDI(lView, tNode, InjectFlags.Default); ngDevMode && - assertEqual( - success, true, - 'Because flags do not contain \`SkipSelf\' we expect this to always succeed.'); + assertEqual( + success, + true, + "Because flags do not contain `SkipSelf' we expect this to always succeed.", + ); try { value = lView[index] = factory.factory(undefined, tData, lView, tNode); @@ -675,7 +778,7 @@ export function getNodeInjectable( ngDevMode && setInjectorProfilerContext(prevInjectContext!); previousInjectImplementation !== null && - setInjectImplementation(previousInjectImplementation); + setInjectImplementation(previousInjectImplementation); setIncludeViewProviders(previousIncludeViewProviders); factory.resolving = false; leaveDI(); @@ -696,21 +799,23 @@ export function getNodeInjectable( * @returns the matching bit to check in the bloom filter or `null` if the token is not known. * When the returned value is negative then it represents special values such as `Injector`. */ -export function bloomHashBitOrFactory(token: ProviderToken|string): number|Function|undefined { +export function bloomHashBitOrFactory( + token: ProviderToken | string, +): number | Function | undefined { ngDevMode && assertDefined(token, 'token must be defined'); if (typeof token === 'string') { return token.charCodeAt(0) || 0; } - const tokenId: number|undefined = - // First check with `hasOwnProperty` so we don't get an inherited ID. - token.hasOwnProperty(NG_ELEMENT_ID) ? (token as any)[NG_ELEMENT_ID] : undefined; + const tokenId: number | undefined = + // First check with `hasOwnProperty` so we don't get an inherited ID. + token.hasOwnProperty(NG_ELEMENT_ID) ? (token as any)[NG_ELEMENT_ID] : undefined; // Negative token IDs are used for special objects such as `Injector` if (typeof tokenId === 'number') { if (tokenId >= 0) { return tokenId & BLOOM_MASK; } else { ngDevMode && - assertEqual(tokenId, InjectorMarkers.Injector, 'Expecting to get Special Injector Id'); + assertEqual(tokenId, InjectorMarkers.Injector, 'Expecting to get Special Injector Id'); return createNodeInjector; } } else { @@ -718,7 +823,11 @@ export function bloomHashBitOrFactory(token: ProviderToken|string): number| } } -export function bloomHasToken(bloomHash: number, injectorIndex: number, injectorView: LView|TData) { +export function bloomHasToken( + bloomHash: number, + injectorIndex: number, + injectorView: LView | TData, +) { // Create a mask that targets the specific bit associated with the directive we're looking for. // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. @@ -735,7 +844,7 @@ export function bloomHasToken(bloomHash: number, injectorIndex: number, injector } /** Returns true if flags prevent parent injector from being searched for tokens */ -function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): boolean|number { +function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): boolean | number { return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode); } @@ -743,20 +852,30 @@ export function getNodeInjectorLView(nodeInjector: NodeInjector): LView { return (nodeInjector as any)._lView as LView; } -export function getNodeInjectorTNode(nodeInjector: NodeInjector): TElementNode|TContainerNode| - TElementContainerNode|null { - return (nodeInjector as any)._tNode as TElementNode | TContainerNode | TElementContainerNode | - null; +export function getNodeInjectorTNode( + nodeInjector: NodeInjector, +): TElementNode | TContainerNode | TElementContainerNode | null { + return (nodeInjector as any)._tNode as + | TElementNode + | TContainerNode + | TElementContainerNode + | null; } export class NodeInjector implements Injector { constructor( - private _tNode: TElementNode|TContainerNode|TElementContainerNode|null, - private _lView: LView) {} + private _tNode: TElementNode | TContainerNode | TElementContainerNode | null, + private _lView: LView, + ) {} - get(token: any, notFoundValue?: any, flags?: InjectFlags|InjectOptions): any { + get(token: any, notFoundValue?: any, flags?: InjectFlags | InjectOptions): any { return getOrCreateInjectable( - this._tNode, this._lView, token, convertToBitFlags(flags), notFoundValue); + this._tNode, + this._lView, + token, + convertToBitFlags(flags), + notFoundValue, + ); } } @@ -799,7 +918,7 @@ export function ɵɵgetInheritedFactory(type: Type): (type: Type) => }); } -function getFactoryOf(type: Type): ((type?: Type) => T | null)|null { +function getFactoryOf(type: Type): ((type?: Type) => T | null) | null { if (isForwardRef(type)) { return () => { const factory = getFactoryOf(resolveForwardRef(type)); @@ -820,10 +939,14 @@ function getFactoryOf(type: Type): ((type?: Type) => T | null)|null { * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ function lookupTokenUsingEmbeddedInjector( - tNode: TDirectiveHostNode, lView: LView, token: ProviderToken, flags: InjectFlags, - notFoundValue?: any) { - let currentTNode: TDirectiveHostNode|null = tNode; - let currentLView: LView|null = lView; + tNode: TDirectiveHostNode, + lView: LView, + token: ProviderToken, + flags: InjectFlags, + notFoundValue?: any, +) { + let currentTNode: TDirectiveHostNode | null = tNode; + let currentLView: LView | null = lView; // When an LView with an embedded view injector is inserted, it'll likely be interlaced with // nodes who may have injectors (e.g. node injector -> embedded view injector -> node injector). @@ -831,22 +954,30 @@ function lookupTokenUsingEmbeddedInjector( // have a way of extracting the records from an injector, the only way to maintain the correct // hierarchy when resolving the value is to walk it node-by-node while attempting to resolve // the token at each level. - while (currentTNode !== null && currentLView !== null && - (currentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector) && - !(currentLView[FLAGS] & LViewFlags.IsRoot)) { + while ( + currentTNode !== null && + currentLView !== null && + currentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector && + !(currentLView[FLAGS] & LViewFlags.IsRoot) + ) { ngDevMode && assertTNodeForLView(currentTNode, currentLView); // Note that this lookup on the node injector is using the `Self` flag, because // we don't want the node injector to look at any parent injectors since we // may hit the embedded view injector first. const nodeInjectorValue = lookupTokenUsingNodeInjector( - currentTNode, currentLView, token, flags | InjectFlags.Self, NOT_FOUND); + currentTNode, + currentLView, + token, + flags | InjectFlags.Self, + NOT_FOUND, + ); if (nodeInjectorValue !== NOT_FOUND) { return nodeInjectorValue; } // Has an explicit type due to a TS bug: https://github.com/microsoft/TypeScript/issues/33191 - let parentTNode: TElementNode|TContainerNode|null = currentTNode.parent; + let parentTNode: TElementNode | TContainerNode | null = currentTNode.parent; // `TNode.parent` includes the parent within the current view only. If it doesn't exist, // it means that we've hit the view boundary and we need to go up to the next view. @@ -854,8 +985,11 @@ function lookupTokenUsingEmbeddedInjector( // Before we go to the next LView, check if the token exists on the current embedded injector. const embeddedViewInjector = currentLView[EMBEDDED_VIEW_INJECTOR]; if (embeddedViewInjector) { - const embeddedViewInjectorValue = - embeddedViewInjector.get(token, NOT_FOUND as T | {}, flags); + const embeddedViewInjectorValue = embeddedViewInjector.get( + token, + NOT_FOUND as T | {}, + flags, + ); if (embeddedViewInjectorValue !== NOT_FOUND) { return embeddedViewInjectorValue; } @@ -873,7 +1007,7 @@ function lookupTokenUsingEmbeddedInjector( } /** Gets the TNode associated with an LView inside of the declaration view. */ -function getTNodeFromLView(lView: LView): TElementNode|TElementContainerNode|null { +function getTNodeFromLView(lView: LView): TElementNode | TElementContainerNode | null { const tView = lView[TVIEW]; const tViewType = tView.type; diff --git a/packages/core/src/render3/di_setup.ts b/packages/core/src/render3/di_setup.ts index b56b92bd4ea7a..1279691bacca9 100644 --- a/packages/core/src/render3/di_setup.ts +++ b/packages/core/src/render3/di_setup.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {resolveForwardRef} from '../di/forward_ref'; import {ClassProvider, Provider} from '../di/interface/provider'; import {isClassProvider, isTypeProvider, SingleProvider} from '../di/provider_collection'; @@ -14,17 +13,26 @@ import {providerToFactory} from '../di/r3_injector'; import {assertDefined} from '../util/assert'; import {emitProviderConfiguredEvent, runInInjectorProfilerContext} from './debug/injector_profiler'; -import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode, NodeInjector} from './di'; +import { + diPublicInInjector, + getNodeInjectable, + getOrCreateNodeInjectorForNode, + NodeInjector, +} from './di'; import {ɵɵdirectiveInject} from './instructions/all'; import {DirectiveDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; -import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNodeProviderIndexes} from './interfaces/node'; +import { + TContainerNode, + TDirectiveHostNode, + TElementContainerNode, + TElementNode, + TNodeProviderIndexes, +} from './interfaces/node'; import {isComponentDef} from './interfaces/type_checks'; import {DestroyHookData, LView, TData, TVIEW, TView} from './interfaces/view'; import {getCurrentTNode, getLView, getTView} from './state'; - - /** * Resolves the providers which are defined in the DirectiveDef. * @@ -44,7 +52,10 @@ import {getCurrentTNode, getLView, getTView} from './state'; * @param viewProviders: Array of `viewProviders`. */ export function providersResolver( - def: DirectiveDef, providers: Provider[], viewProviders: Provider[]): void { + def: DirectiveDef, + providers: Provider[], + viewProviders: Provider[], +): void { const tView = getTView(); if (tView.firstCreatePass) { const isComponent = isComponentDef(def); @@ -61,8 +72,12 @@ export function providersResolver( * Resolves a provider and publishes it to the DI system. */ function resolveProvider( - provider: Provider, tInjectables: TData, lInjectablesBlueprint: NodeInjectorFactory[], - isComponent: boolean, isViewProvider: boolean): void { + provider: Provider, + tInjectables: TData, + lInjectablesBlueprint: NodeInjectorFactory[], + isComponent: boolean, + isViewProvider: boolean, +): void { provider = resolveForwardRef(provider); if (Array.isArray(provider)) { // Recursively call `resolveProvider` @@ -70,7 +85,12 @@ function resolveProvider( // cloning of the initial state. for (let i = 0; i < provider.length; i++) { resolveProvider( - provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider); + provider[i], + tInjectables, + lInjectablesBlueprint, + isComponent, + isViewProvider, + ); } } else { const tView = getTView(); @@ -80,8 +100,10 @@ function resolveProvider( const providerFactory = providerToFactory(provider); if (ngDevMode) { - const injector = - new NodeInjector(tNode as TElementNode | TContainerNode | TElementContainerNode, lView); + const injector = new NodeInjector( + tNode as TElementNode | TContainerNode | TElementContainerNode, + lView, + ); runInInjectorProfilerContext(injector, token, () => { emitProviderConfiguredEvent(provider as SingleProvider, isViewProvider); }); @@ -90,19 +112,26 @@ function resolveProvider( const beginIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask; const endIndex = tNode.directiveStart; const cptViewProvidersCount = - tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; + tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift; if (isTypeProvider(provider) || !provider.multi) { // Single provider case: the factory is created and pushed immediately const factory = new NodeInjectorFactory(providerFactory, isViewProvider, ɵɵdirectiveInject); const existingFactoryIndex = indexOf( - token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount, - endIndex); + token, + tInjectables, + isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount, + endIndex, + ); if (existingFactoryIndex === -1) { diPublicInInjector( - getOrCreateNodeInjectorForNode( - tNode as TElementNode | TContainerNode | TElementContainerNode, lView), - tView, token); + getOrCreateNodeInjectorForNode( + tNode as TElementNode | TContainerNode | TElementContainerNode, + lView, + ), + tView, + token, + ); registerDestroyHooksIfSupported(tView, provider, tInjectables.length); tInjectables.push(token); tNode.directiveStart++; @@ -138,25 +167,44 @@ function resolveProvider( // It is also linked to the multi factory for view providers, if it exists. // b) Else, the multi provider is added to the existing multi factory. - const existingProvidersFactoryIndex = - indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex); - const existingViewProvidersFactoryIndex = - indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount); - const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 && - lInjectablesBlueprint[existingProvidersFactoryIndex]; - const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 && - lInjectablesBlueprint[existingViewProvidersFactoryIndex]; + const existingProvidersFactoryIndex = indexOf( + token, + tInjectables, + beginIndex + cptViewProvidersCount, + endIndex, + ); + const existingViewProvidersFactoryIndex = indexOf( + token, + tInjectables, + beginIndex, + beginIndex + cptViewProvidersCount, + ); + const doesProvidersFactoryExist = + existingProvidersFactoryIndex >= 0 && lInjectablesBlueprint[existingProvidersFactoryIndex]; + const doesViewProvidersFactoryExist = + existingViewProvidersFactoryIndex >= 0 && + lInjectablesBlueprint[existingViewProvidersFactoryIndex]; - if (isViewProvider && !doesViewProvidersFactoryExist || - !isViewProvider && !doesProvidersFactoryExist) { + if ( + (isViewProvider && !doesViewProvidersFactoryExist) || + (!isViewProvider && !doesProvidersFactoryExist) + ) { // Cases 1.a and 2.a diPublicInInjector( - getOrCreateNodeInjectorForNode( - tNode as TElementNode | TContainerNode | TElementContainerNode, lView), - tView, token); + getOrCreateNodeInjectorForNode( + tNode as TElementNode | TContainerNode | TElementContainerNode, + lView, + ), + tView, + token, + ); const factory = multiFactory( - isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver, - lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory); + isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver, + lInjectablesBlueprint.length, + isViewProvider, + isComponent, + providerFactory, + ); if (!isViewProvider && doesViewProvidersFactoryExist) { lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory; } @@ -172,15 +220,20 @@ function resolveProvider( } else { // Cases 1.b and 2.b const indexInFactory = multiFactoryAdd( - lInjectablesBlueprint! - [isViewProvider ? existingViewProvidersFactoryIndex : - existingProvidersFactoryIndex], - providerFactory, !isViewProvider && isComponent); + lInjectablesBlueprint![ + isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex + ], + providerFactory, + !isViewProvider && isComponent, + ); registerDestroyHooksIfSupported( - tView, provider, - existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex : - existingViewProvidersFactoryIndex, - indexInFactory); + tView, + provider, + existingProvidersFactoryIndex > -1 + ? existingProvidersFactoryIndex + : existingViewProvidersFactoryIndex, + indexInFactory, + ); } if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) { lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders!++; @@ -198,8 +251,11 @@ function resolveProvider( * provider factory. */ function registerDestroyHooksIfSupported( - tView: TView, provider: Exclude, contextIndex: number, - indexInFactory?: number) { + tView: TView, + provider: Exclude, + contextIndex: number, + indexInFactory?: number, +) { const providerIsTypeProvider = isTypeProvider(provider); const providerIsClassProvider = isClassProvider(provider); @@ -212,10 +268,12 @@ function registerDestroyHooksIfSupported( if (ngOnDestroy) { const hooks = tView.destroyHooks || (tView.destroyHooks = []); - if (!providerIsTypeProvider && ((provider as ClassProvider)).multi) { + if (!providerIsTypeProvider && (provider as ClassProvider).multi) { ngDevMode && - assertDefined( - indexInFactory, 'indexInFactory when registering multi factory destroy hook'); + assertDefined( + indexInFactory, + 'indexInFactory when registering multi factory destroy hook', + ); const existingCallbacksIndex = hooks.indexOf(contextIndex); if (existingCallbacksIndex === -1) { @@ -235,7 +293,10 @@ function registerDestroyHooksIfSupported( * @returns Index at which the factory was inserted. */ function multiFactoryAdd( - multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): number { + multiFactory: NodeInjectorFactory, + factory: () => any, + isComponentProvider: boolean, +): number { if (isComponentProvider) { multiFactory.componentProviders!++; } @@ -256,8 +317,12 @@ function indexOf(item: any, arr: any[], begin: number, end: number) { * Use this with `multi` `providers`. */ function multiProvidersFactoryResolver( - this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView, - tNode: TDirectiveHostNode): any[] { + this: NodeInjectorFactory, + _: undefined, + tData: TData, + lData: LView, + tNode: TDirectiveHostNode, +): any[] { return multiResolve(this.multi!, []); } @@ -267,14 +332,22 @@ function multiProvidersFactoryResolver( * This factory knows how to concatenate itself with the existing `multi` `providers`. */ function multiViewProvidersFactoryResolver( - this: NodeInjectorFactory, _: undefined, tData: TData, lView: LView, - tNode: TDirectiveHostNode): any[] { + this: NodeInjectorFactory, + _: undefined, + tData: TData, + lView: LView, + tNode: TDirectiveHostNode, +): any[] { const factories = this.multi!; let result: any[]; if (this.providerFactory) { const componentCount = this.providerFactory.componentProviders!; - const multiProviders = - getNodeInjectable(lView, lView[TVIEW], this.providerFactory!.index!, tNode); + const multiProviders = getNodeInjectable( + lView, + lView[TVIEW], + this.providerFactory!.index!, + tNode, + ); // Copy the section of the array which contains `multi` `providers` from the component result = multiProviders.slice(0, componentCount); // Insert the `viewProvider` instances. @@ -306,11 +379,18 @@ function multiResolve(factories: Array<() => any>, result: any[]): any[] { * Creates a multi factory. */ function multiFactory( - factoryFn: ( - this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView, - tNode: TDirectiveHostNode) => any, - index: number, isViewProvider: boolean, isComponent: boolean, - f: () => any): NodeInjectorFactory { + factoryFn: ( + this: NodeInjectorFactory, + _: undefined, + tData: TData, + lData: LView, + tNode: TDirectiveHostNode, + ) => any, + index: number, + isViewProvider: boolean, + isComponent: boolean, + f: () => any, +): NodeInjectorFactory { const factory = new NodeInjectorFactory(factoryFn, isViewProvider, ɵɵdirectiveInject); factory.multi = []; factory.index = index; diff --git a/packages/core/src/render3/errors.ts b/packages/core/src/render3/errors.ts index 8f0c5de6b7ae9..00e582f1fe434 100644 --- a/packages/core/src/render3/errors.ts +++ b/packages/core/src/render3/errors.ts @@ -1,4 +1,3 @@ - /** * @license * Copyright Google LLC All Rights Reserved. @@ -28,11 +27,12 @@ export function assertStandaloneComponentType(type: Type) { const componentDef = getComponentDef(type)!; if (!componentDef.standalone) { throw new RuntimeError( - RuntimeErrorCode.TYPE_IS_NOT_STANDALONE, - `The ${stringifyForError(type)} component is not marked as standalone, ` + - `but Angular expects to have a standalone component here. ` + - `Please make sure the ${stringifyForError(type)} component has ` + - `the \`standalone: true\` flag in the decorator.`); + RuntimeErrorCode.TYPE_IS_NOT_STANDALONE, + `The ${stringifyForError(type)} component is not marked as standalone, ` + + `but Angular expects to have a standalone component here. ` + + `Please make sure the ${stringifyForError(type)} component has ` + + `the \`standalone: true\` flag in the decorator.`, + ); } } @@ -40,37 +40,45 @@ export function assertStandaloneComponentType(type: Type) { export function assertComponentDef(type: Type) { if (!getComponentDef(type)) { throw new RuntimeError( - RuntimeErrorCode.MISSING_GENERATED_DEF, - `The ${stringifyForError(type)} is not an Angular component, ` + - `make sure it has the \`@Component\` decorator.`); + RuntimeErrorCode.MISSING_GENERATED_DEF, + `The ${stringifyForError(type)} is not an Angular component, ` + + `make sure it has the \`@Component\` decorator.`, + ); } } /** Called when there are multiple component selectors that match a given node */ export function throwMultipleComponentError( - tNode: TNode, first: Type, second: Type): never { + tNode: TNode, + first: Type, + second: Type, +): never { throw new RuntimeError( - RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH, - `Multiple components match node with tagname ${tNode.value}: ` + - `${stringifyForError(first)} and ` + - `${stringifyForError(second)}`); + RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH, + `Multiple components match node with tagname ${tNode.value}: ` + + `${stringifyForError(first)} and ` + + `${stringifyForError(second)}`, + ); } /** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */ export function throwErrorIfNoChangesMode( - creationMode: boolean, oldValue: any, currValue: any, propName: string|undefined, - lView: LView): never { + creationMode: boolean, + oldValue: any, + currValue: any, + propName: string | undefined, + lView: LView, +): never { const hostComponentDef = getDeclarationComponentDef(lView); const componentClassName = hostComponentDef?.type?.name; const field = propName ? ` for '${propName}'` : ''; - let msg = - `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${ - field}: '${formatValue(oldValue)}'. Current value: '${formatValue(currValue)}'.${ - componentClassName ? ` Expression location: ${componentClassName} component` : ''}`; + let msg = `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${field}: '${formatValue(oldValue)}'. Current value: '${formatValue(currValue)}'.${ + componentClassName ? ` Expression location: ${componentClassName} component` : '' + }`; if (creationMode) { msg += - ` It seems like the view has been created after its parent and its children have been dirty checked.` + - ` Has it been created in a change detection hook?`; + ` It seems like the view has been created after its parent and its children have been dirty checked.` + + ` Has it been created in a change detection hook?`; } throw new RuntimeError(RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED, msg); } @@ -83,17 +91,22 @@ function formatValue(value: unknown): string { if (Array.isArray(value) || strValue === '[object Object]') { strValue = JSON.stringify(value); } - } catch (error) { - } - return strValue.length > VALUE_STRING_LENGTH_LIMIT ? - (strValue.substring(0, VALUE_STRING_LENGTH_LIMIT) + '…') : - strValue; + } catch (error) {} + return strValue.length > VALUE_STRING_LENGTH_LIMIT + ? strValue.substring(0, VALUE_STRING_LENGTH_LIMIT) + '…' + : strValue; } function constructDetailsForInterpolation( - lView: LView, rootIndex: number, expressionIndex: number, meta: string, changedValue: any) { + lView: LView, + rootIndex: number, + expressionIndex: number, + meta: string, + changedValue: any, +) { const [propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER); - let oldValue = prefix, newValue = prefix; + let oldValue = prefix, + newValue = prefix; for (let i = 0; i < chunks.length; i++) { const slotIdx = rootIndex + i; oldValue += `${lView[slotIdx]}${chunks[i]}`; @@ -111,8 +124,11 @@ function constructDetailsForInterpolation( * function description. */ export function getExpressionChangedErrorDetails( - lView: LView, bindingIndex: number, oldValue: any, - newValue: any): {propName?: string, oldValue: any, newValue: any} { + lView: LView, + bindingIndex: number, + oldValue: any, + newValue: any, +): {propName?: string; oldValue: any; newValue: any} { const tData = lView[TVIEW].data; const metadata = tData[bindingIndex]; @@ -120,7 +136,12 @@ export function getExpressionChangedErrorDetails( // metadata for property interpolation if (metadata.indexOf(INTERPOLATION_DELIMITER) > -1) { return constructDetailsForInterpolation( - lView, bindingIndex, bindingIndex, metadata, newValue); + lView, + bindingIndex, + bindingIndex, + metadata, + newValue, + ); } // metadata for property binding return {propName: metadata, oldValue, newValue}; @@ -140,7 +161,7 @@ export function getExpressionChangedErrorDetails( const matches = meta.match(new RegExp(INTERPOLATION_DELIMITER, 'g')); // first interpolation delimiter separates property name from interpolation parts (in case of // property interpolations), so we subtract one from total number of found delimiters - if (matches && (matches.length - 1) > bindingIndex - idx) { + if (matches && matches.length - 1 > bindingIndex - idx) { return constructDetailsForInterpolation(lView, idx, bindingIndex, meta, newValue); } } diff --git a/packages/core/src/render3/errors_di.ts b/packages/core/src/render3/errors_di.ts index 7bf41b3e3595e..eef67df6ea9e7 100644 --- a/packages/core/src/render3/errors_di.ts +++ b/packages/core/src/render3/errors_di.ts @@ -14,13 +14,13 @@ import {stringify} from '../util/stringify'; import {stringifyForError} from './util/stringify_utils'; - /** Called when directives inject each other (creating a circular dependency) */ export function throwCyclicDependencyError(token: string, path?: string[]): never { const depPath = path ? `. Dependency path: ${path.join(' > ')} > ${token}` : ''; throw new RuntimeError( - RuntimeErrorCode.CYCLIC_DI_DEPENDENCY, - ngDevMode ? `Circular dependency in DI detected for ${token}${depPath}` : token); + RuntimeErrorCode.CYCLIC_DI_DEPENDENCY, + ngDevMode ? `Circular dependency in DI detected for ${token}${depPath}` : token, + ); } export function throwMixedMultiProviderError() { @@ -28,33 +28,41 @@ export function throwMixedMultiProviderError() { } export function throwInvalidProviderError( - ngModuleType?: Type, providers?: any[], provider?: any): never { + ngModuleType?: Type, + providers?: any[], + provider?: any, +): never { if (ngModuleType && providers) { - const providerDetail = providers.map(v => v == provider ? '?' + provider + '?' : '...'); - throw new Error(`Invalid provider for the NgModule '${ - stringify(ngModuleType)}' - only instances of Provider and Type are allowed, got: [${ - providerDetail.join(', ')}]`); + const providerDetail = providers.map((v) => (v == provider ? '?' + provider + '?' : '...')); + throw new Error( + `Invalid provider for the NgModule '${stringify( + ngModuleType, + )}' - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`, + ); } else if (isEnvironmentProviders(provider)) { if (provider.ɵfromNgModule) { throw new RuntimeError( - RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, - `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`); + RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, + `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`, + ); } else { throw new RuntimeError( - RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, - `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`); + RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT, + `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`, + ); } } else { throw new Error('Invalid provider'); } } - /** Throws an error when a token is not found in DI. */ export function throwProviderNotFoundError( - token: ProviderToken, injectorName?: string): never { - const errorMessage = ngDevMode && - `No provider for ${stringifyForError(token)} found${ - injectorName ? ` in ${injectorName}` : ''}`; + token: ProviderToken, + injectorName?: string, +): never { + const errorMessage = + ngDevMode && + `No provider for ${stringifyForError(token)} found${injectorName ? ` in ${injectorName}` : ''}`; throw new RuntimeError(RuntimeErrorCode.PROVIDER_NOT_FOUND, errorMessage); } diff --git a/packages/core/src/render3/features/copy_definition_feature.ts b/packages/core/src/render3/features/copy_definition_feature.ts index f3b862c4a66bb..5897ab25e0fd7 100644 --- a/packages/core/src/render3/features/copy_definition_feature.ts +++ b/packages/core/src/render3/features/copy_definition_feature.ts @@ -64,10 +64,10 @@ const COPY_COMPONENT_FIELDS: Exclude, keyof Directiv * * @codeGenApi */ -export function ɵɵCopyDefinitionFeature(definition: DirectiveDef|ComponentDef): void { +export function ɵɵCopyDefinitionFeature(definition: DirectiveDef | ComponentDef): void { let superType = getSuperType(definition.type)!; - let superDef: DirectiveDef|ComponentDef|undefined = undefined; + let superDef: DirectiveDef | ComponentDef | undefined = undefined; if (isComponentDef(definition)) { // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance. superDef = superType.ɵcmp!; @@ -77,7 +77,7 @@ export function ɵɵCopyDefinitionFeature(definition: DirectiveDef|Componen } // Needed because `definition` fields are readonly. - const defAny = (definition as any); + const defAny = definition as any; // Copy over any fields that apply to either directives or components. for (const field of COPY_DIRECTIVE_FIELDS) { diff --git a/packages/core/src/render3/features/host_directives_feature.ts b/packages/core/src/render3/features/host_directives_feature.ts index 6d232cacfa6ea..2e7cce55c182f 100644 --- a/packages/core/src/render3/features/host_directives_feature.ts +++ b/packages/core/src/render3/features/host_directives_feature.ts @@ -11,14 +11,22 @@ import {Type} from '../../interface/type'; import {assertEqual} from '../../util/assert'; import {EMPTY_OBJ} from '../../util/empty'; import {getComponentDef, getDirectiveDef} from '../definition'; -import {DirectiveDef, DirectiveDefFeature, HostDirectiveBindingMap, HostDirectiveDef, HostDirectiveDefs} from '../interfaces/definition'; +import { + DirectiveDef, + DirectiveDefFeature, + HostDirectiveBindingMap, + HostDirectiveDef, + HostDirectiveDefs, +} from '../interfaces/definition'; /** Values that can be used to define a host directive through the `HostDirectivesFeature`. */ -type HostDirectiveConfig = Type|{ - directive: Type; - inputs?: string[]; - outputs?: string[]; -}; +type HostDirectiveConfig = + | Type + | { + directive: Type; + inputs?: string[]; + outputs?: string[]; + }; /** * This feature adds the host directives behavior to a directive definition by patching a @@ -40,19 +48,21 @@ type HostDirectiveConfig = Type|{ * * @codeGenApi */ -export function ɵɵHostDirectivesFeature(rawHostDirectives: HostDirectiveConfig[]| - (() => HostDirectiveConfig[])) { +export function ɵɵHostDirectivesFeature( + rawHostDirectives: HostDirectiveConfig[] | (() => HostDirectiveConfig[]), +) { const feature: DirectiveDefFeature = (definition: DirectiveDef) => { - const resolved = - (Array.isArray(rawHostDirectives) ? rawHostDirectives : rawHostDirectives()).map(dir => { - return typeof dir === 'function' ? - {directive: resolveForwardRef(dir), inputs: EMPTY_OBJ, outputs: EMPTY_OBJ} : - { - directive: resolveForwardRef(dir.directive), - inputs: bindingArrayToMap(dir.inputs), - outputs: bindingArrayToMap(dir.outputs) - }; - }); + const resolved = ( + Array.isArray(rawHostDirectives) ? rawHostDirectives : rawHostDirectives() + ).map((dir) => { + return typeof dir === 'function' + ? {directive: resolveForwardRef(dir), inputs: EMPTY_OBJ, outputs: EMPTY_OBJ} + : { + directive: resolveForwardRef(dir.directive), + inputs: bindingArrayToMap(dir.inputs), + outputs: bindingArrayToMap(dir.outputs), + }; + }); if (definition.hostDirectives === null) { definition.findHostDirectiveDefs = findHostDirectiveDefs; definition.hostDirectives = resolved; @@ -65,8 +75,10 @@ export function ɵɵHostDirectivesFeature(rawHostDirectives: HostDirectiveConfig } function findHostDirectiveDefs( - currentDef: DirectiveDef, matchedDefs: DirectiveDef[], - hostDirectiveDefs: HostDirectiveDefs): void { + currentDef: DirectiveDef, + matchedDefs: DirectiveDef[], + hostDirectiveDefs: HostDirectiveDefs, +): void { if (currentDef.hostDirectives !== null) { for (const hostDirectiveConfig of currentDef.hostDirectives) { const hostDirectiveDef = getDirectiveDef(hostDirectiveConfig.directive)!; @@ -91,7 +103,7 @@ function findHostDirectiveDefs( * Converts an array in the form of `['publicName', 'alias', 'otherPublicName', 'otherAlias']` into * a map in the form of `{publicName: 'alias', otherPublicName: 'otherAlias'}`. */ -function bindingArrayToMap(bindings: string[]|undefined): HostDirectiveBindingMap { +function bindingArrayToMap(bindings: string[] | undefined): HostDirectiveBindingMap { if (bindings === undefined || bindings.length === 0) { return EMPTY_OBJ; } @@ -125,7 +137,9 @@ function bindingArrayToMap(bindings: string[]|undefined): HostDirectiveBindingMa * `SimpleChanges` object which won't be reached if an input doesn't exist. */ function patchDeclaredInputs( - declaredInputs: Record, exposedInputs: HostDirectiveBindingMap): void { + declaredInputs: Record, + exposedInputs: HostDirectiveBindingMap, +): void { for (const publicName in exposedInputs) { if (exposedInputs.hasOwnProperty(publicName)) { const remappedPublicName = exposedInputs[publicName]; @@ -135,11 +149,15 @@ function patchDeclaredInputs( // inputs on the same property and we have validations against conflicting aliases in // `validateMappings`. If we somehow did, it would lead to `ngOnChanges` being invoked // with the wrong name so we have a non-user-friendly assertion here just in case. - if ((typeof ngDevMode === 'undefined' || ngDevMode) && - declaredInputs.hasOwnProperty(remappedPublicName)) { + if ( + (typeof ngDevMode === 'undefined' || ngDevMode) && + declaredInputs.hasOwnProperty(remappedPublicName) + ) { assertEqual( - declaredInputs[remappedPublicName], declaredInputs[publicName], - `Conflicting host directive input alias ${publicName}.`); + declaredInputs[remappedPublicName], + declaredInputs[publicName], + `Conflicting host directive input alias ${publicName}.`, + ); } declaredInputs[remappedPublicName] = privateName; @@ -153,27 +171,31 @@ function patchDeclaredInputs( * @param directiveDef Directive definition of the host directive. */ function validateHostDirective( - hostDirectiveConfig: HostDirectiveDef, - directiveDef: DirectiveDef|null): asserts directiveDef is DirectiveDef { + hostDirectiveConfig: HostDirectiveDef, + directiveDef: DirectiveDef | null, +): asserts directiveDef is DirectiveDef { const type = hostDirectiveConfig.directive; if (directiveDef === null) { if (getComponentDef(type) !== null) { throw new RuntimeError( - RuntimeErrorCode.HOST_DIRECTIVE_COMPONENT, - `Host directive ${type.name} cannot be a component.`); + RuntimeErrorCode.HOST_DIRECTIVE_COMPONENT, + `Host directive ${type.name} cannot be a component.`, + ); } throw new RuntimeError( - RuntimeErrorCode.HOST_DIRECTIVE_UNRESOLVABLE, - `Could not resolve metadata for host directive ${type.name}. ` + - `Make sure that the ${type.name} class is annotated with an @Directive decorator.`); + RuntimeErrorCode.HOST_DIRECTIVE_UNRESOLVABLE, + `Could not resolve metadata for host directive ${type.name}. ` + + `Make sure that the ${type.name} class is annotated with an @Directive decorator.`, + ); } if (!directiveDef.standalone) { throw new RuntimeError( - RuntimeErrorCode.HOST_DIRECTIVE_NOT_STANDALONE, - `Host directive ${directiveDef.type.name} must be standalone.`); + RuntimeErrorCode.HOST_DIRECTIVE_NOT_STANDALONE, + `Host directive ${directiveDef.type.name} must be standalone.`, + ); } validateMappings('input', directiveDef, hostDirectiveConfig.inputs); @@ -187,8 +209,10 @@ function validateHostDirective( * @param hostDirectiveBindings Host directive mapping object that shold be validated. */ function validateMappings( - bindingType: 'input'|'output', def: DirectiveDef, - hostDirectiveBindings: HostDirectiveBindingMap) { + bindingType: 'input' | 'output', + def: DirectiveDef, + hostDirectiveBindings: HostDirectiveBindingMap, +) { const className = def.type.name; const bindings = bindingType === 'input' ? def.inputs : def.outputs; @@ -196,19 +220,18 @@ function validateMappings( if (hostDirectiveBindings.hasOwnProperty(publicName)) { if (!bindings.hasOwnProperty(publicName)) { throw new RuntimeError( - RuntimeErrorCode.HOST_DIRECTIVE_UNDEFINED_BINDING, - `Directive ${className} does not have an ${bindingType} with a public name of ${ - publicName}.`); + RuntimeErrorCode.HOST_DIRECTIVE_UNDEFINED_BINDING, + `Directive ${className} does not have an ${bindingType} with a public name of ${publicName}.`, + ); } const remappedPublicName = hostDirectiveBindings[publicName]; if (bindings.hasOwnProperty(remappedPublicName) && remappedPublicName !== publicName) { throw new RuntimeError( - RuntimeErrorCode.HOST_DIRECTIVE_CONFLICTING_ALIAS, - `Cannot alias ${bindingType} ${publicName} of host directive ${className} to ${ - remappedPublicName}, because it already has a different ${ - bindingType} with the same public name.`); + RuntimeErrorCode.HOST_DIRECTIVE_CONFLICTING_ALIAS, + `Cannot alias ${bindingType} ${publicName} of host directive ${className} to ${remappedPublicName}, because it already has a different ${bindingType} with the same public name.`, + ); } } } diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index 9e281af033eb3..e250c27739224 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -10,18 +10,27 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {Type, Writable} from '../../interface/type'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../../util/empty'; import {fillProperties} from '../../util/property'; -import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; +import { + ComponentDef, + ContentQueriesFunction, + DirectiveDef, + DirectiveDefFeature, + HostBindingsFunction, + RenderFlags, + ViewQueriesFunction, +} from '../interfaces/definition'; import {TAttributes} from '../interfaces/node'; import {isComponentDef} from '../interfaces/type_checks'; import {mergeHostAttrs} from '../util/attrs_utils'; import {stringifyForError} from '../util/stringify_utils'; -export function getSuperType(type: Type): Type& - {ɵcmp?: ComponentDef, ɵdir?: DirectiveDef} { +export function getSuperType( + type: Type, +): Type & {ɵcmp?: ComponentDef; ɵdir?: DirectiveDef} { return Object.getPrototypeOf(type.prototype).constructor; } -type WritableDef = Writable|ComponentDef>; +type WritableDef = Writable | ComponentDef>; /** * Merges the definition from a super class to a sub class. @@ -29,24 +38,27 @@ type WritableDef = Writable|ComponentDef>; * * @codeGenApi */ -export function ɵɵInheritDefinitionFeature(definition: DirectiveDef|ComponentDef): void { +export function ɵɵInheritDefinitionFeature( + definition: DirectiveDef | ComponentDef, +): void { let superType = getSuperType(definition.type); let shouldInheritFields = true; const inheritanceChain: WritableDef[] = [definition]; while (superType) { - let superDef: DirectiveDef|ComponentDef|undefined = undefined; + let superDef: DirectiveDef | ComponentDef | undefined = undefined; if (isComponentDef(definition)) { // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance. superDef = superType.ɵcmp || superType.ɵdir; } else { if (superType.ɵcmp) { throw new RuntimeError( - RuntimeErrorCode.INVALID_INHERITANCE, - ngDevMode && - `Directives cannot inherit Components. Directive ${ - stringifyForError(definition.type)} is attempting to extend component ${ - stringifyForError(superType)}`); + RuntimeErrorCode.INVALID_INHERITANCE, + ngDevMode && + `Directives cannot inherit Components. Directive ${stringifyForError( + definition.type, + )} is attempting to extend component ${stringifyForError(superType)}`, + ); } // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance. superDef = superType.ɵdir; @@ -155,15 +167,17 @@ function mergeInputsWithTransforms(target: WritableDef, source: DirectiveDef< */ function mergeHostAttrsAcrossInheritance(inheritanceChain: WritableDef[]) { let hostVars: number = 0; - let hostAttrs: TAttributes|null = null; + let hostAttrs: TAttributes | null = null; // We process the inheritance order from the base to the leaves here. for (let i = inheritanceChain.length - 1; i >= 0; i--) { const def = inheritanceChain[i]; // For each `hostVars`, we need to add the superclass amount. - def.hostVars = (hostVars += def.hostVars); + def.hostVars = hostVars += def.hostVars; // for each `hostAttrs` we need to merge it with superclass. - def.hostAttrs = - mergeHostAttrs(def.hostAttrs, hostAttrs = mergeHostAttrs(hostAttrs, def.hostAttrs)); + def.hostAttrs = mergeHostAttrs( + def.hostAttrs, + (hostAttrs = mergeHostAttrs(hostAttrs, def.hostAttrs)), + ); } } @@ -192,7 +206,9 @@ function inheritViewQuery(definition: WritableDef, superViewQuery: ViewQueriesFu } function inheritContentQueries( - definition: WritableDef, superContentQueries: ContentQueriesFunction) { + definition: WritableDef, + superContentQueries: ContentQueriesFunction, +) { const prevContentQueries = definition.contentQueries; if (prevContentQueries) { definition.contentQueries = (rf, ctx, directiveIndex) => { @@ -205,7 +221,9 @@ function inheritContentQueries( } function inheritHostBindings( - definition: WritableDef, superHostBindings: HostBindingsFunction) { + definition: WritableDef, + superHostBindings: HostBindingsFunction, +) { const prevHostBindings = definition.hostBindings; if (prevHostBindings) { definition.hostBindings = (rf: RenderFlags, ctx: any) => { diff --git a/packages/core/src/render3/features/ng_onchanges_feature.ts b/packages/core/src/render3/features/ng_onchanges_feature.ts index 962a2a882f443..0cb790916ab85 100644 --- a/packages/core/src/render3/features/ng_onchanges_feature.ts +++ b/packages/core/src/render3/features/ng_onchanges_feature.ts @@ -83,31 +83,39 @@ function rememberChangeHistoryAndInvokeOnChangesHook(this: OnChanges) { } } - function ngOnChangesSetInput( - this: DirectiveDef, instance: T, inputSignalNode: null|InputSignalNode, - value: unknown, publicName: string, privateName: string): void { + this: DirectiveDef, + instance: T, + inputSignalNode: null | InputSignalNode, + value: unknown, + publicName: string, + privateName: string, +): void { const declaredName = (this.declaredInputs as {[key: string]: string})[publicName]; ngDevMode && assertString(declaredName, 'Name of input in ngOnChanges has to be a string'); - const simpleChangesStore = getSimpleChangesStore(instance) || - setSimpleChangesStore(instance, {previous: EMPTY_OBJ, current: null}); + const simpleChangesStore = + getSimpleChangesStore(instance) || + setSimpleChangesStore(instance, {previous: EMPTY_OBJ, current: null}); const current = simpleChangesStore.current || (simpleChangesStore.current = {}); const previous = simpleChangesStore.previous; const previousChange = previous[declaredName]; current[declaredName] = new SimpleChange( - previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ); + previousChange && previousChange.currentValue, + value, + previous === EMPTY_OBJ, + ); applyValueToInputField(instance, inputSignalNode, privateName, value); } const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__'; -function getSimpleChangesStore(instance: any): null|NgSimpleChangesStore { +function getSimpleChangesStore(instance: any): null | NgSimpleChangesStore { return instance[SIMPLE_CHANGES_STORE] || null; } function setSimpleChangesStore(instance: any, store: NgSimpleChangesStore): NgSimpleChangesStore { - return instance[SIMPLE_CHANGES_STORE] = store; + return (instance[SIMPLE_CHANGES_STORE] = store); } /** @@ -116,5 +124,5 @@ function setSimpleChangesStore(instance: any, store: NgSimpleChangesStore): NgSi */ interface NgSimpleChangesStore { previous: SimpleChanges; - current: SimpleChanges|null; + current: SimpleChanges | null; } diff --git a/packages/core/src/render3/features/providers_feature.ts b/packages/core/src/render3/features/providers_feature.ts index 3c75066753898..0823c8b62e9cc 100644 --- a/packages/core/src/render3/features/providers_feature.ts +++ b/packages/core/src/render3/features/providers_feature.ts @@ -43,12 +43,15 @@ import {DirectiveDef} from '../interfaces/definition'; */ export function ɵɵProvidersFeature(providers: Provider[], viewProviders: Provider[] = []) { return (definition: DirectiveDef) => { - definition.providersResolver = - (def: DirectiveDef, processProvidersFn?: ProcessProvidersFunction) => { - return providersResolver( - def, // - processProvidersFn ? processProvidersFn(providers) : providers, // - viewProviders); - }; + definition.providersResolver = ( + def: DirectiveDef, + processProvidersFn?: ProcessProvidersFunction, + ) => { + return providersResolver( + def, // + processProvidersFn ? processProvidersFn(providers) : providers, // + viewProviders, + ); + }; }; } diff --git a/packages/core/src/render3/features/standalone_feature.ts b/packages/core/src/render3/features/standalone_feature.ts index 1c2d813cdd274..ec816f271bbb7 100644 --- a/packages/core/src/render3/features/standalone_feature.ts +++ b/packages/core/src/render3/features/standalone_feature.ts @@ -20,21 +20,25 @@ import {createEnvironmentInjector} from '../ng_module_ref'; * collected from the imports graph rooted at a given standalone component. */ class StandaloneService implements OnDestroy { - cachedInjectors = new Map, EnvironmentInjector|null>(); + cachedInjectors = new Map, EnvironmentInjector | null>(); constructor(private _injector: EnvironmentInjector) {} - getOrCreateStandaloneInjector(componentDef: ComponentDef): EnvironmentInjector|null { + getOrCreateStandaloneInjector(componentDef: ComponentDef): EnvironmentInjector | null { if (!componentDef.standalone) { return null; } if (!this.cachedInjectors.has(componentDef)) { const providers = internalImportProvidersFrom(false, componentDef.type); - const standaloneInjector = providers.length > 0 ? - createEnvironmentInjector( - [providers], this._injector, `Standalone[${componentDef.type.name}]`) : - null; + const standaloneInjector = + providers.length > 0 + ? createEnvironmentInjector( + [providers], + this._injector, + `Standalone[${componentDef.type.name}]`, + ) + : null; this.cachedInjectors.set(componentDef, standaloneInjector); } @@ -61,7 +65,6 @@ class StandaloneService implements OnDestroy { }); } - /** * A feature that acts as a setup code for the {@link StandaloneService}. * diff --git a/packages/core/src/render3/global_utils_api.ts b/packages/core/src/render3/global_utils_api.ts index b74bedf4afc4c..cc3be1f596ae0 100644 --- a/packages/core/src/render3/global_utils_api.ts +++ b/packages/core/src/render3/global_utils_api.ts @@ -16,4 +16,17 @@ */ export {applyChanges} from './util/change_detection_utils'; -export {ComponentDebugMetadata, DirectiveDebugMetadata, getComponent, getContext, getDirectiveMetadata, getDirectives, getHostElement, getInjector, getListeners, getOwningComponent, getRootComponents, Listener} from './util/discovery_utils'; +export { + ComponentDebugMetadata, + DirectiveDebugMetadata, + getComponent, + getContext, + getDirectiveMetadata, + getDirectives, + getHostElement, + getInjector, + getListeners, + getOwningComponent, + getRootComponents, + Listener, +} from './util/discovery_utils'; diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index adaf25757ab47..6c5b03bde5037 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -8,19 +8,35 @@ import {setActiveConsumer} from '@angular/core/primitives/signals'; -import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from '../interface/lifecycle_hooks'; +import { + AfterContentChecked, + AfterContentInit, + AfterViewChecked, + AfterViewInit, + DoCheck, + OnChanges, + OnDestroy, + OnInit, +} from '../interface/lifecycle_hooks'; import {assertDefined, assertEqual, assertNotEqual} from '../util/assert'; import {assertFirstCreatePass} from './assert'; import {NgOnChangesFeatureImpl} from './features/ng_onchanges_feature'; import {DirectiveDef} from './interfaces/definition'; import {TNode} from './interfaces/node'; -import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view'; +import { + FLAGS, + HookData, + InitPhaseState, + LView, + LViewFlags, + PREORDER_HOOK_FLAGS, + PreOrderHookFlags, + TView, +} from './interfaces/view'; import {profiler, ProfilerEvent} from './profiler'; import {isInCheckNoChangesMode} from './state'; - - /** * Adds all directive lifecycle hooks from the given `DirectiveDef` to the given `TView`. * @@ -34,10 +50,14 @@ import {isInCheckNoChangesMode} from './state'; * @param tView The current TView */ export function registerPreOrderHooks( - directiveIndex: number, directiveDef: DirectiveDef, tView: TView): void { + directiveIndex: number, + directiveDef: DirectiveDef, + tView: TView, +): void { ngDevMode && assertFirstCreatePass(tView); - const {ngOnChanges, ngOnInit, ngDoCheck} = - directiveDef.type.prototype as OnChanges & OnInit & DoCheck; + const {ngOnChanges, ngOnInit, ngDoCheck} = directiveDef.type.prototype as OnChanges & + OnInit & + DoCheck; if (ngOnChanges as Function | undefined) { const wrappedOnChanges = NgOnChangesFeatureImpl(directiveDef); @@ -81,14 +101,17 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void { for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) { const directiveDef = tView.data[i] as DirectiveDef; ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef'); - const lifecycleHooks: AfterContentInit&AfterContentChecked&AfterViewInit&AfterViewChecked& - OnDestroy = directiveDef.type.prototype; + const lifecycleHooks: AfterContentInit & + AfterContentChecked & + AfterViewInit & + AfterViewChecked & + OnDestroy = directiveDef.type.prototype; const { ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, - ngOnDestroy + ngOnDestroy, } = lifecycleHooks; if (ngAfterContentInit) { @@ -135,7 +158,6 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void { * They are stored as flags in LView[PREORDER_HOOK_FLAGS]. */ - /** * Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were * executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read @@ -149,7 +171,7 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void { * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) */ -export function executeCheckHooks(lView: LView, hooks: HookData, nodeIndex?: number|null) { +export function executeCheckHooks(lView: LView, hooks: HookData, nodeIndex?: number | null) { callHooks(lView, hooks, InitPhaseState.InitPhaseCompleted, nodeIndex); } @@ -167,11 +189,17 @@ export function executeCheckHooks(lView: LView, hooks: HookData, nodeIndex?: num * case, when executing select(number)) */ export function executeInitAndCheckHooks( - lView: LView, hooks: HookData, initPhase: InitPhaseState, nodeIndex?: number|null) { + lView: LView, + hooks: HookData, + initPhase: InitPhaseState, + nodeIndex?: number | null, +) { ngDevMode && - assertNotEqual( - initPhase, InitPhaseState.InitPhaseCompleted, - 'Init pre-order hooks should not be called more than once'); + assertNotEqual( + initPhase, + InitPhaseState.InitPhaseCompleted, + 'Init pre-order hooks should not be called more than once', + ); if ((lView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) { callHooks(lView, hooks, initPhase, nodeIndex); } @@ -179,9 +207,11 @@ export function executeInitAndCheckHooks( export function incrementInitPhaseFlags(lView: LView, initPhase: InitPhaseState): void { ngDevMode && - assertNotEqual( - initPhase, InitPhaseState.InitPhaseCompleted, - 'Init hooks phase should not be incremented after all init hooks have been run.'); + assertNotEqual( + initPhase, + InitPhaseState.InitPhaseCompleted, + 'Init hooks phase should not be incremented after all init hooks have been run.', + ); let flags = lView[FLAGS]; if ((flags & LViewFlags.InitPhaseStateMask) === initPhase) { flags &= LViewFlags.IndexWithinInitPhaseReset; @@ -205,17 +235,23 @@ export function incrementInitPhaseFlags(lView: LView, initPhase: InitPhaseState) * case, when executing select(number)) */ function callHooks( - currentView: LView, arr: HookData, initPhase: InitPhaseState, - currentNodeIndex: number|null|undefined): void { + currentView: LView, + arr: HookData, + initPhase: InitPhaseState, + currentNodeIndex: number | null | undefined, +): void { ngDevMode && - assertEqual( - isInCheckNoChangesMode(), false, - 'Hooks should never be run when in check no changes mode.'); - const startIndex = currentNodeIndex !== undefined ? - (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask) : - 0; + assertEqual( + isInCheckNoChangesMode(), + false, + 'Hooks should never be run when in check no changes mode.', + ); + const startIndex = + currentNodeIndex !== undefined + ? currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask + : 0; const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1; - const max = arr.length - 1; // Stop the loop at length - 1, because we look for the hook at i + 1 + const max = arr.length - 1; // Stop the loop at length - 1, because we look for the hook at i + 1 let lastNodeIndexFound = 0; for (let i = startIndex; i < max; i++) { const hook = arr[i + 1] as number | (() => void); @@ -232,8 +268,9 @@ function callHooks( if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) { callHook(currentView, initPhase, arr, i); currentView[PREORDER_HOOK_FLAGS] = - (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.NumberOfInitHooksCalledMask) + i + - 2; + (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.NumberOfInitHooksCalledMask) + + i + + 2; } i++; } @@ -267,14 +304,16 @@ function callHookInternal(directive: any, hook: () => void) { function callHook(currentView: LView, initPhase: InitPhaseState, arr: HookData, i: number) { const isInitHook = (arr[i] as number) < 0; const hook = arr[i + 1] as () => void; - const directiveIndex = isInitHook ? -arr[i] : arr[i] as number; + const directiveIndex = isInitHook ? -arr[i] : (arr[i] as number); const directive = currentView[directiveIndex]; if (isInitHook) { const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift; // The init phase state must be always checked here as it may have been recursively updated. - if (indexWithintInitPhase < - (currentView[PREORDER_HOOK_FLAGS] >> PreOrderHookFlags.NumberOfInitHooksCalledShift) && - (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) { + if ( + indexWithintInitPhase < + currentView[PREORDER_HOOK_FLAGS] >> PreOrderHookFlags.NumberOfInitHooksCalledShift && + (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase + ) { currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer; callHookInternal(directive, hook); } diff --git a/packages/core/src/render3/i18n/i18n_apply.ts b/packages/core/src/render3/i18n/i18n_apply.ts index 29db12430e7c9..ce6098c62f432 100644 --- a/packages/core/src/render3/i18n/i18n_apply.ts +++ b/packages/core/src/render3/i18n/i18n_apply.ts @@ -11,24 +11,59 @@ import {claimDehydratedIcuCase, isI18nHydrationSupportEnabled} from '../../hydra import {locateI18nRNodeByIndex} from '../../hydration/node_lookup_utils'; import {isDisconnectedNode, markRNodeAsClaimedByHydration} from '../../hydration/utils'; import {getPluralCase} from '../../i18n/localization'; -import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, throwError} from '../../util/assert'; +import { + assertDefined, + assertDomNode, + assertEqual, + assertGreaterThan, + assertIndexInRange, + throwError, +} from '../../util/assert'; import {assertIndexInExpandoRange, assertTIcu} from '../assert'; import {attachPatchData} from '../context_discovery'; import {elementPropertyInternal, setElementAttribute} from '../instructions/shared'; -import {ELEMENT_MARKER, I18nCreateOpCode, I18nCreateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode, IcuCreateOpCodes, IcuType, TI18n, TIcu} from '../interfaces/i18n'; +import { + ELEMENT_MARKER, + I18nCreateOpCode, + I18nCreateOpCodes, + I18nUpdateOpCode, + I18nUpdateOpCodes, + ICU_MARKER, + IcuCreateOpCode, + IcuCreateOpCodes, + IcuType, + TI18n, + TIcu, +} from '../interfaces/i18n'; import {TNode} from '../interfaces/node'; import {RElement, RNode, RText} from '../interfaces/renderer_dom'; import {SanitizerFn} from '../interfaces/sanitization'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, TView} from '../interfaces/view'; -import {createCommentNode, createElementNode, createTextNode, nativeInsertBefore, nativeParentNode, nativeRemoveNode, updateTextNode} from '../node_manipulation'; -import {getBindingIndex, isInSkipHydrationBlock, lastNodeWasCreated, wasLastNodeCreated} from '../state'; +import { + createCommentNode, + createElementNode, + createTextNode, + nativeInsertBefore, + nativeParentNode, + nativeRemoveNode, + updateTextNode, +} from '../node_manipulation'; +import { + getBindingIndex, + isInSkipHydrationBlock, + lastNodeWasCreated, + wasLastNodeCreated, +} from '../state'; import {renderStringify} from '../util/stringify_utils'; import {getNativeByIndex, unwrapRNode} from '../util/view_utils'; import {getLocaleId} from './i18n_locale_id'; -import {getCurrentICUCaseIndex, getParentFromIcuCreateOpCode, getRefFromIcuCreateOpCode, getTIcu} from './i18n_util'; - - +import { + getCurrentICUCaseIndex, + getParentFromIcuCreateOpCode, + getRefFromIcuCreateOpCode, + getTIcu, +} from './i18n_util'; /** * Keep track of which input bindings in `ɵɵi18nExp` have changed. @@ -71,8 +106,9 @@ export function applyI18n(tView: TView, lView: LView, index: number) { ngDevMode && assertDefined(tView, `tView should be defined`); const tI18n = tView.data[index] as TI18n | I18nUpdateOpCodes; // When `index` points to an `ɵɵi18nAttributes` then we have an array otherwise `TI18n` - const updateOpCodes: I18nUpdateOpCodes = - Array.isArray(tI18n) ? tI18n as I18nUpdateOpCodes : (tI18n as TI18n).update; + const updateOpCodes: I18nUpdateOpCodes = Array.isArray(tI18n) + ? (tI18n as I18nUpdateOpCodes) + : (tI18n as TI18n).update; const bindingsStartIndex = getBindingIndex() - changeMaskCounter - 1; applyUpdateOpCodes(tView, lView, updateOpCodes, bindingsStartIndex, changeMask); } @@ -82,8 +118,10 @@ export function applyI18n(tView: TView, lView: LView, index: number) { } function createNodeWithoutHydration( - lView: LView, textOrName: string, - nodeType: typeof Node.COMMENT_NODE|typeof Node.TEXT_NODE|typeof Node.ELEMENT_NODE) { + lView: LView, + textOrName: string, + nodeType: typeof Node.COMMENT_NODE | typeof Node.TEXT_NODE | typeof Node.ELEMENT_NODE, +) { const renderer = lView[RENDERER]; switch (nodeType) { @@ -104,12 +142,18 @@ let _locateOrCreateNode: typeof locateOrCreateNodeImpl = (lView, index, textOrNa }; function locateOrCreateNodeImpl( - lView: LView, index: number, textOrName: string, - nodeType: typeof Node.COMMENT_NODE|typeof Node.TEXT_NODE|typeof Node.ELEMENT_NODE) { + lView: LView, + index: number, + textOrName: string, + nodeType: typeof Node.COMMENT_NODE | typeof Node.TEXT_NODE | typeof Node.ELEMENT_NODE, +) { const hydrationInfo = lView[HYDRATION]; const noOffsetIndex = index - HEADER_OFFSET; - const isNodeCreationMode = !isI18nHydrationSupportEnabled() || !hydrationInfo || - isInSkipHydrationBlock() || isDisconnectedNode(hydrationInfo, noOffsetIndex); + const isNodeCreationMode = + !isI18nHydrationSupportEnabled() || + !hydrationInfo || + isInSkipHydrationBlock() || + isDisconnectedNode(hydrationInfo, noOffsetIndex); lastNodeWasCreated(isNodeCreationMode); if (isNodeCreationMode) { @@ -126,10 +170,13 @@ function locateOrCreateNodeImpl( // need to be able to use the AST to generate a similar message. ngDevMode && assertDefined(native, 'expected native element'); ngDevMode && assertEqual((native as Node).nodeType, nodeType, 'expected matching nodeType'); - ngDevMode && nodeType === Node.ELEMENT_NODE && - assertEqual( - (native as HTMLElement).tagName.toLowerCase(), textOrName.toLowerCase(), - 'expecting matching tagName'); + ngDevMode && + nodeType === Node.ELEMENT_NODE && + assertEqual( + (native as HTMLElement).tagName.toLowerCase(), + textOrName.toLowerCase(), + 'expecting matching tagName', + ); ngDevMode && markRNodeAsClaimedByHydration(native); return native; @@ -151,23 +198,30 @@ export function enableLocateOrCreateI18nNodeImpl() { * @param insertInFrontOf DOM node that should be used as an anchor. */ export function applyCreateOpCodes( - lView: LView, createOpCodes: I18nCreateOpCodes, parentRNode: RElement|null, - insertInFrontOf: RElement|null): void { + lView: LView, + createOpCodes: I18nCreateOpCodes, + parentRNode: RElement | null, + insertInFrontOf: RElement | null, +): void { const renderer = lView[RENDERER]; for (let i = 0; i < createOpCodes.length; i++) { const opCode = createOpCodes[i++] as any; const text = createOpCodes[i] as string; const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT; const appendNow = - (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; + (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; const index = opCode >>> I18nCreateOpCode.SHIFT; let rNode = lView[index]; let lastNodeWasCreated = false; if (rNode === null) { // We only create new DOM nodes if they don't already exist: If ICU switches case back to a // case which was already instantiated, no need to create new DOM nodes. - rNode = lView[index] = - _locateOrCreateNode(lView, index, text, isComment ? Node.COMMENT_NODE : Node.TEXT_NODE); + rNode = lView[index] = _locateOrCreateNode( + lView, + index, + text, + isComment ? Node.COMMENT_NODE : Node.TEXT_NODE, + ); lastNodeWasCreated = wasLastNodeCreated(); } if (appendNow && parentRNode !== null && lastNodeWasCreated) { @@ -185,17 +239,21 @@ export function applyCreateOpCodes( * @param anchorRNode place where the i18n node should be inserted. */ export function applyMutableOpCodes( - tView: TView, mutableOpCodes: IcuCreateOpCodes, lView: LView, anchorRNode: RNode): void { + tView: TView, + mutableOpCodes: IcuCreateOpCodes, + lView: LView, + anchorRNode: RNode, +): void { ngDevMode && assertDomNode(anchorRNode); const renderer = lView[RENDERER]; // `rootIdx` represents the node into which all inserts happen. - let rootIdx: number|null = null; + let rootIdx: number | null = null; // `rootRNode` represents the real node into which we insert. This can be different from // `lView[rootIdx]` if we have projection. // - null we don't have a parent (as can be the case in when we are inserting into a root of // LView which has no parent.) // - `RElement` The element representing the root after taking projection into account. - let rootRNode!: RElement|null; + let rootRNode!: RElement | null; for (let i = 0; i < mutableOpCodes.length; i++) { const opCode = mutableOpCodes[i]; if (typeof opCode == 'string') { @@ -216,8 +274,8 @@ export function applyMutableOpCodes( rootIdx = parentIdx; rootRNode = nativeParentNode(renderer, anchorRNode); } - let insertInFrontOf: RNode|null; - let parentRNode: RElement|null; + let insertInFrontOf: RNode | null; + let parentRNode: RElement | null; if (parentIdx === rootIdx) { insertInFrontOf = anchorRNode; parentRNode = rootRNode; @@ -258,14 +316,21 @@ export function applyMutableOpCodes( // This code is used for ICU expressions only, since we don't support // directives/components in ICUs, we don't need to worry about inputs here setElementAttribute( - renderer, getNativeByIndex(elementNodeIndex, lView) as RElement, null, null, attrName, - attrValue, null); + renderer, + getNativeByIndex(elementNodeIndex, lView) as RElement, + null, + null, + attrName, + attrValue, + null, + ); break; default: if (ngDevMode) { throw new RuntimeError( - RuntimeErrorCode.INVALID_I18N_STRUCTURE, - `Unable to determine the type of mutate operation for "${opCode}"`); + RuntimeErrorCode.INVALID_I18N_STRUCTURE, + `Unable to determine the type of mutate operation for "${opCode}"`, + ); } } } else { @@ -275,13 +340,19 @@ export function applyMutableOpCodes( const commentNodeIndex = mutableOpCodes[++i] as number; if (lView[commentNodeIndex] === null) { ngDevMode && - assertEqual( - typeof commentValue, 'string', - `Expected "${commentValue}" to be a comment node value`); + assertEqual( + typeof commentValue, + 'string', + `Expected "${commentValue}" to be a comment node value`, + ); ngDevMode && ngDevMode.rendererCreateComment++; ngDevMode && assertIndexInExpandoRange(lView, commentNodeIndex); - const commentRNode = lView[commentNodeIndex] = - _locateOrCreateNode(lView, commentNodeIndex, commentValue, Node.COMMENT_NODE); + const commentRNode = (lView[commentNodeIndex] = _locateOrCreateNode( + lView, + commentNodeIndex, + commentValue, + Node.COMMENT_NODE, + )); // FIXME(misko): Attaching patch data is only needed for the root (Also add tests) attachPatchData(commentRNode, lView); } @@ -291,27 +362,32 @@ export function applyMutableOpCodes( const elementNodeIndex = mutableOpCodes[++i] as number; if (lView[elementNodeIndex] === null) { ngDevMode && - assertEqual( - typeof tagName, 'string', - `Expected "${tagName}" to be an element node tag name`); + assertEqual( + typeof tagName, + 'string', + `Expected "${tagName}" to be an element node tag name`, + ); ngDevMode && ngDevMode.rendererCreateElement++; ngDevMode && assertIndexInExpandoRange(lView, elementNodeIndex); - const elementRNode = lView[elementNodeIndex] = - _locateOrCreateNode(lView, elementNodeIndex, tagName, Node.ELEMENT_NODE); + const elementRNode = (lView[elementNodeIndex] = _locateOrCreateNode( + lView, + elementNodeIndex, + tagName, + Node.ELEMENT_NODE, + )); // FIXME(misko): Attaching patch data is only needed for the root (Also add tests) attachPatchData(elementRNode, lView); } break; default: ngDevMode && - throwError(`Unable to determine the type of mutate operation for "${opCode}"`); + throwError(`Unable to determine the type of mutate operation for "${opCode}"`); } } } } - /** * Apply `I18nUpdateOpCodes` OpCodes * @@ -323,8 +399,12 @@ export function applyMutableOpCodes( * `bindingsStartIndex`) */ export function applyUpdateOpCodes( - tView: TView, lView: LView, updateOpCodes: I18nUpdateOpCodes, bindingsStartIndex: number, - changeMask: number) { + tView: TView, + lView: LView, + updateOpCodes: I18nUpdateOpCodes, + bindingsStartIndex: number, + changeMask: number, +) { for (let i = 0; i < updateOpCodes.length; i++) { // bit code to check if we should apply the next update const checkBit = updateOpCodes[i] as number; @@ -333,7 +413,7 @@ export function applyUpdateOpCodes( if (checkBit & changeMask) { // The value has been updated since last checked let value = ''; - for (let j = i + 1; j <= (i + skipCodes); j++) { + for (let j = i + 1; j <= i + skipCodes; j++) { const opCode = updateOpCodes[j]; if (typeof opCode == 'string') { value += opCode; @@ -342,7 +422,7 @@ export function applyUpdateOpCodes( // Negative opCode represent `i18nExp` values offset. value += renderStringify(lView[bindingsStartIndex - opCode]); } else { - const nodeIndex = (opCode >>> I18nUpdateOpCode.SHIFT_REF); + const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF; switch (opCode & I18nUpdateOpCode.MASK_OPCODE) { case I18nUpdateOpCode.Attr: const propName = updateOpCodes[++j] as string; @@ -354,12 +434,25 @@ export function applyUpdateOpCodes( // not have TNode), in which case we know that there are no directives, and hence // we use attribute setting. setElementAttribute( - lView[RENDERER], lView[nodeIndex], null, tNodeOrTagName, propName, value, - sanitizeFn); + lView[RENDERER], + lView[nodeIndex], + null, + tNodeOrTagName, + propName, + value, + sanitizeFn, + ); } else { elementPropertyInternal( - tView, tNodeOrTagName, lView, propName, value, lView[RENDERER], sanitizeFn, - false); + tView, + tNodeOrTagName, + lView, + propName, + value, + lView[RENDERER], + sanitizeFn, + false, + ); } break; case I18nUpdateOpCode.Text: @@ -383,7 +476,7 @@ export function applyUpdateOpCodes( // we still need to execute `icuUpdateCase` because the case has changed recently due to // previous `icuSwitchCase` instruction. (`icuSwitchCase` and `icuUpdateCase` always come in // pairs.) - const nodeIndex = (opCode >>> I18nUpdateOpCode.SHIFT_REF); + const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF; const tIcu = getTIcu(tView, nodeIndex)!; const currentIndex = lView[tIcu.currentCaseLViewIndex]; if (currentIndex < 0) { @@ -475,14 +568,13 @@ function applyIcuSwitchCaseRemove(tView: TView, tIcu: TIcu, lView: LView) { } } - /** * Returns the index of the current case of an ICU expression depending on the main binding value * * @param icuExpression * @param bindingValue The value of the main binding used by this ICU expression */ -function getCaseIndex(icuExpression: TIcu, bindingValue: string): number|null { +function getCaseIndex(icuExpression: TIcu, bindingValue: string): number | null { let index = icuExpression.cases.indexOf(bindingValue); if (index === -1) { switch (icuExpression.type) { diff --git a/packages/core/src/render3/i18n/i18n_debug.ts b/packages/core/src/render3/i18n/i18n_debug.ts index cd3c8a6bbf17d..0c1bca94336fc 100644 --- a/packages/core/src/render3/i18n/i18n_debug.ts +++ b/packages/core/src/render3/i18n/i18n_debug.ts @@ -7,10 +7,23 @@ */ import {assertNumber, assertString} from '../../util/assert'; -import {ELEMENT_MARKER, I18nCreateOpCode, I18nCreateOpCodes, I18nRemoveOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode, IcuCreateOpCodes} from '../interfaces/i18n'; - -import {getInstructionFromIcuCreateOpCode, getParentFromIcuCreateOpCode, getRefFromIcuCreateOpCode} from './i18n_util'; +import { + ELEMENT_MARKER, + I18nCreateOpCode, + I18nCreateOpCodes, + I18nRemoveOpCodes, + I18nUpdateOpCode, + I18nUpdateOpCodes, + ICU_MARKER, + IcuCreateOpCode, + IcuCreateOpCodes, +} from '../interfaces/i18n'; +import { + getInstructionFromIcuCreateOpCode, + getParentFromIcuCreateOpCode, + getRefFromIcuCreateOpCode, +} from './i18n_util'; /** * Converts `I18nCreateOpCodes` array into a human readable format. @@ -23,18 +36,23 @@ import {getInstructionFromIcuCreateOpCode, getParentFromIcuCreateOpCode, getRefF * @param opcodes `I18nCreateOpCodes` if invoked as a function. */ export function i18nCreateOpCodesToString( - this: I18nCreateOpCodes|void, opcodes?: I18nCreateOpCodes): string[] { - const createOpCodes: I18nCreateOpCodes = opcodes || (Array.isArray(this) ? this : [] as any); + this: I18nCreateOpCodes | void, + opcodes?: I18nCreateOpCodes, +): string[] { + const createOpCodes: I18nCreateOpCodes = opcodes || (Array.isArray(this) ? this : ([] as any)); let lines: string[] = []; for (let i = 0; i < createOpCodes.length; i++) { const opCode = createOpCodes[i++] as any; const text = createOpCodes[i] as string; const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT; const appendNow = - (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; + (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; const index = opCode >>> I18nCreateOpCode.SHIFT; - lines.push(`lView[${index}] = document.${isComment ? 'createComment' : 'createText'}(${ - JSON.stringify(text)});`); + lines.push( + `lView[${index}] = document.${isComment ? 'createComment' : 'createText'}(${JSON.stringify( + text, + )});`, + ); if (appendNow) { lines.push(`parent.appendChild(lView[${index}]);`); } @@ -53,7 +71,9 @@ export function i18nCreateOpCodesToString( * @param opcodes `I18nUpdateOpCodes` if invoked as a function. */ export function i18nUpdateOpCodesToString( - this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] { + this: I18nUpdateOpCodes | void, + opcodes?: I18nUpdateOpCodes, +): string[] { const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : [])); let lines: string[] = []; @@ -76,7 +96,6 @@ export function i18nUpdateOpCodesToString( throw new Error('unexpected OpCode'); } - while (parser.hasMore()) { let mask = parser.consumeNumber(); let size = parser.consumeNumber(); @@ -115,7 +134,9 @@ export function i18nUpdateOpCodesToString( * @param opcodes `I18nCreateOpCodes` if invoked as a function. */ export function icuCreateOpCodesToString( - this: IcuCreateOpCodes|void, opcodes?: IcuCreateOpCodes): string[] { + this: IcuCreateOpCodes | void, + opcodes?: IcuCreateOpCodes, +): string[] { const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : [])); let lines: string[] = []; @@ -126,8 +147,7 @@ export function icuCreateOpCodesToString( case IcuCreateOpCode.AppendChild: return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`; case IcuCreateOpCode.Attr: - return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${ - parser.consumeString()}")`; + return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${parser.consumeString()}")`; } throw new Error('Unexpected OpCode: ' + getInstructionFromIcuCreateOpCode(opCode)); } @@ -168,7 +188,9 @@ export function icuCreateOpCodesToString( * @param opcodes `I18nRemoveOpCodes` if invoked as a function. */ export function i18nRemoveOpCodesToString( - this: I18nRemoveOpCodes|void, opcodes?: I18nRemoveOpCodes): string[] { + this: I18nRemoveOpCodes | void, + opcodes?: I18nRemoveOpCodes, +): string[] { const removeCodes = opcodes || (Array.isArray(this) ? this : []); let lines: string[] = []; @@ -186,7 +208,6 @@ export function i18nRemoveOpCodesToString( return lines; } - class OpCodeParser { i: number = 0; codes: any[]; @@ -211,7 +232,7 @@ class OpCodeParser { return value; } - consumeFunction(): Function|null { + consumeFunction(): Function | null { let value = this.codes[this.i++]; if (value === null || typeof value === 'function') { return value; @@ -219,7 +240,7 @@ class OpCodeParser { throw new Error('expecting function in OpCode'); } - consumeNumberOrString(): number|string { + consumeNumberOrString(): number | string { let value = this.codes[this.i++]; if (typeof value === 'string') { return value; @@ -228,10 +249,14 @@ class OpCodeParser { return value; } - consumeNumberStringOrMarker(): number|string|ICU_MARKER|ELEMENT_MARKER { + consumeNumberStringOrMarker(): number | string | ICU_MARKER | ELEMENT_MARKER { let value = this.codes[this.i++]; - if (typeof value === 'string' || typeof value === 'number' || value == ICU_MARKER || - value == ELEMENT_MARKER) { + if ( + typeof value === 'string' || + typeof value === 'number' || + value == ICU_MARKER || + value == ELEMENT_MARKER + ) { return value; } assertNumber(value, 'expecting number, string, ICU_MARKER or ELEMENT_MARKER in OpCode'); diff --git a/packages/core/src/render3/i18n/i18n_insert_before_index.ts b/packages/core/src/render3/i18n/i18n_insert_before_index.ts index 9efb2cbc73514..ee89fdb583437 100644 --- a/packages/core/src/render3/i18n/i18n_insert_before_index.ts +++ b/packages/core/src/render3/i18n/i18n_insert_before_index.ts @@ -44,7 +44,7 @@ import {getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore} from '../node_ export function addTNodeAndUpdateInsertBeforeIndex(previousTNodes: TNode[], newTNode: TNode) { // Start with Rule1 ngDevMode && - assertEqual(newTNode.insertBeforeIndex, null, 'We expect that insertBeforeIndex is not set'); + assertEqual(newTNode.insertBeforeIndex, null, 'We expect that insertBeforeIndex is not set'); previousTNodes.push(newTNode); if (previousTNodes.length > 1) { @@ -53,8 +53,10 @@ export function addTNodeAndUpdateInsertBeforeIndex(previousTNodes: TNode[], newT // Text nodes are created eagerly and so they don't need their `indexBeforeIndex` updated. // It is safe to ignore them. if (!isI18nText(existingTNode)) { - if (isNewTNodeCreatedBefore(existingTNode, newTNode) && - getInsertBeforeIndex(existingTNode) === null) { + if ( + isNewTNodeCreatedBefore(existingTNode, newTNode) && + getInsertBeforeIndex(existingTNode) === null + ) { // If it was created before us in time, (and it does not yet have `insertBeforeIndex`) // then add the `insertBeforeIndex`. setInsertBeforeIndex(existingTNode, newTNode.index); @@ -72,7 +74,7 @@ function isNewTNodeCreatedBefore(existingTNode: TNode, newTNode: TNode): boolean return isI18nText(newTNode) || existingTNode.index > newTNode.index; } -function getInsertBeforeIndex(tNode: TNode): number|null { +function getInsertBeforeIndex(tNode: TNode): number | null { const index = tNode.insertBeforeIndex; return Array.isArray(index) ? index[0] : index; } diff --git a/packages/core/src/render3/i18n/i18n_locale_id.ts b/packages/core/src/render3/i18n/i18n_locale_id.ts index 7fff4d11bf9f4..4d38c6bcc00a0 100644 --- a/packages/core/src/render3/i18n/i18n_locale_id.ts +++ b/packages/core/src/render3/i18n/i18n_locale_id.ts @@ -9,7 +9,6 @@ import {DEFAULT_LOCALE_ID} from '../../i18n/localization'; import {assertDefined} from '../../util/assert'; - /** * The locale id that the application is currently using (for translations and ICU expressions). * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine diff --git a/packages/core/src/render3/i18n/i18n_parse.ts b/packages/core/src/render3/i18n/i18n_parse.ts index a0d63b04507d6..c1cf4592d86f5 100644 --- a/packages/core/src/render3/i18n/i18n_parse.ts +++ b/packages/core/src/render3/i18n/i18n_parse.ts @@ -9,26 +9,65 @@ import '../../util/ng_dev_mode'; import '../../util/ng_i18n_closure_mode'; import {XSS_SECURITY_URL} from '../../error_details_base_url'; -import {getTemplateContent, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../../sanitization/html_sanitizer'; +import { + getTemplateContent, + URI_ATTRS, + VALID_ATTRS, + VALID_ELEMENTS, +} from '../../sanitization/html_sanitizer'; import {getInertBodyHelper} from '../../sanitization/inert_body'; import {_sanitizeUrl} from '../../sanitization/url_sanitizer'; -import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertOneOf, assertString} from '../../util/assert'; +import { + assertDefined, + assertEqual, + assertGreaterThanOrEqual, + assertOneOf, + assertString, +} from '../../util/assert'; import {CharCode} from '../../util/char_code'; import {loadIcuContainerVisitor} from '../instructions/i18n_icu_container_visitor'; import {allocExpando, createTNodeAtIndex} from '../instructions/shared'; import {getDocument} from '../interfaces/document'; -import {ELEMENT_MARKER, I18nCreateOpCode, I18nCreateOpCodes, I18nElementNode, I18nNode, I18nNodeKind, I18nPlaceholderNode, I18nPlaceholderType, I18nRemoveOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode, IcuCreateOpCodes, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n'; +import { + ELEMENT_MARKER, + I18nCreateOpCode, + I18nCreateOpCodes, + I18nElementNode, + I18nNode, + I18nNodeKind, + I18nPlaceholderNode, + I18nPlaceholderType, + I18nRemoveOpCodes, + I18nUpdateOpCode, + I18nUpdateOpCodes, + ICU_MARKER, + IcuCreateOpCode, + IcuCreateOpCodes, + IcuExpression, + IcuType, + TI18n, + TIcu, +} from '../interfaces/i18n'; import {TNode, TNodeType} from '../interfaces/node'; import {SanitizerFn} from '../interfaces/sanitization'; import {HEADER_OFFSET, LView, TView} from '../interfaces/view'; import {getCurrentParentTNode, getCurrentTNode, setCurrentTNode} from '../state'; -import {i18nCreateOpCodesToString, i18nRemoveOpCodesToString, i18nUpdateOpCodesToString, icuCreateOpCodesToString} from './i18n_debug'; +import { + i18nCreateOpCodesToString, + i18nRemoveOpCodesToString, + i18nUpdateOpCodesToString, + icuCreateOpCodesToString, +} from './i18n_debug'; import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; import {ensureIcuContainerVisitorLoaded} from './i18n_tree_shaking'; -import {createTNodePlaceholder, icuCreateOpCode, isRootTemplateMessage, setTIcu, setTNodeInsertBeforeIndex} from './i18n_util'; - - +import { + createTNodePlaceholder, + icuCreateOpCode, + isRootTemplateMessage, + setTIcu, + setTNodeInsertBeforeIndex, +} from './i18n_util'; const BINDING_REGEXP = /�(\d+):?\d*�/gi; const ICU_REGEXP = /({\s*�\d+:?\d*�\s*,\s*\S{6}\s*,[\s\S]*})/gi; @@ -63,7 +102,8 @@ function attachDebugGetter(obj: T, debugGetter: (this: T) => any): void { Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false}); } else { throw new Error( - 'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!'); + 'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!', + ); } } @@ -82,8 +122,13 @@ function attachDebugGetter(obj: T, debugGetter: (this: T) => any): void { * `ngIf`) (-1 otherwise) */ export function i18nStartFirstCreatePass( - tView: TView, parentTNodeIndex: number, lView: LView, index: number, message: string, - subTemplateIndex: number) { + tView: TView, + parentTNodeIndex: number, + lView: LView, + index: number, + message: string, + subTemplateIndex: number, +) { const rootTNode = getCurrentParentTNode(); const createOpCodes: I18nCreateOpCodes = [] as any; const updateOpCodes: I18nUpdateOpCodes = [] as any; @@ -109,8 +154,15 @@ export function i18nStartFirstCreatePass( ngDevMode && assertString(text, 'Parsed ICU part should be string'); if (text !== '') { i18nStartFirstCreatePassProcessTextNode( - astStack[0], tView, rootTNode, existingTNodeStack[0], createOpCodes, updateOpCodes, - lView, text); + astStack[0], + tView, + rootTNode, + existingTNodeStack[0], + createOpCodes, + updateOpCodes, + lView, + text, + ); } } else { // `j` is Even therefor `part` is an `ICUExpression` @@ -125,15 +177,30 @@ export function i18nStartFirstCreatePass( throw new Error(`Unable to parse ICU expression in "${message}" message.`); } const icuContainerTNode = createTNodeAndAddOpCode( - tView, rootTNode, existingTNodeStack[0], lView, createOpCodes, - ngDevMode ? `ICU ${index}:${icuExpression.mainBinding}` : '', true); + tView, + rootTNode, + existingTNodeStack[0], + lView, + createOpCodes, + ngDevMode ? `ICU ${index}:${icuExpression.mainBinding}` : '', + true, + ); const icuNodeIndex = icuContainerTNode.index; ngDevMode && - assertGreaterThanOrEqual( - icuNodeIndex, HEADER_OFFSET, 'Index must be in absolute LView offset'); + assertGreaterThanOrEqual( + icuNodeIndex, + HEADER_OFFSET, + 'Index must be in absolute LView offset', + ); icuStart( - astStack[0], tView, lView, updateOpCodes, parentTNodeIndex, icuExpression, - icuNodeIndex); + astStack[0], + tView, + lView, + updateOpCodes, + parentTNodeIndex, + icuExpression, + icuNodeIndex, + ); } } } else { @@ -142,7 +209,7 @@ export function i18nStartFirstCreatePass( const isClosing = value.charCodeAt(0) === CharCode.SLASH; const type = value.charCodeAt(isClosing ? 1 : 0); ngDevMode && assertOneOf(type, CharCode.STAR, CharCode.HASH); - const index = HEADER_OFFSET + Number.parseInt(value.substring((isClosing ? 2 : 1))); + const index = HEADER_OFFSET + Number.parseInt(value.substring(isClosing ? 2 : 1)); if (isClosing) { existingTNodeStack.shift(); astStack.shift(); @@ -156,8 +223,8 @@ export function i18nStartFirstCreatePass( kind: I18nNodeKind.PLACEHOLDER, index, children: [], - type: type === CharCode.HASH ? I18nPlaceholderType.ELEMENT : - I18nPlaceholderType.SUBTEMPLATE, + type: + type === CharCode.HASH ? I18nPlaceholderType.ELEMENT : I18nPlaceholderType.SUBTEMPLATE, }; astStack[0].push(placeholderNode); astStack.unshift(placeholderNode.children); @@ -185,8 +252,14 @@ export function i18nStartFirstCreatePass( * @param isICU true if a `Comment` node for ICU (instead of `Text`) node should be created. */ function createTNodeAndAddOpCode( - tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView, - createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode { + tView: TView, + rootTNode: TNode | null, + existingTNodes: TNode[], + lView: LView, + createOpCodes: I18nCreateOpCodes, + text: string | null, + isICU: boolean, +): TNode { const i18nNodeIdx = allocExpando(tView, lView, 1, null); let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT; let parentTNode = getCurrentParentTNode(); @@ -212,8 +285,12 @@ function createTNodeAndAddOpCode( // We store `{{?}}` so that when looking at debug `TNodeType.template` we can see where the // bindings are. const tNode = createTNodeAtIndex( - tView, i18nNodeIdx, isICU ? TNodeType.Icu : TNodeType.Text, - text === null ? (ngDevMode ? '{{?}}' : '') : text, null); + tView, + i18nNodeIdx, + isICU ? TNodeType.Icu : TNodeType.Text, + text === null ? (ngDevMode ? '{{?}}' : '') : text, + null, + ); addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode); const tNodeIdx = tNode.index; setCurrentTNode(tNode, false /* Text nodes are self closing */); @@ -245,12 +322,25 @@ function createTNodeAndAddOpCode( * @param text The translated text (which may contain binding) */ function i18nStartFirstCreatePassProcessTextNode( - ast: I18nNode[], tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], - createOpCodes: I18nCreateOpCodes, updateOpCodes: I18nUpdateOpCodes, lView: LView, - text: string): void { + ast: I18nNode[], + tView: TView, + rootTNode: TNode | null, + existingTNodes: TNode[], + createOpCodes: I18nCreateOpCodes, + updateOpCodes: I18nUpdateOpCodes, + lView: LView, + text: string, +): void { const hasBinding = text.match(BINDING_REGEXP); const tNode = createTNodeAndAddOpCode( - tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? null : text, false); + tView, + rootTNode, + existingTNodes, + lView, + createOpCodes, + hasBinding ? null : text, + false, + ); const index = tNode.index; if (hasBinding) { generateBindingUpdateOpCodes(updateOpCodes, text, index, null, 0, null); @@ -281,7 +371,8 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str // an invalid string while they're developing. if (ICU_REGEXP.test(message)) { throw new Error( - `ICU expressions are not supported in attributes. Message: "${message}".`); + `ICU expressions are not supported in attributes. Message: "${message}".`, + ); } // i18n attributes that hit this code path are guaranteed to have bindings, because @@ -289,15 +380,19 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str // Since this may not be the first i18n attribute on this element we need to pass in how // many previous bindings there have already been. generateBindingUpdateOpCodes( - updateOpCodes, message, previousElementIndex, attrName, countBindings(updateOpCodes), - null); + updateOpCodes, + message, + previousElementIndex, + attrName, + countBindings(updateOpCodes), + null, + ); } } tView.data[index] = updateOpCodes; } } - /** * Generate the OpCodes to update the bindings of a string. * @@ -310,15 +405,23 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str * @returns The mask value for these bindings */ function generateBindingUpdateOpCodes( - updateOpCodes: I18nUpdateOpCodes, str: string, destinationNode: number, attrName: string|null, - bindingStart: number, sanitizeFn: SanitizerFn|null): number { + updateOpCodes: I18nUpdateOpCodes, + str: string, + destinationNode: number, + attrName: string | null, + bindingStart: number, + sanitizeFn: SanitizerFn | null, +): number { ngDevMode && - assertGreaterThanOrEqual( - destinationNode, HEADER_OFFSET, 'Index must be in absolute LView offset'); - const maskIndex = updateOpCodes.length; // Location of mask - const sizeIndex = maskIndex + 1; // location of size for skipping - updateOpCodes.push(null, null); // Alloc space for mask and size - const startIndex = maskIndex + 2; // location of first allocation. + assertGreaterThanOrEqual( + destinationNode, + HEADER_OFFSET, + 'Index must be in absolute LView offset', + ); + const maskIndex = updateOpCodes.length; // Location of mask + const sizeIndex = maskIndex + 1; // location of size for skipping + updateOpCodes.push(null, null); // Alloc space for mask and size + const startIndex = maskIndex + 2; // location of first allocation. if (ngDevMode) { attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString); } @@ -340,8 +443,9 @@ function generateBindingUpdateOpCodes( } updateOpCodes.push( - destinationNode << I18nUpdateOpCode.SHIFT_REF | - (attrName ? I18nUpdateOpCode.Attr : I18nUpdateOpCode.Text)); + (destinationNode << I18nUpdateOpCode.SHIFT_REF) | + (attrName ? I18nUpdateOpCode.Attr : I18nUpdateOpCode.Text), + ); if (attrName) { updateOpCodes.push(attrName, sanitizeFn); } @@ -409,16 +513,16 @@ function removeInnerTemplateTranslation(message: string): string { } ngDevMode && - assertEqual( - inTemplate, false, - `Tag mismatch: unable to find the end of the sub-template in the translation "${ - message}"`); + assertEqual( + inTemplate, + false, + `Tag mismatch: unable to find the end of the sub-template in the translation "${message}"`, + ); res += message.slice(index); return res; } - /** * Extracts a part of a message and removes the rest. * @@ -441,7 +545,7 @@ export function getTranslationForTemplate(message: string, subTemplateIndex: num } else { // We want a specific sub-template const start = - message.indexOf(`:${subTemplateIndex}${MARKER}`) + 2 + subTemplateIndex.toString().length; + message.indexOf(`:${subTemplateIndex}${MARKER}`) + 2 + subTemplateIndex.toString().length; const end = message.search(new RegExp(`${MARKER}\\/\\*\\d+:${subTemplateIndex}${MARKER}`)); return removeInnerTemplateTranslation(message.substring(start, end)); } @@ -456,8 +560,14 @@ export function getTranslationForTemplate(message: string, subTemplateIndex: num * - `tView.data[anchorIdx]` points to the `TIcuContainerNode` if ICU is root (`null` otherwise) */ function icuStart( - ast: I18nNode[], tView: TView, lView: LView, updateOpCodes: I18nUpdateOpCodes, - parentIdx: number, icuExpression: IcuExpression, anchorIdx: number) { + ast: I18nNode[], + tView: TView, + lView: LView, + updateOpCodes: I18nUpdateOpCodes, + parentIdx: number, + icuExpression: IcuExpression, + anchorIdx: number, +) { ngDevMode && assertDefined(icuExpression, 'ICU expression must be defined'); let bindingMask = 0; const tIcu: TIcu = { @@ -467,7 +577,7 @@ function icuStart( cases: [], create: [], remove: [], - update: [] + update: [], }; addUpdateIcuSwitch(updateOpCodes, icuExpression, anchorIdx); setTIcu(tView, anchorIdx, tIcu); @@ -488,10 +598,18 @@ function icuStart( } const caseAst: I18nNode[] = []; cases.push(caseAst); - bindingMask = parseIcuCase( - caseAst, tView, tIcu, lView, updateOpCodes, parentIdx, icuExpression.cases[i], - valueArr.join(''), nestedIcus) | - bindingMask; + bindingMask = + parseIcuCase( + caseAst, + tView, + tIcu, + lView, + updateOpCodes, + parentIdx, + icuExpression.cases[i], + valueArr.join(''), + nestedIcus, + ) | bindingMask; } if (bindingMask) { addUpdateIcuUpdate(updateOpCodes, bindingMask, anchorIdx); @@ -500,7 +618,7 @@ function icuStart( kind: I18nNodeKind.ICU, index: anchorIdx, cases, - currentCaseLViewIndex: tIcu.currentCaseLViewIndex + currentCaseLViewIndex: tIcu.currentCaseLViewIndex, }); } @@ -513,22 +631,25 @@ function icuStart( */ function parseICUBlock(pattern: string): IcuExpression { const cases = []; - const values: (string|IcuExpression)[][] = []; + const values: (string | IcuExpression)[][] = []; let icuType = IcuType.plural; let mainBinding = 0; - pattern = pattern.replace(ICU_BLOCK_REGEXP, function(str: string, binding: string, type: string) { - if (type === 'select') { - icuType = IcuType.select; - } else { - icuType = IcuType.plural; - } - mainBinding = parseInt(binding.slice(1), 10); - return ''; - }); + pattern = pattern.replace( + ICU_BLOCK_REGEXP, + function (str: string, binding: string, type: string) { + if (type === 'select') { + icuType = IcuType.select; + } else { + icuType = IcuType.plural; + } + mainBinding = parseInt(binding.slice(1), 10); + return ''; + }, + ); const parts = i18nParseTextIntoPartsAndICU(pattern) as string[]; // Looking for (key block)+ sequence. One of the keys has to be "other". - for (let pos = 0; pos < parts.length;) { + for (let pos = 0; pos < parts.length; ) { let key = parts[pos++].trim(); if (icuType === IcuType.plural) { // Key can be "=x", we just want "x" @@ -548,7 +669,6 @@ function parseICUBlock(pattern: string): IcuExpression { return {type: icuType, mainBinding: mainBinding, cases, values}; } - /** * Breaks pattern into strings and top level {...} blocks. * Can be used to break a message into text and ICU expressions, or to break an ICU expression @@ -559,20 +679,20 @@ function parseICUBlock(pattern: string): IcuExpression { * - odd positions: `string` => text between ICU expressions * - even positions: `ICUExpression` => ICU expression parsed into `ICUExpression` record. */ -function i18nParseTextIntoPartsAndICU(pattern: string): (string|IcuExpression)[] { +function i18nParseTextIntoPartsAndICU(pattern: string): (string | IcuExpression)[] { if (!pattern) { return []; } let prevPos = 0; const braceStack = []; - const results: (string|IcuExpression)[] = []; + const results: (string | IcuExpression)[] = []; const braces = /[{}]/g; // lastIndex doesn't get set to 0 so we have to. braces.lastIndex = 0; let match; - while (match = braces.exec(pattern)) { + while ((match = braces.exec(pattern))) { const pos = match.index; if (match[0] == '}') { braceStack.pop(); @@ -603,15 +723,21 @@ function i18nParseTextIntoPartsAndICU(pattern: string): (string|IcuExpression)[] return results; } - /** * Parses a node, its children and its siblings, and generates the mutate & update OpCodes. * */ function parseIcuCase( - ast: I18nNode[], tView: TView, tIcu: TIcu, lView: LView, updateOpCodes: I18nUpdateOpCodes, - parentIdx: number, caseName: string, unsafeCaseHtml: string, - nestedIcus: IcuExpression[]): number { + ast: I18nNode[], + tView: TView, + tIcu: TIcu, + lView: LView, + updateOpCodes: I18nUpdateOpCodes, + parentIdx: number, + caseName: string, + unsafeCaseHtml: string, + nestedIcus: IcuExpression[], +): number { const create: IcuCreateOpCodes = [] as any; const remove: I18nRemoveOpCodes = [] as any; const update: I18nUpdateOpCodes = [] as any; @@ -628,20 +754,41 @@ function parseIcuCase( const inertBodyHelper = getInertBodyHelper(getDocument()); const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeCaseHtml); ngDevMode && assertDefined(inertBodyElement, 'Unable to generate inert body element'); - const inertRootNode = getTemplateContent(inertBodyElement!) as Element || inertBodyElement; + const inertRootNode = (getTemplateContent(inertBodyElement!) as Element) || inertBodyElement; if (inertRootNode) { return walkIcuTree( - ast, tView, tIcu, lView, updateOpCodes, create, remove, update, inertRootNode, parentIdx, - nestedIcus, 0); + ast, + tView, + tIcu, + lView, + updateOpCodes, + create, + remove, + update, + inertRootNode, + parentIdx, + nestedIcus, + 0, + ); } else { return 0; } } function walkIcuTree( - ast: I18nNode[], tView: TView, tIcu: TIcu, lView: LView, sharedUpdateOpCodes: I18nUpdateOpCodes, - create: IcuCreateOpCodes, remove: I18nRemoveOpCodes, update: I18nUpdateOpCodes, - parentNode: Element, parentIdx: number, nestedIcus: IcuExpression[], depth: number): number { + ast: I18nNode[], + tView: TView, + tIcu: TIcu, + lView: LView, + sharedUpdateOpCodes: I18nUpdateOpCodes, + create: IcuCreateOpCodes, + remove: I18nRemoveOpCodes, + update: I18nUpdateOpCodes, + parentNode: Element, + parentIdx: number, + nestedIcus: IcuExpression[], + depth: number, +): number { let bindingMask = 0; let currentNode = parentNode.firstChild; while (currentNode) { @@ -663,16 +810,23 @@ function walkIcuTree( if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) { if (URI_ATTRS[lowerAttrName]) { generateBindingUpdateOpCodes( - update, attr.value, newIndex, attr.name, 0, _sanitizeUrl); + update, + attr.value, + newIndex, + attr.name, + 0, + _sanitizeUrl, + ); } else { generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null); } } else { ngDevMode && - console.warn( - `WARNING: ignoring unsafe attribute value ` + - `${lowerAttrName} on element ${tagName} ` + - `(see ${XSS_SECURITY_URL})`); + console.warn( + `WARNING: ignoring unsafe attribute value ` + + `${lowerAttrName} on element ${tagName} ` + + `(see ${XSS_SECURITY_URL})`, + ); } } else { addCreateAttribute(create, newIndex, attr); @@ -686,10 +840,20 @@ function walkIcuTree( ast.push(elementNode); // Parse the children of this node (if any) bindingMask = - walkIcuTree( - elementNode.children, tView, tIcu, lView, sharedUpdateOpCodes, create, remove, - update, currentNode as Element, newIndex, nestedIcus, depth + 1) | - bindingMask; + walkIcuTree( + elementNode.children, + tView, + tIcu, + lView, + sharedUpdateOpCodes, + create, + remove, + update, + currentNode as Element, + newIndex, + nestedIcus, + depth + 1, + ) | bindingMask; addRemoveNode(remove, newIndex, depth); } break; @@ -700,7 +864,7 @@ function walkIcuTree( addRemoveNode(remove, newIndex, depth); if (hasBinding) { bindingMask = - generateBindingUpdateOpCodes(update, value, newIndex, null, 0, null) | bindingMask; + generateBindingUpdateOpCodes(update, value, newIndex, null, 0, null) | bindingMask; } ast.push({ kind: I18nNodeKind.TEXT, @@ -715,8 +879,12 @@ function walkIcuTree( const icuExpression: IcuExpression = nestedIcus[nestedIcuIndex]; // Create the comment node that will anchor the ICU expression addCreateNodeAndAppend( - create, ICU_MARKER, ngDevMode ? `nested ICU ${nestedIcuIndex}` : '', parentIdx, - newIndex); + create, + ICU_MARKER, + ngDevMode ? `nested ICU ${nestedIcuIndex}` : '', + parentIdx, + newIndex, + ); icuStart(ast, tView, lView, sharedUpdateOpCodes, parentIdx, icuExpression, newIndex); addRemoveNestedIcu(remove, newIndex, depth); } @@ -735,33 +903,49 @@ function addRemoveNode(remove: I18nRemoveOpCodes, index: number, depth: number) function addRemoveNestedIcu(remove: I18nRemoveOpCodes, index: number, depth: number) { if (depth === 0) { - remove.push(~index); // remove ICU at `index` - remove.push(index); // remove ICU comment at `index` + remove.push(~index); // remove ICU at `index` + remove.push(index); // remove ICU comment at `index` } } function addUpdateIcuSwitch( - update: I18nUpdateOpCodes, icuExpression: IcuExpression, index: number) { + update: I18nUpdateOpCodes, + icuExpression: IcuExpression, + index: number, +) { update.push( - toMaskBit(icuExpression.mainBinding), 2, -1 - icuExpression.mainBinding, - index << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch); + toMaskBit(icuExpression.mainBinding), + 2, + -1 - icuExpression.mainBinding, + (index << I18nUpdateOpCode.SHIFT_REF) | I18nUpdateOpCode.IcuSwitch, + ); } function addUpdateIcuUpdate(update: I18nUpdateOpCodes, bindingMask: number, index: number) { - update.push(bindingMask, 1, index << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate); + update.push(bindingMask, 1, (index << I18nUpdateOpCode.SHIFT_REF) | I18nUpdateOpCode.IcuUpdate); } function addCreateNodeAndAppend( - create: IcuCreateOpCodes, marker: null|ICU_MARKER|ELEMENT_MARKER, text: string, - appendToParentIdx: number, createAtIdx: number) { + create: IcuCreateOpCodes, + marker: null | ICU_MARKER | ELEMENT_MARKER, + text: string, + appendToParentIdx: number, + createAtIdx: number, +) { if (marker !== null) { create.push(marker); } create.push( - text, createAtIdx, - icuCreateOpCode(IcuCreateOpCode.AppendChild, appendToParentIdx, createAtIdx)); + text, + createAtIdx, + icuCreateOpCode(IcuCreateOpCode.AppendChild, appendToParentIdx, createAtIdx), + ); } function addCreateAttribute(create: IcuCreateOpCodes, newIndex: number, attr: Attr) { - create.push(newIndex << IcuCreateOpCode.SHIFT_REF | IcuCreateOpCode.Attr, attr.name, attr.value); + create.push( + (newIndex << IcuCreateOpCode.SHIFT_REF) | IcuCreateOpCode.Attr, + attr.name, + attr.value, + ); } diff --git a/packages/core/src/render3/i18n/i18n_postprocess.ts b/packages/core/src/render3/i18n/i18n_postprocess.ts index 10ecf87f092dc..dbf92d1dd712a 100644 --- a/packages/core/src/render3/i18n/i18n_postprocess.ts +++ b/packages/core/src/render3/i18n/i18n_postprocess.ts @@ -20,7 +20,6 @@ const PP_TEMPLATE_ID_REGEXP = /\d+\:(\d+)/; // Contains the following fields: [templateId, isCloseTemplateTag, placeholder] type PostprocessPlaceholder = [number, boolean, string]; - /** * Handles message string post-processing for internationalization. * @@ -42,7 +41,9 @@ type PostprocessPlaceholder = [number, boolean, string]; * @codeGenApi */ export function i18nPostprocess( - message: string, replacements: {[key: string]: (string|string[])} = {}): string { + message: string, + replacements: {[key: string]: string | string[]} = {}, +): string { /** * Step 1: resolve all multi-value placeholders like [�#5�|�*1:1��#2:1�|�#4:1�] * @@ -112,7 +113,7 @@ export function i18nPostprocess( * Step 3: replace all placeholders used inside ICUs in a form of {PLACEHOLDER} */ result = result.replace(PP_ICU_PLACEHOLDERS_REGEXP, (match, key): string => { - return replacements.hasOwnProperty(key) ? replacements[key] as string : match; + return replacements.hasOwnProperty(key) ? (replacements[key] as string) : match; }); /** diff --git a/packages/core/src/render3/i18n/i18n_tree_shaking.ts b/packages/core/src/render3/i18n/i18n_tree_shaking.ts index 52ec6348fab36..654a7d66df5b3 100644 --- a/packages/core/src/render3/i18n/i18n_tree_shaking.ts +++ b/packages/core/src/render3/i18n/i18n_tree_shaking.ts @@ -17,15 +17,18 @@ import {TIcuContainerNode} from '../interfaces/node'; import {RNode} from '../interfaces/renderer_dom'; import {LView} from '../interfaces/view'; - -let _icuContainerIterate: (tIcuContainerNode: TIcuContainerNode, lView: LView) => - (() => RNode | null); +let _icuContainerIterate: ( + tIcuContainerNode: TIcuContainerNode, + lView: LView, +) => () => RNode | null; /** * Iterator which provides ability to visit all of the `TIcuContainerNode` root `RNode`s. */ -export function icuContainerIterate(tIcuContainerNode: TIcuContainerNode, lView: LView): () => - RNode | null { +export function icuContainerIterate( + tIcuContainerNode: TIcuContainerNode, + lView: LView, +): () => RNode | null { return _icuContainerIterate(tIcuContainerNode, lView); } @@ -36,7 +39,8 @@ export function icuContainerIterate(tIcuContainerNode: TIcuContainerNode, lView: * bundler to tree shake ICU logic and only load it if ICU instruction is executed. */ export function ensureIcuContainerVisitorLoaded( - loader: () => ((tIcuContainerNode: TIcuContainerNode, lView: LView) => (() => RNode | null))) { + loader: () => (tIcuContainerNode: TIcuContainerNode, lView: LView) => () => RNode | null, +) { if (_icuContainerIterate === undefined) { // Do not inline this function. We want to keep `ensureIcuContainerVisitorLoaded` light, so it // can be inlined into call-site. diff --git a/packages/core/src/render3/i18n/i18n_util.ts b/packages/core/src/render3/i18n/i18n_util.ts index 67df3b9dc6100..00a89093f32d8 100644 --- a/packages/core/src/render3/i18n/i18n_util.ts +++ b/packages/core/src/render3/i18n/i18n_util.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertEqual, assertGreaterThan, assertGreaterThanOrEqual, throwError} from '../../util/assert'; +import { + assertEqual, + assertGreaterThan, + assertGreaterThanOrEqual, + throwError, +} from '../../util/assert'; import {assertTIcu, assertTNode} from '../assert'; import {createTNodeAtIndex} from '../instructions/shared'; import {IcuCreateOpCode, TIcu} from '../interfaces/i18n'; @@ -18,7 +23,6 @@ import {getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore} from '../node_ import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; - /** * Retrieve `TIcu` at a given `index`. * @@ -32,19 +36,22 @@ import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; * @param tView Current `TView`. * @param index Index where the value should be read from. */ -export function getTIcu(tView: TView, index: number): TIcu|null { +export function getTIcu(tView: TView, index: number): TIcu | null { const value = tView.data[index] as null | TIcu | TIcuContainerNode | string; if (value === null || typeof value === 'string') return null; - if (ngDevMode && - !(value.hasOwnProperty('tView') || value.hasOwnProperty('currentCaseLViewIndex'))) { - throwError('We expect to get \'null\'|\'TIcu\'|\'TIcuContainer\', but got: ' + value); + if ( + ngDevMode && + !(value.hasOwnProperty('tView') || value.hasOwnProperty('currentCaseLViewIndex')) + ) { + throwError("We expect to get 'null'|'TIcu'|'TIcuContainer', but got: " + value); } // Here the `value.hasOwnProperty('currentCaseLViewIndex')` is a polymorphic read as it can be // either TIcu or TIcuContainerNode. This is not ideal, but we still think it is OK because it // will be just two cases which fits into the browser inline cache (inline cache can take up to // 4) - const tIcu = value.hasOwnProperty('currentCaseLViewIndex') ? value as TIcu : - (value as TIcuContainerNode).value; + const tIcu = value.hasOwnProperty('currentCaseLViewIndex') + ? (value as TIcu) + : (value as TIcuContainerNode).value; ngDevMode && assertTIcu(tIcu); return tIcu; } @@ -66,9 +73,11 @@ export function getTIcu(tView: TView, index: number): TIcu|null { export function setTIcu(tView: TView, index: number, tIcu: TIcu): void { const tNode = tView.data[index] as null | TIcuContainerNode; ngDevMode && - assertEqual( - tNode === null || tNode.hasOwnProperty('tView'), true, - 'We expect to get \'null\'|\'TIcuContainer\''); + assertEqual( + tNode === null || tNode.hasOwnProperty('tView'), + true, + "We expect to get 'null'|'TIcuContainer'", + ); if (tNode === null) { tView.data[index] = tIcu; } else { @@ -87,8 +96,10 @@ export function setTNodeInsertBeforeIndex(tNode: TNode, index: number) { let insertBeforeIndex = tNode.insertBeforeIndex; if (insertBeforeIndex === null) { setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore); - insertBeforeIndex = tNode.insertBeforeIndex = - [null!/* may be updated to number later */, index]; + insertBeforeIndex = tNode.insertBeforeIndex = [ + null! /* may be updated to number later */, + index, + ]; } else { assertEqual(Array.isArray(insertBeforeIndex), true, 'Expecting array here'); (insertBeforeIndex as number[]).push(index); @@ -101,13 +112,15 @@ export function setTNodeInsertBeforeIndex(tNode: TNode, index: number) { * See `TNodeType.Placeholder` for more information. */ export function createTNodePlaceholder( - tView: TView, previousTNodes: TNode[], index: number): TNode { + tView: TView, + previousTNodes: TNode[], + index: number, +): TNode { const tNode = createTNodeAtIndex(tView, index, TNodeType.Placeholder, null, null); addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tNode); return tNode; } - /** * Returns current ICU case. * @@ -117,8 +130,8 @@ export function createTNodePlaceholder( * for cases which have just been switched. This function removes the negative flag. */ export function getCurrentICUCaseIndex(tIcu: TIcu, lView: LView) { - const currentCase: number|null = lView[tIcu.currentCaseLViewIndex]; - return currentCase === null ? currentCase : (currentCase < 0 ? ~currentCase : currentCase); + const currentCase: number | null = lView[tIcu.currentCaseLViewIndex]; + return currentCase === null ? currentCase : currentCase < 0 ? ~currentCase : currentCase; } export function getParentFromIcuCreateOpCode(mergedCode: number): number { @@ -136,11 +149,13 @@ export function getInstructionFromIcuCreateOpCode(mergedCode: number): number { export function icuCreateOpCode(opCode: IcuCreateOpCode, parentIdx: number, refIdx: number) { ngDevMode && assertGreaterThanOrEqual(parentIdx, 0, 'Missing parent index'); ngDevMode && assertGreaterThan(refIdx, 0, 'Missing ref index'); - return opCode | parentIdx << IcuCreateOpCode.SHIFT_PARENT | refIdx << IcuCreateOpCode.SHIFT_REF; + return ( + opCode | (parentIdx << IcuCreateOpCode.SHIFT_PARENT) | (refIdx << IcuCreateOpCode.SHIFT_REF) + ); } // Returns whether the given value corresponds to a root template message, // or a sub-template. -export function isRootTemplateMessage(subTemplateIndex: number): subTemplateIndex is - 1 { +export function isRootTemplateMessage(subTemplateIndex: number): subTemplateIndex is -1 { return subTemplateIndex === -1; } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 03e07ffadc319..10289aaf49cc2 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -14,10 +14,32 @@ import {ɵɵInputTransformsFeature} from './features/input_transforms_feature'; import {ɵɵNgOnChangesFeature} from './features/ng_onchanges_feature'; import {ɵɵProvidersFeature} from './features/providers_feature'; import {ɵɵStandaloneFeature} from './features/standalone_feature'; -import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, PipeDef} from './interfaces/definition'; -import {ɵɵComponentDeclaration, ɵɵDirectiveDeclaration, ɵɵFactoryDeclaration, ɵɵInjectorDeclaration, ɵɵNgModuleDeclaration, ɵɵPipeDeclaration} from './interfaces/public_definitions'; +import { + ComponentDef, + ComponentTemplate, + ComponentType, + DirectiveDef, + DirectiveType, + PipeDef, +} from './interfaces/definition'; +import { + ɵɵComponentDeclaration, + ɵɵDirectiveDeclaration, + ɵɵFactoryDeclaration, + ɵɵInjectorDeclaration, + ɵɵNgModuleDeclaration, + ɵɵPipeDeclaration, +} from './interfaces/public_definitions'; import {ɵɵsetComponentScope, ɵɵsetNgModuleScope} from './scope'; -import {ComponentDebugMetadata, DirectiveDebugMetadata, getComponent, getDirectiveMetadata, getDirectives, getHostElement, getRenderedText} from './util/discovery_utils'; +import { + ComponentDebugMetadata, + DirectiveDebugMetadata, + getComponent, + getDirectiveMetadata, + getDirectives, + getHostElement, + getRenderedText, +} from './util/discovery_utils'; export {NgModuleType} from '../metadata/ng_module_def'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref'; @@ -27,7 +49,6 @@ export {getLocaleId, setLocaleId} from './i18n/i18n_locale_id'; export { store, ɵɵadvance, - ɵɵattribute, ɵɵattributeInterpolate1, ɵɵattributeInterpolate2, @@ -38,7 +59,6 @@ export { ɵɵattributeInterpolate7, ɵɵattributeInterpolate8, ɵɵattributeInterpolateV, - ɵɵclassMap, ɵɵclassMapInterpolate1, ɵɵclassMapInterpolate2, @@ -49,34 +69,24 @@ export { ɵɵclassMapInterpolate7, ɵɵclassMapInterpolate8, ɵɵclassMapInterpolateV, - ɵɵclassProp, - ɵɵcomponentInstance, - ɵɵdirectiveInject, - ɵɵelement, - ɵɵelementContainer, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, - ɵɵgetCurrentView, ɵɵhostProperty, ɵɵinjectAttribute, ɵɵinvalidFactory, - ɵɵlistener, - ɵɵnamespaceHTML, ɵɵnamespaceMathML, ɵɵnamespaceSVG, - ɵɵnextContext, - ɵɵprojection, ɵɵprojectionDef, ɵɵproperty, @@ -90,23 +100,18 @@ export { ɵɵpropertyInterpolate7, ɵɵpropertyInterpolate8, ɵɵpropertyInterpolateV, - ɵɵcontentQuery, ɵɵcontentQuerySignal, - ɵɵloadQuery, ɵɵqueryRefresh, ɵɵqueryAdvance, ɵɵviewQuery, ɵɵviewQuerySignal, - ɵɵreference, - ɵɵrepeater, ɵɵrepeaterCreate, ɵɵrepeaterTrackByIdentity, ɵɵrepeaterTrackByIndex, - ɵɵstyleMap, ɵɵstyleMapInterpolate1, ɵɵstyleMapInterpolate2, @@ -117,7 +122,6 @@ export { ɵɵstyleMapInterpolate7, ɵɵstyleMapInterpolate8, ɵɵstyleMapInterpolateV, - ɵɵstyleProp, ɵɵstylePropInterpolate1, ɵɵstylePropInterpolate2, @@ -128,14 +132,10 @@ export { ɵɵstylePropInterpolate7, ɵɵstylePropInterpolate8, ɵɵstylePropInterpolateV, - ɵɵsyntheticHostListener, ɵɵsyntheticHostProperty, - ɵɵtemplate, - ɵɵconditional, - ɵɵdefer, ɵɵdeferWhen, ɵɵdeferOnIdle, @@ -152,7 +152,6 @@ export { ɵɵdeferPrefetchOnInteraction, ɵɵdeferPrefetchOnViewport, ɵɵdeferEnableTimerScheduling, - ɵɵtext, ɵɵtextInterpolate, ɵɵtextInterpolate1, @@ -164,11 +163,9 @@ export { ɵɵtextInterpolate7, ɵɵtextInterpolate8, ɵɵtextInterpolateV, - ɵɵtwoWayProperty, ɵɵtwoWayBindingSet, ɵɵtwoWayListener, - ɵgetUnknownElementStrictMode, ɵsetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, @@ -178,28 +175,22 @@ export { DEFER_BLOCK_DEPENDENCY_INTERCEPTOR as ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR, DEFER_BLOCK_CONFIG as ɵDEFER_BLOCK_CONFIG, } from '../defer/instructions'; +export {DeferBlockDependencyInterceptor as ɵDeferBlockDependencyInterceptor} from '../defer/interfaces'; export { - DeferBlockDependencyInterceptor as ɵDeferBlockDependencyInterceptor, -} from '../defer/interfaces'; -export {ɵɵi18n, ɵɵi18nApply, ɵɵi18nAttributes, ɵɵi18nEnd, ɵɵi18nExp,ɵɵi18nPostprocess, ɵɵi18nStart} from './instructions/i18n'; + ɵɵi18n, + ɵɵi18nApply, + ɵɵi18nAttributes, + ɵɵi18nEnd, + ɵɵi18nExp, + ɵɵi18nPostprocess, + ɵɵi18nStart, +} from './instructions/i18n'; export {RenderFlags} from './interfaces/definition'; -export { - AttributeMarker -} from './interfaces/attribute_marker'; +export {AttributeMarker} from './interfaces/attribute_marker'; export {CssSelectorList, ProjectionSlots} from './interfaces/projection'; -export { - setClassMetadata, - setClassMetadataAsync, -} from './metadata'; +export {setClassMetadata, setClassMetadataAsync} from './metadata'; export {NgModuleFactory, NgModuleRef, createEnvironmentInjector} from './ng_module_ref'; -export { - ɵɵpipe, - ɵɵpipeBind1, - ɵɵpipeBind2, - ɵɵpipeBind3, - ɵɵpipeBind4, - ɵɵpipeBindV, -} from './pipe'; +export {ɵɵpipe, ɵɵpipeBind1, ɵɵpipeBind2, ɵɵpipeBind3, ɵɵpipeBind4, ɵɵpipeBindV} from './pipe'; export { ɵɵpureFunction0, ɵɵpureFunction1, @@ -212,16 +203,10 @@ export { ɵɵpureFunction8, ɵɵpureFunctionV, } from './pure_function'; -export { - ɵɵdisableBindings, - - ɵɵenableBindings, - ɵɵresetView, - ɵɵrestoreView, -} from './state'; +export {ɵɵdisableBindings, ɵɵenableBindings, ɵɵresetView, ɵɵrestoreView} from './state'; export {NO_CHANGE} from './tokens'; -export { ɵɵresolveBody, ɵɵresolveDocument,ɵɵresolveWindow} from './util/misc_utils'; -export { ɵɵtemplateRefExtractor} from './view_engine_compatibility_prebound'; +export {ɵɵresolveBody, ɵɵresolveDocument, ɵɵresolveWindow} from './util/misc_utils'; +export {ɵɵtemplateRefExtractor} from './view_engine_compatibility_prebound'; export {ɵɵgetComponentDepsFactory} from './local_compilation'; export {ɵsetClassDebugInfo} from './debug/set_debug_info'; // clang-format on diff --git a/packages/core/src/render3/instructions/advance.ts b/packages/core/src/render3/instructions/advance.ts index aaf8148e78b71..891fe17ca1152 100644 --- a/packages/core/src/render3/instructions/advance.ts +++ b/packages/core/src/render3/instructions/advance.ts @@ -9,8 +9,13 @@ import {assertGreaterThan} from '../../util/assert'; import {assertIndexInDeclRange} from '../assert'; import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks'; import {FLAGS, InitPhaseState, LView, LViewFlags, TVIEW, TView} from '../interfaces/view'; -import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelectedIndex} from '../state'; - +import { + getLView, + getSelectedIndex, + getTView, + isInCheckNoChangesMode, + setSelectedIndex, +} from '../state'; /** * Advances to an element for later binding instructions. @@ -38,18 +43,26 @@ import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelecte export function ɵɵadvance(delta: number = 1): void { ngDevMode && assertGreaterThan(delta, 0, 'Can only advance forward'); selectIndexInternal( - getTView(), getLView(), getSelectedIndex() + delta, !!ngDevMode && isInCheckNoChangesMode()); + getTView(), + getLView(), + getSelectedIndex() + delta, + !!ngDevMode && isInCheckNoChangesMode(), + ); } export function selectIndexInternal( - tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) { + tView: TView, + lView: LView, + index: number, + checkNoChangesMode: boolean, +) { ngDevMode && assertIndexInDeclRange(lView[TVIEW], index); // Flush the initial hooks for elements in the view that have been added up to this point. // PERF WARNING: do NOT extract this to a separate function without running benchmarks if (!checkNoChangesMode) { const hooksInitPhaseCompleted = - (lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; + (lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; if (hooksInitPhaseCompleted) { const preOrderCheckHooks = tView.preOrderCheckHooks; if (preOrderCheckHooks !== null) { diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index 8eee3b76de8bd..d911f3712cf40 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -37,7 +37,12 @@ export * from './di'; export * from './di_attr'; export * from './element'; export * from './element_container'; -export {ɵgetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, ɵsetUnknownElementStrictMode, ɵsetUnknownPropertyStrictMode} from './element_validation'; +export { + ɵgetUnknownElementStrictMode, + ɵgetUnknownPropertyStrictMode, + ɵsetUnknownElementStrictMode, + ɵsetUnknownPropertyStrictMode, +} from './element_validation'; export * from './get_current_view'; export * from './host_property'; export * from './i18n'; diff --git a/packages/core/src/render3/instructions/attribute.ts b/packages/core/src/render3/instructions/attribute.ts index 3ba7ee3309795..f3b4ddab3652c 100644 --- a/packages/core/src/render3/instructions/attribute.ts +++ b/packages/core/src/render3/instructions/attribute.ts @@ -10,8 +10,6 @@ import {SanitizerFn} from '../interfaces/sanitization'; import {getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state'; import {elementAttributeInternal, storePropertyBindingMetadata} from './shared'; - - /** * Updates the value of or removes a bound attribute on an Element. * @@ -26,8 +24,11 @@ import {elementAttributeInternal, storePropertyBindingMetadata} from './shared'; * @codeGenApi */ export function ɵɵattribute( - name: string, value: any, sanitizer?: SanitizerFn|null, - namespace?: string): typeof ɵɵattribute { + name: string, + value: any, + sanitizer?: SanitizerFn | null, + namespace?: string, +): typeof ɵɵattribute { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { diff --git a/packages/core/src/render3/instructions/attribute_interpolation.ts b/packages/core/src/render3/instructions/attribute_interpolation.ts index 7d8a0a8350990..c15491af9468f 100644 --- a/packages/core/src/render3/instructions/attribute_interpolation.ts +++ b/packages/core/src/render3/instructions/attribute_interpolation.ts @@ -8,11 +8,19 @@ import {SanitizerFn} from '../interfaces/sanitization'; import {getBindingIndex, getLView, getSelectedTNode, getTView} from '../state'; import {NO_CHANGE} from '../tokens'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {elementAttributeInternal, storePropertyBindingMetadata} from './shared'; - - /** * * Update an interpolated attribute on an element with single bound value surrounded by text. @@ -38,16 +46,27 @@ import {elementAttributeInternal, storePropertyBindingMetadata} from './shared'; * @codeGenApi */ export function ɵɵattributeInterpolate1( - attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn, - namespace?: string): typeof ɵɵattributeInterpolate1 { + attrName: string, + prefix: string, + v0: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate1 { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 1, prefix, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 1, + prefix, + suffix, + ); } return ɵɵattributeInterpolate1; } @@ -79,16 +98,30 @@ export function ɵɵattributeInterpolate1( * @codeGenApi */ export function ɵɵattributeInterpolate2( - attrName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, - sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate2 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate2 { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 2, prefix, i0, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 2, + prefix, + i0, + suffix, + ); } return ɵɵattributeInterpolate2; } @@ -123,17 +156,33 @@ export function ɵɵattributeInterpolate2( * @codeGenApi */ export function ɵɵattributeInterpolate3( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, - suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate3 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate3 { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 3, prefix, i0, i1, - suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 3, + prefix, + i0, + i1, + suffix, + ); } return ɵɵattributeInterpolate3; } @@ -170,18 +219,36 @@ export function ɵɵattributeInterpolate3( * @codeGenApi */ export function ɵɵattributeInterpolate4( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, suffix: string, sanitizer?: SanitizerFn, - namespace?: string): typeof ɵɵattributeInterpolate4 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate4 { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 4, prefix, i0, i1, i2, - suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 4, + prefix, + i0, + i1, + i2, + suffix, + ); } return ɵɵattributeInterpolate4; } @@ -220,19 +287,52 @@ export function ɵɵattributeInterpolate4( * @codeGenApi */ export function ɵɵattributeInterpolate5( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn, - namespace?: string): typeof ɵɵattributeInterpolate5 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate5 { const lView = getLView(); - const interpolatedValue = - interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + const interpolatedValue = interpolation5( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 5, prefix, i0, i1, i2, - i3, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 5, + prefix, + i0, + i1, + i2, + i3, + suffix, + ); } return ɵɵattributeInterpolate5; } @@ -273,19 +373,57 @@ export function ɵɵattributeInterpolate5( * @codeGenApi */ export function ɵɵattributeInterpolate6( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn, - namespace?: string): typeof ɵɵattributeInterpolate6 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate6 { const lView = getLView(); - const interpolatedValue = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolatedValue = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 6, prefix, i0, i1, i2, - i3, i4, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 6, + prefix, + i0, + i1, + i2, + i3, + i4, + suffix, + ); } return ɵɵattributeInterpolate6; } @@ -328,19 +466,62 @@ export function ɵɵattributeInterpolate6( * @codeGenApi */ export function ɵɵattributeInterpolate7( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, - sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate7 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate7 { const lView = getLView(); - const interpolatedValue = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolatedValue = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 7, prefix, i0, i1, i2, - i3, i4, i5, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 7, + prefix, + i0, + i1, + i2, + i3, + i4, + i5, + suffix, + ); } return ɵɵattributeInterpolate7; } @@ -385,19 +566,67 @@ export function ɵɵattributeInterpolate7( * @codeGenApi */ export function ɵɵattributeInterpolate8( - attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string, sanitizer?: SanitizerFn, namespace?: string): typeof ɵɵattributeInterpolate8 { + attrName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolate8 { const lView = getLView(); const interpolatedValue = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace); ngDevMode && - storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 8, prefix, i0, i1, i2, - i3, i4, i5, i6, suffix); + storePropertyBindingMetadata( + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - 8, + prefix, + i0, + i1, + i2, + i3, + i4, + i5, + i6, + suffix, + ); } return ɵɵattributeInterpolate8; } @@ -429,21 +658,28 @@ export function ɵɵattributeInterpolate8( * @codeGenApi */ export function ɵɵattributeInterpolateV( - attrName: string, values: any[], sanitizer?: SanitizerFn, - namespace?: string): typeof ɵɵattributeInterpolateV { + attrName: string, + values: any[], + sanitizer?: SanitizerFn, + namespace?: string, +): typeof ɵɵattributeInterpolateV { const lView = getLView(); const interpolated = interpolationV(lView, values); if (interpolated !== NO_CHANGE) { const tNode = getSelectedTNode(); elementAttributeInternal(tNode, lView, attrName, interpolated, sanitizer, namespace); if (ngDevMode) { - const interpolationInBetween = [values[0]]; // prefix + const interpolationInBetween = [values[0]]; // prefix for (let i = 2; i < values.length; i += 2) { interpolationInBetween.push(values[i]); } storePropertyBindingMetadata( - getTView().data, tNode, 'attr.' + attrName, - getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween); + getTView().data, + tNode, + 'attr.' + attrName, + getBindingIndex() - interpolationInBetween.length + 1, + ...interpolationInBetween, + ); } } return ɵɵattributeInterpolateV; diff --git a/packages/core/src/render3/instructions/change_detection.ts b/packages/core/src/render3/instructions/change_detection.ts index 251b9a74e094c..275a47ed63d12 100644 --- a/packages/core/src/render3/instructions/change_detection.ts +++ b/packages/core/src/render3/instructions/change_detection.ts @@ -6,21 +6,70 @@ * found in the LICENSE file at https://angular.io/license */ -import {consumerAfterComputation, consumerBeforeComputation, consumerPollProducersForChange, ReactiveNode} from '@angular/core/primitives/signals'; +import { + consumerAfterComputation, + consumerBeforeComputation, + consumerPollProducersForChange, + ReactiveNode, +} from '@angular/core/primitives/signals'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; import {assertDefined, assertEqual} from '../../util/assert'; import {assertLContainer} from '../assert'; import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks'; -import {CONTAINER_HEADER_OFFSET, LContainer, LContainerFlags, MOVED_VIEWS} from '../interfaces/container'; +import { + CONTAINER_HEADER_OFFSET, + LContainer, + LContainerFlags, + MOVED_VIEWS, +} from '../interfaces/container'; import {ComponentTemplate, RenderFlags} from '../interfaces/definition'; -import {CONTEXT, EFFECTS_TO_SCHEDULE, ENVIRONMENT, FLAGS, InitPhaseState, LView, LViewFlags, PARENT, REACTIVE_TEMPLATE_CONSUMER, TVIEW, TView, TViewType} from '../interfaces/view'; -import {getOrBorrowReactiveLViewConsumer, maybeReturnReactiveLViewConsumer, ReactiveLViewConsumer} from '../reactive_lview_consumer'; -import {enterView, isInCheckNoChangesMode, isRefreshingViews, leaveView, setBindingIndex, setIsInCheckNoChangesMode, setIsRefreshingViews} from '../state'; +import { + CONTEXT, + EFFECTS_TO_SCHEDULE, + ENVIRONMENT, + FLAGS, + InitPhaseState, + LView, + LViewFlags, + PARENT, + REACTIVE_TEMPLATE_CONSUMER, + TVIEW, + TView, + TViewType, +} from '../interfaces/view'; +import { + getOrBorrowReactiveLViewConsumer, + maybeReturnReactiveLViewConsumer, + ReactiveLViewConsumer, +} from '../reactive_lview_consumer'; +import { + enterView, + isInCheckNoChangesMode, + isRefreshingViews, + leaveView, + setBindingIndex, + setIsInCheckNoChangesMode, + setIsRefreshingViews, +} from '../state'; import {getFirstLContainer, getNextLContainer} from '../util/view_traversal_utils'; -import {getComponentLViewByIndex, isCreationMode, markAncestorsForTraversal, markViewForRefresh, requiresRefreshOrTraversal, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils'; - -import {executeTemplate, executeViewQueryFn, handleError, processHostBindingOpCodes, refreshContentQueries} from './shared'; +import { + getComponentLViewByIndex, + isCreationMode, + markAncestorsForTraversal, + markViewForRefresh, + requiresRefreshOrTraversal, + resetPreOrderHookFlags, + viewAttachedToChangeDetector, +} from '../util/view_utils'; + +import { + executeTemplate, + executeViewQueryFn, + handleError, + processHostBindingOpCodes, + refreshContentQueries, +} from './shared'; /** * The maximum number of times the change detection traversal will rerun before throwing an error. @@ -28,7 +77,10 @@ import {executeTemplate, executeViewQueryFn, handleError, processHostBindingOpCo export const MAXIMUM_REFRESH_RERUNS = 100; export function detectChangesInternal( - lView: LView, notifyErrorHandler = true, mode = ChangeDetectionMode.Global) { + lView: LView, + notifyErrorHandler = true, + mode = ChangeDetectionMode.Global, +) { const environment = lView[ENVIRONMENT]; const rendererFactory = environment.rendererFactory; @@ -73,11 +125,12 @@ function detectChangesInViewWhileDirty(lView: LView, mode: ChangeDetectionMode) while (requiresRefreshOrTraversal(lView)) { if (retries === MAXIMUM_REFRESH_RERUNS) { throw new RuntimeError( - RuntimeErrorCode.INFINITE_CHANGE_DETECTION, - ngDevMode && - 'Infinite change detection while trying to refresh views. ' + - 'There may be components which each cause the other to require a refresh, ' + - 'causing an infinite loop.'); + RuntimeErrorCode.INFINITE_CHANGE_DETECTION, + ngDevMode && + 'Infinite change detection while trying to refresh views. ' + + 'There may be components which each cause the other to require a refresh, ' + + 'causing an infinite loop.', + ); } retries++; // Even if this view is detached, we still detect changes in targeted mode because this was @@ -99,7 +152,6 @@ export function checkNoChangesInternal(lView: LView, notifyErrorHandler = true) } } - /** * Different modes of traversing the logical view tree during change detection. * @@ -129,7 +181,11 @@ export const enum ChangeDetectionMode { */ export function refreshView( - tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) { + tView: TView, + lView: LView, + templateFn: ComponentTemplate<{}> | null, + context: T, +) { ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode'); const flags = lView[FLAGS]; if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return; @@ -140,13 +196,12 @@ export function refreshView( !isInCheckNoChangesPass && lView[ENVIRONMENT].inlineEffectRunner?.flush(); - // Start component reactive context // - We might already be in a reactive context if this is an embedded view of the host. // - We might be descending into a view that needs a consumer. enterView(lView); - let prevConsumer: ReactiveNode|null = null; - let currentConsumer: ReactiveLViewConsumer|null = null; + let prevConsumer: ReactiveNode | null = null; + let currentConsumer: ReactiveLViewConsumer | null = null; if (!isInCheckNoChangesPass && viewShouldHaveReactiveConsumer(tView)) { currentConsumer = getOrBorrowReactiveLViewConsumer(lView); prevConsumer = consumerBeforeComputation(currentConsumer); @@ -161,7 +216,7 @@ export function refreshView( } const hooksInitPhaseCompleted = - (flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; + (flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; // execute pre-order hooks (OnInit, OnChanges, DoCheck) // PERF WARNING: do NOT extract this to a separate function without running benchmarks @@ -203,7 +258,10 @@ export function refreshView( const contentHooks = tView.contentHooks; if (contentHooks !== null) { executeInitAndCheckHooks( - lView, contentHooks, InitPhaseState.AfterContentInitHooksToBeRun); + lView, + contentHooks, + InitPhaseState.AfterContentInitHooksToBeRun, + ); } incrementInitPhaseFlags(lView, InitPhaseState.AfterContentInitHooksToBeRun); } @@ -310,8 +368,11 @@ function viewShouldHaveReactiveConsumer(tView: TView) { * them by executing an associated template function. */ function detectChangesInEmbeddedViews(lView: LView, mode: ChangeDetectionMode) { - for (let lContainer = getFirstLContainer(lView); lContainer !== null; - lContainer = getNextLContainer(lContainer)) { + for ( + let lContainer = getFirstLContainer(lView); + lContainer !== null; + lContainer = getNextLContainer(lContainer) + ) { for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { const embeddedLView = lContainer[i]; detectChangesInViewIfAttached(embeddedLView, mode); @@ -325,8 +386,11 @@ function detectChangesInEmbeddedViews(lView: LView, mode: ChangeDetectionMode) { * @param lView The `LView` that may have transplanted views. */ function markTransplantedViewsForRefresh(lView: LView) { - for (let lContainer = getFirstLContainer(lView); lContainer !== null; - lContainer = getNextLContainer(lContainer)) { + for ( + let lContainer = getFirstLContainer(lView); + lContainer !== null; + lContainer = getNextLContainer(lContainer) + ) { if (!(lContainer[FLAGS] & LContainerFlags.HasTransplantedViews)) continue; const movedViews = lContainer[MOVED_VIEWS]!; @@ -345,7 +409,10 @@ function markTransplantedViewsForRefresh(lView: LView) { * @param componentHostIdx Element index in LView[] (adjusted for HEADER_OFFSET) */ function detectChangesInComponent( - hostLView: LView, componentHostIdx: number, mode: ChangeDetectionMode): void { + hostLView: LView, + componentHostIdx: number, + mode: ChangeDetectionMode, +): void { ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode'); const componentView = getComponentLViewByIndex(componentHostIdx, hostLView); detectChangesInViewIfAttached(componentView, mode); @@ -380,8 +447,9 @@ function detectChangesInView(lView: LView, mode: ChangeDetectionMode) { const consumer = lView[REACTIVE_TEMPLATE_CONSUMER]; // Refresh CheckAlways views in Global mode. - let shouldRefreshView: boolean = - !!(mode === ChangeDetectionMode.Global && flags & LViewFlags.CheckAlways); + let shouldRefreshView: boolean = !!( + mode === ChangeDetectionMode.Global && flags & LViewFlags.CheckAlways + ); // Refresh Dirty views in Global mode, as long as we're not in checkNoChanges. // CheckNoChanges never worked with `OnPush` components because the `Dirty` flag was @@ -390,7 +458,10 @@ function detectChangesInView(lView: LView, mode: ChangeDetectionMode) { // before the CheckNoChanges pass. We don't want existing errors that are hidden by the // current CheckNoChanges bug to surface when making unrelated changes. shouldRefreshView ||= !!( - flags & LViewFlags.Dirty && mode === ChangeDetectionMode.Global && !isInCheckNoChangesPass); + flags & LViewFlags.Dirty && + mode === ChangeDetectionMode.Global && + !isInCheckNoChangesPass + ); // Always refresh views marked for refresh, regardless of mode. shouldRefreshView ||= !!(flags & LViewFlags.RefreshView); @@ -418,7 +489,10 @@ function detectChangesInView(lView: LView, mode: ChangeDetectionMode) { /** Refreshes child components in the current view (update mode). */ function detectChangesInChildComponents( - hostLView: LView, components: number[], mode: ChangeDetectionMode): void { + hostLView: LView, + components: number[], + mode: ChangeDetectionMode, +): void { for (let i = 0; i < components.length; i++) { detectChangesInComponent(hostLView, components[i], mode); } diff --git a/packages/core/src/render3/instructions/class_map_interpolation.ts b/packages/core/src/render3/instructions/class_map_interpolation.ts index 598da87a121b2..48dada1972f37 100644 --- a/packages/core/src/render3/instructions/class_map_interpolation.ts +++ b/packages/core/src/render3/instructions/class_map_interpolation.ts @@ -8,11 +8,19 @@ import {keyValueArraySet} from '../../util/array_utils'; import {getLView} from '../state'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {checkStylingMap, classStringParser} from './styling'; - - /** * * Update an interpolated class on an element with single bound value surrounded by text. @@ -64,7 +72,12 @@ export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string * @codeGenApi */ export function ɵɵclassMapInterpolate2( - prefix: string, v0: any, i0: string, v1: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); @@ -97,7 +110,14 @@ export function ɵɵclassMapInterpolate2( * @codeGenApi */ export function ɵɵclassMapInterpolate3( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); @@ -132,8 +152,16 @@ export function ɵɵclassMapInterpolate3( * @codeGenApi */ export function ɵɵclassMapInterpolate4( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); @@ -170,11 +198,33 @@ export function ɵɵclassMapInterpolate4( * @codeGenApi */ export function ɵɵclassMapInterpolate5( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + const interpolatedValue = interpolation5( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + suffix, + ); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } @@ -211,11 +261,37 @@ export function ɵɵclassMapInterpolate5( * @codeGenApi */ export function ɵɵclassMapInterpolate6( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolatedValue = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } @@ -254,11 +330,41 @@ export function ɵɵclassMapInterpolate6( * @codeGenApi */ export function ɵɵclassMapInterpolate7( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolatedValue = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } @@ -299,12 +405,45 @@ export function ɵɵclassMapInterpolate7( * @codeGenApi */ export function ɵɵclassMapInterpolate8( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } diff --git a/packages/core/src/render3/instructions/component_instance.ts b/packages/core/src/render3/instructions/component_instance.ts index bc449b53a7de9..ace1d3664330a 100644 --- a/packages/core/src/render3/instructions/component_instance.ts +++ b/packages/core/src/render3/instructions/component_instance.ts @@ -10,7 +10,6 @@ import {assertDefined} from '../../util/assert'; import {CONTEXT, DECLARATION_COMPONENT_VIEW} from '../interfaces/view'; import {getLView} from '../state'; - /** * Instruction that returns the component instance in which the current instruction is executing. * This is a constant-time version of `nextContent` for the case where we know that we need the diff --git a/packages/core/src/render3/instructions/control_flow.ts b/packages/core/src/render3/instructions/control_flow.ts index 1710a5e6c4310..b5523d6c18e38 100644 --- a/packages/core/src/render3/instructions/control_flow.ts +++ b/packages/core/src/render3/instructions/control_flow.ts @@ -19,13 +19,27 @@ import {bindingUpdated} from '../bindings'; import {CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentTemplate} from '../interfaces/definition'; import {TNode} from '../interfaces/node'; -import {CONTEXT, DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, HYDRATION, LView, TVIEW, TView} from '../interfaces/view'; +import { + CONTEXT, + DECLARATION_COMPONENT_VIEW, + HEADER_OFFSET, + HYDRATION, + LView, + TVIEW, + TView, +} from '../interfaces/view'; import {LiveCollection, reconcile} from '../list_reconciliation'; import {destroyLView, detachView} from '../node_manipulation'; import {getLView, getSelectedIndex, getTView, nextBindingIndex} from '../state'; import {NO_CHANGE} from '../tokens'; import {getConstant, getTNode} from '../util/view_utils'; -import {addLViewToLContainer, createAndRenderEmbeddedLView, getLViewFromLContainer, removeLViewFromLContainer, shouldAddViewToDom} from '../view_manipulation'; +import { + addLViewToLContainer, + createAndRenderEmbeddedLView, + getLViewFromLContainer, + removeLViewFromLContainer, + shouldAddViewToDom, +} from '../view_manipulation'; import {declareTemplate} from './template'; @@ -45,10 +59,11 @@ export function ɵɵconditional(matchingTemplateIndex: number, contextValue?: const hostLView = getLView(); const bindingIndex = nextBindingIndex(); const prevMatchingTemplateIndex: number = - hostLView[bindingIndex] !== NO_CHANGE ? hostLView[bindingIndex] : -1; - const prevContainer = prevMatchingTemplateIndex !== -1 ? - getLContainer(hostLView, HEADER_OFFSET + prevMatchingTemplateIndex) : - undefined; + hostLView[bindingIndex] !== NO_CHANGE ? hostLView[bindingIndex] : -1; + const prevContainer = + prevMatchingTemplateIndex !== -1 + ? getLContainer(hostLView, HEADER_OFFSET + prevMatchingTemplateIndex) + : undefined; const viewInContainerIdx = 0; if (bindingUpdated(hostLView, bindingIndex, matchingTemplateIndex)) { @@ -67,14 +82,20 @@ export function ɵɵconditional(matchingTemplateIndex: number, contextValue?: const nextContainer = getLContainer(hostLView, nextLContainerIndex); const templateTNode = getExistingTNode(hostLView[TVIEW], nextLContainerIndex); - const dehydratedView = - findMatchingDehydratedView(nextContainer, templateTNode.tView!.ssrId); - const embeddedLView = - createAndRenderEmbeddedLView(hostLView, templateTNode, contextValue, {dehydratedView}); + const dehydratedView = findMatchingDehydratedView( + nextContainer, + templateTNode.tView!.ssrId, + ); + const embeddedLView = createAndRenderEmbeddedLView(hostLView, templateTNode, contextValue, { + dehydratedView, + }); addLViewToLContainer( - nextContainer, embeddedLView, viewInContainerIdx, - shouldAddViewToDom(templateTNode, dehydratedView)); + nextContainer, + embeddedLView, + viewInContainerIdx, + shouldAddViewToDom(templateTNode, dehydratedView), + ); } } finally { setActiveConsumer(prevConsumer); @@ -82,7 +103,7 @@ export function ɵɵconditional(matchingTemplateIndex: number, contextValue?: } else if (prevContainer !== undefined) { // We might keep displaying the same template but the actual value of the expression could have // changed - re-bind in context. - const lView = getLViewFromLContainer(prevContainer, viewInContainerIdx); + const lView = getLViewFromLContainer(prevContainer, viewInContainerIdx); if (lView !== undefined) { lView[CONTEXT] = contextValue; } @@ -90,7 +111,11 @@ export function ɵɵconditional(matchingTemplateIndex: number, contextValue?: } export class RepeaterContext { - constructor(private lContainer: LContainer, public $implicit: T, public $index: number) {} + constructor( + private lContainer: LContainer, + public $implicit: T, + public $index: number, + ) {} get $count(): number { return this.lContainer.length - CONTAINER_HEADER_OFFSET; @@ -122,8 +147,10 @@ export function ɵɵrepeaterTrackByIdentity(_: number, value: T) { class RepeaterMetadata { constructor( - public hasEmptyBlock: boolean, public trackByFn: TrackByFunction, - public liveCollection?: LiveCollectionLContainerImpl) {} + public hasEmptyBlock: boolean, + public trackByFn: TrackByFunction, + public liveCollection?: LiveCollectionLContainerImpl, + ) {} } /** @@ -153,42 +180,67 @@ class RepeaterMetadata { * @codeGenApi */ export function ɵɵrepeaterCreate( - index: number, templateFn: ComponentTemplate, decls: number, vars: number, - tagName: string|null, attrsIndex: number|null, trackByFn: TrackByFunction, - trackByUsesComponentInstance?: boolean, emptyTemplateFn?: ComponentTemplate, - emptyDecls?: number, emptyVars?: number, emptyTagName?: string|null, - emptyAttrsIndex?: number|null): void { + index: number, + templateFn: ComponentTemplate, + decls: number, + vars: number, + tagName: string | null, + attrsIndex: number | null, + trackByFn: TrackByFunction, + trackByUsesComponentInstance?: boolean, + emptyTemplateFn?: ComponentTemplate, + emptyDecls?: number, + emptyVars?: number, + emptyTagName?: string | null, + emptyAttrsIndex?: number | null, +): void { performanceMarkFeature('NgControlFlow'); ngDevMode && - assertFunction( - trackByFn, `A track expression must be a function, was ${typeof trackByFn} instead.`); + assertFunction( + trackByFn, + `A track expression must be a function, was ${typeof trackByFn} instead.`, + ); const lView = getLView(); const tView = getTView(); const hasEmptyBlock = emptyTemplateFn !== undefined; const hostLView = getLView(); - const boundTrackBy = trackByUsesComponentInstance ? - // We only want to bind when necessary, because it produces a + const boundTrackBy = trackByUsesComponentInstance + ? // We only want to bind when necessary, because it produces a // new function. For pure functions it's not necessary. - trackByFn.bind(hostLView[DECLARATION_COMPONENT_VIEW][CONTEXT]) : - trackByFn; + trackByFn.bind(hostLView[DECLARATION_COMPONENT_VIEW][CONTEXT]) + : trackByFn; const metadata = new RepeaterMetadata(hasEmptyBlock, boundTrackBy); hostLView[HEADER_OFFSET + index] = metadata; declareTemplate( - lView, tView, index + 1, templateFn, decls, vars, tagName, - getConstant(tView.consts, attrsIndex)); + lView, + tView, + index + 1, + templateFn, + decls, + vars, + tagName, + getConstant(tView.consts, attrsIndex), + ); if (hasEmptyBlock) { ngDevMode && - assertDefined(emptyDecls, 'Missing number of declarations for the empty repeater block.'); + assertDefined(emptyDecls, 'Missing number of declarations for the empty repeater block.'); ngDevMode && - assertDefined(emptyVars, 'Missing number of bindings for the empty repeater block.'); + assertDefined(emptyVars, 'Missing number of bindings for the empty repeater block.'); declareTemplate( - lView, tView, index + 2, emptyTemplateFn, emptyDecls!, emptyVars!, emptyTagName, - getConstant(tView.consts, emptyAttrsIndex)); + lView, + tView, + index + 2, + emptyTemplateFn, + emptyDecls!, + emptyVars!, + emptyTagName, + getConstant(tView.consts, emptyAttrsIndex), + ); } } @@ -226,8 +278,10 @@ class OperationsCounter { } } -class LiveCollectionLContainerImpl extends - LiveCollection>, unknown> { +class LiveCollectionLContainerImpl extends LiveCollection< + LView>, + unknown +> { operationsCounter = ngDevMode ? new OperationsCounter() : undefined; /** @@ -237,7 +291,10 @@ class LiveCollectionLContainerImpl extends */ private needsIndexUpdate = false; constructor( - private lContainer: LContainer, private hostLView: LView, private templateTNode: TNode) { + private lContainer: LContainer, + private hostLView: LView, + private templateTNode: TNode, + ) { super(); } @@ -251,18 +308,27 @@ class LiveCollectionLContainerImpl extends const dehydratedView = lView[HYDRATION] as DehydratedContainerView; this.needsIndexUpdate ||= index !== this.length; addLViewToLContainer( - this.lContainer, lView, index, shouldAddViewToDom(this.templateTNode, dehydratedView)); + this.lContainer, + lView, + index, + shouldAddViewToDom(this.templateTNode, dehydratedView), + ); } override detach(index: number): LView> { this.needsIndexUpdate ||= index !== this.length - 1; return detachExistingView>(this.lContainer, index); } override create(index: number, value: unknown): LView> { - const dehydratedView = - findMatchingDehydratedView(this.lContainer, this.templateTNode.tView!.ssrId); + const dehydratedView = findMatchingDehydratedView( + this.lContainer, + this.templateTNode.tView!.ssrId, + ); const embeddedLView = createAndRenderEmbeddedLView( - this.hostLView, this.templateTNode, new RepeaterContext(this.lContainer, value, index), - {dehydratedView}); + this.hostLView, + this.templateTNode, + new RepeaterContext(this.lContainer, value, index), + {dehydratedView}, + ); this.operationsCounter?.recordCreate(); return embeddedLView; @@ -300,7 +366,7 @@ class LiveCollectionLContainerImpl extends * @param collection - the collection instance to be checked for changes * @codeGenApi */ -export function ɵɵrepeater(collection: Iterable|undefined|null): void { +export function ɵɵrepeater(collection: Iterable | undefined | null): void { const prevConsumer = setActiveConsumer(null); const metadataSlotIdx = getSelectedIndex(); try { @@ -312,8 +378,11 @@ export function ɵɵrepeater(collection: Iterable|undefined|null): void if (metadata.liveCollection === undefined) { const itemTemplateTNode = getExistingTNode(hostTView, containerIndex); - metadata.liveCollection = - new LiveCollectionLContainerImpl(lContainer, hostLView, itemTemplateTNode); + metadata.liveCollection = new LiveCollectionLContainerImpl( + lContainer, + hostLView, + itemTemplateTNode, + ); } else { metadata.liveCollection.reset(); } @@ -325,15 +394,18 @@ export function ɵɵrepeater(collection: Iterable|undefined|null): void // reconciliation pass. Note that this warning might be "overreacting" and report cases where // the collection re-creation is the intended behavior. Still, the assumption is that most of // the time it is undesired. - if (ngDevMode && metadata.trackByFn === ɵɵrepeaterTrackByIdentity && - liveCollection.operationsCounter?.wasReCreated(liveCollection.length) && - isViewExpensiveToRecreate(getExistingLViewFromLContainer(lContainer, 0))) { + if ( + ngDevMode && + metadata.trackByFn === ɵɵrepeaterTrackByIdentity && + liveCollection.operationsCounter?.wasReCreated(liveCollection.length) && + isViewExpensiveToRecreate(getExistingLViewFromLContainer(lContainer, 0)) + ) { const message = formatRuntimeError( - RuntimeErrorCode.LOOP_TRACK_RECREATE, - `The configured tracking expression (track by identity) caused re-creation of the entire collection of size ${ - liveCollection.length}. ` + - 'This is an expensive operation requiring destruction and subsequent creation of DOM nodes, directives, components etc. ' + - 'Please review the "track expression" and make sure that it uniquely identifies items in a collection.'); + RuntimeErrorCode.LOOP_TRACK_RECREATE, + `The configured tracking expression (track by identity) caused re-creation of the entire collection of size ${liveCollection.length}. ` + + 'This is an expensive operation requiring destruction and subsequent creation of DOM nodes, directives, components etc. ' + + 'Please review the "track expression" and make sure that it uniquely identifies items in a collection.', + ); console.warn(message); } @@ -349,13 +421,22 @@ export function ɵɵrepeater(collection: Iterable|undefined|null): void const lContainerForEmpty = getLContainer(hostLView, emptyTemplateIndex); if (isCollectionEmpty) { const emptyTemplateTNode = getExistingTNode(hostTView, emptyTemplateIndex); - const dehydratedView = - findMatchingDehydratedView(lContainerForEmpty, emptyTemplateTNode.tView!.ssrId); + const dehydratedView = findMatchingDehydratedView( + lContainerForEmpty, + emptyTemplateTNode.tView!.ssrId, + ); const embeddedLView = createAndRenderEmbeddedLView( - hostLView, emptyTemplateTNode, undefined, {dehydratedView}); + hostLView, + emptyTemplateTNode, + undefined, + {dehydratedView}, + ); addLViewToLContainer( - lContainerForEmpty, embeddedLView, 0, - shouldAddViewToDom(emptyTemplateTNode, dehydratedView)); + lContainerForEmpty, + embeddedLView, + 0, + shouldAddViewToDom(emptyTemplateTNode, dehydratedView), + ); } else { removeLViewFromLContainer(lContainerForEmpty, 0); } diff --git a/packages/core/src/render3/instructions/di.ts b/packages/core/src/render3/instructions/di.ts index 81870eeb1d8cc..a1577ac503b29 100644 --- a/packages/core/src/render3/instructions/di.ts +++ b/packages/core/src/render3/instructions/di.ts @@ -41,7 +41,10 @@ import {getCurrentTNode, getLView} from '../state'; */ export function ɵɵdirectiveInject(token: ProviderToken): T; export function ɵɵdirectiveInject(token: ProviderToken, flags: InjectFlags): T; -export function ɵɵdirectiveInject(token: ProviderToken, flags = InjectFlags.Default): T|null { +export function ɵɵdirectiveInject( + token: ProviderToken, + flags = InjectFlags.Default, +): T | null { const lView = getLView(); // Fall back to inject() if view hasn't been created. This situation can happen in tests // if inject utilities are used before bootstrapping. @@ -51,8 +54,12 @@ export function ɵɵdirectiveInject(token: ProviderToken, flags = InjectFl return ɵɵinject(token, flags); } const tNode = getCurrentTNode(); - const value = - getOrCreateInjectable(tNode as TDirectiveHostNode, lView, resolveForwardRef(token), flags); + const value = getOrCreateInjectable( + tNode as TDirectiveHostNode, + lView, + resolveForwardRef(token), + flags, + ); ngDevMode && emitInjectEvent(token as Type, value, flags); return value; } @@ -70,7 +77,8 @@ export function ɵɵdirectiveInject(token: ProviderToken, flags = InjectFl * @codeGenApi */ export function ɵɵinvalidFactory(): never { - const msg = - ngDevMode ? `This constructor was not compatible with Dependency Injection.` : 'invalid'; + const msg = ngDevMode + ? `This constructor was not compatible with Dependency Injection.` + : 'invalid'; throw new Error(msg); } diff --git a/packages/core/src/render3/instructions/di_attr.ts b/packages/core/src/render3/instructions/di_attr.ts index cbaaf1f3f1a36..0f2e2b969b5b0 100644 --- a/packages/core/src/render3/instructions/di_attr.ts +++ b/packages/core/src/render3/instructions/di_attr.ts @@ -13,6 +13,6 @@ import {getCurrentTNode} from '../state'; * * @codeGenApi */ -export function ɵɵinjectAttribute(attrNameToInject: string): string|null { +export function ɵɵinjectAttribute(attrNameToInject: string): string | null { return injectAttributeImpl(getCurrentTNode()!, attrNameToInject); } diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 40c974b65ae5f..3a8fba2a28456 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -6,34 +6,88 @@ * found in the LICENSE file at https://angular.io/license */ -import {invalidSkipHydrationHost, validateMatchingNode, validateNodeExists} from '../../hydration/error_handling'; +import { + invalidSkipHydrationHost, + validateMatchingNode, + validateNodeExists, +} from '../../hydration/error_handling'; import {locateNextRNode} from '../../hydration/node_lookup_utils'; -import {hasSkipHydrationAttrOnRElement, hasSkipHydrationAttrOnTNode} from '../../hydration/skip_hydration'; -import {getSerializedContainerViews, isDisconnectedNode, markRNodeAsClaimedByHydration, markRNodeAsSkippedByHydration, setSegmentHead} from '../../hydration/utils'; +import { + hasSkipHydrationAttrOnRElement, + hasSkipHydrationAttrOnTNode, +} from '../../hydration/skip_hydration'; +import { + getSerializedContainerViews, + isDisconnectedNode, + markRNodeAsClaimedByHydration, + markRNodeAsSkippedByHydration, + setSegmentHead, +} from '../../hydration/utils'; import {isDetachedByI18n} from '../../i18n/utils'; import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert'; import {assertFirstCreatePass, assertHasParent} from '../assert'; import {attachPatchData} from '../context_discovery'; import {registerPostOrderHooks} from '../hooks'; -import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node'; +import { + hasClassInput, + hasStyleInput, + TAttributes, + TElementNode, + TNode, + TNodeFlags, + TNodeType, +} from '../interfaces/node'; import {Renderer} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer_dom'; import {isComponentHost, isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, TView} from '../interfaces/view'; import {assertTNodeType} from '../node_assert'; -import {appendChild, clearElementContents, createElementNode, setupStaticAttributes} from '../node_manipulation'; -import {decreaseElementDepthCount, enterSkipHydrationBlock, getBindingIndex, getCurrentTNode, getElementDepthCount, getLView, getNamespace, getTView, increaseElementDepthCount, isCurrentTNodeParent, isInSkipHydrationBlock, isSkipHydrationRootTNode, lastNodeWasCreated, leaveSkipHydrationBlock, setCurrentTNode, setCurrentTNodeAsNotParent, wasLastNodeCreated} from '../state'; +import { + appendChild, + clearElementContents, + createElementNode, + setupStaticAttributes, +} from '../node_manipulation'; +import { + decreaseElementDepthCount, + enterSkipHydrationBlock, + getBindingIndex, + getCurrentTNode, + getElementDepthCount, + getLView, + getNamespace, + getTView, + increaseElementDepthCount, + isCurrentTNodeParent, + isInSkipHydrationBlock, + isSkipHydrationRootTNode, + lastNodeWasCreated, + leaveSkipHydrationBlock, + setCurrentTNode, + setCurrentTNodeAsNotParent, + wasLastNodeCreated, +} from '../state'; import {computeStaticStyling} from '../styling/static_styling'; import {getConstant} from '../util/view_utils'; import {validateElementIsKnown} from './element_validation'; import {setDirectiveInputsWhichShadowsStyling} from './property'; -import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; - +import { + createDirectivesInstances, + executeContentQueries, + getOrCreateTNode, + resolveDirectives, + saveResolvedLocalsInData, +} from './shared'; function elementStartFirstCreatePass( - index: number, tView: TView, lView: LView, name: string, attrsIndex?: number|null, - localRefsIndex?: number): TElementNode { + index: number, + tView: TView, + lView: LView, + name: string, + attrsIndex?: number | null, + localRefsIndex?: number, +): TElementNode { ngDevMode && assertFirstCreatePass(tView); ngDevMode && ngDevMode.firstCreatePass++; @@ -74,22 +128,27 @@ function elementStartFirstCreatePass( * @codeGenApi */ export function ɵɵelementStart( - index: number, name: string, attrsIndex?: number|null, - localRefsIndex?: number): typeof ɵɵelementStart { + index: number, + name: string, + attrsIndex?: number | null, + localRefsIndex?: number, +): typeof ɵɵelementStart { const lView = getLView(); const tView = getTView(); const adjustedIndex = HEADER_OFFSET + index; ngDevMode && - assertEqual( - getBindingIndex(), tView.bindingStartIndex, - 'elements should be created before any bindings'); + assertEqual( + getBindingIndex(), + tView.bindingStartIndex, + 'elements should be created before any bindings', + ); ngDevMode && assertIndexInRange(lView, adjustedIndex); const renderer = lView[RENDERER]; - const tNode = tView.firstCreatePass ? - elementStartFirstCreatePass(adjustedIndex, tView, lView, name, attrsIndex, localRefsIndex) : - tView.data[adjustedIndex] as TElementNode; + const tNode = tView.firstCreatePass + ? elementStartFirstCreatePass(adjustedIndex, tView, lView, name, attrsIndex, localRefsIndex) + : (tView.data[adjustedIndex] as TElementNode); const native = _locateOrCreateElementNode(tView, lView, tNode, renderer, name, index); lView[adjustedIndex] = native; @@ -183,29 +242,46 @@ export function ɵɵelementEnd(): typeof ɵɵelementEnd { * @codeGenApi */ export function ɵɵelement( - index: number, name: string, attrsIndex?: number|null, - localRefsIndex?: number): typeof ɵɵelement { + index: number, + name: string, + attrsIndex?: number | null, + localRefsIndex?: number, +): typeof ɵɵelement { ɵɵelementStart(index, name, attrsIndex, localRefsIndex); ɵɵelementEnd(); return ɵɵelement; } -let _locateOrCreateElementNode: typeof locateOrCreateElementNodeImpl = - (tView: TView, lView: LView, tNode: TNode, renderer: Renderer, name: string, index: number) => { - lastNodeWasCreated(true); - return createElementNode(renderer, name, getNamespace()); - }; +let _locateOrCreateElementNode: typeof locateOrCreateElementNodeImpl = ( + tView: TView, + lView: LView, + tNode: TNode, + renderer: Renderer, + name: string, + index: number, +) => { + lastNodeWasCreated(true); + return createElementNode(renderer, name, getNamespace()); +}; /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode of element nodes. */ function locateOrCreateElementNodeImpl( - tView: TView, lView: LView, tNode: TNode, renderer: Renderer, name: string, - index: number): RElement { + tView: TView, + lView: LView, + tNode: TNode, + renderer: Renderer, + name: string, + index: number, +): RElement { const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = + !hydrationInfo || + isInSkipHydrationBlock() || + isDetachedByI18n(tNode) || + isDisconnectedNode(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. @@ -234,8 +310,10 @@ function locateOrCreateElementNodeImpl( // skip attempting to hydrate this block. We check both TNode and RElement for an // attribute: the RElement case is needed for i18n cases, when we add it to host // elements during the annotation phase (after all internal data structures are setup). - if (hydrationInfo && - (hasSkipHydrationAttrOnTNode(tNode) || hasSkipHydrationAttrOnRElement(native))) { + if ( + hydrationInfo && + (hasSkipHydrationAttrOnTNode(tNode) || hasSkipHydrationAttrOnRElement(native)) + ) { if (isComponentHost(tNode)) { enterSkipHydrationBlock(tNode); @@ -244,7 +322,6 @@ function locateOrCreateElementNodeImpl( clearElementContents(native); ngDevMode && markRNodeAsSkippedByHydration(native); - } else if (ngDevMode) { // If this is not a component host, throw an error. // Hydration can be skipped on per-component basis only. diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index 3c629fd0b2fda..a3f071d05e6e9 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -7,7 +7,11 @@ */ import {validateMatchingNode, validateNodeExists} from '../../hydration/error_handling'; import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils'; -import {getNgContainerSize, markRNodeAsClaimedByHydration, setSegmentHead} from '../../hydration/utils'; +import { + getNgContainerSize, + markRNodeAsClaimedByHydration, + setSegmentHead, +} from '../../hydration/utils'; import {isDetachedByI18n} from '../../i18n/utils'; import {assertEqual, assertIndexInRange, assertNumber} from '../../util/assert'; import {assertHasParent} from '../assert'; @@ -19,15 +23,36 @@ import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, TView} from '../interfaces/view'; import {assertTNodeType} from '../node_assert'; import {appendChild, createCommentNode} from '../node_manipulation'; -import {getBindingIndex, getCurrentTNode, getLView, getTView, isCurrentTNodeParent, isInSkipHydrationBlock, lastNodeWasCreated, setCurrentTNode, setCurrentTNodeAsNotParent, wasLastNodeCreated} from '../state'; +import { + getBindingIndex, + getCurrentTNode, + getLView, + getTView, + isCurrentTNodeParent, + isInSkipHydrationBlock, + lastNodeWasCreated, + setCurrentTNode, + setCurrentTNodeAsNotParent, + wasLastNodeCreated, +} from '../state'; import {computeStaticStyling} from '../styling/static_styling'; import {getConstant} from '../util/view_utils'; -import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; +import { + createDirectivesInstances, + executeContentQueries, + getOrCreateTNode, + resolveDirectives, + saveResolvedLocalsInData, +} from './shared'; function elementContainerStartFirstCreatePass( - index: number, tView: TView, lView: LView, attrsIndex?: number|null, - localRefsIndex?: number): TElementContainerNode { + index: number, + tView: TView, + lView: LView, + attrsIndex?: number | null, + localRefsIndex?: number, +): TElementContainerNode { ngDevMode && ngDevMode.firstCreatePass++; const tViewConsts = tView.consts; @@ -66,22 +91,25 @@ function elementContainerStartFirstCreatePass( * @codeGenApi */ export function ɵɵelementContainerStart( - index: number, attrsIndex?: number|null, - localRefsIndex?: number): typeof ɵɵelementContainerStart { + index: number, + attrsIndex?: number | null, + localRefsIndex?: number, +): typeof ɵɵelementContainerStart { const lView = getLView(); const tView = getTView(); const adjustedIndex = index + HEADER_OFFSET; ngDevMode && assertIndexInRange(lView, adjustedIndex); ngDevMode && - assertEqual( - getBindingIndex(), tView.bindingStartIndex, - 'element containers should be created before any bindings'); - - const tNode = tView.firstCreatePass ? - elementContainerStartFirstCreatePass( - adjustedIndex, tView, lView, attrsIndex, localRefsIndex) : - tView.data[adjustedIndex] as TElementContainerNode; + assertEqual( + getBindingIndex(), + tView.bindingStartIndex, + 'element containers should be created before any bindings', + ); + + const tNode = tView.firstCreatePass + ? elementContainerStartFirstCreatePass(adjustedIndex, tView, lView, attrsIndex, localRefsIndex) + : (tView.data[adjustedIndex] as TElementContainerNode); setCurrentTNode(tNode, true); const comment = _locateOrCreateElementContainerNode(tView, lView, tNode, index); @@ -144,17 +172,24 @@ export function ɵɵelementContainerEnd(): typeof ɵɵelementContainerEnd { * @codeGenApi */ export function ɵɵelementContainer( - index: number, attrsIndex?: number|null, localRefsIndex?: number): typeof ɵɵelementContainer { + index: number, + attrsIndex?: number | null, + localRefsIndex?: number, +): typeof ɵɵelementContainer { ɵɵelementContainerStart(index, attrsIndex, localRefsIndex); ɵɵelementContainerEnd(); return ɵɵelementContainer; } -let _locateOrCreateElementContainerNode: typeof locateOrCreateElementContainerNode = - (tView: TView, lView: LView, tNode: TNode, index: number) => { - lastNodeWasCreated(true); - return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : ''); - }; +let _locateOrCreateElementContainerNode: typeof locateOrCreateElementContainerNode = ( + tView: TView, + lView: LView, + tNode: TNode, + index: number, +) => { + lastNodeWasCreated(true); + return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : ''); +}; /** * Enables hydration code path (to lookup existing elements in DOM) @@ -162,7 +197,11 @@ let _locateOrCreateElementContainerNode: typeof locateOrCreateElementContainerNo * represent 's anchor. */ function locateOrCreateElementContainerNode( - tView: TView, lView: LView, tNode: TNode, index: number): RComment { + tView: TView, + lView: LView, + tNode: TNode, + index: number, +): RComment { let comment: RComment; const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock() || isDetachedByI18n(tNode); @@ -180,10 +219,10 @@ function locateOrCreateElementContainerNode( const ngContainerSize = getNgContainerSize(hydrationInfo, index) as number; ngDevMode && - assertNumber( - ngContainerSize, - 'Unexpected state: hydrating an , ' + - 'but no hydration info is available.'); + assertNumber( + ngContainerSize, + 'Unexpected state: hydrating an , ' + 'but no hydration info is available.', + ); setSegmentHead(hydrationInfo, index, currentRNode); comment = siblingAfter(ngContainerSize, currentRNode)!; diff --git a/packages/core/src/render3/instructions/element_validation.ts b/packages/core/src/render3/instructions/element_validation.ts index 8eac61106a9a7..3bbbf94487104 100644 --- a/packages/core/src/render3/instructions/element_validation.ts +++ b/packages/core/src/render3/instructions/element_validation.ts @@ -72,8 +72,12 @@ export function ɵgetUnknownPropertyStrictMode() { * @param hasDirectives Boolean indicating that the element matches any directive */ export function validateElementIsKnown( - element: RElement, lView: LView, tagName: string|null, schemas: SchemaMetadata[]|null, - hasDirectives: boolean): void { + element: RElement, + lView: LView, + tagName: string | null, + schemas: SchemaMetadata[] | null, + hasDirectives: boolean, +): void { // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT // mode where this check happens at compile time. In JIT mode, `schemas` is always present and // defined as an array (as an empty array in case `schemas` field is not defined) and we should @@ -86,12 +90,14 @@ export function validateElementIsKnown( // as a custom element. Note that unknown elements with a dash in their name won't be instances // of HTMLUnknownElement in browsers that support web components. const isUnknown = - // Note that we can't check for `typeof HTMLUnknownElement === 'function'` because - // Domino doesn't expose HTMLUnknownElement globally. - (typeof HTMLUnknownElement !== 'undefined' && HTMLUnknownElement && - element instanceof HTMLUnknownElement) || - (typeof customElements !== 'undefined' && tagName.indexOf('-') > -1 && - !customElements.get(tagName)); + // Note that we can't check for `typeof HTMLUnknownElement === 'function'` because + // Domino doesn't expose HTMLUnknownElement globally. + (typeof HTMLUnknownElement !== 'undefined' && + HTMLUnknownElement && + element instanceof HTMLUnknownElement) || + (typeof customElements !== 'undefined' && + tagName.indexOf('-') > -1 && + !customElements.get(tagName)); if (isUnknown && !matchingSchemas(schemas, tagName)) { const isHostStandalone = isHostComponentStandalone(lView); @@ -100,15 +106,14 @@ export function validateElementIsKnown( let message = `'${tagName}' is not a known element${templateLocation}:\n`; message += `1. If '${tagName}' is an Angular component, then verify that it is ${ - isHostStandalone ? 'included in the \'@Component.imports\' of this component' : - 'a part of an @NgModule where this component is declared'}.\n`; + isHostStandalone + ? "included in the '@Component.imports' of this component" + : 'a part of an @NgModule where this component is declared' + }.\n`; if (tagName && tagName.indexOf('-') > -1) { - message += - `2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${ - schemas} of this component to suppress this message.`; + message += `2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.`; } else { - message += - `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`; + message += `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`; } if (shouldThrowErrorOnUnknownElement) { throw new RuntimeError(RuntimeErrorCode.UNKNOWN_ELEMENT, message); @@ -136,8 +141,11 @@ export function validateElementIsKnown( * @param schemas Array of schemas */ export function isPropertyValid( - element: RElement|RComment, propName: string, tagName: string|null, - schemas: SchemaMetadata[]|null): boolean { + element: RElement | RComment, + propName: string, + tagName: string | null, + schemas: SchemaMetadata[] | null, +): boolean { // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT // mode where this check happens at compile time. In JIT mode, `schemas` is always present and // defined as an array (as an empty array in case `schemas` field is not defined) and we should @@ -163,7 +171,11 @@ export function isPropertyValid( * @param lView An `LView` that represents a current component */ export function handleUnknownPropertyError( - propName: string, tagName: string|null, nodeType: TNodeType, lView: LView): void { + propName: string, + tagName: string | null, + nodeType: TNodeType, + lView: LView, +): void { // Special-case a situation when a structural directive is applied to // an `` element, for example: ``. // In this case the compiler generates the `ɵɵtemplate` instruction with @@ -177,34 +189,37 @@ export function handleUnknownPropertyError( const isHostStandalone = isHostComponentStandalone(lView); const templateLocation = getTemplateLocationDetails(lView); - let message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'${ - templateLocation}.`; + let message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'${templateLocation}.`; const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`; - const importLocation = isHostStandalone ? - 'included in the \'@Component.imports\' of this component' : - 'a part of an @NgModule where this component is declared'; + const importLocation = isHostStandalone + ? "included in the '@Component.imports' of this component" + : 'a part of an @NgModule where this component is declared'; if (KNOWN_CONTROL_FLOW_DIRECTIVES.has(propName)) { // Most likely this is a control flow directive (such as `*ngIf`) used in // a template, but the directive or the `CommonModule` is not imported. const correspondingImport = KNOWN_CONTROL_FLOW_DIRECTIVES.get(propName); - message += `\nIf the '${propName}' is an Angular control flow directive, ` + - `please make sure that either the '${ - correspondingImport}' directive or the 'CommonModule' is ${importLocation}.`; + message += + `\nIf the '${propName}' is an Angular control flow directive, ` + + `please make sure that either the '${correspondingImport}' directive or the 'CommonModule' is ${importLocation}.`; } else { // May be an Angular component, which is not imported/declared? - message += `\n1. If '${tagName}' is an Angular component and it has the ` + - `'${propName}' input, then verify that it is ${importLocation}.`; + message += + `\n1. If '${tagName}' is an Angular component and it has the ` + + `'${propName}' input, then verify that it is ${importLocation}.`; // May be a Web Component? if (tagName && tagName.indexOf('-') > -1) { - message += `\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' ` + - `to the ${schemas} of this component to suppress this message.`; - message += `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to ` + - `the ${schemas} of this component.`; + message += + `\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' ` + + `to the ${schemas} of this component to suppress this message.`; + message += + `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to ` + + `the ${schemas} of this component.`; } else { // If it's expected, the error can be suppressed by the `NO_ERRORS_SCHEMA` schema. - message += `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to ` + - `the ${schemas} of this component.`; + message += + `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to ` + + `the ${schemas} of this component.`; } } @@ -228,7 +243,7 @@ export function reportUnknownPropertyError(message: string) { * * @param lView An `LView` that represents a current component that is being rendered. */ -export function getDeclarationComponentDef(lView: LView): ComponentDef|null { +export function getDeclarationComponentDef(lView: LView): ComponentDef | null { !ngDevMode && throwError('Must never be called in production mode'); const declarationLView = lView[DECLARATION_COMPONENT_VIEW] as LView>; @@ -281,20 +296,24 @@ export function getTemplateLocationDetails(lView: LView): string { * that the `CommonModule` should also be included. */ export const KNOWN_CONTROL_FLOW_DIRECTIVES = new Map([ - ['ngIf', 'NgIf'], ['ngFor', 'NgFor'], ['ngSwitchCase', 'NgSwitchCase'], - ['ngSwitchDefault', 'NgSwitchDefault'] + ['ngIf', 'NgIf'], + ['ngFor', 'NgFor'], + ['ngSwitchCase', 'NgSwitchCase'], + ['ngSwitchDefault', 'NgSwitchDefault'], ]); /** * Returns true if the tag name is allowed by specified schemas. * @param schemas Array of schemas * @param tagName Name of the tag */ -export function matchingSchemas(schemas: SchemaMetadata[]|null, tagName: string|null): boolean { +export function matchingSchemas(schemas: SchemaMetadata[] | null, tagName: string | null): boolean { if (schemas !== null) { for (let i = 0; i < schemas.length; i++) { const schema = schemas[i]; - if (schema === NO_ERRORS_SCHEMA || - schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) { + if ( + schema === NO_ERRORS_SCHEMA || + (schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) + ) { return true; } } diff --git a/packages/core/src/render3/instructions/host_property.ts b/packages/core/src/render3/instructions/host_property.ts index a6d2549e68ded..73c1c28e8f825 100644 --- a/packages/core/src/render3/instructions/host_property.ts +++ b/packages/core/src/render3/instructions/host_property.ts @@ -8,10 +8,20 @@ import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; import {RENDERER} from '../interfaces/view'; -import {getCurrentDirectiveDef, getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state'; +import { + getCurrentDirectiveDef, + getLView, + getSelectedTNode, + getTView, + nextBindingIndex, +} from '../state'; import {NO_CHANGE} from '../tokens'; -import {elementPropertyInternal, loadComponentRenderer, storePropertyBindingMetadata} from './shared'; +import { + elementPropertyInternal, + loadComponentRenderer, + storePropertyBindingMetadata, +} from './shared'; /** * Update a property on a host element. Only applies to native node properties, not inputs. @@ -28,7 +38,10 @@ import {elementPropertyInternal, loadComponentRenderer, storePropertyBindingMeta * @codeGenApi */ export function ɵɵhostProperty( - propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵhostProperty { + propName: string, + value: T, + sanitizer?: SanitizerFn | null, +): typeof ɵɵhostProperty { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { @@ -40,7 +53,6 @@ export function ɵɵhostProperty( return ɵɵhostProperty; } - /** * Updates a synthetic host binding (e.g. `[@foo]`) on a component or directive. * @@ -63,8 +75,10 @@ export function ɵɵhostProperty( * @codeGenApi */ export function ɵɵsyntheticHostProperty( - propName: string, value: T|NO_CHANGE, - sanitizer?: SanitizerFn|null): typeof ɵɵsyntheticHostProperty { + propName: string, + value: T | NO_CHANGE, + sanitizer?: SanitizerFn | null, +): typeof ɵɵsyntheticHostProperty { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { diff --git a/packages/core/src/render3/instructions/i18n.ts b/packages/core/src/render3/instructions/i18n.ts index 7dbdd71295ddd..07c6d96e475c4 100644 --- a/packages/core/src/render3/instructions/i18n.ts +++ b/packages/core/src/render3/instructions/i18n.ts @@ -16,9 +16,22 @@ import {i18nAttributesFirstPass, i18nStartFirstCreatePass} from '../i18n/i18n_pa import {i18nPostprocess} from '../i18n/i18n_postprocess'; import {TI18n} from '../interfaces/i18n'; import {TElementNode, TNodeType} from '../interfaces/node'; -import {DECLARATION_COMPONENT_VIEW, FLAGS, HEADER_OFFSET, LViewFlags, T_HOST, TViewType} from '../interfaces/view'; +import { + DECLARATION_COMPONENT_VIEW, + FLAGS, + HEADER_OFFSET, + LViewFlags, + T_HOST, + TViewType, +} from '../interfaces/view'; import {getClosestRElement} from '../node_manipulation'; -import {getCurrentParentTNode, getLView, getTView, nextBindingIndex, setInI18nBlock} from '../state'; +import { + getCurrentParentTNode, + getLView, + getTView, + nextBindingIndex, + setInI18nBlock, +} from '../state'; import {getConstant} from '../util/view_utils'; /** @@ -47,7 +60,10 @@ import {getConstant} from '../util/view_utils'; * @codeGenApi */ export function ɵɵi18nStart( - index: number, messageIndex: number, subTemplateIndex: number = -1): void { + index: number, + messageIndex: number, + subTemplateIndex: number = -1, +): void { const tView = getTView(); const lView = getLView(); const adjustedIndex = HEADER_OFFSET + index; @@ -56,8 +72,13 @@ export function ɵɵi18nStart( const parentTNode = getCurrentParentTNode() as TElementNode | null; if (tView.firstCreatePass) { i18nStartFirstCreatePass( - tView, parentTNode === null ? 0 : parentTNode.index, lView, adjustedIndex, message, - subTemplateIndex); + tView, + parentTNode === null ? 0 : parentTNode.index, + lView, + adjustedIndex, + message, + subTemplateIndex, + ); } // Set a flag that this LView has i18n blocks. @@ -77,16 +98,13 @@ export function ɵɵi18nStart( const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView); // If `parentTNode` is an `ElementContainer` than it has ``. // When we do inserts we have to make sure to insert in front of ``. - const insertInFrontOf = parentTNode && (parentTNode.type & TNodeType.ElementContainer) ? - lView[parentTNode.index] : - null; + const insertInFrontOf = + parentTNode && parentTNode.type & TNodeType.ElementContainer ? lView[parentTNode.index] : null; prepareI18nBlockForHydration(lView, adjustedIndex, parentTNode, subTemplateIndex); applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf); setInI18nBlock(true); } - - /** * Translates a translation block marked by `i18nStart` and `i18nEnd`. It inserts the text/ICU nodes * into the render tree, moves the placeholder nodes and removes the deleted nodes. @@ -143,7 +161,6 @@ export function ɵɵi18nAttributes(index: number, attrsIndex: number): void { i18nAttributesFirstPass(tView, index + HEADER_OFFSET, attrs); } - /** * Stores the values of the bindings during each update cycle in order to determine if we need to * update the translated nodes. @@ -193,6 +210,8 @@ export function ɵɵi18nApply(index: number) { * @codeGenApi */ export function ɵɵi18nPostprocess( - message: string, replacements: {[key: string]: (string|string[])} = {}): string { + message: string, + replacements: {[key: string]: string | string[]} = {}, +): string { return i18nPostprocess(message, replacements); } diff --git a/packages/core/src/render3/instructions/i18n_icu_container_visitor.ts b/packages/core/src/render3/instructions/i18n_icu_container_visitor.ts index acb42f498cf69..2ac99ecda2aab 100644 --- a/packages/core/src/render3/instructions/i18n_icu_container_visitor.ts +++ b/packages/core/src/render3/instructions/i18n_icu_container_visitor.ts @@ -37,8 +37,10 @@ export function loadIcuContainerVisitor() { * @param tIcuContainerNode Current `TIcuContainerNode` * @param lView `LView` where the `RNode`s should be looked up. */ - function icuContainerIteratorStart(tIcuContainerNode: TIcuContainerNode, lView: LView): () => - RNode | null { + function icuContainerIteratorStart( + tIcuContainerNode: TIcuContainerNode, + lView: LView, + ): () => RNode | null { _lView = lView; while (_stack.length) _stack.pop(); ngDevMode && assertTNodeForLView(tIcuContainerNode, lView); @@ -57,8 +59,7 @@ export function loadIcuContainerVisitor() { } } - - function icuContainerIteratorNext(): RNode|null { + function icuContainerIteratorNext(): RNode | null { if (_index < _removes.length) { const removeOpCode = _removes[_index++] as number; ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number'); diff --git a/packages/core/src/render3/instructions/interpolation.ts b/packages/core/src/render3/instructions/interpolation.ts index ec64e3bbcf5d9..635983dcae9a0 100644 --- a/packages/core/src/render3/instructions/interpolation.ts +++ b/packages/core/src/render3/instructions/interpolation.ts @@ -13,8 +13,6 @@ import {getBindingIndex, incrementBindingIndex, nextBindingIndex, setBindingInde import {NO_CHANGE} from '../tokens'; import {renderStringify} from '../util/stringify_utils'; - - /** * Create interpolation bindings with a variable number of expressions. * @@ -27,7 +25,7 @@ import {renderStringify} from '../util/stringify_utils'; * * Returns the concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. */ -export function interpolationV(lView: LView, values: any[]): string|NO_CHANGE { +export function interpolationV(lView: LView, values: any[]): string | NO_CHANGE { ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values'); ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values'); let isBindingUpdated = false; @@ -59,8 +57,12 @@ export function interpolationV(lView: LView, values: any[]): string|NO_CHANGE { * @param v0 value checked for change. * @param suffix static value used for concatenation only. */ -export function interpolation1(lView: LView, prefix: string, v0: any, suffix: string): string| - NO_CHANGE { +export function interpolation1( + lView: LView, + prefix: string, + v0: any, + suffix: string, +): string | NO_CHANGE { const different = bindingUpdated(lView, nextBindingIndex(), v0); return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE; } @@ -69,7 +71,13 @@ export function interpolation1(lView: LView, prefix: string, v0: any, suffix: st * Creates an interpolation binding with 2 expressions. */ export function interpolation2( - lView: LView, prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); const different = bindingUpdated2(lView, bindingIndex, v0, v1); incrementBindingIndex(2); @@ -81,97 +89,224 @@ export function interpolation2( * Creates an interpolation binding with 3 expressions. */ export function interpolation3( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, - suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2); incrementBindingIndex(3); - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix : - NO_CHANGE; + return different + ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix + : NO_CHANGE; } /** * Create an interpolation binding with 4 expressions. */ export function interpolation4( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); incrementBindingIndex(4); - return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + - renderStringify(v2) + i2 + renderStringify(v3) + suffix : - NO_CHANGE; + return different + ? prefix + + renderStringify(v0) + + i0 + + renderStringify(v1) + + i1 + + renderStringify(v2) + + i2 + + renderStringify(v3) + + suffix + : NO_CHANGE; } /** * Creates an interpolation binding with 5 expressions. */ export function interpolation5( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated(lView, bindingIndex + 4, v4) || different; incrementBindingIndex(5); - return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + - renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + suffix : - NO_CHANGE; + return different + ? prefix + + renderStringify(v0) + + i0 + + renderStringify(v1) + + i1 + + renderStringify(v2) + + i2 + + renderStringify(v3) + + i3 + + renderStringify(v4) + + suffix + : NO_CHANGE; } /** * Creates an interpolation binding with 6 expressions. */ export function interpolation6( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different; incrementBindingIndex(6); - return different ? - prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + - renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix : - NO_CHANGE; + return different + ? prefix + + renderStringify(v0) + + i0 + + renderStringify(v1) + + i1 + + renderStringify(v2) + + i2 + + renderStringify(v3) + + i3 + + renderStringify(v4) + + i4 + + renderStringify(v5) + + suffix + : NO_CHANGE; } /** * Creates an interpolation binding with 7 expressions. */ export function interpolation7( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): string| - NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different; incrementBindingIndex(7); - return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + - renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + i4 + - renderStringify(v5) + i5 + renderStringify(v6) + suffix : - NO_CHANGE; + return different + ? prefix + + renderStringify(v0) + + i0 + + renderStringify(v1) + + i1 + + renderStringify(v2) + + i2 + + renderStringify(v3) + + i3 + + renderStringify(v4) + + i4 + + renderStringify(v5) + + i5 + + renderStringify(v6) + + suffix + : NO_CHANGE; } /** * Creates an interpolation binding with 8 expressions. */ export function interpolation8( - lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): string|NO_CHANGE { + lView: LView, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, +): string | NO_CHANGE { const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different; incrementBindingIndex(8); - return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + - renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + i4 + - renderStringify(v5) + i5 + renderStringify(v6) + i6 + renderStringify(v7) + suffix : - NO_CHANGE; + return different + ? prefix + + renderStringify(v0) + + i0 + + renderStringify(v1) + + i1 + + renderStringify(v2) + + i2 + + renderStringify(v3) + + i3 + + renderStringify(v4) + + i4 + + renderStringify(v5) + + i5 + + renderStringify(v6) + + i6 + + renderStringify(v7) + + suffix + : NO_CHANGE; } diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts index d64f6a88e4c10..6ad6e4f154cf0 100644 --- a/packages/core/src/render3/instructions/listener.ts +++ b/packages/core/src/render3/instructions/listener.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {setActiveConsumer} from '@angular/core/primitives/signals'; import {NotificationSource} from '../../change_detection/scheduling/zoneless_scheduling'; @@ -22,9 +21,12 @@ import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../st import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; import {markViewDirty} from './mark_view_dirty'; -import {getOrCreateLViewCleanup, getOrCreateTViewCleanup, handleError, loadComponentRenderer} from './shared'; - - +import { + getOrCreateLViewCleanup, + getOrCreateTViewCleanup, + handleError, + loadComponentRenderer, +} from './shared'; /** * Adds an event listener to the current node. @@ -42,13 +44,23 @@ import {getOrCreateLViewCleanup, getOrCreateTViewCleanup, handleError, loadCompo * @codeGenApi */ export function ɵɵlistener( - eventName: string, listenerFn: (e?: any) => any, useCapture?: boolean, - eventTargetResolver?: GlobalTargetResolver): typeof ɵɵlistener { - const lView = getLView<{}|null>(); + eventName: string, + listenerFn: (e?: any) => any, + useCapture?: boolean, + eventTargetResolver?: GlobalTargetResolver, +): typeof ɵɵlistener { + const lView = getLView<{} | null>(); const tView = getTView(); const tNode = getCurrentTNode()!; listenerInternal( - tView, lView, lView[RENDERER], tNode, eventName, listenerFn, eventTargetResolver); + tView, + lView, + lView[RENDERER], + tNode, + eventName, + listenerFn, + eventTargetResolver, + ); return ɵɵlistener; } @@ -74,9 +86,11 @@ export function ɵɵlistener( * @codeGenApi */ export function ɵɵsyntheticHostListener( - eventName: string, listenerFn: (e?: any) => any): typeof ɵɵsyntheticHostListener { + eventName: string, + listenerFn: (e?: any) => any, +): typeof ɵɵsyntheticHostListener { const tNode = getCurrentTNode()!; - const lView = getLView<{}|null>(); + const lView = getLView<{} | null>(); const tView = getTView(); const currentDef = getCurrentDirectiveDef(tView.data); const renderer = loadComponentRenderer(currentDef, tNode, lView); @@ -90,7 +104,11 @@ export function ɵɵsyntheticHostListener( * are registered for a given element. */ function findExistingListener( - tView: TView, lView: LView, eventName: string, tNodeIdx: number): ((e?: any) => any)|null { + tView: TView, + lView: LView, + eventName: string, + tNodeIdx: number, +): ((e?: any) => any) | null { const tCleanup = tView.cleanup; if (tCleanup != null) { for (let i = 0; i < tCleanup.length - 1; i += 2) { @@ -117,11 +135,17 @@ function findExistingListener( } export function listenerInternal( - tView: TView, lView: LView<{}|null>, renderer: Renderer, tNode: TNode, eventName: string, - listenerFn: (e?: any) => any, eventTargetResolver?: GlobalTargetResolver): void { + tView: TView, + lView: LView<{} | null>, + renderer: Renderer, + tNode: TNode, + eventName: string, + listenerFn: (e?: any) => any, + eventTargetResolver?: GlobalTargetResolver, +): void { const isTNodeDirectiveHost = isDirectiveHost(tNode); const firstCreatePass = tView.firstCreatePass; - const tCleanup: false|any[] = firstCreatePass && getOrCreateTViewCleanup(tView); + const tCleanup: false | any[] = firstCreatePass && getOrCreateTViewCleanup(tView); const context = lView[CONTEXT]; // When the ɵɵlistener instruction was generated and is executed we know that there is either a @@ -137,13 +161,13 @@ export function listenerInternal( // - The corresponding TNode represents a DOM element. // - The event target has a resolver (usually resulting in a global object, // such as `window` or `document`). - if ((tNode.type & TNodeType.AnyRNode) || eventTargetResolver) { + if (tNode.type & TNodeType.AnyRNode || eventTargetResolver) { const native = getNativeByTNode(tNode, lView) as RElement; const target = eventTargetResolver ? eventTargetResolver(native) : native; const lCleanupIndex = lCleanup.length; - const idxOrTargetGetter = eventTargetResolver ? - (_lView: LView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])) : - tNode.index; + const idxOrTargetGetter = eventTargetResolver + ? (_lView: LView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])) + : tNode.index; // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). @@ -185,7 +209,6 @@ export function listenerInternal( lCleanup.push(listenerFn, cleanupFn); tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, lCleanupIndex + 1); } - } else { // Even if there is no native listener to add, we still need to wrap the listener so that OnPush // ancestors are marked dirty when an event occurs. @@ -194,7 +217,7 @@ export function listenerInternal( // subscribe to directive outputs const outputs = tNode.outputs; - let props: NodeOutputBindings[keyof NodeOutputBindings]|undefined; + let props: NodeOutputBindings[keyof NodeOutputBindings] | undefined; if (processOutputs && outputs !== null && (props = outputs[eventName])) { const propsLength = props.length; if (propsLength) { @@ -206,8 +229,9 @@ export function listenerInternal( const output = directiveInstance[minifiedName]; if (ngDevMode && !isOutputSubscribable(output)) { - throw new Error(`@Output ${minifiedName} not initialized in '${ - directiveInstance.constructor.name}'.`); + throw new Error( + `@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`, + ); } const subscription = (output as SubscribableOutput).subscribe(listenerFn); @@ -220,7 +244,11 @@ export function listenerInternal( } function executeListenerWithErrorHandling( - lView: LView, context: {}|null, listenerFn: (e?: any) => any, e: any): boolean { + lView: LView, + context: {} | null, + listenerFn: (e?: any) => any, + e: any, +): boolean { const prevConsumer = setActiveConsumer(null); try { profiler(ProfilerEvent.OutputStart, context, listenerFn); @@ -246,8 +274,12 @@ function executeListenerWithErrorHandling( * (the procedural renderer does this already, so in those cases, we should skip) */ function wrapListener( - tNode: TNode, lView: LView<{}|null>, context: {}|null, listenerFn: (e?: any) => any, - wrapWithPreventDefault: boolean): EventListener { + tNode: TNode, + lView: LView<{} | null>, + context: {} | null, + listenerFn: (e?: any) => any, + wrapWithPreventDefault: boolean, +): EventListener { // Note: we are performing most of the work in the listener function itself // to optimize listener registration. return function wrapListenerIn_markDirtyAndPreventDefault(e: any) { @@ -260,7 +292,7 @@ function wrapListener( // In order to be backwards compatible with View Engine, events on component host nodes // must also mark the component view itself dirty (i.e. the view that it owns). const startView = - tNode.componentOffset > -1 ? getComponentLViewByIndex(tNode.index, lView) : lView; + tNode.componentOffset > -1 ? getComponentLViewByIndex(tNode.index, lView) : lView; markViewDirty(startView, NotificationSource.Listener); let result = executeListenerWithErrorHandling(lView, context, listenerFn, e); @@ -283,7 +315,7 @@ function wrapListener( /** Describes a subscribable output field value. */ interface SubscribableOutput { - subscribe(listener: (v: T) => void): {unsubscribe: () => void;}; + subscribe(listener: (v: T) => void): {unsubscribe: () => void}; } /** @@ -293,6 +325,7 @@ interface SubscribableOutput { * `OutputEmitter`. */ function isOutputSubscribable(value: unknown): value is SubscribableOutput { - return value != null && - typeof (value as Partial>).subscribe === 'function'; + return ( + value != null && typeof (value as Partial>).subscribe === 'function' + ); } diff --git a/packages/core/src/render3/instructions/mark_view_dirty.ts b/packages/core/src/render3/instructions/mark_view_dirty.ts index 380e0696f42a9..2dd8c2910a79a 100644 --- a/packages/core/src/render3/instructions/mark_view_dirty.ts +++ b/packages/core/src/render3/instructions/mark_view_dirty.ts @@ -23,15 +23,15 @@ import {getLViewParent} from '../util/view_utils'; * @param lView The starting LView to mark dirty * @returns the root LView */ -export function markViewDirty(lView: LView, source: NotificationSource): LView|null { - const dirtyBitsToUse = isRefreshingViews() ? - // When we are actively refreshing views, we only use the `Dirty` bit to mark a view +export function markViewDirty(lView: LView, source: NotificationSource): LView | null { + const dirtyBitsToUse = isRefreshingViews() + ? // When we are actively refreshing views, we only use the `Dirty` bit to mark a view // for check. This bit is ignored in ChangeDetectionMode.Targeted, which is used to // synchronously rerun change detection on a specific set of views (those which have // the `RefreshView` flag and those with dirty signal consumers). `LViewFlags.Dirty` // does not support re-entrant change detection on its own. - LViewFlags.Dirty : - // When we are not actively refreshing a view tree, it is absolutely + LViewFlags.Dirty + : // When we are not actively refreshing a view tree, it is absolutely // valid to update state and mark views dirty. We use the `RefreshView` flag in this // case to allow synchronously rerunning change detection. This applies today to // afterRender hooks as well as animation listeners which execute after detecting diff --git a/packages/core/src/render3/instructions/projection.ts b/packages/core/src/render3/instructions/projection.ts index 56176c57cfb44..bc12118ef7d98 100644 --- a/packages/core/src/render3/instructions/projection.ts +++ b/packages/core/src/render3/instructions/projection.ts @@ -11,17 +11,30 @@ import {assertLContainer, assertTNode} from '../assert'; import {ComponentTemplate} from '../interfaces/definition'; import {TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node'; import {ProjectionSlots} from '../interfaces/projection'; -import {DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, HYDRATION, LView, T_HOST, TView} from '../interfaces/view'; +import { + DECLARATION_COMPONENT_VIEW, + HEADER_OFFSET, + HYDRATION, + LView, + T_HOST, + TView, +} from '../interfaces/view'; import {applyProjection} from '../node_manipulation'; -import {getProjectAsAttrValue, isNodeMatchingSelectorList, isSelectorInSelectorList} from '../node_selector_matcher'; +import { + getProjectAsAttrValue, + isNodeMatchingSelectorList, + isSelectorInSelectorList, +} from '../node_selector_matcher'; import {getLView, getTView, isInSkipHydrationBlock, setCurrentTNodeAsNotParent} from '../state'; -import {addLViewToLContainer, createAndRenderEmbeddedLView, shouldAddViewToDom} from '../view_manipulation'; +import { + addLViewToLContainer, + createAndRenderEmbeddedLView, + shouldAddViewToDom, +} from '../view_manipulation'; import {getOrCreateTNode} from './shared'; import {declareTemplate} from './template'; - - /** * Checks a given node against matching projection slots and returns the * determined slot index. Returns "null" if no slot matched the given node. @@ -30,8 +43,10 @@ import {declareTemplate} from './template'; * node's attributes. If present, it will check whether the ngProjectAs selector * matches any of the projection slot selectors. */ -export function matchingProjectionSlotIndex(tNode: TNode, projectionSlots: ProjectionSlots): number| - null { +export function matchingProjectionSlotIndex( + tNode: TNode, + projectionSlots: ProjectionSlots, +): number | null { let wildcardNgContentIndex = null; const ngProjectAsAttrVal = getProjectAsAttrValue(tNode); for (let i = 0; i < projectionSlots.length; i++) { @@ -44,10 +59,12 @@ export function matchingProjectionSlotIndex(tNode: TNode, projectionSlots: Proje } // If we ran into an `ngProjectAs` attribute, we should match its parsed selector // to the list of selectors, otherwise we fall back to matching against the node. - if (ngProjectAsAttrVal === null ? - isNodeMatchingSelectorList(tNode, slotValue, /* isProjectionMode */ true) : - isSelectorInSelectorList(ngProjectAsAttrVal, slotValue)) { - return i; // first matching selector "captures" a given node + if ( + ngProjectAsAttrVal === null + ? isNodeMatchingSelectorList(tNode, slotValue, /* isProjectionMode */ true) + : isSelectorInSelectorList(ngProjectAsAttrVal, slotValue) + ) { + return i; // first matching selector "captures" a given node } } return wildcardNgContentIndex; @@ -85,15 +102,18 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void { // If no explicit projection slots are defined, fall back to a single // projection slot with the wildcard selector. const numProjectionSlots = projectionSlots ? projectionSlots.length : 1; - const projectionHeads: (TNode|null)[] = componentNode.projection = - newArray(numProjectionSlots, null! as TNode); - const tails: (TNode|null)[] = projectionHeads.slice(); + const projectionHeads: (TNode | null)[] = (componentNode.projection = newArray( + numProjectionSlots, + null! as TNode, + )); + const tails: (TNode | null)[] = projectionHeads.slice(); - let componentChild: TNode|null = componentNode.child; + let componentChild: TNode | null = componentNode.child; while (componentChild !== null) { - const slotIndex = - projectionSlots ? matchingProjectionSlotIndex(componentChild, projectionSlots) : 0; + const slotIndex = projectionSlots + ? matchingProjectionSlotIndex(componentChild, projectionSlots) + : 0; if (slotIndex !== null) { if (tails[slotIndex]) { @@ -109,7 +129,6 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void { } } - /** * Inserts previously re-distributed projected nodes. This instruction must be preceded by a call * to the projectionDef instruction. @@ -127,9 +146,13 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void { * @codeGenApi */ export function ɵɵprojection( - nodeIndex: number, selectorIndex: number = 0, attrs?: TAttributes, - fallbackTemplateFn?: ComponentTemplate, fallbackDecls?: number, - fallbackVars?: number): void { + nodeIndex: number, + selectorIndex: number = 0, + attrs?: TAttributes, + fallbackTemplateFn?: ComponentTemplate, + fallbackDecls?: number, + fallbackVars?: number, +): void { const lView = getLView(); const tView = getTView(); const fallbackIndex = fallbackTemplateFn ? nodeIndex + 1 : null; @@ -139,12 +162,24 @@ export function ɵɵprojection( // the projection node in order to work correctly with hydration. if (fallbackIndex !== null) { declareTemplate( - lView, tView, fallbackIndex, fallbackTemplateFn!, fallbackDecls!, fallbackVars!, null, - attrs); + lView, + tView, + fallbackIndex, + fallbackTemplateFn!, + fallbackDecls!, + fallbackVars!, + null, + attrs, + ); } - const tProjectionNode = - getOrCreateTNode(tView, HEADER_OFFSET + nodeIndex, TNodeType.Projection, null, attrs || null); + const tProjectionNode = getOrCreateTNode( + tView, + HEADER_OFFSET + nodeIndex, + TNodeType.Projection, + null, + attrs || null, + ); // We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views. if (tProjectionNode.projection === null) { @@ -163,8 +198,9 @@ export function ɵɵprojection( if (isEmpty && fallbackIndex !== null) { insertFallbackContent(lView, tView, fallbackIndex); } else if ( - isNodeCreationMode && - (tProjectionNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) { + isNodeCreationMode && + (tProjectionNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached + ) { // re-distribution of projectable nodes is stored on a component's view level applyProjection(tView, lView, tProjectionNode); } @@ -179,8 +215,13 @@ function insertFallbackContent(lView: LView, tView: TView, fallbackIndex: number ngDevMode && assertLContainer(fallbackLContainer); const dehydratedView = findMatchingDehydratedView(fallbackLContainer, fallbackTNode.tView!.ssrId); - const fallbackLView = - createAndRenderEmbeddedLView(lView, fallbackTNode, undefined, {dehydratedView}); + const fallbackLView = createAndRenderEmbeddedLView(lView, fallbackTNode, undefined, { + dehydratedView, + }); addLViewToLContainer( - fallbackLContainer, fallbackLView, 0, shouldAddViewToDom(fallbackTNode, dehydratedView)); + fallbackLContainer, + fallbackLView, + 0, + shouldAddViewToDom(fallbackTNode, dehydratedView), + ); } diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index 565e7441bfcaf..ed3c359da5a41 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -11,8 +11,11 @@ import {SanitizerFn} from '../interfaces/sanitization'; import {LView, RENDERER, TView} from '../interfaces/view'; import {getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state'; -import {elementPropertyInternal, setInputsForProperty, storePropertyBindingMetadata} from './shared'; - +import { + elementPropertyInternal, + setInputsForProperty, + storePropertyBindingMetadata, +} from './shared'; /** * Update a property on a selected element. @@ -33,14 +36,25 @@ import {elementPropertyInternal, setInputsForProperty, storePropertyBindingMetad * @codeGenApi */ export function ɵɵproperty( - propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵproperty { + propName: string, + value: T, + sanitizer?: SanitizerFn | null, +): typeof ɵɵproperty { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + value, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex); } return ɵɵproperty; @@ -51,7 +65,12 @@ export function ɵɵproperty( * directive input. */ export function setDirectiveInputsWhichShadowsStyling( - tView: TView, tNode: TNode, lView: LView, value: any, isClassBased: boolean) { + tView: TView, + tNode: TNode, + lView: LView, + value: any, + isClassBased: boolean, +) { const inputs = tNode.inputs!; const property = isClassBased ? 'class' : 'style'; // We support both 'class' and `className` hence the fallback. diff --git a/packages/core/src/render3/instructions/property_interpolation.ts b/packages/core/src/render3/instructions/property_interpolation.ts index 37cf538b884bb..c8b835eb1dc38 100644 --- a/packages/core/src/render3/instructions/property_interpolation.ts +++ b/packages/core/src/render3/instructions/property_interpolation.ts @@ -10,10 +10,19 @@ import {RENDERER} from '../interfaces/view'; import {getBindingIndex, getLView, getSelectedTNode, getTView} from '../state'; import {NO_CHANGE} from '../tokens'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {elementPropertyInternal, storePropertyBindingMetadata} from './shared'; - /** * * Update an interpolated property on an element with a lone bound value @@ -44,12 +53,14 @@ import {elementPropertyInternal, storePropertyBindingMetadata} from './shared'; * @codeGenApi */ export function ɵɵpropertyInterpolate( - propName: string, v0: any, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate { + propName: string, + v0: any, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate { ɵɵpropertyInterpolate1(propName, '', v0, '', sanitizer); return ɵɵpropertyInterpolate; } - /** * * Update an interpolated property on an element with single bound value surrounded by text. @@ -79,18 +90,36 @@ export function ɵɵpropertyInterpolate( * @codeGenApi */ export function ɵɵpropertyInterpolate1( - propName: string, prefix: string, v0: any, suffix: string, - sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate1 { + propName: string, + prefix: string, + v0: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate1 { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 1, prefix, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 1, + prefix, + suffix, + ); } return ɵɵpropertyInterpolate1; } @@ -126,18 +155,39 @@ export function ɵɵpropertyInterpolate1( * @codeGenApi */ export function ɵɵpropertyInterpolate2( - propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, - sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate2 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate2 { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 2, prefix, i0, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 2, + prefix, + i0, + suffix, + ); } return ɵɵpropertyInterpolate2; } @@ -176,18 +226,42 @@ export function ɵɵpropertyInterpolate2( * @codeGenApi */ export function ɵɵpropertyInterpolate3( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, - suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate3 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate3 { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 3, prefix, i0, i1, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 3, + prefix, + i0, + i1, + suffix, + ); } return ɵɵpropertyInterpolate3; } @@ -228,18 +302,45 @@ export function ɵɵpropertyInterpolate3( * @codeGenApi */ export function ɵɵpropertyInterpolate4( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate4 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate4 { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 4, prefix, i0, i1, i2, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 4, + prefix, + i0, + i1, + i2, + suffix, + ); } return ɵɵpropertyInterpolate4; } @@ -282,20 +383,61 @@ export function ɵɵpropertyInterpolate4( * @codeGenApi */ export function ɵɵpropertyInterpolate5( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, suffix: string, - sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate5 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate5 { const lView = getLView(); - const interpolatedValue = - interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + const interpolatedValue = interpolation5( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 5, prefix, i0, i1, i2, i3, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 5, + prefix, + i0, + i1, + i2, + i3, + suffix, + ); } return ɵɵpropertyInterpolate5; } @@ -340,20 +482,66 @@ export function ɵɵpropertyInterpolate5( * @codeGenApi */ export function ɵɵpropertyInterpolate6( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, - sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate6 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate6 { const lView = getLView(); - const interpolatedValue = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolatedValue = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 6, prefix, i0, i1, i2, i3, i4, suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 6, + prefix, + i0, + i1, + i2, + i3, + i4, + suffix, + ); } return ɵɵpropertyInterpolate6; } @@ -400,21 +588,71 @@ export function ɵɵpropertyInterpolate6( * @codeGenApi */ export function ɵɵpropertyInterpolate7( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, - sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate7 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate7 { const lView = getLView(); - const interpolatedValue = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolatedValue = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 7, prefix, i0, i1, i2, i3, i4, i5, - suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 7, + prefix, + i0, + i1, + i2, + i3, + i4, + i5, + suffix, + ); } return ɵɵpropertyInterpolate7; } @@ -463,21 +701,76 @@ export function ɵɵpropertyInterpolate7( * @codeGenApi */ export function ɵɵpropertyInterpolate8( - propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string, sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolate8 { + propName: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolate8 { const lView = getLView(); const interpolatedValue = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && - storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - 8, prefix, i0, i1, i2, i3, i4, i5, i6, - suffix); + storePropertyBindingMetadata( + tView.data, + tNode, + propName, + getBindingIndex() - 8, + prefix, + i0, + i1, + i2, + i3, + i4, + i5, + i6, + suffix, + ); } return ɵɵpropertyInterpolate8; } @@ -513,22 +806,37 @@ export function ɵɵpropertyInterpolate8( * @codeGenApi */ export function ɵɵpropertyInterpolateV( - propName: string, values: any[], sanitizer?: SanitizerFn): typeof ɵɵpropertyInterpolateV { + propName: string, + values: any[], + sanitizer?: SanitizerFn, +): typeof ɵɵpropertyInterpolateV { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + interpolatedValue, + lView[RENDERER], + sanitizer, + false, + ); if (ngDevMode) { - const interpolationInBetween = [values[0]]; // prefix + const interpolationInBetween = [values[0]]; // prefix for (let i = 2; i < values.length; i += 2) { interpolationInBetween.push(values[i]); } storePropertyBindingMetadata( - tView.data, tNode, propName, getBindingIndex() - interpolationInBetween.length + 1, - ...interpolationInBetween); + tView.data, + tNode, + propName, + getBindingIndex() - interpolationInBetween.length + 1, + ...interpolationInBetween, + ); } } return ɵɵpropertyInterpolateV; diff --git a/packages/core/src/render3/instructions/queries.ts b/packages/core/src/render3/instructions/queries.ts index 1a2868d1bf747..86626e43f0721 100644 --- a/packages/core/src/render3/instructions/queries.ts +++ b/packages/core/src/render3/instructions/queries.ts @@ -10,7 +10,13 @@ import {ProviderToken} from '../../di'; import {unwrapElementRef} from '../../linker/element_ref'; import {QueryList} from '../../linker/query_list'; import {QueryFlags} from '../interfaces/query'; -import {createContentQuery, createViewQuery, getQueryResults, getTQuery, loadQueryInternal} from '../query'; +import { + createContentQuery, + createViewQuery, + getQueryResults, + getTQuery, + loadQueryInternal, +} from '../query'; import {getCurrentQueryIndex, getLView, getTView, setCurrentQueryIndex} from '../state'; import {isCreationMode} from '../util/view_utils'; @@ -27,8 +33,11 @@ import {isCreationMode} from '../util/view_utils'; * @codeGenApi */ export function ɵɵcontentQuery( - directiveIndex: number, predicate: ProviderToken|string|string[], flags: QueryFlags, - read?: any): void { + directiveIndex: number, + predicate: ProviderToken | string | string[], + flags: QueryFlags, + read?: any, +): void { createContentQuery(directiveIndex, predicate, flags, read); } @@ -42,7 +51,10 @@ export function ɵɵcontentQuery( * @codeGenApi */ export function ɵɵviewQuery( - predicate: ProviderToken|string|string[], flags: QueryFlags, read?: any): void { + predicate: ProviderToken | string | string[], + flags: QueryFlags, + read?: any, +): void { createViewQuery(predicate, flags, read); } @@ -63,9 +75,11 @@ export function ɵɵqueryRefresh(queryList: QueryList): boolean { setCurrentQueryIndex(queryIndex + 1); const tQuery = getTQuery(tView, queryIndex); - if (queryList.dirty && - (isCreationMode(lView) === - ((tQuery.metadata.flags & QueryFlags.isStatic) === QueryFlags.isStatic))) { + if ( + queryList.dirty && + isCreationMode(lView) === + ((tQuery.metadata.flags & QueryFlags.isStatic) === QueryFlags.isStatic) + ) { if (tQuery.matches === null) { queryList.reset([]); } else { diff --git a/packages/core/src/render3/instructions/queries_signals.ts b/packages/core/src/render3/instructions/queries_signals.ts index 9247352821350..093ab829af28f 100644 --- a/packages/core/src/render3/instructions/queries_signals.ts +++ b/packages/core/src/render3/instructions/queries_signals.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - import {ProviderToken} from '../../di/provider_token'; import {QueryFlags} from '../interfaces/query'; import {createContentQuery, createViewQuery} from '../query'; @@ -26,8 +25,12 @@ import {getCurrentQueryIndex, setCurrentQueryIndex} from '../state'; * @codeGenApi */ export function ɵɵcontentQuerySignal( - directiveIndex: number, target: Signal, predicate: ProviderToken|string[], - flags: QueryFlags, read?: any): void { + directiveIndex: number, + target: Signal, + predicate: ProviderToken | string[], + flags: QueryFlags, + read?: any, +): void { bindQueryToSignal(target, createContentQuery(directiveIndex, predicate, flags, read)); } @@ -43,8 +46,11 @@ export function ɵɵcontentQuerySignal( * @codeGenApi */ export function ɵɵviewQuerySignal( - target: Signal, predicate: ProviderToken|string[], flags: QueryFlags, - read?: ProviderToken): void { + target: Signal, + predicate: ProviderToken | string[], + flags: QueryFlags, + read?: ProviderToken, +): void { bindQueryToSignal(target, createViewQuery(predicate, flags, read)); } diff --git a/packages/core/src/render3/instructions/render.ts b/packages/core/src/render3/instructions/render.ts index 5d649bb49c5e5..464e4dc2f2e26 100644 --- a/packages/core/src/render3/instructions/render.ts +++ b/packages/core/src/render3/instructions/render.ts @@ -9,7 +9,18 @@ import {retrieveHydrationInfo} from '../../hydration/utils'; import {assertEqual, assertNotReactive} from '../../util/assert'; import {RenderFlags} from '../interfaces/definition'; -import {CONTEXT, FLAGS, HOST, HYDRATION, INJECTOR, LView, LViewFlags, QUERIES, TVIEW, TView} from '../interfaces/view'; +import { + CONTEXT, + FLAGS, + HOST, + HYDRATION, + INJECTOR, + LView, + LViewFlags, + QUERIES, + TVIEW, + TView, +} from '../interfaces/view'; import {enterView, leaveView} from '../state'; import {getComponentLViewByIndex, isCreationMode} from '../util/view_utils'; @@ -119,7 +130,6 @@ export function renderView(tView: TView, lView: LView, context: T): void { if (components !== null) { renderChildComponents(lView, components); } - } catch (error) { // If we didn't manage to get past the first template pass due to // an error, mark the view as corrupted so we can try to recover. diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 55003b7b3af8b..611650802df59 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -19,40 +19,136 @@ import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks'; import {Writable} from '../../interface/type'; import {SchemaMetadata} from '../../metadata/schema'; import {ViewEncapsulation} from '../../metadata/view'; -import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization'; -import {assertDefined, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert'; +import { + validateAgainstEventAttributes, + validateAgainstEventProperties, +} from '../../sanitization/sanitization'; +import { + assertDefined, + assertEqual, + assertGreaterThan, + assertGreaterThanOrEqual, + assertIndexInRange, + assertNotEqual, + assertNotSame, + assertSame, + assertString, +} from '../../util/assert'; import {escapeCommentText} from '../../util/dom'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect'; import {stringify} from '../../util/stringify'; import {applyValueToInputField} from '../apply_value_input_field'; -import {assertFirstCreatePass, assertFirstUpdatePass, assertLView, assertNoDuplicateDirectives, assertTNodeForLView, assertTNodeForTView} from '../assert'; +import { + assertFirstCreatePass, + assertFirstUpdatePass, + assertLView, + assertNoDuplicateDirectives, + assertTNodeForLView, + assertTNodeForTView, +} from '../assert'; import {attachPatchData} from '../context_discovery'; import {getFactoryDef} from '../definition_factory'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di'; import {throwMultipleComponentError} from '../errors'; import {AttributeMarker} from '../interfaces/attribute_marker'; import {CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; -import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, HostDirectiveBindingMap, HostDirectiveDefs, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; +import { + ComponentDef, + ComponentTemplate, + DirectiveDef, + DirectiveDefListOrFactory, + HostBindingsFunction, + HostDirectiveBindingMap, + HostDirectiveDefs, + PipeDefListOrFactory, + RenderFlags, + ViewQueriesFunction, +} from '../interfaces/definition'; import {NodeInjectorFactory} from '../interfaces/injector'; import {InputFlags} from '../interfaces/input_flags'; import {getUniqueLViewId} from '../interfaces/lview_tracking'; -import {InitialInputData, InitialInputs, LocalRefExtractor, NodeInputBindings, NodeOutputBindings, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node'; +import { + InitialInputData, + InitialInputs, + LocalRefExtractor, + NodeInputBindings, + NodeOutputBindings, + TAttributes, + TConstantsOrFactory, + TContainerNode, + TDirectiveHostNode, + TElementContainerNode, + TElementNode, + TIcuContainerNode, + TNode, + TNodeFlags, + TNodeType, + TProjectionNode, +} from '../interfaces/node'; import {Renderer} from '../interfaces/renderer'; import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom'; import {SanitizerFn} from '../interfaces/sanitization'; import {TStylingRange} from '../interfaces/styling'; import {isComponentDef, isComponentHost, isContentQueryHost} from '../interfaces/type_checks'; -import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, EMBEDDED_VIEW_INJECTOR, ENVIRONMENT, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, HYDRATION, ID, INJECTOR, LView, LViewEnvironment, LViewFlags, NEXT, PARENT, RENDERER, T_HOST, TData, TVIEW, TView, TViewType} from '../interfaces/view'; +import { + CHILD_HEAD, + CHILD_TAIL, + CLEANUP, + CONTEXT, + DECLARATION_COMPONENT_VIEW, + DECLARATION_VIEW, + EMBEDDED_VIEW_INJECTOR, + ENVIRONMENT, + FLAGS, + HEADER_OFFSET, + HOST, + HostBindingOpCodes, + HYDRATION, + ID, + INJECTOR, + LView, + LViewEnvironment, + LViewFlags, + NEXT, + PARENT, + RENDERER, + T_HOST, + TData, + TVIEW, + TView, + TViewType, +} from '../interfaces/view'; import {assertPureTNodeType, assertTNodeType} from '../node_assert'; import {clearElementContents, updateTextNode} from '../node_manipulation'; import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher'; import {profiler, ProfilerEvent} from '../profiler'; -import {getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, isInSkipHydrationBlock, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setSelectedIndex} from '../state'; +import { + getBindingsEnabled, + getCurrentDirectiveIndex, + getCurrentParentTNode, + getCurrentTNodePlaceholderOk, + getSelectedIndex, + isCurrentTNodeParent, + isInCheckNoChangesMode, + isInI18nBlock, + isInSkipHydrationBlock, + setBindingRootForHostBindings, + setCurrentDirectiveIndex, + setCurrentQueryIndex, + setCurrentTNode, + setSelectedIndex, +} from '../state'; import {NO_CHANGE} from '../tokens'; import {mergeHostAttrs} from '../util/attrs_utils'; import {INTERPOLATION_DELIMITER} from '../util/misc_utils'; import {renderStringify} from '../util/stringify_utils'; -import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, resetPreOrderHookFlags, unwrapLView} from '../util/view_utils'; +import { + getComponentLViewByIndex, + getNativeByIndex, + getNativeByTNode, + resetPreOrderHookFlags, + unwrapLView, +} from '../util/view_utils'; import {selectIndexInternal} from './advance'; import {ɵɵdirectiveInject} from './di'; @@ -93,38 +189,54 @@ export function processHostBindingOpCodes(tView: TView, lView: LView): void { } export function createLView( - parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null, - tHostNode: TNode|null, environment: LViewEnvironment|null, renderer: Renderer|null, - injector: Injector|null, embeddedViewInjector: Injector|null, - hydrationInfo: DehydratedView|null): LView { + parentLView: LView | null, + tView: TView, + context: T | null, + flags: LViewFlags, + host: RElement | null, + tHostNode: TNode | null, + environment: LViewEnvironment | null, + renderer: Renderer | null, + injector: Injector | null, + embeddedViewInjector: Injector | null, + hydrationInfo: DehydratedView | null, +): LView { const lView = tView.blueprint.slice() as LView; lView[HOST] = host; - lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass | - LViewFlags.Dirty; - if (embeddedViewInjector !== null || - (parentLView && (parentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector))) { + lView[FLAGS] = + flags | + LViewFlags.CreationMode | + LViewFlags.Attached | + LViewFlags.FirstLViewPass | + LViewFlags.Dirty; + if ( + embeddedViewInjector !== null || + (parentLView && parentLView[FLAGS] & LViewFlags.HasEmbeddedViewInjector) + ) { lView[FLAGS] |= LViewFlags.HasEmbeddedViewInjector; } resetPreOrderHookFlags(lView); ngDevMode && tView.declTNode && parentLView && assertTNodeForLView(tView.declTNode, parentLView); lView[PARENT] = lView[DECLARATION_VIEW] = parentLView; lView[CONTEXT] = context; - lView[ENVIRONMENT] = (environment || parentLView && parentLView[ENVIRONMENT])!; + lView[ENVIRONMENT] = (environment || (parentLView && parentLView[ENVIRONMENT]))!; ngDevMode && assertDefined(lView[ENVIRONMENT], 'LViewEnvironment is required'); - lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER])!; + lView[RENDERER] = (renderer || (parentLView && parentLView[RENDERER]))!; ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required'); - lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null; + lView[INJECTOR as any] = injector || (parentLView && parentLView[INJECTOR]) || null; lView[T_HOST] = tHostNode; lView[ID] = getUniqueLViewId(); lView[HYDRATION] = hydrationInfo; lView[EMBEDDED_VIEW_INJECTOR as any] = embeddedViewInjector; ngDevMode && - assertEqual( - tView.type == TViewType.Embedded ? parentLView !== null : true, true, - 'Embedded views must have parentLView'); + assertEqual( + tView.type == TViewType.Embedded ? parentLView !== null : true, + true, + 'Embedded views must have parentLView', + ); lView[DECLARATION_COMPONENT_VIEW] = - tView.type == TViewType.Embedded ? parentLView![DECLARATION_COMPONENT_VIEW] : lView; + tView.type == TViewType.Embedded ? parentLView![DECLARATION_COMPONENT_VIEW] : lView; return lView as LView; } @@ -140,26 +252,51 @@ export function createLView( * @param attrs Any attrs for the native element, if applicable */ export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null, - attrs: TAttributes|null): TElementNode; + tView: TView, + index: number, + type: TNodeType.Element | TNodeType.Text, + name: string | null, + attrs: TAttributes | null, +): TElementNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Container, name: string|null, - attrs: TAttributes|null): TContainerNode; + tView: TView, + index: number, + type: TNodeType.Container, + name: string | null, + attrs: TAttributes | null, +): TContainerNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Projection, name: null, - attrs: TAttributes|null): TProjectionNode; + tView: TView, + index: number, + type: TNodeType.Projection, + name: null, + attrs: TAttributes | null, +): TProjectionNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null, - attrs: TAttributes|null): TElementContainerNode; + tView: TView, + index: number, + type: TNodeType.ElementContainer, + name: string | null, + attrs: TAttributes | null, +): TElementContainerNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Icu, name: null, - attrs: TAttributes|null): TElementContainerNode; + tView: TView, + index: number, + type: TNodeType.Icu, + name: null, + attrs: TAttributes | null, +): TElementContainerNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null): - TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode { - ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in - // `view_engine_compatibility` for additional context. - assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.'); + tView: TView, + index: number, + type: TNodeType, + name: string | null, + attrs: TAttributes | null, +): TElementNode & TContainerNode & TElementContainerNode & TProjectionNode & TIcuContainerNode { + ngDevMode && + index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in + // `view_engine_compatibility` for additional context. + assertGreaterThanOrEqual(index, HEADER_OFFSET, "TNodes can't be in the LView header."); // Keep this function short, so that the VM will inline it. ngDevMode && assertPureTNodeType(type); let tNode = tView.data[index] as TNode; @@ -182,18 +319,32 @@ export function getOrCreateTNode( ngDevMode && assertEqual(index, tNode.index, 'Expecting same index'); } setCurrentTNode(tNode, true); - return tNode as TElementNode & TContainerNode & TElementContainerNode & TProjectionNode & - TIcuContainerNode; + return tNode as TElementNode & + TContainerNode & + TElementContainerNode & + TProjectionNode & + TIcuContainerNode; } export function createTNodeAtIndex( - tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null) { + tView: TView, + index: number, + type: TNodeType, + name: string | null, + attrs: TAttributes | null, +) { const currentTNode = getCurrentTNodePlaceholderOk(); const isParent = isCurrentTNodeParent(); const parent = isParent ? currentTNode : currentTNode && currentTNode.parent; // Parents cannot cross component boundaries because components will be used in multiple places. - const tNode = tView.data[index] = - createTNode(tView, parent as TElementNode | TContainerNode, type, index, name, attrs); + const tNode = (tView.data[index] = createTNode( + tView, + parent as TElementNode | TContainerNode, + type, + index, + name, + attrs, + )); // Assign a pointer to the first child node of a given view. The first node is not always the one // at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has // the index 1 or more, so we can't just check node index. @@ -230,14 +381,21 @@ export function createTNodeAtIndex( * @param initialValue Initial value to store in blueprint */ export function allocExpando( - tView: TView, lView: LView, numSlotsToAlloc: number, initialValue: any): number { + tView: TView, + lView: LView, + numSlotsToAlloc: number, + initialValue: any, +): number { if (numSlotsToAlloc === 0) return -1; if (ngDevMode) { assertFirstCreatePass(tView); assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!'); assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView'); assertEqual( - tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView'); + tView.data.length, + tView.blueprint.length, + 'Expecting Blueprint to be same size as TView', + ); assertFirstUpdatePass(tView); } const allocIdx = lView.length; @@ -250,7 +408,12 @@ export function allocExpando( } export function executeTemplate( - tView: TView, lView: LView, templateFn: ComponentTemplate, rf: RenderFlags, context: T) { + tView: TView, + lView: LView, + templateFn: ComponentTemplate, + rf: RenderFlags, + context: T, +) { const prevSelectedIndex = getSelectedIndex(); const isUpdatePhase = rf & RenderFlags.Update; try { @@ -261,15 +424,17 @@ export function executeTemplate( selectIndexInternal(tView, lView, HEADER_OFFSET, !!ngDevMode && isInCheckNoChangesMode()); } - const preHookType = - isUpdatePhase ? ProfilerEvent.TemplateUpdateStart : ProfilerEvent.TemplateCreateStart; + const preHookType = isUpdatePhase + ? ProfilerEvent.TemplateUpdateStart + : ProfilerEvent.TemplateCreateStart; profiler(preHookType, context as unknown as {}); templateFn(rf, context); } finally { setSelectedIndex(prevSelectedIndex); - const postHookType = - isUpdatePhase ? ProfilerEvent.TemplateUpdateEnd : ProfilerEvent.TemplateCreateEnd; + const postHookType = isUpdatePhase + ? ProfilerEvent.TemplateUpdateEnd + : ProfilerEvent.TemplateCreateEnd; profiler(postHookType, context as unknown as {}); } } @@ -289,8 +454,10 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView) if (def.contentQueries) { const directiveInstance = lView[directiveIndex]; ngDevMode && - assertDefined( - directiveIndex, 'Incorrect reference to a directive defining a content query'); + assertDefined( + directiveIndex, + 'Incorrect reference to a directive defining a content query', + ); def.contentQueries(RenderFlags.Create, directiveInstance, directiveIndex); } } @@ -300,7 +467,6 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView) } } - /** * Creates directive instances. */ @@ -317,17 +483,22 @@ export function createDirectivesInstances(tView: TView, lView: LView, tNode: TDi * to LView in the same order as they are loaded in the template with load(). */ export function saveResolvedLocalsInData( - viewData: LView, tNode: TDirectiveHostNode, - localRefExtractor: LocalRefExtractor = getNativeByTNode): void { + viewData: LView, + tNode: TDirectiveHostNode, + localRefExtractor: LocalRefExtractor = getNativeByTNode, +): void { const localNames = tNode.localNames; if (localNames !== null) { let localIndex = tNode.index + 1; for (let i = 0; i < localNames.length; i += 2) { const index = localNames[i + 1] as number; - const value = index === -1 ? - localRefExtractor( - tNode as TElementNode | TContainerNode | TElementContainerNode, viewData) : - viewData[index]; + const value = + index === -1 + ? localRefExtractor( + tNode as TElementNode | TContainerNode | TElementContainerNode, + viewData, + ) + : viewData[index]; viewData[localIndex++] = value; } } @@ -349,15 +520,24 @@ export function getOrCreateComponentTView(def: ComponentDef): TView { // Declaration node here is null since this function is called when we dynamically create a // component and hence there is no declaration. const declTNode = null; - return def.tView = createTView( - TViewType.Component, declTNode, def.template, def.decls, def.vars, def.directiveDefs, - def.pipeDefs, def.viewQuery, def.schemas, def.consts, def.id); + return (def.tView = createTView( + TViewType.Component, + declTNode, + def.template, + def.decls, + def.vars, + def.directiveDefs, + def.pipeDefs, + def.viewQuery, + def.schemas, + def.consts, + def.id, + )); } return tView; } - /** * Creates a TView instance * @@ -372,10 +552,18 @@ export function getOrCreateComponentTView(def: ComponentDef): TView { * @param consts Constants for this view */ export function createTView( - type: TViewType, declTNode: TNode|null, templateFn: ComponentTemplate|null, decls: number, - vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null, - viewQuery: ViewQueriesFunction|null, schemas: SchemaMetadata[]|null, - constsOrFactory: TConstantsOrFactory|null, ssrId: string|null): TView { + type: TViewType, + declTNode: TNode | null, + templateFn: ComponentTemplate | null, + decls: number, + vars: number, + directives: DirectiveDefListOrFactory | null, + pipes: PipeDefListOrFactory | null, + viewQuery: ViewQueriesFunction | null, + schemas: SchemaMetadata[] | null, + constsOrFactory: TConstantsOrFactory | null, + ssrId: string | null, +): TView { ngDevMode && ngDevMode.tView++; const bindingStartIndex = HEADER_OFFSET + decls; // This length does not yet contain host bindings from child directives because at this point, @@ -384,7 +572,7 @@ export function createTView( const initialViewLength = bindingStartIndex + vars; const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength); const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory; - const tView = blueprint[TVIEW as any] = { + const tView = (blueprint[TVIEW as any] = { type: type, blueprint: blueprint, template: templateFn, @@ -416,7 +604,7 @@ export function createTView( consts: consts, incompleteFirstPass: false, ssrId, - }; + }); if (ngDevMode) { // For performance reasons it is important that the tView retains the same shape during runtime. // (To make sure that all of the code is monomorphic.) For this reason we seal the object to @@ -445,8 +633,11 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe * @param injector Root view injector instance. */ export function locateHostElement( - renderer: Renderer, elementOrSelector: RElement|string, encapsulation: ViewEncapsulation, - injector: Injector): RElement { + renderer: Renderer, + elementOrSelector: RElement | string, + encapsulation: ViewEncapsulation, + injector: Injector, +): RElement { // Note: we use default value for the `PRESERVE_HOST_CONTENT` here even though it's a // tree-shakable one (providedIn:'root'). This code path can be triggered during dynamic // component creation (after calling ViewContainerRef.createComponent) when an injector @@ -515,7 +706,11 @@ export function enableApplyRootElementTransformImpl() { * - Index of context we just saved in LView.cleanupInstances */ export function storeCleanupWithContext( - tView: TView, lView: LView, context: any, cleanupFn: Function): void { + tView: TView, + lView: LView, + context: any, + cleanupFn: Function, +): void { const lCleanup = getOrCreateLViewCleanup(lView); // Historically the `storeCleanupWithContext` was used to register both framework-level and @@ -523,8 +718,10 @@ export function storeCleanupWithContext( // This dev mode checks assures that user-level cleanup callbacks are _not_ stored in data // structures reserved for framework-specific hooks. ngDevMode && - assertDefined( - context, 'Cleanup context is mandatory when registering framework-level destroy hooks'); + assertDefined( + context, + 'Cleanup context is mandatory when registering framework-level destroy hooks', + ); lCleanup.push(context); if (tView.firstCreatePass) { @@ -550,30 +747,66 @@ export function storeCleanupWithContext( * @returns the TNode object */ export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container, - index: number, tagName: string|null, attrs: TAttributes|null): TContainerNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType.Container, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TContainerNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text, - index: number, tagName: string|null, attrs: TAttributes|null): TElementNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType.Element | TNodeType.Text, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TElementNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer, - index: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType.ElementContainer, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TElementContainerNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu, index: number, - tagName: string|null, attrs: TAttributes|null): TIcuContainerNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType.Icu, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TIcuContainerNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection, - index: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType.Projection, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TProjectionNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number, - tagName: string|null, attrs: TAttributes|null): TNode; + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType, + index: number, + tagName: string | null, + attrs: TAttributes | null, +): TNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number, - value: string|null, attrs: TAttributes|null): TNode { - ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in - // `view_engine_compatibility` for additional context. - assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.'); - ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\''); + tView: TView, + tParent: TElementNode | TContainerNode | null, + type: TNodeType, + index: number, + value: string | null, + attrs: TAttributes | null, +): TNode { + ngDevMode && + index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in + // `view_engine_compatibility` for additional context. + assertGreaterThanOrEqual(index, HEADER_OFFSET, "TNodes can't be in the LView header."); + ngDevMode && assertNotSame(attrs, undefined, "'undefined' is not valid value for 'attrs'"); ngDevMode && ngDevMode.tNode++; ngDevMode && tParent && assertTNodeForTView(tParent, tView); let injectorIndex = tParent ? tParent.injectorIndex : -1; @@ -640,9 +873,12 @@ const enum CaptureNodeBindingMode { * name inputs/outputs should be exposed under. */ function captureNodeBindings( - mode: CaptureNodeBindingMode.Inputs, inputs: DirectiveDef['inputs'], directiveIndex: number, - bindingsResult: NodeInputBindings|null, - hostDirectiveAliasMap: HostDirectiveBindingMap|null): NodeInputBindings|null; + mode: CaptureNodeBindingMode.Inputs, + inputs: DirectiveDef['inputs'], + directiveIndex: number, + bindingsResult: NodeInputBindings | null, + hostDirectiveAliasMap: HostDirectiveBindingMap | null, +): NodeInputBindings | null; /** * Captures node output bindings for the given directive based on the output metadata. * This will be called multiple times to combine inputs from various directives on a node. @@ -652,15 +888,20 @@ function captureNodeBindings( * name inputs/outputs should be exposed under. */ function captureNodeBindings( - mode: CaptureNodeBindingMode.Outputs, outputs: DirectiveDef['outputs'], - directiveIndex: number, bindingsResult: NodeOutputBindings|null, - hostDirectiveAliasMap: HostDirectiveBindingMap|null): NodeOutputBindings|null; + mode: CaptureNodeBindingMode.Outputs, + outputs: DirectiveDef['outputs'], + directiveIndex: number, + bindingsResult: NodeOutputBindings | null, + hostDirectiveAliasMap: HostDirectiveBindingMap | null, +): NodeOutputBindings | null; function captureNodeBindings( - mode: CaptureNodeBindingMode, aliasMap: DirectiveDef['inputs']|DirectiveDef['outputs'], - directiveIndex: number, bindingsResult: NodeInputBindings|NodeOutputBindings|null, - hostDirectiveAliasMap: HostDirectiveBindingMap|null): NodeInputBindings|NodeOutputBindings| - null { + mode: CaptureNodeBindingMode, + aliasMap: DirectiveDef['inputs'] | DirectiveDef['outputs'], + directiveIndex: number, + bindingsResult: NodeInputBindings | NodeOutputBindings | null, + hostDirectiveAliasMap: HostDirectiveBindingMap | null, +): NodeInputBindings | NodeOutputBindings | null { for (let publicName in aliasMap) { if (!aliasMap.hasOwnProperty(publicName)) { continue; @@ -703,27 +944,46 @@ function captureNodeBindings( if (mode === CaptureNodeBindingMode.Inputs) { addPropertyBinding( - bindingsResult as NodeInputBindings, directiveIndex, finalPublicName, internalName, - inputFlags); + bindingsResult as NodeInputBindings, + directiveIndex, + finalPublicName, + internalName, + inputFlags, + ); } else { addPropertyBinding( - bindingsResult as NodeOutputBindings, directiveIndex, finalPublicName, internalName); + bindingsResult as NodeOutputBindings, + directiveIndex, + finalPublicName, + internalName, + ); } } return bindingsResult; } function addPropertyBinding( - bindings: NodeInputBindings, directiveIndex: number, publicName: string, internalName: string, - inputFlags: InputFlags): void; + bindings: NodeInputBindings, + directiveIndex: number, + publicName: string, + internalName: string, + inputFlags: InputFlags, +): void; function addPropertyBinding( - bindings: NodeOutputBindings, directiveIndex: number, publicName: string, - internalName: string): void; + bindings: NodeOutputBindings, + directiveIndex: number, + publicName: string, + internalName: string, +): void; function addPropertyBinding( - bindings: NodeInputBindings|NodeOutputBindings, directiveIndex: number, publicName: string, - internalName: string, inputFlags?: InputFlags) { - let values: (typeof bindings[typeof publicName]); + bindings: NodeInputBindings | NodeOutputBindings, + directiveIndex: number, + publicName: string, + internalName: string, + inputFlags?: InputFlags, +) { + let values: (typeof bindings)[typeof publicName]; if (bindings.hasOwnProperty(publicName)) { (values = bindings[publicName]).push(directiveIndex, internalName); @@ -741,7 +1001,10 @@ function addPropertyBinding( * Initialization is done for all directives matched on a given TNode. */ function initializeInputAndOutputAliases( - tView: TView, tNode: TNode, hostDirectiveDefinitionMap: HostDirectiveDefs|null): void { + tView: TView, + tNode: TNode, + hostDirectiveDefinitionMap: HostDirectiveDefs | null, +): void { ngDevMode && assertFirstCreatePass(tView); const start = tNode.directiveStart; @@ -750,30 +1013,39 @@ function initializeInputAndOutputAliases( const tNodeAttrs = tNode.attrs; const inputsFromAttrs: InitialInputData = []; - let inputsStore: NodeInputBindings|null = null; - let outputsStore: NodeOutputBindings|null = null; + let inputsStore: NodeInputBindings | null = null; + let outputsStore: NodeOutputBindings | null = null; for (let directiveIndex = start; directiveIndex < end; directiveIndex++) { const directiveDef = tViewData[directiveIndex] as DirectiveDef; - const aliasData = - hostDirectiveDefinitionMap ? hostDirectiveDefinitionMap.get(directiveDef) : null; + const aliasData = hostDirectiveDefinitionMap + ? hostDirectiveDefinitionMap.get(directiveDef) + : null; const aliasedInputs = aliasData ? aliasData.inputs : null; const aliasedOutputs = aliasData ? aliasData.outputs : null; inputsStore = captureNodeBindings( - CaptureNodeBindingMode.Inputs, directiveDef.inputs, directiveIndex, inputsStore, - aliasedInputs); + CaptureNodeBindingMode.Inputs, + directiveDef.inputs, + directiveIndex, + inputsStore, + aliasedInputs, + ); outputsStore = captureNodeBindings( - CaptureNodeBindingMode.Outputs, directiveDef.outputs, directiveIndex, outputsStore, - aliasedOutputs); + CaptureNodeBindingMode.Outputs, + directiveDef.outputs, + directiveIndex, + outputsStore, + aliasedOutputs, + ); // Do not use unbound attributes as inputs to structural directives, since structural // directive inputs can only be set using microsyntax (e.g. `
`). // TODO(FW-1930): microsyntax expressions may also contain unbound/static attributes, which // should be set for inline templates. const initialInputs = - (inputsStore !== null && tNodeAttrs !== null && !isInlineTemplate(tNode)) ? - generateInitialInputs(inputsStore, directiveIndex, tNodeAttrs) : - null; + inputsStore !== null && tNodeAttrs !== null && !isInlineTemplate(tNode) + ? generateInitialInputs(inputsStore, directiveIndex, tNodeAttrs) + : null; inputsFromAttrs.push(initialInputs); } @@ -812,12 +1084,19 @@ function mapPropName(name: string): string { } export function elementPropertyInternal( - tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer, - sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean): void { + tView: TView, + tNode: TNode, + lView: LView, + propName: string, + value: T, + renderer: Renderer, + sanitizer: SanitizerFn | null | undefined, + nativeOnly: boolean, +): void { ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); const element = getNativeByTNode(tNode, lView) as RElement | RComment; let inputData = tNode.inputs; - let dataValue: NodeInputBindings[typeof propName]|undefined; + let dataValue: NodeInputBindings[typeof propName] | undefined; if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) { setInputsForProperty(tView, lView, dataValue, propName, value); if (isComponentHost(tNode)) markDirtyIfOnPush(lView, tNode.index); @@ -858,26 +1137,36 @@ export function markDirtyIfOnPush(lView: LView, viewIndex: number): void { } function setNgReflectProperty( - lView: LView, element: RElement|RComment, type: TNodeType, attrName: string, value: any) { + lView: LView, + element: RElement | RComment, + type: TNodeType, + attrName: string, + value: any, +) { const renderer = lView[RENDERER]; attrName = normalizeDebugBindingName(attrName); const debugValue = normalizeDebugBindingValue(value); if (type & TNodeType.AnyRNode) { if (value == null) { - renderer.removeAttribute((element as RElement), attrName); + renderer.removeAttribute(element as RElement, attrName); } else { - renderer.setAttribute((element as RElement), attrName, debugValue); + renderer.setAttribute(element as RElement, attrName, debugValue); } } else { - const textContent = - escapeCommentText(`bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`); - renderer.setValue((element as RComment), textContent); + const textContent = escapeCommentText( + `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`, + ); + renderer.setValue(element as RComment, textContent); } } export function setNgReflectProperties( - lView: LView, element: RElement|RComment, type: TNodeType, dataValue: NodeInputBindings[string], - value: any) { + lView: LView, + element: RElement | RComment, + type: TNodeType, + dataValue: NodeInputBindings[string], + value: any, +) { if (type & (TNodeType.AnyRNode | TNodeType.Container)) { /** * dataValue is an array containing runtime input or output names for the directives: @@ -897,17 +1186,20 @@ export function setNgReflectProperties( * Resolve the matched directives on a node. */ export function resolveDirectives( - tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode, - localRefs: string[]|null): void { + tView: TView, + lView: LView, + tNode: TElementNode | TContainerNode | TElementContainerNode, + localRefs: string[] | null, +): void { // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in // tsickle. ngDevMode && assertFirstCreatePass(tView); if (getBindingsEnabled()) { - const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1}; + const exportsMap: {[key: string]: number} | null = localRefs === null ? null : {'': -1}; const matchResult = findDirectiveDefMatches(tView, tNode); - let directiveDefs: DirectiveDef[]|null; - let hostDirectiveDefs: HostDirectiveDefs|null; + let directiveDefs: DirectiveDef[] | null; + let hostDirectiveDefs: HostDirectiveDefs | null; if (matchResult === null) { directiveDefs = hostDirectiveDefs = null; @@ -926,9 +1218,13 @@ export function resolveDirectives( /** Initializes the data structures necessary for a list of directives to be instantiated. */ export function initializeDirectives( - tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode, - directives: DirectiveDef[], exportsMap: {[key: string]: number;}|null, - hostDirectiveDefs: HostDirectiveDefs|null) { + tView: TView, + lView: LView, + tNode: TElementNode | TContainerNode | TElementContainerNode, + directives: DirectiveDef[], + exportsMap: {[key: string]: number} | null, + hostDirectiveDefs: HostDirectiveDefs | null, +) { ngDevMode && assertFirstCreatePass(tView); // Publishes the directive types to DI so they can be injected. Needs to @@ -953,9 +1249,11 @@ export function initializeDirectives( let preOrderCheckHooksFound = false; let directiveIdx = allocExpando(tView, lView, directives.length, null); ngDevMode && - assertSame( - directiveIdx, tNode.directiveStart, - 'TNode.directiveStart should point to just allocated space'); + assertSame( + directiveIdx, + tNode.directiveStart, + 'TNode.directiveStart should point to just allocated space', + ); for (let i = 0; i < directives.length; i++) { const def = directives[i]; @@ -970,11 +1268,13 @@ export function initializeDirectives( if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0) tNode.flags |= TNodeFlags.hasHostBindings; - const lifeCycleHooks: Partial = def.type.prototype; + const lifeCycleHooks: Partial = def.type.prototype; // Only push a node index into the preOrderHooks array if this is the first // pre-order hook found on this node. - if (!preOrderHooksFound && - (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngOnInit || lifeCycleHooks.ngDoCheck)) { + if ( + !preOrderHooksFound && + (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngOnInit || lifeCycleHooks.ngDoCheck) + ) { // We will push the actual hook function into this array later during dir instantiation. // We cannot do it now because we must ensure hooks are registered in the same // order that directives are created (i.e. injection order). @@ -1003,8 +1303,12 @@ export function initializeDirectives( * @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add. */ export function registerHostBindingOpCodes( - tView: TView, tNode: TNode, directiveIdx: number, directiveVarsIdx: number, - def: ComponentDef|DirectiveDef): void { + tView: TView, + tNode: TNode, + directiveIdx: number, + directiveVarsIdx: number, + def: ComponentDef | DirectiveDef, +): void { ngDevMode && assertFirstCreatePass(tView); const hostBindings = def.hostBindings; @@ -1043,12 +1347,15 @@ function lastSelectedElementIdx(hostBindingOpCodes: HostBindingOpCodes): number return 0; } - /** * Instantiate all the directives that were previously resolved on the current node. */ function instantiateAllDirectives( - tView: TView, lView: LView, tNode: TDirectiveHostNode, native: RNode) { + tView: TView, + lView: LView, + tNode: TDirectiveHostNode, + native: RNode, +) { const start = tNode.directiveStart; const end = tNode.directiveEnd; @@ -1057,8 +1364,10 @@ function instantiateAllDirectives( if (isComponentHost(tNode)) { ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode); addComponentLogic( - lView, tNode as TElementNode, - tView.data[start + tNode.componentOffset] as ComponentDef); + lView, + tNode as TElementNode, + tView.data[start + tNode.componentOffset] as ComponentDef, + ); } if (!tView.firstCreatePass) { @@ -1122,26 +1431,29 @@ export function invokeHostBindingsInCreationMode(def: DirectiveDef, directi * If a component is matched (at most one), it is returned in first position in the array. */ function findDirectiveDefMatches( - tView: TView, tNode: TElementNode|TContainerNode|TElementContainerNode): - [matches: DirectiveDef[], hostDirectiveDefs: HostDirectiveDefs|null]|null { + tView: TView, + tNode: TElementNode | TContainerNode | TElementContainerNode, +): [matches: DirectiveDef[], hostDirectiveDefs: HostDirectiveDefs | null] | null { ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer); const registry = tView.directiveRegistry; - let matches: DirectiveDef[]|null = null; - let hostDirectiveDefs: HostDirectiveDefs|null = null; + let matches: DirectiveDef[] | null = null; + let hostDirectiveDefs: HostDirectiveDefs | null = null; if (registry) { for (let i = 0; i < registry.length; i++) { - const def = registry[i] as ComponentDef| DirectiveDef; + const def = registry[i] as ComponentDef | DirectiveDef; if (isNodeMatchingSelectorList(tNode, def.selectors!, /* isProjectionMode */ false)) { matches || (matches = []); if (isComponentDef(def)) { if (ngDevMode) { assertTNodeType( - tNode, TNodeType.Element, - `"${tNode.value}" tags cannot be used as component hosts. ` + - `Please use a different tag to activate the ${stringify(def.type)} component.`); + tNode, + TNodeType.Element, + `"${tNode.value}" tags cannot be used as component hosts. ` + + `Please use a different tag to activate the ${stringify(def.type)} component.`, + ); if (isComponentHost(tNode)) { throwMultipleComponentError(tNode, matches.find(isComponentDef)!.type, def.type); @@ -1202,9 +1514,12 @@ export function markAsComponentHost(tView: TView, hostTNode: TNode, componentOff /** Caches local names and their matching directive indices for query and template lookups. */ function cacheMatchingLocalNames( - tNode: TNode, localRefs: string[]|null, exportsMap: {[key: string]: number}): void { + tNode: TNode, + localRefs: string[] | null, + exportsMap: {[key: string]: number}, +): void { if (localRefs) { - const localNames: (string|number)[] = tNode.localNames = []; + const localNames: (string | number)[] = (tNode.localNames = []); // Local names must be stored in tNode in the same order that localRefs are defined // in the template to ensure the data is loaded in the same slots as their refs @@ -1213,8 +1528,9 @@ function cacheMatchingLocalNames( const index = exportsMap[localRefs[i + 1]]; if (index == null) throw new RuntimeError( - RuntimeErrorCode.EXPORT_NOT_FOUND, - ngDevMode && `Export of name '${localRefs[i + 1]}' not found!`); + RuntimeErrorCode.EXPORT_NOT_FOUND, + ngDevMode && `Export of name '${localRefs[i + 1]}' not found!`, + ); localNames.push(localRefs[i], index); } } @@ -1225,8 +1541,10 @@ function cacheMatchingLocalNames( * to their directive instances. */ function saveNameToExportMap( - directiveIdx: number, def: DirectiveDef|ComponentDef, - exportsMap: {[key: string]: number}|null) { + directiveIdx: number, + def: DirectiveDef | ComponentDef, + exportsMap: {[key: string]: number} | null, +) { if (exportsMap) { if (def.exportAs) { for (let i = 0; i < def.exportAs.length; i++) { @@ -1244,9 +1562,11 @@ function saveNameToExportMap( */ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) { ngDevMode && - assertNotEqual( - numberOfDirectives, tNode.directiveEnd - tNode.directiveStart, - 'Reached the max number of directives'); + assertNotEqual( + numberOfDirectives, + tNode.directiveEnd - tNode.directiveStart, + 'Reached the max number of directives', + ); tNode.flags |= TNodeFlags.isDirectiveHost; // When the first directive is created on a node, save the index tNode.directiveStart = index; @@ -1267,22 +1587,35 @@ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: * @param def `DirectiveDef` */ export function configureViewWithDirective( - tView: TView, tNode: TNode, lView: LView, directiveIndex: number, def: DirectiveDef): void { + tView: TView, + tNode: TNode, + lView: LView, + directiveIndex: number, + def: DirectiveDef, +): void { ngDevMode && - assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section'); + assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section'); tView.data[directiveIndex] = def; const directiveFactory = - def.factory || ((def as Writable>).factory = getFactoryDef(def.type, true)); + def.factory || ((def as Writable>).factory = getFactoryDef(def.type, true)); // Even though `directiveFactory` will already be using `ɵɵdirectiveInject` in its generated code, // we also want to support `inject()` directly from the directive constructor context so we set // `ɵɵdirectiveInject` as the inject implementation here too. - const nodeInjectorFactory = - new NodeInjectorFactory(directiveFactory, isComponentDef(def), ɵɵdirectiveInject); + const nodeInjectorFactory = new NodeInjectorFactory( + directiveFactory, + isComponentDef(def), + ɵɵdirectiveInject, + ); tView.blueprint[directiveIndex] = nodeInjectorFactory; lView[directiveIndex] = nodeInjectorFactory; registerHostBindingOpCodes( - tView, tNode, directiveIndex, allocExpando(tView, lView, def.hostVars, NO_CHANGE), def); + tView, + tNode, + directiveIndex, + allocExpando(tView, lView, def.hostVars, NO_CHANGE), + def, + ); } function addComponentLogic(lView: LView, hostTNode: TElementNode, def: ComponentDef): void { @@ -1299,10 +1632,21 @@ function addComponentLogic(lView: LView, hostTNode: TElementNode, def: Compon lViewFlags = LViewFlags.Dirty; } const componentView = addToViewTree( + lView, + createLView( lView, - createLView( - lView, tView, null, lViewFlags, native, hostTNode as TElementNode, null, - rendererFactory.createRenderer(native, def), null, null, null)); + tView, + null, + lViewFlags, + native, + hostTNode as TElementNode, + null, + rendererFactory.createRenderer(native, def), + null, + null, + null, + ), + ); // Component view will always be created before any injected LContainers, // so this is a regular element, wrap it with the component view @@ -1310,31 +1654,43 @@ function addComponentLogic(lView: LView, hostTNode: TElementNode, def: Compon } export function elementAttributeInternal( - tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn|null|undefined, - namespace: string|null|undefined) { + tNode: TNode, + lView: LView, + name: string, + value: any, + sanitizer: SanitizerFn | null | undefined, + namespace: string | null | undefined, +) { if (ngDevMode) { assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); validateAgainstEventAttributes(name); assertTNodeType( - tNode, TNodeType.Element, - `Attempted to set attribute \`${name}\` on a container node. ` + - `Host bindings are not valid on ng-container or ng-template.`); + tNode, + TNodeType.Element, + `Attempted to set attribute \`${name}\` on a container node. ` + + `Host bindings are not valid on ng-container or ng-template.`, + ); } const element = getNativeByTNode(tNode, lView) as RElement; setElementAttribute(lView[RENDERER], element, namespace, tNode.value, name, value, sanitizer); } export function setElementAttribute( - renderer: Renderer, element: RElement, namespace: string|null|undefined, tagName: string|null, - name: string, value: any, sanitizer: SanitizerFn|null|undefined) { + renderer: Renderer, + element: RElement, + namespace: string | null | undefined, + tagName: string | null, + name: string, + value: any, + sanitizer: SanitizerFn | null | undefined, +) { if (value == null) { ngDevMode && ngDevMode.rendererRemoveAttribute++; renderer.removeAttribute(element, name, namespace); } else { ngDevMode && ngDevMode.rendererSetAttribute++; const strValue = - sanitizer == null ? renderStringify(value) : sanitizer(value, tagName || '', name); - + sanitizer == null ? renderStringify(value) : sanitizer(value, tagName || '', name); renderer.setAttribute(element, name, strValue as string, namespace); } @@ -1350,11 +1706,16 @@ export function setElementAttribute( * @param tNode The static data for this node */ function setInputsFromAttrs( - lView: LView, directiveIndex: number, instance: T, def: DirectiveDef, tNode: TNode, - initialInputData: InitialInputData): void { - const initialInputs: InitialInputs|null = initialInputData![directiveIndex]; + lView: LView, + directiveIndex: number, + instance: T, + def: DirectiveDef, + tNode: TNode, + initialInputData: InitialInputData, +): void { + const initialInputs: InitialInputs | null = initialInputData![directiveIndex]; if (initialInputs !== null) { - for (let i = 0; i < initialInputs.length;) { + for (let i = 0; i < initialInputs.length; ) { const publicName = initialInputs[i++] as string; const privateName = initialInputs[i++] as string; const flags = initialInputs[i++] as InputFlags; @@ -1386,8 +1747,11 @@ function setInputsFromAttrs( * @param attrs Static attrs on this node. */ function generateInitialInputs( - inputs: NodeInputBindings, directiveIndex: number, attrs: TAttributes): InitialInputs|null { - let inputsToStore: InitialInputs|null = null; + inputs: NodeInputBindings, + directiveIndex: number, + attrs: TAttributes, +): InitialInputs | null { + let inputsToStore: InitialInputs | null = null; let i = 0; while (i < attrs.length) { const attrName = attrs[i]; @@ -1414,8 +1778,11 @@ function generateInitialInputs( for (let j = 0; j < inputConfig.length; j += 3) { if (inputConfig[j] === directiveIndex) { inputsToStore.push( - attrName as string, inputConfig[j + 1] as string, inputConfig[j + 2] as InputFlags, - attrs[i + 1] as string); + attrName as string, + inputConfig[j + 1] as string, + inputConfig[j + 2] as InputFlags, + attrs[i + 1] as string, + ); // A directive can't have multiple inputs with the same name so we can break here. break; } @@ -1442,25 +1809,30 @@ function generateInitialInputs( * @returns LContainer */ export function createLContainer( - hostNative: RElement|RComment|LView, currentView: LView, native: RComment, - tNode: TNode): LContainer { + hostNative: RElement | RComment | LView, + currentView: LView, + native: RComment, + tNode: TNode, +): LContainer { ngDevMode && assertLView(currentView); const lContainer: LContainer = [ - hostNative, // host native - true, // Boolean `true` in this position signifies that this is an `LContainer` - 0, // flags - currentView, // parent - null, // next - tNode, // t_host - null, // dehydrated views - native, // native, - null, // view refs - null, // moved views + hostNative, // host native + true, // Boolean `true` in this position signifies that this is an `LContainer` + 0, // flags + currentView, // parent + null, // next + tNode, // t_host + null, // dehydrated views + native, // native, + null, // view refs + null, // moved views ]; ngDevMode && - assertEqual( - lContainer.length, CONTAINER_HEADER_OFFSET, - 'Should allocate correct number of slots for LContainer header.'); + assertEqual( + lContainer.length, + CONTAINER_HEADER_OFFSET, + 'Should allocate correct number of slots for LContainer header.', + ); return lContainer; } @@ -1477,8 +1849,7 @@ export function refreshContentQueries(tView: TView, lView: LView): void { const directiveDef = tView.data[directiveDefIdx] as DirectiveDef; ngDevMode && assertDefined(directiveDef, 'DirectiveDef not found.'); ngDevMode && - assertDefined( - directiveDef.contentQueries, 'contentQueries function should be defined'); + assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); setCurrentQueryIndex(queryStartIdx); directiveDef.contentQueries!(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx); } @@ -1500,7 +1871,7 @@ export function refreshContentQueries(tView: TView, lView: LView): void { * @param lViewOrLContainer The LView or LContainer to add to the view tree * @returns The state passed in */ -export function addToViewTree(lView: LView, lViewOrLContainer: T): T { +export function addToViewTree(lView: LView, lViewOrLContainer: T): T { // TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer // to the end of the queue, which means if the developer retrieves the LContainers from RNodes out // of order, the change detection will run out of order, as the act of retrieving the the @@ -1519,7 +1890,10 @@ export function addToViewTree(lView: LView, lViewOrL /////////////////////////////// export function executeViewQueryFn( - flags: RenderFlags, viewQueryFn: ViewQueriesFunction, component: T): void { + flags: RenderFlags, + viewQueryFn: ViewQueriesFunction, + component: T, +): void { ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.'); setCurrentQueryIndex(0); const prevConsumer = setActiveConsumer(null); @@ -1556,8 +1930,12 @@ export function executeViewQueryFn( * @param interpolationParts static interpolation parts (for property interpolations) */ export function storePropertyBindingMetadata( - tData: TData, tNode: TNode, propertyName: string, bindingIndex: number, - ...interpolationParts: string[]) { + tData: TData, + tNode: TNode, + propertyName: string, + bindingIndex: number, + ...interpolationParts: string[] +) { // Binding meta-data are stored only the first time a given property instruction is processed. // Since we don't have a concept of the "first update pass" we need to check for presence of the // binding meta-data to decide if one should be stored (or if was stored already). @@ -1568,7 +1946,7 @@ export function storePropertyBindingMetadata( let bindingMetadata = propertyName; if (interpolationParts.length > 0) { bindingMetadata += - INTERPOLATION_DELIMITER + interpolationParts.join(INTERPOLATION_DELIMITER); + INTERPOLATION_DELIMITER + interpolationParts.join(INTERPOLATION_DELIMITER); } tData[bindingIndex] = bindingMetadata; } @@ -1589,7 +1967,10 @@ export function getOrCreateTViewCleanup(tView: TView): any[] { * instead of the current renderer (see the componentSyntheticHost* instructions). */ export function loadComponentRenderer( - currentDef: DirectiveDef|null, tNode: TNode, lView: LView): Renderer { + currentDef: DirectiveDef | null, + tNode: TNode, + lView: LView, +): Renderer { // TODO(FW-2043): the `currentDef` is null when host bindings are invoked while creating root // component (see packages/core/src/render3/component.ts). This is not consistent with the process // of creating inner components, when current directive index is available in the state. In order @@ -1619,9 +2000,13 @@ export function handleError(lView: LView, error: any): void { * @param value Value to set. */ export function setInputsForProperty( - tView: TView, lView: LView, inputs: NodeInputBindings[typeof publicName], publicName: string, - value: unknown): void { - for (let i = 0; i < inputs.length;) { + tView: TView, + lView: LView, + inputs: NodeInputBindings[typeof publicName], + publicName: string, + value: unknown, +): void { + for (let i = 0; i < inputs.length; ) { const index = inputs[i++] as number; const privateName = inputs[i++] as string; const flags = inputs[i++] as InputFlags; diff --git a/packages/core/src/render3/instructions/storage.ts b/packages/core/src/render3/instructions/storage.ts index 676ee4d92824c..39b1dd17523b3 100644 --- a/packages/core/src/render3/instructions/storage.ts +++ b/packages/core/src/render3/instructions/storage.ts @@ -9,7 +9,6 @@ import {HEADER_OFFSET, LView, TView} from '../interfaces/view'; import {getContextLView} from '../state'; import {load} from '../util/view_utils'; - /** Store a value in the `data` at a given `index`. */ export function store(tView: TView, lView: LView, index: number, value: T): void { // We don't store any static data for local variables, so the first time diff --git a/packages/core/src/render3/instructions/style_map_interpolation.ts b/packages/core/src/render3/instructions/style_map_interpolation.ts index 42466eec0dd23..f279e21bba632 100644 --- a/packages/core/src/render3/instructions/style_map_interpolation.ts +++ b/packages/core/src/render3/instructions/style_map_interpolation.ts @@ -7,10 +7,19 @@ */ import {getLView} from '../state'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {ɵɵstyleMap} from './styling'; - /** * * Update an interpolated style on an element with single bound value surrounded by text. @@ -62,7 +71,12 @@ export function ɵɵstyleMapInterpolate1(prefix: string, v0: any, suffix: string * @codeGenApi */ export function ɵɵstyleMapInterpolate2( - prefix: string, v0: any, i0: string, v1: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); ɵɵstyleMap(interpolatedValue); @@ -95,7 +109,14 @@ export function ɵɵstyleMapInterpolate2( * @codeGenApi */ export function ɵɵstyleMapInterpolate3( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); ɵɵstyleMap(interpolatedValue); @@ -130,8 +151,16 @@ export function ɵɵstyleMapInterpolate3( * @codeGenApi */ export function ɵɵstyleMapInterpolate4( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); ɵɵstyleMap(interpolatedValue); @@ -168,11 +197,33 @@ export function ɵɵstyleMapInterpolate4( * @codeGenApi */ export function ɵɵstyleMapInterpolate5( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + const interpolatedValue = interpolation5( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + suffix, + ); ɵɵstyleMap(interpolatedValue); } @@ -211,11 +262,37 @@ export function ɵɵstyleMapInterpolate5( * @codeGenApi */ export function ɵɵstyleMapInterpolate6( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolatedValue = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); ɵɵstyleMap(interpolatedValue); } @@ -256,11 +333,41 @@ export function ɵɵstyleMapInterpolate6( * @codeGenApi */ export function ɵɵstyleMapInterpolate7( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, +): void { const lView = getLView(); - const interpolatedValue = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolatedValue = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); ɵɵstyleMap(interpolatedValue); } @@ -303,12 +410,45 @@ export function ɵɵstyleMapInterpolate7( * @codeGenApi */ export function ɵɵstyleMapInterpolate8( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): void { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, +): void { const lView = getLView(); const interpolatedValue = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); ɵɵstyleMap(interpolatedValue); } diff --git a/packages/core/src/render3/instructions/style_prop_interpolation.ts b/packages/core/src/render3/instructions/style_prop_interpolation.ts index edb4dd2f48dd6..7ac6b7983cf09 100644 --- a/packages/core/src/render3/instructions/style_prop_interpolation.ts +++ b/packages/core/src/render3/instructions/style_prop_interpolation.ts @@ -6,11 +6,20 @@ * found in the LICENSE file at https://angular.io/license */ -import {getLView,} from '../state'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import {getLView} from '../state'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {checkStylingProperty} from './styling'; - /** * * Update an interpolated style property on an element with single bound value surrounded by text. @@ -38,8 +47,12 @@ import {checkStylingProperty} from './styling'; * @codeGenApi */ export function ɵɵstylePropInterpolate1( - prop: string, prefix: string, v0: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate1 { + prop: string, + prefix: string, + v0: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate1 { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); @@ -75,8 +88,14 @@ export function ɵɵstylePropInterpolate1( * @codeGenApi */ export function ɵɵstylePropInterpolate2( - prop: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate2 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate2 { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); @@ -114,8 +133,16 @@ export function ɵɵstylePropInterpolate2( * @codeGenApi */ export function ɵɵstylePropInterpolate3( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate3 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate3 { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); @@ -155,8 +182,18 @@ export function ɵɵstylePropInterpolate3( * @codeGenApi */ export function ɵɵstylePropInterpolate4( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, suffix: string, valueSuffix?: string|null): typeof ɵɵstylePropInterpolate4 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate4 { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); @@ -198,12 +235,35 @@ export function ɵɵstylePropInterpolate4( * @codeGenApi */ export function ɵɵstylePropInterpolate5( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate5 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate5 { const lView = getLView(); - const interpolatedValue = - interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + const interpolatedValue = interpolation5( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + suffix, + ); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate5; } @@ -245,12 +305,39 @@ export function ɵɵstylePropInterpolate5( * @codeGenApi */ export function ɵɵstylePropInterpolate6( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate6 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate6 { const lView = getLView(); - const interpolatedValue = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolatedValue = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate6; } @@ -295,12 +382,43 @@ export function ɵɵstylePropInterpolate6( * @codeGenApi */ export function ɵɵstylePropInterpolate7( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, - valueSuffix?: string|null): typeof ɵɵstylePropInterpolate7 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate7 { const lView = getLView(); - const interpolatedValue = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolatedValue = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate7; } @@ -347,12 +465,47 @@ export function ɵɵstylePropInterpolate7( * @codeGenApi */ export function ɵɵstylePropInterpolate8( - prop: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, - v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string, valueSuffix?: string|null): typeof ɵɵstylePropInterpolate8 { + prop: string, + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolate8 { const lView = getLView(); const interpolatedValue = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate8; } @@ -388,7 +541,10 @@ export function ɵɵstylePropInterpolate8( * @codeGenApi */ export function ɵɵstylePropInterpolateV( - prop: string, values: any[], valueSuffix?: string|null): typeof ɵɵstylePropInterpolateV { + prop: string, + values: any[], + valueSuffix?: string | null, +): typeof ɵɵstylePropInterpolateV { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index 449f3d8f4cb1b..fe43b244cef48 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -8,28 +8,53 @@ import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass'; import {KeyValueArray, keyValueArrayGet, keyValueArraySet} from '../../util/array_utils'; -import {assertDefined, assertEqual, assertLessThan, assertNotEqual, throwError} from '../../util/assert'; +import { + assertDefined, + assertEqual, + assertLessThan, + assertNotEqual, + throwError, +} from '../../util/assert'; import {EMPTY_ARRAY} from '../../util/empty'; import {concatStringsWithSpace, stringify} from '../../util/stringify'; import {assertFirstUpdatePass} from '../assert'; import {bindingUpdated} from '../bindings'; -import { AttributeMarker } from '../interfaces/attribute_marker'; +import {AttributeMarker} from '../interfaces/attribute_marker'; import {DirectiveDef} from '../interfaces/definition'; import {TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node'; import {Renderer} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer_dom'; -import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling'; +import { + getTStylingRangeNext, + getTStylingRangeNextDuplicate, + getTStylingRangePrev, + getTStylingRangePrevDuplicate, + TStylingKey, + TStylingRange, +} from '../interfaces/styling'; import {LView, RENDERER, TData, TView} from '../interfaces/view'; import {applyStyling} from '../node_manipulation'; -import {getCurrentDirectiveDef, getLView, getSelectedIndex, getTView, incrementBindingIndex} from '../state'; +import { + getCurrentDirectiveDef, + getLView, + getSelectedIndex, + getTView, + incrementBindingIndex, +} from '../state'; import {insertTStylingBinding} from '../styling/style_binding_list'; -import {getLastParsedKey, getLastParsedValue, parseClassName, parseClassNameNext, parseStyle, parseStyleNext} from '../styling/styling_parser'; +import { + getLastParsedKey, + getLastParsedValue, + parseClassName, + parseClassNameNext, + parseStyle, + parseStyleNext, +} from '../styling/styling_parser'; import {NO_CHANGE} from '../tokens'; import {getNativeByIndex} from '../util/view_utils'; import {setDirectiveInputsWhichShadowsStyling} from './property'; - /** * Update a style binding on an element with the provided value. * @@ -50,8 +75,10 @@ import {setDirectiveInputsWhichShadowsStyling} from './property'; * @codeGenApi */ export function ɵɵstyleProp( - prop: string, value: string|number|SafeValue|undefined|null, - suffix?: string|null): typeof ɵɵstyleProp { + prop: string, + value: string | number | SafeValue | undefined | null, + suffix?: string | null, +): typeof ɵɵstyleProp { checkStylingProperty(prop, value, suffix, false); return ɵɵstyleProp; } @@ -71,12 +98,14 @@ export function ɵɵstyleProp( * * @codeGenApi */ -export function ɵɵclassProp(className: string, value: boolean|undefined|null): typeof ɵɵclassProp { +export function ɵɵclassProp( + className: string, + value: boolean | undefined | null, +): typeof ɵɵclassProp { checkStylingProperty(className, value, null, true); return ɵɵclassProp; } - /** * Update style bindings using an object literal on an element. * @@ -96,11 +125,10 @@ export function ɵɵclassProp(className: string, value: boolean|undefined|null): * * @codeGenApi */ -export function ɵɵstyleMap(styles: {[styleName: string]: any}|string|undefined|null): void { +export function ɵɵstyleMap(styles: {[styleName: string]: any} | string | undefined | null): void { checkStylingMap(styleKeyValueArraySet, styleStringParser, styles, false); } - /** * Parse text as style and add values to KeyValueArray. * @@ -116,7 +144,6 @@ export function styleStringParser(keyValueArray: KeyValueArray, text: strin } } - /** * Update class bindings using an object literal or class-string on an element. * @@ -135,8 +162,9 @@ export function styleStringParser(keyValueArray: KeyValueArray, text: strin * * @codeGenApi */ -export function ɵɵclassMap(classes: {[className: string]: boolean|undefined|null}|string|undefined| - null): void { +export function ɵɵclassMap( + classes: {[className: string]: boolean | undefined | null} | string | undefined | null, +): void { checkStylingMap(classKeyValueArraySet, classStringParser, classes, true); } @@ -164,8 +192,11 @@ export function classStringParser(keyValueArray: KeyValueArray, text: strin * @param isClassBased `true` if `class` change (`false` if `style`) */ export function checkStylingProperty( - prop: string, value: any|NO_CHANGE, suffix: string|undefined|null, - isClassBased: boolean): void { + prop: string, + value: any | NO_CHANGE, + suffix: string | undefined | null, + isClassBased: boolean, +): void { const lView = getLView(); const tView = getTView(); // Styling instructions use 2 slots per binding. @@ -178,8 +209,15 @@ export function checkStylingProperty( if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) { const tNode = tView.data[getSelectedIndex()] as TNode; updateStyling( - tView, tNode, lView, lView[RENDERER], prop, - lView[bindingIndex + 1] = normalizeSuffix(value, suffix), isClassBased, bindingIndex); + tView, + tNode, + lView, + lView[RENDERER], + prop, + (lView[bindingIndex + 1] = normalizeSuffix(value, suffix)), + isClassBased, + bindingIndex, + ); } } @@ -194,9 +232,11 @@ export function checkStylingProperty( * @param isClassBased `true` if `class` change (`false` if `style`) */ export function checkStylingMap( - keyValueArraySet: (keyValueArray: KeyValueArray, key: string, value: any) => void, - stringParser: (styleKeyValueArray: KeyValueArray, text: string) => void, - value: any|NO_CHANGE, isClassBased: boolean): void { + keyValueArraySet: (keyValueArray: KeyValueArray, key: string, value: any) => void, + stringParser: (styleKeyValueArray: KeyValueArray, text: string) => void, + value: any | NO_CHANGE, + isClassBased: boolean, +): void { const tView = getTView(); const bindingIndex = incrementBindingIndex(2); if (tView.firstUpdatePass) { @@ -213,8 +253,10 @@ export function checkStylingMap( // processing this binding in styling resolution. const tStylingKey = tView.data[bindingIndex]; assertEqual( - Array.isArray(tStylingKey) ? tStylingKey[1] : tStylingKey, false, - 'Styling linked list shadow input should be marked as \'false\''); + Array.isArray(tStylingKey) ? tStylingKey[1] : tStylingKey, + false, + "Styling linked list shadow input should be marked as 'false'", + ); } // VE does not concatenate the static portion like we are doing here. // Instead VE just ignores the static completely if dynamic binding is present. @@ -224,9 +266,10 @@ export function checkStylingMap( // thing as it would think that the static portion was removed. For this reason we // concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed. let staticPrefix = isClassBased ? tNode.classesWithoutHost : tNode.stylesWithoutHost; - ngDevMode && isClassBased === false && staticPrefix !== null && - assertEqual( - staticPrefix.endsWith(';'), true, 'Expecting static portion to end with \';\''); + ngDevMode && + isClassBased === false && + staticPrefix !== null && + assertEqual(staticPrefix.endsWith(';'), true, "Expecting static portion to end with ';'"); if (staticPrefix !== null) { // We want to make sure that falsy values of `value` become empty strings. value = concatStringsWithSpace(staticPrefix, value ? value : ''); @@ -236,9 +279,15 @@ export function checkStylingMap( setDirectiveInputsWhichShadowsStyling(tView, tNode, lView, value, isClassBased); } else { updateStylingMap( - tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1], - lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value), - isClassBased, bindingIndex); + tView, + tNode, + lView, + lView[RENDERER], + lView[bindingIndex + 1], + (lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value)), + isClassBased, + bindingIndex, + ); } } } @@ -264,7 +313,11 @@ function isInHostBindings(tView: TView, bindingIndex: number): boolean { * @param isClassBased `true` if `class` change (`false` if `style`) */ function stylingFirstUpdatePass( - tView: TView, tStylingKey: TStylingKey, bindingIndex: number, isClassBased: boolean): void { + tView: TView, + tStylingKey: TStylingKey, + bindingIndex: number, + isClassBased: boolean, +): void { ngDevMode && assertFirstUpdatePass(tView); const tData = tView.data; if (tData[bindingIndex + 1] === null) { @@ -303,7 +356,11 @@ function stylingFirstUpdatePass( * @param isClassBased `true` if `class` (`false` if `style`) */ export function wrapInStaticStylingKey( - tData: TData, tNode: TNode, stylingKey: TStylingKey, isClassBased: boolean): TStylingKey { + tData: TData, + tNode: TNode, + stylingKey: TStylingKey, + isClassBased: boolean, +): TStylingKey { const hostDirectiveDef = getCurrentDirectiveDef(tData); let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles; if (hostDirectiveDef === null) { @@ -312,7 +369,7 @@ export function wrapInStaticStylingKey( // styling and there is no need to collect them again. We know that we are the first styling // instruction because the `TNode.*Bindings` points to 0 (nothing has been inserted yet). const isFirstStylingInstructionInTemplate = - (isClassBased ? tNode.classBindings : tNode.styleBindings) as any as number === 0; + ((isClassBased ? tNode.classBindings : tNode.styleBindings) as any as number) === 0; if (isFirstStylingInstructionInTemplate) { // It would be nice to be able to get the statics from `mergeAttrs`, however, at this point // they are already merged and it would not be possible to figure which property belongs where @@ -327,10 +384,15 @@ export function wrapInStaticStylingKey( // This means that we need to compute the residual. const directiveStylingLast = tNode.directiveStylingLast; const isFirstStylingInstructionInHostBinding = - directiveStylingLast === -1 || tData[directiveStylingLast] !== hostDirectiveDef; + directiveStylingLast === -1 || tData[directiveStylingLast] !== hostDirectiveDef; if (isFirstStylingInstructionInHostBinding) { - stylingKey = - collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased); + stylingKey = collectStylingFromDirectives( + hostDirectiveDef, + tData, + tNode, + stylingKey, + isClassBased, + ); if (residual === null) { // - If `null` than either: // - Template styling instruction already ran and it has consumed the static @@ -344,10 +406,17 @@ export function wrapInStaticStylingKey( // then there is nothing to do since this operation can only produce less static keys, not // more.) templateStylingKey = collectStylingFromDirectives( - null, tData, tNode, templateStylingKey[1] /* unwrap previous statics */, - isClassBased); - templateStylingKey = - collectStylingFromTAttrs(templateStylingKey, tNode.attrs, isClassBased); + null, + tData, + tNode, + templateStylingKey[1] /* unwrap previous statics */, + isClassBased, + ); + templateStylingKey = collectStylingFromTAttrs( + templateStylingKey, + tNode.attrs, + isClassBased, + ); setTemplateHeadTStylingKey(tData, tNode, isClassBased, templateStylingKey); } } else { @@ -379,8 +448,11 @@ export function wrapInStaticStylingKey( * @param isClassBased `true` if `class` (`false` if `style`) * @return `TStylingKey` if found or `undefined` if not found. */ -function getTemplateHeadTStylingKey(tData: TData, tNode: TNode, isClassBased: boolean): TStylingKey| - undefined { +function getTemplateHeadTStylingKey( + tData: TData, + tNode: TNode, + isClassBased: boolean, +): TStylingKey | undefined { const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings; if (getTStylingRangeNext(bindings) === 0) { // There does not seem to be a styling instruction in the `template`. @@ -442,12 +514,18 @@ function getTemplateHeadTStylingKey(tData: TData, tNode: TNode, isClassBased: bo * @param tStylingKey New `TStylingKey` which is replacing the old one. */ function setTemplateHeadTStylingKey( - tData: TData, tNode: TNode, isClassBased: boolean, tStylingKey: TStylingKey): void { + tData: TData, + tNode: TNode, + isClassBased: boolean, + tStylingKey: TStylingKey, +): void { const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings; ngDevMode && - assertNotEqual( - getTStylingRangeNext(bindings), 0, - 'Expecting to have at least one template styling binding.'); + assertNotEqual( + getTStylingRangeNext(bindings), + 0, + 'Expecting to have at least one template styling binding.', + ); tData[getTStylingRangePrev(bindings)] = tStylingKey; } @@ -461,21 +539,26 @@ function setTemplateHeadTStylingKey( * @param tNode `TNode` which contains the directive range. * @param isClassBased `true` if `class` (`false` if `style`) */ -function collectResidual(tData: TData, tNode: TNode, isClassBased: boolean): KeyValueArray| - null { - let residual: KeyValueArray|null|undefined = undefined; +function collectResidual( + tData: TData, + tNode: TNode, + isClassBased: boolean, +): KeyValueArray | null { + let residual: KeyValueArray | null | undefined = undefined; const directiveEnd = tNode.directiveEnd; ngDevMode && - assertNotEqual( - tNode.directiveStylingLast, -1, - 'By the time this function gets called at least one hostBindings-node styling instruction must have executed.'); + assertNotEqual( + tNode.directiveStylingLast, + -1, + 'By the time this function gets called at least one hostBindings-node styling instruction must have executed.', + ); // We add `1 + tNode.directiveStart` because we need to skip the current directive (as we are // collecting things after the last `hostBindings` directive which had a styling instruction.) for (let i = 1 + tNode.directiveStylingLast; i < directiveEnd; i++) { const attrs = (tData[i] as DirectiveDef).hostAttrs; - residual = collectStylingFromTAttrs(residual, attrs, isClassBased) as KeyValueArray| null; + residual = collectStylingFromTAttrs(residual, attrs, isClassBased) as KeyValueArray | null; } - return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased) as KeyValueArray| null; + return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased) as KeyValueArray | null; } /** @@ -491,11 +574,15 @@ function collectResidual(tData: TData, tNode: TNode, isClassBased: boolean): Key * @param isClassBased `true` if `class` (`false` if `style`) */ function collectStylingFromDirectives( - hostDirectiveDef: DirectiveDef|null, tData: TData, tNode: TNode, stylingKey: TStylingKey, - isClassBased: boolean): TStylingKey { + hostDirectiveDef: DirectiveDef | null, + tData: TData, + tNode: TNode, + stylingKey: TStylingKey, + isClassBased: boolean, +): TStylingKey { // We need to loop because there can be directives which have `hostAttrs` but don't have // `hostBindings` so this loop catches up to the current directive.. - let currentDirective: DirectiveDef|null = null; + let currentDirective: DirectiveDef | null = null; const directiveEnd = tNode.directiveEnd; let directiveStylingLast = tNode.directiveStylingLast; if (directiveStylingLast === -1) { @@ -527,8 +614,10 @@ function collectStylingFromDirectives( * @param isClassBased `true` if `class` (`false` if `style`) */ function collectStylingFromTAttrs( - stylingKey: TStylingKey|undefined, attrs: TAttributes|null, - isClassBased: boolean): TStylingKey { + stylingKey: TStylingKey | undefined, + attrs: TAttributes | null, + isClassBased: boolean, +): TStylingKey { const desiredMarker = isClassBased ? AttributeMarker.Classes : AttributeMarker.Styles; let currentMarker = AttributeMarker.ImplicitAttributes; if (attrs !== null) { @@ -539,10 +628,13 @@ function collectStylingFromTAttrs( } else { if (currentMarker === desiredMarker) { if (!Array.isArray(stylingKey)) { - stylingKey = stylingKey === undefined ? [] : ['', stylingKey] as any; + stylingKey = stylingKey === undefined ? [] : (['', stylingKey] as any); } keyValueArraySet( - stylingKey as KeyValueArray, item, isClassBased ? true : attrs[++i]); + stylingKey as KeyValueArray, + item, + isClassBased ? true : attrs[++i], + ); } } } @@ -579,9 +671,10 @@ function collectStylingFromTAttrs( * @param value The value to parse/convert to `KeyValueArray` */ export function toStylingKeyValueArray( - keyValueArraySet: (keyValueArray: KeyValueArray, key: string, value: any) => void, - stringParser: (styleKeyValueArray: KeyValueArray, text: string) => void, - value: string|string[]|{[key: string]: any}|SafeValue|null|undefined): KeyValueArray { + keyValueArraySet: (keyValueArray: KeyValueArray, key: string, value: any) => void, + stringParser: (styleKeyValueArray: KeyValueArray, text: string) => void, + value: string | string[] | {[key: string]: any} | SafeValue | null | undefined, +): KeyValueArray { if (value == null /*|| value === undefined */ || value === '') return EMPTY_ARRAY as any; const styleKeyValueArray: KeyValueArray = [] as any; const unwrappedValue = unwrapSafeValue(value) as string | string[] | {[key: string]: any}; @@ -599,7 +692,7 @@ export function toStylingKeyValueArray( stringParser(styleKeyValueArray, unwrappedValue); } else { ngDevMode && - throwError('Unsupported styling type ' + typeof unwrappedValue + ': ' + unwrappedValue); + throwError('Unsupported styling type ' + typeof unwrappedValue + ': ' + unwrappedValue); } return styleKeyValueArray; } @@ -657,25 +750,31 @@ export function classKeyValueArraySet(keyValueArray: KeyValueArray, key: un * @param bindingIndex Binding index of the binding. */ function updateStylingMap( - tView: TView, tNode: TNode, lView: LView, renderer: Renderer, - oldKeyValueArray: KeyValueArray, newKeyValueArray: KeyValueArray, - isClassBased: boolean, bindingIndex: number) { - if (oldKeyValueArray as KeyValueArray| NO_CHANGE === NO_CHANGE) { + tView: TView, + tNode: TNode, + lView: LView, + renderer: Renderer, + oldKeyValueArray: KeyValueArray, + newKeyValueArray: KeyValueArray, + isClassBased: boolean, + bindingIndex: number, +) { + if ((oldKeyValueArray as KeyValueArray | NO_CHANGE) === NO_CHANGE) { // On first execution the oldKeyValueArray is NO_CHANGE => treat it as empty KeyValueArray. oldKeyValueArray = EMPTY_ARRAY as any; } let oldIndex = 0; let newIndex = 0; - let oldKey: string|null = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null; - let newKey: string|null = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null; + let oldKey: string | null = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null; + let newKey: string | null = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null; while (oldKey !== null || newKey !== null) { ngDevMode && assertLessThan(oldIndex, 999, 'Are we stuck in infinite loop?'); ngDevMode && assertLessThan(newIndex, 999, 'Are we stuck in infinite loop?'); const oldValue = - oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined; + oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined; const newValue = - newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined; - let setKey: string|null = null; + newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined; + let setKey: string | null = null; let setValue: any = undefined; if (oldKey === newKey) { // UPDATE: Keys are equal => new value is overwriting old value. @@ -685,7 +784,7 @@ function updateStylingMap( setKey = newKey; setValue = newValue; } - } else if (newKey === null || oldKey !== null && oldKey < newKey!) { + } else if (newKey === null || (oldKey !== null && oldKey < newKey!)) { // DELETE: oldKey key is missing or we did not find the oldKey in the newValue // (because the keyValueArray is sorted and `newKey` is found later alphabetically). // `"background" < "color"` so we need to delete `"background"` because it is not found in the @@ -727,8 +826,15 @@ function updateStylingMap( * @param bindingIndex Binding index of the binding. */ function updateStyling( - tView: TView, tNode: TNode, lView: LView, renderer: Renderer, prop: string, - value: string|undefined|null|boolean, isClassBased: boolean, bindingIndex: number) { + tView: TView, + tNode: TNode, + lView: LView, + renderer: Renderer, + prop: string, + value: string | undefined | null | boolean, + isClassBased: boolean, + bindingIndex: number, +) { if (!(tNode.type & TNodeType.AnyRNode)) { // It is possible to have styling on non-elements (such as ng-container). // This is rare, but it does happen. In such a case, just ignore the binding. @@ -736,9 +842,9 @@ function updateStyling( } const tData = tView.data; const tRange = tData[bindingIndex + 1] as TStylingRange; - const higherPriorityValue = getTStylingRangeNextDuplicate(tRange) ? - findStylingValue(tData, tNode, lView, prop, getTStylingRangeNext(tRange), isClassBased) : - undefined; + const higherPriorityValue = getTStylingRangeNextDuplicate(tRange) + ? findStylingValue(tData, tNode, lView, prop, getTStylingRangeNext(tRange), isClassBased) + : undefined; if (!isStylingValuePresent(higherPriorityValue)) { // We don't have a next duplicate, or we did not find a duplicate value. if (!isStylingValuePresent(value)) { @@ -782,8 +888,13 @@ function updateStyling( * @param isClassBased `true` if `class` (`false` if `style`) */ function findStylingValue( - tData: TData, tNode: TNode|null, lView: LView, prop: string, index: number, - isClassBased: boolean): any { + tData: TData, + tNode: TNode | null, + lView: LView, + prop: string, + index: number, + isClassBased: boolean, +): any { // `TNode` to use for resolving static styling. Also controls search direction. // - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true. // If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value. @@ -808,8 +919,11 @@ function findStylingValue( // binding actually executes.) valueAtLViewIndex = isStylingMap ? EMPTY_ARRAY : undefined; } - let currentValue = isStylingMap ? keyValueArrayGet(valueAtLViewIndex, prop) : - (key === prop ? valueAtLViewIndex : undefined); + let currentValue = isStylingMap + ? keyValueArrayGet(valueAtLViewIndex, prop) + : key === prop + ? valueAtLViewIndex + : undefined; if (containsStatics && !isStylingValuePresent(currentValue)) { currentValue = keyValueArrayGet(rawKey as KeyValueArray, prop); } @@ -854,7 +968,10 @@ function isStylingValuePresent(value: any): boolean { * @param value * @param suffix */ -function normalizeSuffix(value: any, suffix: string|undefined|null): string|null|undefined|boolean { +function normalizeSuffix( + value: any, + suffix: string | undefined | null, +): string | null | undefined | boolean { if (value == null || value === '') { // do nothing // Do not add the suffix if the value is going to be empty. @@ -868,7 +985,6 @@ function normalizeSuffix(value: any, suffix: string|undefined|null): string|null return value; } - /** * Tests if the `TNode` has input shadow. * diff --git a/packages/core/src/render3/instructions/template.ts b/packages/core/src/render3/instructions/template.ts index 9c6ea30fe6bde..d18c9355a76a8 100644 --- a/packages/core/src/render3/instructions/template.ts +++ b/packages/core/src/render3/instructions/template.ts @@ -8,7 +8,12 @@ import {validateMatchingNode, validateNodeExists} from '../../hydration/error_handling'; import {TEMPLATES} from '../../hydration/interfaces'; import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils'; -import {calcSerializedContainerSize, isDisconnectedNode, markRNodeAsClaimedByHydration, setSegmentHead} from '../../hydration/utils'; +import { + calcSerializedContainerSize, + isDisconnectedNode, + markRNodeAsClaimedByHydration, + setSegmentHead, +} from '../../hydration/utils'; import {isDetachedByI18n} from '../../i18n/utils'; import {populateDehydratedViewsInLContainer} from '../../linker/view_container_ref'; import {assertEqual} from '../../util/assert'; @@ -21,15 +26,37 @@ import {RComment} from '../interfaces/renderer_dom'; import {isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, TView, TViewType} from '../interfaces/view'; import {appendChild} from '../node_manipulation'; -import {getLView, getTView, isInSkipHydrationBlock, lastNodeWasCreated, setCurrentTNode, wasLastNodeCreated} from '../state'; +import { + getLView, + getTView, + isInSkipHydrationBlock, + lastNodeWasCreated, + setCurrentTNode, + wasLastNodeCreated, +} from '../state'; import {getConstant} from '../util/view_utils'; -import {addToViewTree, createDirectivesInstances, createLContainer, createTView, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; +import { + addToViewTree, + createDirectivesInstances, + createLContainer, + createTView, + getOrCreateTNode, + resolveDirectives, + saveResolvedLocalsInData, +} from './shared'; function templateFirstCreatePass( - index: number, tView: TView, lView: LView, templateFn: ComponentTemplate|null, - decls: number, vars: number, tagName?: string|null, attrs?: TAttributes|null, - localRefsIndex?: number|null): TContainerNode { + index: number, + tView: TView, + lView: LView, + templateFn: ComponentTemplate | null, + decls: number, + vars: number, + tagName?: string | null, + attrs?: TAttributes | null, + localRefsIndex?: number | null, +): TContainerNode { ngDevMode && assertFirstCreatePass(tView); ngDevMode && ngDevMode.firstCreatePass++; const tViewConsts = tView.consts; @@ -40,9 +67,19 @@ function templateFirstCreatePass( resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex)); registerPostOrderHooks(tView, tNode); - const embeddedTView = tNode.tView = createTView( - TViewType.Embedded, tNode, templateFn, decls, vars, tView.directiveRegistry, - tView.pipeRegistry, null, tView.schemas, tViewConsts, null /* ssrId */); + const embeddedTView = (tNode.tView = createTView( + TViewType.Embedded, + tNode, + templateFn, + decls, + vars, + tView.directiveRegistry, + tView.pipeRegistry, + null, + tView.schemas, + tViewConsts, + null /* ssrId */, + )); if (tView.queries !== null) { tView.queries.template(tView, tNode); @@ -68,20 +105,39 @@ function templateFirstCreatePass( * Defaults to the current element associated with the local-ref. */ export function declareTemplate( - declarationLView: LView, declarationTView: TView, index: number, - templateFn: ComponentTemplate|null, decls: number, vars: number, tagName?: string|null, - attrs?: TAttributes|null, localRefsIndex?: number|null, - localRefExtractor?: LocalRefExtractor): TNode { + declarationLView: LView, + declarationTView: TView, + index: number, + templateFn: ComponentTemplate | null, + decls: number, + vars: number, + tagName?: string | null, + attrs?: TAttributes | null, + localRefsIndex?: number | null, + localRefExtractor?: LocalRefExtractor, +): TNode { const adjustedIndex = index + HEADER_OFFSET; - const tNode = declarationTView.firstCreatePass ? - templateFirstCreatePass( - adjustedIndex, declarationTView, declarationLView, templateFn, decls, vars, tagName, - attrs, localRefsIndex) : - declarationTView.data[adjustedIndex] as TContainerNode; + const tNode = declarationTView.firstCreatePass + ? templateFirstCreatePass( + adjustedIndex, + declarationTView, + declarationLView, + templateFn, + decls, + vars, + tagName, + attrs, + localRefsIndex, + ) + : (declarationTView.data[adjustedIndex] as TContainerNode); setCurrentTNode(tNode, false); - const comment = - _locateOrCreateContainerAnchor(declarationTView, declarationLView, tNode, index) as RComment; + const comment = _locateOrCreateContainerAnchor( + declarationTView, + declarationLView, + tNode, + index, + ) as RComment; if (wasLastNodeCreated()) { appendChild(declarationTView, declarationLView, comment, tNode); @@ -128,15 +184,30 @@ export function declareTemplate( * @codeGenApi */ export function ɵɵtemplate( - index: number, templateFn: ComponentTemplate|null, decls: number, vars: number, - tagName?: string|null, attrsIndex?: number|null, localRefsIndex?: number|null, - localRefExtractor?: LocalRefExtractor): typeof ɵɵtemplate { + index: number, + templateFn: ComponentTemplate | null, + decls: number, + vars: number, + tagName?: string | null, + attrsIndex?: number | null, + localRefsIndex?: number | null, + localRefExtractor?: LocalRefExtractor, +): typeof ɵɵtemplate { const lView = getLView(); const tView = getTView(); const attrs = getConstant(tView.consts, attrsIndex); declareTemplate( - lView, tView, index, templateFn, decls, vars, tagName, attrs, localRefsIndex, - localRefExtractor); + lView, + tView, + index, + templateFn, + decls, + vars, + tagName, + attrs, + localRefsIndex, + localRefExtractor, + ); return ɵɵtemplate; } @@ -146,7 +217,11 @@ let _locateOrCreateContainerAnchor = createContainerAnchorImpl; * Regular creation mode for LContainers and their anchor (comment) nodes. */ function createContainerAnchorImpl( - tView: TView, lView: LView, tNode: TNode, index: number): RComment { + tView: TView, + lView: LView, + tNode: TNode, + index: number, +): RComment { lastNodeWasCreated(true); return lView[RENDERER].createComment(ngDevMode ? 'container' : ''); } @@ -157,10 +232,17 @@ function createContainerAnchorImpl( * anchor (comment) nodes. */ function locateOrCreateContainerAnchorImpl( - tView: TView, lView: LView, tNode: TNode, index: number): RComment { + tView: TView, + lView: LView, + tNode: TNode, + index: number, +): RComment { const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = + !hydrationInfo || + isInSkipHydrationBlock() || + isDetachedByI18n(tNode) || + isDisconnectedNode(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. @@ -183,7 +265,7 @@ function locateOrCreateContainerAnchorImpl( tNode.tView.ssrId = ssrId; } else { ngDevMode && - assertEqual(tNode.tView.ssrId, ssrId, 'Unexpected value of the `ssrId` for this TView'); + assertEqual(tNode.tView.ssrId, ssrId, 'Unexpected value of the `ssrId` for this TView'); } } diff --git a/packages/core/src/render3/instructions/text.ts b/packages/core/src/render3/instructions/text.ts index 0b824c9fb2c4c..1b4cd5b270aa4 100644 --- a/packages/core/src/render3/instructions/text.ts +++ b/packages/core/src/render3/instructions/text.ts @@ -14,12 +14,18 @@ import {TElementNode, TNode, TNodeType} from '../interfaces/node'; import {RText} from '../interfaces/renderer_dom'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, T_HOST, TView} from '../interfaces/view'; import {appendChild, createTextNode} from '../node_manipulation'; -import {getBindingIndex, getLView, getTView, isInSkipHydrationBlock, lastNodeWasCreated, setCurrentTNode, wasLastNodeCreated} from '../state'; +import { + getBindingIndex, + getLView, + getTView, + isInSkipHydrationBlock, + lastNodeWasCreated, + setCurrentTNode, + wasLastNodeCreated, +} from '../state'; import {getOrCreateTNode} from './shared'; - - /** * Create static text node * @@ -34,14 +40,16 @@ export function ɵɵtext(index: number, value: string = ''): void { const adjustedIndex = index + HEADER_OFFSET; ngDevMode && - assertEqual( - getBindingIndex(), tView.bindingStartIndex, - 'text nodes should be created before any bindings'); + assertEqual( + getBindingIndex(), + tView.bindingStartIndex, + 'text nodes should be created before any bindings', + ); ngDevMode && assertIndexInRange(lView, adjustedIndex); - const tNode = tView.firstCreatePass ? - getOrCreateTNode(tView, adjustedIndex, TNodeType.Text, value, null) : - tView.data[adjustedIndex] as TElementNode; + const tNode = tView.firstCreatePass + ? getOrCreateTNode(tView, adjustedIndex, TNodeType.Text, value, null) + : (tView.data[adjustedIndex] as TElementNode); const textNative = _locateOrCreateTextNode(tView, lView, tNode, value, index); lView[adjustedIndex] = textNative; @@ -54,21 +62,34 @@ export function ɵɵtext(index: number, value: string = ''): void { setCurrentTNode(tNode, false); } -let _locateOrCreateTextNode: typeof locateOrCreateTextNodeImpl = - (tView: TView, lView: LView, tNode: TNode, value: string, index: number) => { - lastNodeWasCreated(true); - return createTextNode(lView[RENDERER], value); - }; +let _locateOrCreateTextNode: typeof locateOrCreateTextNodeImpl = ( + tView: TView, + lView: LView, + tNode: TNode, + value: string, + index: number, +) => { + lastNodeWasCreated(true); + return createTextNode(lView[RENDERER], value); +}; /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode of text nodes. */ function locateOrCreateTextNodeImpl( - tView: TView, lView: LView, tNode: TNode, value: string, index: number): RText { + tView: TView, + lView: LView, + tNode: TNode, + value: string, + index: number, +): RText { const hydrationInfo = lView[HYDRATION]; - const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock() || - isDetachedByI18n(tNode) || isDisconnectedNode(hydrationInfo, index); + const isNodeCreationMode = + !hydrationInfo || + isInSkipHydrationBlock() || + isDetachedByI18n(tNode) || + isDisconnectedNode(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. diff --git a/packages/core/src/render3/instructions/text_interpolation.ts b/packages/core/src/render3/instructions/text_interpolation.ts index d66a595a4dcfb..622dc3dfdbc83 100644 --- a/packages/core/src/render3/instructions/text_interpolation.ts +++ b/packages/core/src/render3/instructions/text_interpolation.ts @@ -8,10 +8,19 @@ import {getLView, getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; -import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; +import { + interpolation1, + interpolation2, + interpolation3, + interpolation4, + interpolation5, + interpolation6, + interpolation7, + interpolation8, + interpolationV, +} from './interpolation'; import {textBindingInternal} from './shared'; - /** * * Update text content with a lone bound value @@ -37,7 +46,6 @@ export function ɵɵtextInterpolate(v0: any): typeof ɵɵtextInterpolate { return ɵɵtextInterpolate; } - /** * * Update text content with single bound value surrounded by other text. @@ -58,7 +66,10 @@ export function ɵɵtextInterpolate(v0: any): typeof ɵɵtextInterpolate { * @codeGenApi */ export function ɵɵtextInterpolate1( - prefix: string, v0: any, suffix: string): typeof ɵɵtextInterpolate1 { + prefix: string, + v0: any, + suffix: string, +): typeof ɵɵtextInterpolate1 { const lView = getLView(); const interpolated = interpolation1(lView, prefix, v0, suffix); if (interpolated !== NO_CHANGE) { @@ -87,7 +98,12 @@ export function ɵɵtextInterpolate1( * @codeGenApi */ export function ɵɵtextInterpolate2( - prefix: string, v0: any, i0: string, v1: any, suffix: string): typeof ɵɵtextInterpolate2 { + prefix: string, + v0: any, + i0: string, + v1: any, + suffix: string, +): typeof ɵɵtextInterpolate2 { const lView = getLView(); const interpolated = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolated !== NO_CHANGE) { @@ -117,8 +133,14 @@ export function ɵɵtextInterpolate2( * @codeGenApi */ export function ɵɵtextInterpolate3( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, - suffix: string): typeof ɵɵtextInterpolate3 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + suffix: string, +): typeof ɵɵtextInterpolate3 { const lView = getLView(); const interpolated = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolated !== NO_CHANGE) { @@ -148,8 +170,16 @@ export function ɵɵtextInterpolate3( * @codeGenApi */ export function ɵɵtextInterpolate4( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - suffix: string): typeof ɵɵtextInterpolate4 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + suffix: string, +): typeof ɵɵtextInterpolate4 { const lView = getLView(); const interpolated = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolated !== NO_CHANGE) { @@ -179,8 +209,18 @@ export function ɵɵtextInterpolate4( * @codeGenApi */ export function ɵɵtextInterpolate5( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, suffix: string): typeof ɵɵtextInterpolate5 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + suffix: string, +): typeof ɵɵtextInterpolate5 { const lView = getLView(); const interpolated = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); if (interpolated !== NO_CHANGE) { @@ -212,11 +252,37 @@ export function ɵɵtextInterpolate5( * @codeGenApi */ export function ɵɵtextInterpolate6( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, suffix: string): typeof ɵɵtextInterpolate6 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + suffix: string, +): typeof ɵɵtextInterpolate6 { const lView = getLView(); - const interpolated = - interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + const interpolated = interpolation6( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + suffix, + ); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated as string); } @@ -244,12 +310,41 @@ export function ɵɵtextInterpolate6( * @codeGenApi */ export function ɵɵtextInterpolate7( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, - suffix: string): typeof ɵɵtextInterpolate7 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + suffix: string, +): typeof ɵɵtextInterpolate7 { const lView = getLView(); - const interpolated = - interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const interpolated = interpolation7( + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + suffix, + ); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated as string); } @@ -277,12 +372,45 @@ export function ɵɵtextInterpolate7( * @codeGenApi */ export function ɵɵtextInterpolate8( - prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, - i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, - suffix: string): typeof ɵɵtextInterpolate8 { + prefix: string, + v0: any, + i0: string, + v1: any, + i1: string, + v2: any, + i2: string, + v3: any, + i3: string, + v4: any, + i4: string, + v5: any, + i5: string, + v6: any, + i6: string, + v7: any, + suffix: string, +): typeof ɵɵtextInterpolate8 { const lView = getLView(); const interpolated = interpolation8( - lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, + prefix, + v0, + i0, + v1, + i1, + v2, + i2, + v3, + i3, + v4, + i4, + v5, + i5, + v6, + i6, + v7, + suffix, + ); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated as string); } diff --git a/packages/core/src/render3/instructions/two_way.ts b/packages/core/src/render3/instructions/two_way.ts index 695aeba2c507d..f18d4d9c58f70 100644 --- a/packages/core/src/render3/instructions/two_way.ts +++ b/packages/core/src/render3/instructions/two_way.ts @@ -15,7 +15,6 @@ import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} import {listenerInternal} from './listener'; import {elementPropertyInternal, storePropertyBindingMetadata} from './shared'; - /** * Update a two-way bound property on a selected element. * @@ -30,8 +29,10 @@ import {elementPropertyInternal, storePropertyBindingMetadata} from './shared'; * @codeGenApi */ export function ɵɵtwoWayProperty( - propName: string, value: T|WritableSignal, - sanitizer?: SanitizerFn|null): typeof ɵɵtwoWayProperty { + propName: string, + value: T | WritableSignal, + sanitizer?: SanitizerFn | null, +): typeof ɵɵtwoWayProperty { // TODO(crisbeto): perf impact of re-evaluating this on each change detection? if (isWritableSignal(value)) { value = value(); @@ -43,7 +44,15 @@ export function ɵɵtwoWayProperty( const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal( - tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false); + tView, + tNode, + lView, + propName, + value, + lView[RENDERER], + sanitizer, + false, + ); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex); } @@ -73,8 +82,10 @@ export function ɵɵtwoWayBindingSet(target: unknown, value: T): boolean { * @codeGenApi */ export function ɵɵtwoWayListener( - eventName: string, listenerFn: (e?: any) => any): typeof ɵɵtwoWayListener { - const lView = getLView<{}|null>(); + eventName: string, + listenerFn: (e?: any) => any, +): typeof ɵɵtwoWayListener { + const lView = getLView<{} | null>(); const tView = getTView(); const tNode = getCurrentTNode()!; listenerInternal(tView, lView, lView[RENDERER], tNode, eventName, listenerFn); diff --git a/packages/core/src/render3/instructions/write_to_directive_input.ts b/packages/core/src/render3/instructions/write_to_directive_input.ts index 47dadd1c2c4dd..d901575bfd8a5 100644 --- a/packages/core/src/render3/instructions/write_to_directive_input.ts +++ b/packages/core/src/render3/instructions/write_to_directive_input.ts @@ -12,18 +12,23 @@ import {InputSignalWithTransform} from '../../authoring/input/input_signal'; import {InputSignalNode} from '../../authoring/input/input_signal_node'; import {applyValueToInputField} from '../apply_value_input_field'; import {DirectiveDef} from '../interfaces/definition'; -import { InputFlags } from '../interfaces/input_flags'; +import {InputFlags} from '../interfaces/input_flags'; export function writeToDirectiveInput( - def: DirectiveDef, instance: T, publicName: string, privateName: string, flags: InputFlags, - value: unknown) { + def: DirectiveDef, + instance: T, + publicName: string, + privateName: string, + flags: InputFlags, + value: unknown, +) { const prevConsumer = setActiveConsumer(null); try { // If we know we are dealing with a signal input, we cache its reference // in a tree-shakable way. The input signal node can then be used for // value transform execution or actual value updates without introducing // additional megamorphic accesses for accessing the instance field. - let inputSignalNode: InputSignalNode|null = null; + let inputSignalNode: InputSignalNode | null = null; if ((flags & InputFlags.SignalBased) !== 0) { const field = (instance as any)[privateName] as InputSignalWithTransform; inputSignalNode = field[SIGNAL]; diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 7d608a9187fec..84d124897e094 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -12,7 +12,6 @@ import {TNode} from './node'; import {RComment, RElement} from './renderer_dom'; import {FLAGS, HOST, LView, NEXT, PARENT, T_HOST} from './view'; - /** * Special location which allows easy identification of type. If we have an array which was * retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is @@ -57,7 +56,7 @@ export interface LContainer extends Array { * The host could be an LView if this container is on a component node. * In that case, the component LView is its HOST. */ - readonly[HOST]: RElement|RComment|LView; + readonly [HOST]: RElement | RComment | LView; /** * This is a type field which allows us to differentiate `LContainer` from `StylingContext` in an @@ -78,14 +77,14 @@ export interface LContainer extends Array { * This allows us to jump from a container to a sibling container or component * view with the same parent, so we can remove listeners efficiently. */ - [NEXT]: LView|LContainer|null; + [NEXT]: LView | LContainer | null; /** * A collection of views created based on the underlying `` element but inserted into * a different `LContainer`. We need to track views created from a given declaration point since * queries collect matches from the embedded view declaration point and _not_ the insertion point. */ - [MOVED_VIEWS]: LView[]|null; + [MOVED_VIEWS]: LView[] | null; /** * Pointer to the `TNode` which represents the host of the container. @@ -103,7 +102,7 @@ export interface LContainer extends Array { * NOTE: This is stored as `any[]` because render3 should really not be aware of `ViewRef` and * doing so creates circular dependency. */ - [VIEW_REFS]: unknown[]|null; + [VIEW_REFS]: unknown[] | null; /** * Array of dehydrated views within this container. @@ -115,7 +114,7 @@ export interface LContainer extends Array { * "garbage-collected" later on, i.e. removed from the DOM once the hydration * logic finishes. */ - [DEHYDRATED_VIEWS]: DehydratedContainerView[]|null; + [DEHYDRATED_VIEWS]: DehydratedContainerView[] | null; } /** Flags associated with an LContainer (saved in LContainer[FLAGS]) */ diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts index e097719190d11..c9217c90cde86 100644 --- a/packages/core/src/render3/interfaces/context.ts +++ b/packages/core/src/render3/interfaces/context.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ - import {getLViewById} from './lview_tracking'; import {RNode} from './renderer_dom'; import {LView} from './view'; - /** * The internal view context which is specific to a given DOM element, directive or * component instance. Each value in here (besides the LView and element node details) @@ -26,37 +24,38 @@ export class LContext { /** * The instance of the Component node. */ - public component: {}|null|undefined; + public component: {} | null | undefined; /** * The list of active directives that exist on this element. */ - public directives: any[]|null|undefined; + public directives: any[] | null | undefined; /** * The map of local references (local reference name => element or directive instance) that * exist on this element. */ - public localRefs: {[key: string]: any}|null|undefined; + public localRefs: {[key: string]: any} | null | undefined; /** Component's parent view data. */ - get lView(): LView|null { + get lView(): LView | null { return getLViewById(this.lViewId); } constructor( - /** - * ID of the component's parent view data. - */ - private lViewId: number, - - /** - * The index instance of the node. - */ - public nodeIndex: number, - - /** - * The instance of the DOM node that is attached to the lNode. - */ - public native: RNode) {} + /** + * ID of the component's parent view data. + */ + private lViewId: number, + + /** + * The index instance of the node. + */ + public nodeIndex: number, + + /** + * The instance of the DOM node that is attached to the lNode. + */ + public native: RNode, + ) {} } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 0e51ce40567ac..245595222dd1f 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -19,7 +19,6 @@ import {CssSelectorList} from './projection'; import type {TView} from './view'; import {InputFlags} from './input_flags'; - /** * Definition of what a template rendering function should look like for a component. */ @@ -27,7 +26,7 @@ export type ComponentTemplate = { // Note: the ctx parameter is typed as T|U, as using only U would prevent a template with // e.g. ctx: {} from being assigned to ComponentTemplate as TypeScript won't infer U = any // in that scenario. By including T this incompatibility is resolved. - (rf: RenderFlags, ctx: T|U): void; + (rf: RenderFlags, ctx: T | U): void; }; /** @@ -38,8 +37,11 @@ export type ViewQueriesFunction = (rf: RenderFlags, ctx: U) => v /** * Definition of what a content queries function should look like. */ -export type ContentQueriesFunction = - (rf: RenderFlags, ctx: U, directiveIndex: number) => void; +export type ContentQueriesFunction = ( + rf: RenderFlags, + ctx: U, + directiveIndex: number, +) => void; export interface ClassDebugInfo { className: string; @@ -61,7 +63,7 @@ export const enum RenderFlags { Create = 0b01, /* Whether to run the update block (e.g. refresh bindings) */ - Update = 0b10 + Update = 0b10, } /** @@ -108,7 +110,7 @@ export interface DirectiveDef { * A dictionary mapping the inputs' public name to their minified property names * (along with flags if there are any). */ - readonly inputs: {[P in keyof T]?: string|[minifiedName: string, flags: InputFlags]}; + readonly inputs: {[P in keyof T]?: string | [minifiedName: string, flags: InputFlags]}; /** * A dictionary mapping the private names of inputs to their transformation functions. @@ -118,14 +120,15 @@ export interface DirectiveDef { * Note: Signal inputs will not have transforms captured here. This is because their * transform function is already integrated into the `InputSignal`. */ - readonly inputTransforms: {[classPropertyName: string]: InputTransformFunction}|null; + readonly inputTransforms: {[classPropertyName: string]: InputTransformFunction} | null; /** * Contains the raw input information produced by the compiler. Can be * used to do further processing after the `inputs` have been inverted. */ - readonly inputConfig: - {[P in keyof T]?: string|[InputFlags, string, string?, InputTransformFunction?]}; + readonly inputConfig: { + [P in keyof T]?: string | [InputFlags, string, string?, InputTransformFunction?]; + }; /** * @deprecated This is only here because `NgOnChanges` incorrectly uses declared name instead of @@ -143,19 +146,19 @@ export interface DirectiveDef { /** * Function to create and refresh content queries associated with a given directive. */ - contentQueries: ContentQueriesFunction|null; + contentQueries: ContentQueriesFunction | null; /** * Query-related instructions for a directive. Note that while directives don't have a * view and as such view queries won't necessarily do anything, there might be * components that extend the directive. */ - viewQuery: ViewQueriesFunction|null; + viewQuery: ViewQueriesFunction | null; /** * Refreshes host bindings on the associated directive. */ - readonly hostBindings: HostBindingsFunction|null; + readonly hostBindings: HostBindingsFunction | null; /** * The number of bindings in this directive `hostBindings` (including pure fn bindings). @@ -195,15 +198,15 @@ export interface DirectiveDef { * the entries. The marker values themselves are set via entries found in the * [AttributeMarker] enum. */ - readonly hostAttrs: TAttributes|null; + readonly hostAttrs: TAttributes | null; /** Token representing the directive. Used by DI. */ readonly type: Type; /** Function that resolves providers and publishes them into the DI system. */ providersResolver: - ((def: DirectiveDef, processProvidersFn?: ProcessProvidersFunction) => - void)|null; + | ((def: DirectiveDef, processProvidersFn?: ProcessProvidersFunction) => void) + | null; /** The selectors that will be used to match nodes to this directive. */ readonly selectors: CssSelectorList; @@ -211,7 +214,7 @@ export interface DirectiveDef { /** * Name under which the directive is exported (for use with local references in template) */ - readonly exportAs: string[]|null; + readonly exportAs: string[] | null; /** * Whether this directive (or component) is standalone. @@ -227,18 +230,18 @@ export interface DirectiveDef { * Factory function used to create a new directive instance. Will be null initially. * Populated when the factory is first requested by directive instantiation logic. */ - readonly factory: FactoryFn|null; + readonly factory: FactoryFn | null; /** * The features applied to this directive */ - readonly features: DirectiveDefFeature[]|null; + readonly features: DirectiveDefFeature[] | null; /** * Info related to debugging/troubleshooting for this component. This info is only available in * dev mode. */ - debugInfo: ClassDebugInfo|null; + debugInfo: ClassDebugInfo | null; /** * Function that will add the host directives to the list of matches during directive matching. @@ -250,17 +253,26 @@ export interface DirectiveDef { * configuration. Host directives will be added to the map as they're being matched to the node. */ findHostDirectiveDefs: - ((currentDef: DirectiveDef, matchedDefs: DirectiveDef[], - hostDirectiveDefs: HostDirectiveDefs) => void)|null; + | (( + currentDef: DirectiveDef, + matchedDefs: DirectiveDef[], + hostDirectiveDefs: HostDirectiveDefs, + ) => void) + | null; /** Additional directives to be applied whenever the directive has been matched. */ - hostDirectives: HostDirectiveDef[]|null; + hostDirectives: HostDirectiveDef[] | null; setInput: - (( - this: DirectiveDef, instance: U, - inputSignalNode: null|InputSignalNode, value: any, publicName: string, - privateName: string) => void)|null; + | (( + this: DirectiveDef, + instance: U, + inputSignalNode: null | InputSignalNode, + value: any, + publicName: string, + privateName: string, + ) => void) + | null; } /** @@ -288,7 +300,7 @@ export interface ComponentDef extends DirectiveDef { readonly template: ComponentTemplate; /** Constants associated with the component's view. */ - readonly consts: TConstantsOrFactory|null; + readonly consts: TConstantsOrFactory | null; /** * An array of `ngContent[selector]` values that were found in the template. @@ -320,7 +332,7 @@ export interface ComponentDef extends DirectiveDef { /** * Query-related instructions for a component. */ - viewQuery: ViewQueriesFunction|null; + viewQuery: ViewQueriesFunction | null; /** * The view encapsulation type, which determines how styles are applied to @@ -338,8 +350,8 @@ export interface ComponentDef extends DirectiveDef { * This is useful for renderers that delegate to other renderers. */ readonly data: { - [kind: string]: any, - animation?: any[], + [kind: string]: any; + animation?: any[]; }; /** Whether or not this component's ChangeDetectionStrategy is OnPush */ @@ -354,7 +366,7 @@ export interface ComponentDef extends DirectiveDef { * The property is either an array of `DirectiveDef`s or a function which returns the array of * `DirectiveDef`s. The function is necessary to be able to support forward declarations. */ - directiveDefs: DirectiveDefListOrFactory|null; + directiveDefs: DirectiveDefListOrFactory | null; /** * Registry of pipes that may be found in this view. @@ -362,29 +374,31 @@ export interface ComponentDef extends DirectiveDef { * The property is either an array of `PipeDefs`s or a function which returns the array of * `PipeDefs`s. The function is necessary to be able to support forward declarations. */ - pipeDefs: PipeDefListOrFactory|null; + pipeDefs: PipeDefListOrFactory | null; /** * Unfiltered list of all dependencies of a component, or `null` if none. */ - dependencies: TypeOrFactory|null; + dependencies: TypeOrFactory | null; /** * The set of schemas that declare elements to be allowed in the component's template. */ - schemas: SchemaMetadata[]|null; + schemas: SchemaMetadata[] | null; /** * Ivy runtime uses this place to store the computed tView for the component. This gets filled on * the first run of component. */ - tView: TView|null; + tView: TView | null; /** * A function added by the {@link ɵɵStandaloneFeature} and used by the framework to create * standalone injectors. */ - getStandaloneInjector: ((parentInjector: EnvironmentInjector) => EnvironmentInjector | null)|null; + getStandaloneInjector: + | ((parentInjector: EnvironmentInjector) => EnvironmentInjector | null) + | null; /** * Used to store the result of `noSideEffects` function so that it is not removed by closure @@ -420,7 +434,7 @@ export interface PipeDef { * Factory function used to create a new pipe instance. Will be null initially. * Populated when the factory is first requested by pipe instantiation logic. */ - factory: FactoryFn|null; + factory: FactoryFn | null; /** * Whether or not the pipe is pure. @@ -436,7 +450,7 @@ export interface PipeDef { readonly standalone: boolean; /* The following are lifecycle hooks for this pipe */ - onDestroy: (() => void)|null; + onDestroy: (() => void) | null; } export interface DirectiveDefFeature { @@ -470,7 +484,7 @@ export interface HostDirectiveDef { * the author has decided to expose. */ export type HostDirectiveBindingMap = { - [publicName: string]: string + [publicName: string]: string; }; /** @@ -500,23 +514,25 @@ export type InputTransformFunction = (value: any) => any; * * The function is necessary to be able to support forward declarations. */ -export type DirectiveDefListOrFactory = (() => DirectiveDefList)|DirectiveDefList; +export type DirectiveDefListOrFactory = (() => DirectiveDefList) | DirectiveDefList; -export type DirectiveDefList = (DirectiveDef|ComponentDef)[]; +export type DirectiveDefList = (DirectiveDef | ComponentDef)[]; -export type DependencyDef = DirectiveDef|ComponentDef|PipeDef; +export type DependencyDef = DirectiveDef | ComponentDef | PipeDef; -export type DirectiveTypesOrFactory = (() => DirectiveTypeList)|DirectiveTypeList; +export type DirectiveTypesOrFactory = (() => DirectiveTypeList) | DirectiveTypeList; -export type DirectiveTypeList = - (DirectiveType|ComponentType| - Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; +export type DirectiveTypeList = ( + | DirectiveType + | ComponentType + | Type +) /* Type as workaround for: Microsoft/TypeScript/issues/4881 */[]; -export type DependencyType = DirectiveType|ComponentType|PipeType|Type; +export type DependencyType = DirectiveType | ComponentType | PipeType | Type; export type DependencyTypeList = Array; -export type TypeOrFactory = T|(() => T); +export type TypeOrFactory = T | (() => T); export type HostBindingsFunction = (rf: RenderFlags, ctx: U) => void; @@ -525,14 +541,16 @@ export type HostBindingsFunction = (rf: RenderFlags, ctx: U) => * * The function is necessary to be able to support forward declarations. */ -export type PipeDefListOrFactory = (() => PipeDefList)|PipeDefList; +export type PipeDefListOrFactory = (() => PipeDefList) | PipeDefList; export type PipeDefList = PipeDef[]; -export type PipeTypesOrFactory = (() => PipeTypeList)|PipeTypeList; +export type PipeTypesOrFactory = (() => PipeTypeList) | PipeTypeList; -export type PipeTypeList = - (PipeType|Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; +export type PipeTypeList = ( + | PipeType + | Type +) /* Type as workaround for: Microsoft/TypeScript/issues/4881 */[]; /** * NgModule scope info as provided by AoT compiler @@ -547,23 +565,23 @@ export type PipeTypeList = */ export interface NgModuleScopeInfoFromDecorator { /** List of components, directives, and pipes declared by this module. */ - declarations?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; + declarations?: Type[] | (() => Type[]) | RawScopeInfoFromDecorator[]; /** List of modules or `ModuleWithProviders` or standalone components imported by this module. */ - imports?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; + imports?: Type[] | (() => Type[]) | RawScopeInfoFromDecorator[]; /** * List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this * module. */ - exports?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; + exports?: Type[] | (() => Type[]) | RawScopeInfoFromDecorator[]; /** * The set of components that are bootstrapped when this module is bootstrapped. This field is * only available in local compilation mode. In full compilation mode bootstrap info is passed * directly to the module def runtime after statically analyzed and resolved. */ - bootstrap?: Type[]|(() => Type[])|RawScopeInfoFromDecorator[]; + bootstrap?: Type[] | (() => Type[]) | RawScopeInfoFromDecorator[]; } /** @@ -572,4 +590,8 @@ export interface NgModuleScopeInfoFromDecorator { * - standalone component annotation imports field */ export type RawScopeInfoFromDecorator = - Type|ModuleWithProviders|(() => Type)|(() => ModuleWithProviders)|any[]; + | Type + | ModuleWithProviders + | (() => Type) + | (() => ModuleWithProviders) + | any[]; diff --git a/packages/core/src/render3/interfaces/document.ts b/packages/core/src/render3/interfaces/document.ts index 72aec8a5d4553..30b0045844d08 100644 --- a/packages/core/src/render3/interfaces/document.ts +++ b/packages/core/src/render3/interfaces/document.ts @@ -24,7 +24,7 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors'; * Angular does this for us in each of the standard platforms (`Browser` and `Server`) * by calling `setDocument()` when providing the `DOCUMENT` token. */ -let DOCUMENT: Document|undefined = undefined; +let DOCUMENT: Document | undefined = undefined; /** * Tell ivy what the `document` is for this platform. @@ -33,7 +33,7 @@ let DOCUMENT: Document|undefined = undefined; * * @param document The object representing the global `document` in this environment. */ -export function setDocument(document: Document|undefined): void { +export function setDocument(document: Document | undefined): void { DOCUMENT = document; } @@ -51,9 +51,10 @@ export function getDocument(): Document { } throw new RuntimeError( - RuntimeErrorCode.MISSING_DOCUMENT, - (typeof ngDevMode === 'undefined' || ngDevMode) && - `The document object is not available in this context. Make sure the DOCUMENT injection token is provided.`); + RuntimeErrorCode.MISSING_DOCUMENT, + (typeof ngDevMode === 'undefined' || ngDevMode) && + `The document object is not available in this context. Make sure the DOCUMENT injection token is provided.`, + ); // No "document" can be found. This should only happen if we are running ivy outside Angular and // the current platform is not a browser. Since this is not a supported scenario at the moment diff --git a/packages/core/src/render3/interfaces/i18n.ts b/packages/core/src/render3/interfaces/i18n.ts index 97f8cd8ad1007..2802e40e87fcd 100644 --- a/packages/core/src/render3/interfaces/i18n.ts +++ b/packages/core/src/render3/interfaces/i18n.ts @@ -8,7 +8,6 @@ import {SanitizerFn} from './sanitization'; - /** * Stores a list of nodes which need to be removed. * @@ -75,7 +74,6 @@ export const enum IcuCreateOpCode { Attr = 0b1, } - /** * Array storing OpCode for dynamically creating `i18n` blocks. * @@ -121,8 +119,9 @@ export const enum IcuCreateOpCode { * ]; * ``` */ -export interface IcuCreateOpCodes extends Array, - I18nDebug { +export interface IcuCreateOpCodes + extends Array, + I18nDebug { __brand__: 'I18nCreateOpCodes'; } @@ -160,7 +159,7 @@ export const enum I18nUpdateOpCode { * See `I18nMutateOpCodes` documentation. */ export const ELEMENT_MARKER: ELEMENT_MARKER = { - marker: 'element' + marker: 'element', }; export interface ELEMENT_MARKER { marker: 'element'; @@ -172,7 +171,7 @@ export interface ELEMENT_MARKER { * See `I18nMutateOpCodes` documentation. */ export const ICU_MARKER: ICU_MARKER = { - marker: 'ICU' + marker: 'ICU', }; export interface ICU_MARKER { @@ -223,7 +222,7 @@ export interface I18nDebug { * } * ``` */ -export interface I18nCreateOpCodes extends Array, I18nDebug { +export interface I18nCreateOpCodes extends Array, I18nDebug { __brand__: 'I18nCreateOpCodes'; } @@ -248,7 +247,6 @@ export enum I18nCreateOpCode { COMMENT = 0b10, } - /** * Stores DOM operations which need to be applied to update DOM render tree due to changes in * expressions. @@ -321,7 +319,7 @@ export enum I18nCreateOpCode { * ``` * */ -export interface I18nUpdateOpCodes extends Array, I18nDebug { +export interface I18nUpdateOpCodes extends Array, I18nDebug { __brand__: 'I18nUpdateOpCodes'; } @@ -412,11 +410,11 @@ export interface IcuExpression { type: IcuType; mainBinding: number; cases: string[]; - values: (string|IcuExpression)[][]; + values: (string | IcuExpression)[][]; } // A parsed I18n AST Node -export type I18nNode = I18nTextNode|I18nElementNode|I18nICUNode|I18nPlaceholderNode; +export type I18nNode = I18nTextNode | I18nElementNode | I18nICUNode | I18nPlaceholderNode; /** * Represents a block of text in a translation, such as `Hello, {{ name }}!`. diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index 2d3bce896005c..ed0bd2e07724e 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -64,7 +64,7 @@ export const enum NodeInjectorOffset { * The interfaces encodes number of parents `LView`s to traverse and index in the `LView` * pointing to the parent injector. */ -export type RelativeInjectorLocation = number&{ +export type RelativeInjectorLocation = number & { __brand__: 'RelativeInjectorLocationFlags'; }; @@ -175,7 +175,7 @@ export class NodeInjectorFactory { /** * The inject implementation to be activated when using the factory. */ - injectImpl: null|((token: ProviderToken, flags?: InjectFlags) => T); + injectImpl: null | ((token: ProviderToken, flags?: InjectFlags) => T); /** * Marker set to true during factory invocation to see if we get into recursive loop. @@ -251,34 +251,36 @@ export class NodeInjectorFactory { * `providers` (`['all']`) and then extend it with `viewProviders` (`['all'] + ['viewOnly'] = * ['all', 'viewOnly']`). */ - providerFactory?: NodeInjectorFactory|null; - + providerFactory?: NodeInjectorFactory | null; constructor( + /** + * Factory to invoke in order to create a new instance. + */ + public factory: ( + this: NodeInjectorFactory, + _: undefined, + /** + * array where injectables tokens are stored. This is used in + * case of an error reporting to produce friendlier errors. + */ + tData: TData, /** - * Factory to invoke in order to create a new instance. + * array where existing instances of injectables are stored. This is used in case + * of multi shadow is needed. See `multi` field documentation. */ - public factory: - (this: NodeInjectorFactory, _: undefined, - /** - * array where injectables tokens are stored. This is used in - * case of an error reporting to produce friendlier errors. - */ - tData: TData, - /** - * array where existing instances of injectables are stored. This is used in case - * of multi shadow is needed. See `multi` field documentation. - */ - lView: LView, - /** - * The TNode of the same element injector. - */ - tNode: TDirectiveHostNode) => any, + lView: LView, /** - * Set to `true` if the token is declared in `viewProviders` (or if it is component). + * The TNode of the same element injector. */ - isViewProvider: boolean, - injectImplementation: null|((token: ProviderToken, flags?: InjectFlags) => T)) { + tNode: TDirectiveHostNode, + ) => any, + /** + * Set to `true` if the token is declared in `viewProviders` (or if it is component). + */ + isViewProvider: boolean, + injectImplementation: null | ((token: ProviderToken, flags?: InjectFlags) => T), + ) { ngDevMode && assertDefined(factory, 'Factory not specified'); ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.'); this.canSeeViewProviders = isViewProvider; diff --git a/packages/core/src/render3/interfaces/lview_tracking.ts b/packages/core/src/render3/interfaces/lview_tracking.ts index 7123fbcbbc263..607867891cb95 100644 --- a/packages/core/src/render3/interfaces/lview_tracking.ts +++ b/packages/core/src/render3/interfaces/lview_tracking.ts @@ -28,7 +28,7 @@ export function registerLView(lView: LView): void { } /** Gets an LView by its unique ID. */ -export function getLViewById(id: number): LView|null { +export function getLViewById(id: number): LView | null { ngDevMode && assertNumber(id, 'ID used for LView lookup must be a number'); return TRACKED_LVIEWS.get(id) || null; } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 17c711f959f5f..3233facd9a881 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -7,7 +7,7 @@ */ import {KeyValueArray} from '../../util/array_utils'; import {TStylingRange} from '../interfaces/styling'; -import { AttributeMarker } from './attribute_marker'; +import {AttributeMarker} from './attribute_marker'; import {InputFlags} from './input_flags'; import {TIcu} from './i18n'; @@ -15,7 +15,6 @@ import {CssSelector} from './projection'; import {RNode} from './renderer_dom'; import type {LView, TView} from './view'; - /** * TNodeType corresponds to the {@link TNode} `type` property. * @@ -78,8 +77,8 @@ export const enum TNodeType { // if `TNode.type` is one of several choices. // See: https://github.com/microsoft/TypeScript/issues/35875 why we can't refer to existing enum. - AnyRNode = 0b11, // Text | Element - AnyContainer = 0b1100, // Container | ElementContainer + AnyRNode = 0b11, // Text | Element + AnyContainer = 0b1100, // Container | ElementContainer } /** @@ -88,13 +87,13 @@ export const enum TNodeType { */ export function toTNodeTypeAsString(tNodeType: TNodeType): string { let text = ''; - (tNodeType & TNodeType.Text) && (text += '|Text'); - (tNodeType & TNodeType.Element) && (text += '|Element'); - (tNodeType & TNodeType.Container) && (text += '|Container'); - (tNodeType & TNodeType.ElementContainer) && (text += '|ElementContainer'); - (tNodeType & TNodeType.Projection) && (text += '|Projection'); - (tNodeType & TNodeType.Icu) && (text += '|IcuContainer'); - (tNodeType & TNodeType.Placeholder) && (text += '|Placeholder'); + tNodeType & TNodeType.Text && (text += '|Text'); + tNodeType & TNodeType.Element && (text += '|Element'); + tNodeType & TNodeType.Container && (text += '|Container'); + tNodeType & TNodeType.ElementContainer && (text += '|ElementContainer'); + tNodeType & TNodeType.Projection && (text += '|Projection'); + tNodeType & TNodeType.Icu && (text += '|IcuContainer'); + tNodeType & TNodeType.Placeholder && (text += '|Placeholder'); return text.length > 0 ? text.substring(1) : text; } @@ -108,10 +107,13 @@ export function toTNodeTypeAsString(tNodeType: TNodeType): string { * within `TView.data`. */ export function isTNodeShape(value: unknown): value is TNode { - return value != null && typeof value === 'object' && - ((value as TNode).insertBeforeIndex === null || - typeof (value as TNode).insertBeforeIndex === 'number' || - Array.isArray((value as TNode).insertBeforeIndex)); + return ( + value != null && + typeof value === 'object' && + ((value as TNode).insertBeforeIndex === null || + typeof (value as TNode).insertBeforeIndex === 'number' || + Array.isArray((value as TNode).insertBeforeIndex)) + ); } /** @@ -171,7 +173,7 @@ export const enum TNodeProviderIndexes { * - Special markers acting as flags to alter attributes processing. * - Parsed ngProjectAs selectors. */ -export type TAttributes = (string|AttributeMarker|CssSelector)[]; +export type TAttributes = (string | AttributeMarker | CssSelector)[]; /** * Constants that are associated with a view. Includes: @@ -179,7 +181,7 @@ export type TAttributes = (string|AttributeMarker|CssSelector)[]; * - Local definition arrays. * - Translated messages (i18n). */ -export type TConstants = (TAttributes|string)[]; +export type TConstants = (TAttributes | string)[]; /** * Factory function that returns an array of consts. Consts can be represented as a function in @@ -193,7 +195,7 @@ export type TConstantsFactory = () => TConstants; * TConstants type that describes how the `consts` field is generated on ComponentDef: it can be * either an array or a factory function that returns that array. */ -export type TConstantsOrFactory = TConstants|TConstantsFactory; +export type TConstantsOrFactory = TConstants | TConstantsFactory; /** * Binding data (flyweight) for a particular node that is shared between all templates @@ -330,7 +332,7 @@ export interface TNode { * Stores indexes of property bindings. This field is only set in the ngDevMode and holds * indexes of property bindings so TestBed can get bound property metadata for a given node. */ - propertyBindings: number[]|null; + propertyBindings: number[] | null; /** * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput @@ -370,7 +372,7 @@ export interface TNode { * This array can contain flags that will indicate "special attributes" (attributes with * namespaces, attributes extracted from bindings and outputs). */ - attrs: TAttributes|null; + attrs: TAttributes | null; /** * Same as `TNode.attrs` but contains merged data across all directive host bindings. @@ -383,7 +385,7 @@ export interface TNode { * - Directives' `hostAttrs` * - Template `TNode.attrs` associated with the current `TNode`. */ - mergedAttrs: TAttributes|null; + mergedAttrs: TAttributes | null; /** * A set of local names under which a given element is exported in a template and @@ -402,22 +404,22 @@ export interface TNode { * - `` => `["foo", myCmptIdx, "bar", directiveIdx]` * - `
` => `["foo", -1, "bar", directiveIdx]` */ - localNames: (string|number)[]|null; + localNames: (string | number)[] | null; /** Information about input properties that need to be set once from attribute data. */ - initialInputs: InitialInputData|null|undefined; + initialInputs: InitialInputData | null | undefined; /** * Input data for all directives on this node. `null` means that there are no directives with * inputs on this node. */ - inputs: NodeInputBindings|null; + inputs: NodeInputBindings | null; /** * Output data for all directives on this node. `null` means that there are no directives with * outputs on this node. */ - outputs: NodeOutputBindings|null; + outputs: NodeOutputBindings | null; /** * The TView attached to this node. @@ -427,19 +429,19 @@ export interface TNode { * * If this TNode corresponds to an element, tView will be `null`. */ - tView: TView|null; + tView: TView | null; /** * The next sibling node. Necessary so we can propagate through the root nodes of a view * to insert them or remove them from the DOM. */ - next: TNode|null; + next: TNode | null; /** * The previous sibling node. * This simplifies operations when we need a pointer to the previous node. */ - prev: TNode|null; + prev: TNode | null; /** * The next projected sibling. Since in Angular content projection works on the node-by-node @@ -447,7 +449,7 @@ export interface TNode { * (target view). At the same time we need to keep initial relationship between nodes as * expressed in content view. */ - projectionNext: TNode|null; + projectionNext: TNode | null; /** * First child of the current node. @@ -455,7 +457,7 @@ export interface TNode { * For component nodes, the child will always be a ContentChild (in same view). * For embedded view nodes, the child will be in their child view. */ - child: TNode|null; + child: TNode | null; /** * Parent node (in the same view only). @@ -471,7 +473,7 @@ export interface TNode { * * If this is an inline view node (V), the parent will be its container. */ - parent: TElementNode|TContainerNode|null; + parent: TElementNode | TContainerNode | null; /** * List of projected TNodes for a given component host element OR index into the said nodes. @@ -512,7 +514,7 @@ export interface TNode { * If `projection` is of type `RNode[][]` than we have a collection of native nodes passed as * projectable nodes during dynamic component creation. */ - projection: (TNode|RNode[])[]|number|null; + projection: (TNode | RNode[])[] | number | null; /** * A collection of all `style` static values for an element (including from host). @@ -523,8 +525,7 @@ export interface TNode { * - There are one or more initial `style`s on a directive/component host * (e.g. `@Directive({host: {style: "width:200px;" } }`) */ - styles: string|null; - + styles: string | null; /** * A collection of all `style` static values for an element excluding host sources. @@ -537,7 +538,7 @@ export interface TNode { * would have to concatenate the attributes on every template pass. Instead, we process once on * first create pass and store here. */ - stylesWithoutHost: string|null; + stylesWithoutHost: string | null; /** * A `KeyValueArray` version of residual `styles`. @@ -566,7 +567,7 @@ export interface TNode { * - `null`: initialized but `styles` is `null` * - `KeyValueArray`: parsed version of `styles`. */ - residualStyles: KeyValueArray|undefined|null; + residualStyles: KeyValueArray | undefined | null; /** * A collection of all class static values for an element (including from host). @@ -577,7 +578,7 @@ export interface TNode { * - There are one or more initial classes on an directive/component host * (e.g. `@Directive({host: {class: "SOME_CLASS" } }`) */ - classes: string|null; + classes: string | null; /** * A collection of all class static values for an element excluding host sources. @@ -590,7 +591,7 @@ export interface TNode { * `tNode.attrs`, we would have to concatenate the attributes on every template pass. Instead, * we process once on first create pass and store here. */ - classesWithoutHost: string|null; + classesWithoutHost: string | null; /** * A `KeyValueArray` version of residual `classes`. @@ -601,7 +602,7 @@ export interface TNode { * - `null`: initialized but `classes` is `null` * - `KeyValueArray`: parsed version of `classes`. */ - residualClasses: KeyValueArray|undefined|null; + residualClasses: KeyValueArray | undefined | null; /** * Stores the head/tail index of the class bindings. @@ -637,19 +638,19 @@ export interface TNode { /** * See `TNode.insertBeforeIndex` */ -export type InsertBeforeIndex = null|number|number[]; +export type InsertBeforeIndex = null | number | number[]; /** Static data for an element */ export interface TElementNode extends TNode { /** Index in the data[] array */ index: number; - child: TElementNode|TTextNode|TElementContainerNode|TContainerNode|TProjectionNode|null; + child: TElementNode | TTextNode | TElementContainerNode | TContainerNode | TProjectionNode | null; /** * Element nodes will have parents unless they are the first node of a component or * embedded view (which means their parent is in a different view and must be * retrieved using viewData[HOST_NODE]). */ - parent: TElementNode|TElementContainerNode|null; + parent: TElementNode | TElementContainerNode | null; tView: null; /** @@ -657,7 +658,7 @@ export interface TElementNode extends TNode { * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node * or a component without projection, it will be null. */ - projection: (TNode|RNode[])[]|null; + projection: (TNode | RNode[])[] | null; /** * Stores TagName @@ -675,7 +676,7 @@ export interface TTextNode extends TNode { * embedded view (which means their parent is in a different view and must be * retrieved using LView.node). */ - parent: TElementNode|TElementContainerNode|null; + parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; } @@ -697,8 +698,8 @@ export interface TContainerNode extends TNode { * - They are the first node of a component or embedded view * - They are dynamically created */ - parent: TElementNode|TElementContainerNode|null; - tView: TView|null; + parent: TElementNode | TElementContainerNode | null; + tView: TView | null; projection: null; value: null; } @@ -707,8 +708,8 @@ export interface TContainerNode extends TNode { export interface TElementContainerNode extends TNode { /** Index in the LView[] array. */ index: number; - child: TElementNode|TTextNode|TContainerNode|TElementContainerNode|TProjectionNode|null; - parent: TElementNode|TElementContainerNode|null; + child: TElementNode | TTextNode | TContainerNode | TElementContainerNode | TProjectionNode | null; + parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; } @@ -718,7 +719,7 @@ export interface TIcuContainerNode extends TNode { /** Index in the LView[] array. */ index: number; child: null; - parent: TElementNode|TElementContainerNode|null; + parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; value: TIcu; @@ -733,7 +734,7 @@ export interface TProjectionNode extends TNode { * or embedded view (which means their parent is in a different view and must be * retrieved using LView.node). */ - parent: TElementNode|TElementContainerNode|null; + parent: TElementNode | TElementContainerNode | null; tView: null; /** Index of the projection node. (See TNode.projection for more info.) */ @@ -744,7 +745,7 @@ export interface TProjectionNode extends TNode { /** * A union type representing all TNode types that can host a directive. */ -export type TDirectiveHostNode = TElementNode|TContainerNode|TElementContainerNode; +export type TDirectiveHostNode = TElementNode | TContainerNode | TElementContainerNode; /** * Store the runtime output names for all the directives. @@ -758,7 +759,7 @@ export type TDirectiveHostNode = TElementNode|TContainerNode|TElementContainerNo * "publicName": [0, 'change-minified'] * } */ -export type NodeOutputBindings = Record; +export type NodeOutputBindings = Record; /** * Store the runtime input for all directives applied to a node. @@ -777,7 +778,7 @@ export type NodeOutputBindings = Record; * } * ``` */ -export type NodeInputBindings = Record; +export type NodeInputBindings = Record; /** * This array contains information about input properties that @@ -797,7 +798,7 @@ export type NodeInputBindings = Record; * * e.g. [null, ['role-min', 'minified-input', 'button']] */ -export type InitialInputData = (InitialInputs|null)[]; +export type InitialInputData = (InitialInputs | null)[]; /** * Used by InitialInputData to store input properties @@ -810,12 +811,12 @@ export type InitialInputData = (InitialInputs|null)[]; * * e.g. ['role-min', 'minified-input', 'button'] */ -export type InitialInputs = (string|InputFlags)[]; +export type InitialInputs = (string | InputFlags)[]; /** * Type representing a set of TNodes that can have local refs (`#foo`) placed on them. */ -export type TNodeWithLocalRefs = TContainerNode|TElementNode|TElementContainerNode; +export type TNodeWithLocalRefs = TContainerNode | TElementNode | TElementContainerNode; /** * Type for a function that extracts a value for a local refs. diff --git a/packages/core/src/render3/interfaces/projection.ts b/packages/core/src/render3/interfaces/projection.ts index 66c3c7435699d..38b9a27ec8d48 100644 --- a/packages/core/src/render3/interfaces/projection.ts +++ b/packages/core/src/render3/interfaces/projection.ts @@ -1,4 +1,3 @@ - /** * @license * Copyright Google LLC All Rights Reserved. @@ -7,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - /** * Expresses a single CSS Selector. * @@ -37,7 +35,7 @@ * * See more examples in node_selector_matcher_spec.ts */ -export type CssSelector = (string|SelectorFlags)[]; +export type CssSelector = (string | SelectorFlags)[]; /** * A list of CssSelectors. @@ -58,7 +56,7 @@ export type CssSelectorList = CssSelector[]; * using {@link ViewContainerRef#createComponent}. The last slot that specifies the * wildcard selector will retrieve all projectable nodes which do not match any selector. */ -export type ProjectionSlots = (CssSelectorList|'*')[]; +export type ProjectionSlots = (CssSelectorList | '*')[]; /** Flags used to build up CssSelectors */ export const enum SelectorFlags { diff --git a/packages/core/src/render3/interfaces/public_definitions.ts b/packages/core/src/render3/interfaces/public_definitions.ts index 4d436122f39b6..00fd19ef78a5f 100644 --- a/packages/core/src/render3/interfaces/public_definitions.ts +++ b/packages/core/src/render3/interfaces/public_definitions.ts @@ -19,7 +19,9 @@ export type ɵɵDirectiveDeclaration< 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}}, + 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 @@ -29,7 +31,8 @@ export type ɵɵDirectiveDeclaration< // are not standalone. IsStandalone extends boolean = false, HostDirectives = never, - IsSignal extends boolean = false> = unknown; + IsSignal extends boolean = false, +> = unknown; /** * @publicApi @@ -39,7 +42,7 @@ export type ɵɵComponentDeclaration< 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}}, + InputMap extends {[key: string]: string | {alias: string | null; required: boolean}}, OutputMap extends {[key: string]: string}, QueryFields extends string[], NgContentSelectors extends string[], @@ -47,7 +50,8 @@ export type ɵɵComponentDeclaration< // are not standalone. IsStandalone extends boolean = false, HostDirectives = never, - IsSignal extends boolean = false> = unknown; + IsSignal extends boolean = false, +> = unknown; /** * @publicApi @@ -56,13 +60,14 @@ export type ɵɵNgModuleDeclaration = 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; + IsStandalone extends boolean = false, +> = unknown; // clang-format on /** @@ -87,7 +92,7 @@ export type CtorDependency = { * attribute name is a dynamic expression instead of a string literal, this will be the unknown * type. */ - attribute?: string|unknown; + attribute?: string | unknown; /** * If `@Optional()` is used, this key is set to true. @@ -108,4 +113,4 @@ export type CtorDependency = { * If `@SkipSelf` is used, this key is set to true. */ skipSelf?: true; -}|null; +} | null; diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index 8f9aabb21a006..d1d1c1dec475a 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -16,7 +16,7 @@ import {TView} from './view'; * An object representing query metadata extracted from query annotations. */ export interface TQueryMetadata { - predicate: ProviderToken|string[]; + predicate: ProviderToken | string[]; read: any; flags: QueryFlags; } @@ -87,7 +87,7 @@ export interface TQuery { * ng-template and ElementRef for other elements); * - a positive number - index of an injectable to be read from the element injector. */ - matches: number[]|null; + matches: number[] | null; /** * A flag indicating if a given query crosses an element. This flag exists for @@ -126,7 +126,7 @@ export interface TQuery { * @param tNode * @param childQueryIndex */ - embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null; + embeddedTView(tNode: TNode, childQueryIndex: number): TQuery | null; } /** @@ -179,7 +179,7 @@ export interface TQueries { * `embeddedTView` on each and every TQuery. * @param tNode */ - embeddedTView(tNode: TNode): TQueries|null; + embeddedTView(tNode: TNode): TQueries | null; } /** @@ -193,7 +193,7 @@ export interface LQuery { * Materialized query matches for a given view only (!). Results are initialized lazily so the * array of matches is set to `null` initially. */ - matches: (T|null)[]|null; + matches: (T | null)[] | null; /** * A QueryList where materialized query results should be reported. @@ -226,7 +226,7 @@ export interface LQueries { * for a new embedded view is instantiated (cloned) from the declaration view. * @param tView */ - createEmbeddedView(tView: TView): LQueries|null; + createEmbeddedView(tView: TView): LQueries | null; /** * A method called when an embedded view is inserted into a container. As a result all impacted diff --git a/packages/core/src/render3/interfaces/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts index ee5d2bb7aebce..b00e80dbe0d15 100644 --- a/packages/core/src/render3/interfaces/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -20,7 +20,7 @@ import {RComment, RElement, RNode, RText} from './renderer_dom'; * it will be easy to implement such API. */ -export type GlobalTargetName = 'document'|'window'|'body'; +export type GlobalTargetName = 'document' | 'window' | 'body'; export type GlobalTargetResolver = (element: any) => EventTarget; @@ -33,41 +33,46 @@ export type GlobalTargetResolver = (element: any) => EventTarget; export interface Renderer { destroy(): void; createComment(value: string): RComment; - createElement(name: string, namespace?: string|null): RElement; + createElement(name: string, namespace?: string | null): RElement; createText(value: string): RText; /** * This property is allowed to be null / undefined, * in which case the view engine won't call it. * This is used as a performance optimization for production mode. */ - destroyNode?: ((node: RNode) => void)|null; + destroyNode?: ((node: RNode) => void) | null; appendChild(parent: RElement, newChild: RNode): void; - insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null, isMove?: boolean): void; + insertBefore(parent: RNode, newChild: RNode, refChild: RNode | null, isMove?: boolean): void; removeChild(parent: RElement, oldChild: RNode, isHostElement?: boolean): void; - selectRootElement(selectorOrNode: string|any, preserveContent?: boolean): RElement; + selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): RElement; - parentNode(node: RNode): RElement|null; - nextSibling(node: RNode): RNode|null; + parentNode(node: RNode): RElement | null; + nextSibling(node: RNode): RNode | null; setAttribute( - el: RElement, name: string, value: string|TrustedHTML|TrustedScript|TrustedScriptURL, - namespace?: string|null): void; - removeAttribute(el: RElement, name: string, namespace?: string|null): void; + el: RElement, + name: string, + value: string | TrustedHTML | TrustedScript | TrustedScriptURL, + namespace?: string | null, + ): void; + removeAttribute(el: RElement, name: string, namespace?: string | null): void; addClass(el: RElement, name: string): void; removeClass(el: RElement, name: string): void; setStyle(el: RElement, style: string, value: any, flags?: RendererStyleFlags2): void; removeStyle(el: RElement, style: string, flags?: RendererStyleFlags2): void; setProperty(el: RElement, name: string, value: any): void; - setValue(node: RText|RComment, value: string): void; + setValue(node: RText | RComment, value: string): void; // TODO(misko): Deprecate in favor of addEventListener/removeEventListener listen( - target: GlobalTargetName|RNode, eventName: string, - callback: (event: any) => boolean | void): () => void; + target: GlobalTargetName | RNode, + eventName: string, + callback: (event: any) => boolean | void, + ): () => void; } export interface RendererFactory { - createRenderer(hostElement: RElement|null, rendererType: RendererType2|null): Renderer; + createRenderer(hostElement: RElement | null, rendererType: RendererType2 | null): Renderer; begin?(): void; end?(): void; } diff --git a/packages/core/src/render3/interfaces/renderer_dom.ts b/packages/core/src/render3/interfaces/renderer_dom.ts index f6a0d7d4c3d5f..3830bf46af3b4 100644 --- a/packages/core/src/render3/interfaces/renderer_dom.ts +++ b/packages/core/src/render3/interfaces/renderer_dom.ts @@ -22,18 +22,17 @@ export interface RNode { /** * Returns the parent Element, Document, or DocumentFragment */ - parentNode: RNode|null; - + parentNode: RNode | null; /** * Returns the parent Element if there is one */ - parentElement: RElement|null; + parentElement: RElement | null; /** * Gets the Node immediately following this one in the parent's childNodes */ - nextSibling: RNode|null; + nextSibling: RNode | null; /** * Removes a child from the current node and returns the removed node @@ -46,7 +45,7 @@ export interface RNode { * * Used exclusively for adding View root nodes into ViewAnchor location. */ - insertBefore(newChild: RNode, refChild: RNode|null, isViewRoot: boolean): void; + insertBefore(newChild: RNode, refChild: RNode | null, isViewRoot: boolean): void; /** * Append a child node. @@ -61,19 +60,21 @@ export interface RNode { * listeners on Element. */ export interface RElement extends RNode { - firstChild: RNode|null; + firstChild: RNode | null; style: RCssStyleDeclaration; classList: RDomTokenList; className: string; tagName: string; - textContent: string|null; + textContent: string | null; hasAttribute(name: string): boolean; - getAttribute(name: string): string|null; - setAttribute(name: string, value: string|TrustedHTML|TrustedScript|TrustedScriptURL): void; + getAttribute(name: string): string | null; + setAttribute(name: string, value: string | TrustedHTML | TrustedScript | TrustedScriptURL): void; removeAttribute(name: string): void; setAttributeNS( - namespaceURI: string, qualifiedName: string, - value: string|TrustedHTML|TrustedScript|TrustedScriptURL): void; + namespaceURI: string, + qualifiedName: string, + value: string | TrustedHTML | TrustedScript | TrustedScriptURL, + ): void; addEventListener(type: string, listener: EventListener, useCapture?: boolean): void; removeEventListener(type: string, listener?: EventListener, options?: boolean): void; @@ -82,7 +83,7 @@ export interface RElement extends RNode { export interface RCssStyleDeclaration { removeProperty(propertyName: string): string; - setProperty(propertyName: string, value: string|null, priority?: string): void; + setProperty(propertyName: string, value: string | null, priority?: string): void; } export interface RDomTokenList { @@ -91,11 +92,11 @@ export interface RDomTokenList { } export interface RText extends RNode { - textContent: string|null; + textContent: string | null; } export interface RComment extends RNode { - textContent: string|null; + textContent: string | null; } export interface RTemplate extends RElement { diff --git a/packages/core/src/render3/interfaces/sanitization.ts b/packages/core/src/render3/interfaces/sanitization.ts index 3ebbb7c35affd..b8c09549a9347 100644 --- a/packages/core/src/render3/interfaces/sanitization.ts +++ b/packages/core/src/render3/interfaces/sanitization.ts @@ -11,5 +11,8 @@ import {TrustedHTML, TrustedScript, TrustedScriptURL} from '../../util/security/ /** * Function used to sanitize the value before writing it into the renderer. */ -export type SanitizerFn = (value: any, tagName?: string, propName?: string) => - string|TrustedHTML|TrustedScript|TrustedScriptURL; +export type SanitizerFn = ( + value: any, + tagName?: string, + propName?: string, +) => string | TrustedHTML | TrustedScript | TrustedScriptURL; diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 59755ef585a76..54aacff920b60 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -14,8 +14,7 @@ import {assertNumber, assertNumberInRange} from '../../util/assert'; * * See: `TStylingKeyPrimitive` and `TStylingStatic` */ -export type TStylingKey = TStylingKeyPrimitive|TStylingStatic; - +export type TStylingKey = TStylingKeyPrimitive | TStylingStatic; /** * The primitive portion (`TStylingStatic` removed) of the value stored in the `TData` which is @@ -27,7 +26,7 @@ export type TStylingKey = TStylingKeyPrimitive|TStylingStatic; * is combined with directive which shadows its input `@Input('class')`. That way the binding * should not participate in the styling resolution. */ -export type TStylingKeyPrimitive = string|null|false; +export type TStylingKeyPrimitive = string | null | false; /** * Store the static values for the styling binding. @@ -119,7 +118,7 @@ export interface TStylingStatic extends KeyValueArray {} * * NOTE: `0` has special significance and represents `null` as in no additional pointer. */ -export type TStylingRange = number&{ +export type TStylingRange = number & { __brand__: 'TStylingRange'; }; @@ -130,15 +129,15 @@ export const enum StylingRange { /// Number of bits to shift for the previous pointer PREV_SHIFT = 17, /// Previous pointer mask. - PREV_MASK = 0xFFFE0000, + PREV_MASK = 0xfffe0000, /// Number of bits to shift for the next pointer NEXT_SHIFT = 2, /// Next pointer mask. - NEXT_MASK = 0x001FFFC, + NEXT_MASK = 0x001fffc, // Mask to remove negative bit. (interpret number as positive) - UNSIGNED_MASK = 0x7FFF, + UNSIGNED_MASK = 0x7fff, /** * This bit is set if the previous bindings contains a binding which could possibly cause a @@ -157,11 +156,10 @@ export const enum StylingRange { NEXT_DUPLICATE = 0x01, } - export function toTStylingRange(prev: number, next: number): TStylingRange { ngDevMode && assertNumberInRange(prev, 0, StylingRange.UNSIGNED_MASK); ngDevMode && assertNumberInRange(next, 0, StylingRange.UNSIGNED_MASK); - return (prev << StylingRange.PREV_SHIFT | next << StylingRange.NEXT_SHIFT) as TStylingRange; + return ((prev << StylingRange.PREV_SHIFT) | (next << StylingRange.NEXT_SHIFT)) as TStylingRange; } export function getTStylingRangePrev(tStylingRange: TStylingRange): number { @@ -175,11 +173,13 @@ export function getTStylingRangePrevDuplicate(tStylingRange: TStylingRange): boo } export function setTStylingRangePrev( - tStylingRange: TStylingRange, previous: number): TStylingRange { + tStylingRange: TStylingRange, + previous: number, +): TStylingRange { ngDevMode && assertNumber(tStylingRange, 'expected number'); ngDevMode && assertNumberInRange(previous, 0, StylingRange.UNSIGNED_MASK); - return ((tStylingRange & ~StylingRange.PREV_MASK) | (previous << StylingRange.PREV_SHIFT)) as - TStylingRange; + return ((tStylingRange & ~StylingRange.PREV_MASK) | + (previous << StylingRange.PREV_SHIFT)) as TStylingRange; } export function setTStylingRangePrevDuplicate(tStylingRange: TStylingRange): TStylingRange { @@ -195,13 +195,13 @@ export function getTStylingRangeNext(tStylingRange: TStylingRange): number { export function setTStylingRangeNext(tStylingRange: TStylingRange, next: number): TStylingRange { ngDevMode && assertNumber(tStylingRange, 'expected number'); ngDevMode && assertNumberInRange(next, 0, StylingRange.UNSIGNED_MASK); - return ((tStylingRange & ~StylingRange.NEXT_MASK) | // - next << StylingRange.NEXT_SHIFT) as TStylingRange; + return ((tStylingRange & ~StylingRange.NEXT_MASK) | // + (next << StylingRange.NEXT_SHIFT)) as TStylingRange; } export function getTStylingRangeNextDuplicate(tStylingRange: TStylingRange): boolean { ngDevMode && assertNumber(tStylingRange, 'expected number'); - return ((tStylingRange) & StylingRange.NEXT_DUPLICATE) === StylingRange.NEXT_DUPLICATE; + return (tStylingRange & StylingRange.NEXT_DUPLICATE) === StylingRange.NEXT_DUPLICATE; } export function setTStylingRangeNextDuplicate(tStylingRange: TStylingRange): TStylingRange { diff --git a/packages/core/src/render3/interfaces/type_checks.ts b/packages/core/src/render3/interfaces/type_checks.ts index 3239b4cc25221..cb813ce063545 100644 --- a/packages/core/src/render3/interfaces/type_checks.ts +++ b/packages/core/src/render3/interfaces/type_checks.ts @@ -12,12 +12,11 @@ import {TNode, TNodeFlags, TNodeType} from './node'; import {RNode} from './renderer_dom'; import {FLAGS, LView, LViewFlags} from './view'; - /** * True if `value` is `LView`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ -export function isLView(value: RNode|LView|LContainer|{}|null): value is LView { +export function isLView(value: RNode | LView | LContainer | {} | null): value is LView { return Array.isArray(value) && typeof value[TYPE] === 'object'; } @@ -25,7 +24,7 @@ export function isLView(value: RNode|LView|LContainer|{}|null): value is LView { * True if `value` is `LContainer`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ -export function isLContainer(value: RNode|LView|LContainer|{}|null): value is LContainer { +export function isLContainer(value: RNode | LView | LContainer | {} | null): value is LContainer { return Array.isArray(value) && value[TYPE] === true; } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 8a321f6db2b0f..04eb6fb5fe78a 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -18,7 +18,16 @@ import type {ReactiveLViewConsumer} from '../reactive_lview_consumer'; import type {EffectScheduler} from '../reactivity/effect'; import {LContainer} from './container'; -import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition'; +import { + ComponentDef, + ComponentTemplate, + DirectiveDef, + DirectiveDefList, + HostBindingsFunction, + PipeDef, + PipeDefList, + ViewQueriesFunction, +} from './definition'; import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n'; import {TConstants, TNode} from './node'; import type {LQueries, TQueries} from './query'; @@ -26,8 +35,6 @@ import {Renderer, RendererFactory} from './renderer'; import {RElement} from './renderer_dom'; import {TStylingKey, TStylingRange} from './styling'; - - // Below are constants for LView indices to help us look up LView members // without having to remember the specific indices. // Uglify will inline these when minifying so there shouldn't be a cost. @@ -70,7 +77,6 @@ export const REACTIVE_TEMPLATE_CONSUMER = 23; */ export const HEADER_OFFSET = 25; - // This interface replaces the real LView interface if it is an arg or a // return value of a public instruction. This ensures we don't need to expose // the actual interface, which should be kept private. @@ -78,7 +84,6 @@ export interface OpaqueViewState { '__brand__': 'Brand for OpaqueViewState that nothing will match'; } - /** * `LView` stores all of the information needed to process the instructions as * they are invoked from the template. Each embedded view and component view has its @@ -93,14 +98,14 @@ export interface LView extends Array { /** * The node into which this `LView` is inserted. */ - [HOST]: RElement|null; + [HOST]: RElement | null; /** * The static data for this view. We need a reference to this so we can easily walk up the * node tree in DI and get the TView.data array associated with a node (where the * directive defs are stored). */ - readonly[TVIEW]: TView; + readonly [TVIEW]: TView; /** Flags for this view. See LViewFlags for more info. */ [FLAGS]: LViewFlags; @@ -114,7 +119,7 @@ export interface LView extends Array { * * `LContainer` - The current view is part of a container, and is an embedded view. */ - [PARENT]: LView|LContainer|null; + [PARENT]: LView | LContainer | null; /** * @@ -125,10 +130,10 @@ export interface LView extends Array { * views in the same container. We need a way to link component views and views * across containers as well. */ - [NEXT]: LView|LContainer|null; + [NEXT]: LView | LContainer | null; /** Queries active for this view - nodes from a view are reported to those queries. */ - [QUERIES]: LQueries|null; + [QUERIES]: LQueries | null; /** * Store the `TNode` of the location where the current `LView` is inserted into. @@ -159,7 +164,7 @@ export interface LView extends Array { * If `null`, this is the root view of an application (root component is in this view) and it has * no parents. */ - [T_HOST]: TNode|null; + [T_HOST]: TNode | null; /** * When a view is destroyed, listeners need to be released and outputs need to be @@ -173,7 +178,7 @@ export interface LView extends Array { * end of the `lView[CLEANUP]` because we know that no more `T` level cleanup functions will be * added here. */ - [CLEANUP]: any[]|null; + [CLEANUP]: any[] | null; /** * - For dynamic views, this is the context with which to render the template (e.g. @@ -185,7 +190,7 @@ export interface LView extends Array { [CONTEXT]: T; /** An optional Module Injector to be used as fall back after Element Injectors are consulted. */ - readonly[INJECTOR]: Injector|null; + readonly [INJECTOR]: Injector | null; /** * Contextual data that is shared across multiple instances of `LView` in the same application. @@ -202,7 +207,7 @@ export interface LView extends Array { * Necessary to store this so views can traverse through their nested views * to remove listeners and call onDestroy callbacks. */ - [CHILD_HEAD]: LView|LContainer|null; + [CHILD_HEAD]: LView | LContainer | null; /** * The last LView or LContainer beneath this LView in the hierarchy. @@ -210,7 +215,7 @@ export interface LView extends Array { * The tail allows us to quickly add a new state to the end of the view list * without having to propagate starting from the first child. */ - [CHILD_TAIL]: LView|LContainer|null; + [CHILD_TAIL]: LView | LContainer | null; /** * View where this view's template was declared. @@ -234,8 +239,7 @@ export interface LView extends Array { * template function during change detection, we need the declaration view to get inherited * context. */ - [DECLARATION_VIEW]: LView|null; - + [DECLARATION_VIEW]: LView | null; /** * Points to the declaration component view, used to track transplanted `LView`s. @@ -316,7 +320,7 @@ export interface LView extends Array { * query matches in a proper order (query matches are ordered based on their declaration point and * _not_ the insertion point). */ - [DECLARATION_LCONTAINER]: LContainer|null; + [DECLARATION_LCONTAINER]: LContainer | null; /** * More flags for this view. See PreOrderHookFlags for more info. @@ -329,25 +333,25 @@ export interface LView extends Array { /** * A container related to hydration annotation information that's associated with this LView. */ - [HYDRATION]: DehydratedView|null; + [HYDRATION]: DehydratedView | null; /** * Optional injector assigned to embedded views that takes * precedence over the element and module injectors. */ - readonly[EMBEDDED_VIEW_INJECTOR]: Injector|null; + readonly [EMBEDDED_VIEW_INJECTOR]: Injector | null; /** * Effect scheduling operations that need to run during this views's update pass. */ - [EFFECTS_TO_SCHEDULE]: Array<() => void>|null; + [EFFECTS_TO_SCHEDULE]: Array<() => void> | null; /** * A collection of callbacks functions that are executed when a given LView is destroyed. Those * are user defined, LView-specific destroy callbacks that don't have any corresponding TView * entries. */ - [ON_DESTROY_HOOKS]: Array<() => void>|null; + [ON_DESTROY_HOOKS]: Array<() => void> | null; /** * The `Consumer` for this `LView`'s template so that signal reads can be tracked. @@ -355,7 +359,7 @@ export interface LView extends Array { * This is initially `null` and gets assigned a consumer after template execution * if any signals were read. */ - [REACTIVE_TEMPLATE_CONSUMER]: ReactiveLViewConsumer|null; + [REACTIVE_TEMPLATE_CONSUMER]: ReactiveLViewConsumer | null; } /** @@ -366,16 +370,16 @@ export interface LViewEnvironment { rendererFactory: RendererFactory; /** An optional custom sanitizer. */ - sanitizer: Sanitizer|null; + sanitizer: Sanitizer | null; /** Container for reactivity system `effect`s. */ - inlineEffectRunner: EffectScheduler|null; + inlineEffectRunner: EffectScheduler | null; /** Container for after render hooks */ - afterRenderEventManager: AfterRenderEventManager|null; + afterRenderEventManager: AfterRenderEventManager | null; /** Scheduler for change detection to notify when application state changes. */ - changeDetectionScheduler: ChangeDetectionScheduler|null; + changeDetectionScheduler: ChangeDetectionScheduler | null; } /** Flags associated with an LView (saved in LView[FLAGS]) */ @@ -547,7 +551,7 @@ export const enum PreOrderHookFlags { * ``` * */ -export interface HostBindingOpCodes extends Array> { +export interface HostBindingOpCodes extends Array> { __brand__: 'HostBindingOpCodes'; debug?: string[]; } @@ -603,17 +607,17 @@ export interface TView { * The template function used to refresh the view of dynamically created views * and components. Will be null for inline views. */ - template: ComponentTemplate<{}>|null; + template: ComponentTemplate<{}> | null; /** * A function containing query-related instructions. */ - viewQuery: ViewQueriesFunction<{}>|null; + viewQuery: ViewQueriesFunction<{}> | null; /** * A `TNode` representing the declaration location of this `TView` (not part of this TView). */ - declTNode: TNode|null; + declTNode: TNode | null; // FIXME(misko): Why does `TView` not have `declarationTView` property? @@ -677,14 +681,14 @@ export interface TView { /** * A reference to the first child node located in the view. */ - firstChild: TNode|null; + firstChild: TNode | null; /** * Stores the OpCodes to be replayed during change-detection to process the `HostBindings` * * See `HostBindingOpCodes` for encoding details. */ - hostBindingOpCodes: HostBindingOpCodes|null; + hostBindingOpCodes: HostBindingOpCodes | null; /** * Full registry of directives and components that may be found in this view. @@ -692,7 +696,7 @@ export interface TView { * It's necessary to keep a copy of the full def list on the TView so it's possible * to render template functions without a host component. */ - directiveRegistry: DirectiveDefList|null; + directiveRegistry: DirectiveDefList | null; /** * Full registry of pipes that may be found in this view. @@ -703,7 +707,7 @@ export interface TView { * It's necessary to keep a copy of the full def list on the TView so it's possible * to render template functions without a host component. */ - pipeRegistry: PipeDefList|null; + pipeRegistry: PipeDefList | null; /** * Array of ngOnInit, ngOnChanges and ngDoCheck hooks that should be executed for this view in @@ -716,14 +720,14 @@ export interface TView { * function. This is done so that at runtime the system can efficiently iterate over all of the * functions to invoke without having to make any decisions/lookups. */ - preOrderHooks: HookData|null; + preOrderHooks: HookData | null; /** * Array of ngOnChanges and ngDoCheck hooks that should be executed for this view in update mode. * * This array has the same structure as the `preOrderHooks` one. */ - preOrderCheckHooks: HookData|null; + preOrderCheckHooks: HookData | null; /** * Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed @@ -732,7 +736,7 @@ export interface TView { * Even indices: Directive index * Odd indices: Hook function */ - contentHooks: HookData|null; + contentHooks: HookData | null; /** * Array of ngAfterContentChecked hooks that should be executed for this view in update @@ -741,7 +745,7 @@ export interface TView { * Even indices: Directive index * Odd indices: Hook function */ - contentCheckHooks: HookData|null; + contentCheckHooks: HookData | null; /** * Array of ngAfterViewInit and ngAfterViewChecked hooks that should be executed for @@ -750,7 +754,7 @@ export interface TView { * Even indices: Directive index * Odd indices: Hook function */ - viewHooks: HookData|null; + viewHooks: HookData | null; /** * Array of ngAfterViewChecked hooks that should be executed for this view in @@ -759,7 +763,7 @@ export interface TView { * Even indices: Directive index * Odd indices: Hook function */ - viewCheckHooks: HookData|null; + viewCheckHooks: HookData | null; /** * Array of ngOnDestroy hooks that should be executed when this view is destroyed. @@ -767,7 +771,7 @@ export interface TView { * Even indices: Directive index * Odd indices: Hook function */ - destroyHooks: DestroyHookData|null; + destroyHooks: DestroyHookData | null; /** * When a view is destroyed, listeners need to be released and outputs need to be @@ -795,7 +799,7 @@ export interface TView { * 2nd index is: index of function context in LView.cleanupInstances[] * `tView.cleanup[i+0].call(lView[CLEANUP][tView.cleanup[i+1]])` */ - cleanup: any[]|null; + cleanup: any[] | null; /** * A list of element indices for child components that will need to be @@ -803,12 +807,12 @@ export interface TView { * already been adjusted for the HEADER_OFFSET. * */ - components: number[]|null; + components: number[] | null; /** * A collection of queries tracked in a given view. */ - queries: TQueries|null; + queries: TQueries | null; /** * An array of indices pointing to directives with content queries alongside with the @@ -819,18 +823,18 @@ export interface TView { * We are storing those indexes so we can refresh content queries as part of a view refresh * process. */ - contentQueries: number[]|null; + contentQueries: number[] | null; /** * Set of schemas that declare elements to be allowed inside the view. */ - schemas: SchemaMetadata[]|null; + schemas: SchemaMetadata[] | null; /** * Array of constants for the view. Includes attribute arrays, local definition arrays etc. * Used for directive matching, attribute bindings, local definitions and more. */ - consts: TConstants|null; + consts: TConstants | null; /** * Indicates that there was an error before we managed to complete the first create pass of the @@ -844,7 +848,7 @@ export interface TView { * - TViewType.Component: an id generated based on component properties * (see `getComponentId` function for details) */ - ssrId: string|null; + ssrId: string | null; } /** Single hook callback function. */ @@ -854,7 +858,7 @@ export type HookFn = () => void; * Information necessary to call a hook. E.g. the callback that * needs to invoked and the index at which to find its context. */ -export type HookEntry = number|HookFn; +export type HookEntry = number | HookFn; /** * Array of hooks that should be executed for a view and their directive indices. @@ -888,7 +892,7 @@ export type HookData = HookEntry[]; * `CService` and `DService` are part of a `multi` provider where only `BService` and `DService` * have an `ngOnDestroy` hook. */ -export type DestroyHookData = (HookEntry|HookData)[]; +export type DestroyHookData = (HookEntry | HookData)[]; /** * Static data that corresponds to the instance-specific data array on an LView. @@ -917,6 +921,19 @@ export type DestroyHookData = (HookEntry|HookData)[]; * * Injector bloom filters are also stored here. */ -export type TData = - (TNode|PipeDef|DirectiveDef|ComponentDef|number|TStylingRange|TStylingKey| - ProviderToken|TI18n|I18nUpdateOpCodes|TIcu|null|string|TDeferBlockDetails)[]; +export type TData = ( + | TNode + | PipeDef + | DirectiveDef + | ComponentDef + | number + | TStylingRange + | TStylingKey + | ProviderToken + | TI18n + | I18nUpdateOpCodes + | TIcu + | null + | string + | TDeferBlockDetails +)[]; diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 185b108473285..0300b477128a4 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -6,14 +6,24 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, JitCompilerUsage, R3DirectiveMetadataFacade} from '../../compiler/compiler_facade'; -import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface'; +import { + getCompilerFacade, + JitCompilerUsage, + R3DirectiveMetadataFacade, +} from '../../compiler/compiler_facade'; +import { + R3ComponentMetadataFacade, + R3QueryMetadataFacade, +} from '../../compiler/compiler_facade_interface'; import {resolveForwardRef} from '../../di/forward_ref'; import {getReflect, reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; import {Query} from '../../metadata/di'; import {Component, Directive, Input} from '../../metadata/directives'; -import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading'; +import { + componentNeedsResolution, + maybeQueueResolutionOfComponentResources, +} from '../../metadata/resource_loading'; import {ViewEncapsulation} from '../../metadata/view'; import {flatten} from '../../util/array_utils'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../../util/empty'; @@ -26,7 +36,11 @@ import {stringifyForError} from '../util/stringify_utils'; import {angularCoreEnv} from './environment'; import {getJitOptions} from './jit_options'; -import {flushModuleScopingQueueAsMuchAsPossible, patchComponentDefWithScope, transitiveScopesFor} from './module'; +import { + flushModuleScopingQueueAsMuchAsPossible, + patchComponentDefWithScope, + transitiveScopesFor, +} from './module'; import {isComponent, verifyStandaloneImport} from './util'; /** @@ -58,7 +72,7 @@ export function compileComponent(type: Type, metadata: Component): void { // See the `initNgDevMode` docstring for more information. (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode(); - let ngComponentDef: ComponentDef|null = null; + let ngComponentDef: ComponentDef | null = null; // Metadata may have resources which need to be resolved. maybeQueueResolutionOfComponentResources(type, metadata); @@ -71,8 +85,11 @@ export function compileComponent(type: Type, metadata: Component): void { Object.defineProperty(type, NG_COMP_DEF, { get: () => { if (ngComponentDef === null) { - const compiler = - getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'component', type: type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'component', + type: type, + }); if (componentNeedsResolution(metadata)) { const error = [`Component '${type.name}' is not resolved:`]; @@ -117,8 +134,10 @@ export function compileComponent(type: Type, metadata: Component): void { typeSourceSpan: compiler.createParseSourceSpan('Component', type.name, templateUrl), template: metadata.template || '', preserveWhitespaces, - styles: typeof metadata.styles === 'string' ? [metadata.styles] : - (metadata.styles || EMPTY_ARRAY), + styles: + typeof metadata.styles === 'string' + ? [metadata.styles] + : metadata.styles || EMPTY_ARRAY, animations: metadata.animations, // JIT components are always compiled against an empty set of `declarations`. Instead, the // `directiveDefs` and `pipeDefs` are updated at a later point: @@ -137,8 +156,11 @@ export function compileComponent(type: Type, metadata: Component): void { if (meta.usesInheritance) { addDirectiveDefToUndecoratedParents(type); } - ngComponentDef = - compiler.compileComponent(angularCoreEnv, templateUrl, meta) as ComponentDef; + ngComponentDef = compiler.compileComponent( + angularCoreEnv, + templateUrl, + meta, + ) as ComponentDef; if (metadata.standalone) { // Patch the component definition for standalone components with `directiveDefs` and @@ -177,8 +199,11 @@ export function compileComponent(type: Type, metadata: Component): void { if (metadata.standalone) { ngComponentDef.schemas = metadata.schemas; } else { - throw new Error(`The 'schemas' was specified for the ${ - stringifyForError(type)} but is only valid on a component that is standalone.`); + throw new Error( + `The 'schemas' was specified for the ${stringifyForError( + type, + )} but is only valid on a component that is standalone.`, + ); } } else if (metadata.standalone) { ngComponentDef.schemas = []; @@ -197,12 +222,15 @@ export function compileComponent(type: Type, metadata: Component): void { * memoized functions here allows for the delayed resolution of any `forwardRef`s present in the * component's `imports`. */ -function getStandaloneDefFunctions(type: Type, imports: Type[]): { - directiveDefs: () => DirectiveDefList, - pipeDefs: () => PipeDefList, +function getStandaloneDefFunctions( + type: Type, + imports: Type[], +): { + directiveDefs: () => DirectiveDefList; + pipeDefs: () => PipeDefList; } { - let cachedDirectiveDefs: DirectiveDefList|null = null; - let cachedPipeDefs: PipeDefList|null = null; + let cachedDirectiveDefs: DirectiveDefList | null = null; + let cachedPipeDefs: PipeDefList | null = null; const directiveDefs = () => { if (!USE_RUNTIME_DEPS_TRACKER_FOR_JIT) { if (cachedDirectiveDefs === null) { @@ -252,8 +280,8 @@ function getStandaloneDefFunctions(type: Type, imports: Type[]): { const scope = depsTracker.getStandaloneComponentScope(type, imports); return [...scope.compilation.directives] - .map(p => (getComponentDef(p) || getDirectiveDef(p))!) - .filter(d => d !== null); + .map((p) => (getComponentDef(p) || getDirectiveDef(p))!) + .filter((d) => d !== null); } }; @@ -301,7 +329,7 @@ function getStandaloneDefFunctions(type: Type, imports: Type[]): { const scope = depsTracker.getStandaloneComponentScope(type, imports); - return [...scope.compilation.pipes].map(p => getPipeDef(p)!).filter(d => d !== null); + return [...scope.compilation.pipes].map((p) => getPipeDef(p)!).filter((d) => d !== null); } }; @@ -311,8 +339,9 @@ function getStandaloneDefFunctions(type: Type, imports: Type[]): { }; } -function hasSelectorScope(component: Type): - component is(Type& {ngSelectorScope: Type}) { +function hasSelectorScope( + component: Type, +): component is Type & {ngSelectorScope: Type} { return (component as {ngSelectorScope?: any}).ngSelectorScope !== undefined; } @@ -323,7 +352,7 @@ function hasSelectorScope(component: Type): * In the event that compilation is not immediate, `compileDirective` will return a `Promise` which * will resolve when compilation completes and the directive becomes usable. */ -export function compileDirective(type: Type, directive: Directive|null): void { +export function compileDirective(type: Type, directive: Directive | null): void { let ngDirectiveDef: any = null; addDirectiveFactoryDef(type, directive || {}); @@ -335,10 +364,16 @@ export function compileDirective(type: Type, directive: Directive|null): vo // that use `@Directive()` with no selector. In that case, pass empty object to the // `directiveMetadata` function instead of null. const meta = getDirectiveMetadata(type, directive || {}); - const compiler = - getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type}); - ngDirectiveDef = - compiler.compileDirective(angularCoreEnv, meta.sourceMapUrl, meta.metadata); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'directive', + type, + }); + ngDirectiveDef = compiler.compileDirective( + angularCoreEnv, + meta.sourceMapUrl, + meta.metadata, + ); } return ngDirectiveDef; }, @@ -359,21 +394,24 @@ function getDirectiveMetadata(type: Type, metadata: Directive) { return {metadata: facade, sourceMapUrl}; } -function addDirectiveFactoryDef(type: Type, metadata: Directive|Component) { +function addDirectiveFactoryDef(type: Type, metadata: Directive | Component) { let ngFactoryDef: any = null; Object.defineProperty(type, NG_FACTORY_DEF, { get: () => { if (ngFactoryDef === null) { const meta = getDirectiveMetadata(type, metadata); - const compiler = - getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'directive', + type, + }); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${type.name}/ɵfac.js`, { name: meta.metadata.name, type: meta.metadata.type, typeArgumentCount: 0, deps: reflectDependencies(type), - target: compiler.FactoryTarget.Directive + target: compiler.FactoryTarget.Directive, }); } return ngFactoryDef; @@ -413,9 +451,10 @@ export function directiveMetadata(type: Type, metadata: Directive): R3Direc viewQueries: extractQueriesMetadata(type, propMetadata, isViewQuery), isStandalone: !!metadata.standalone, isSignal: !!metadata.signals, - hostDirectives: metadata.hostDirectives?.map( - directive => typeof directive === 'function' ? {directive} : directive) || - null + hostDirectives: + metadata.hostDirectives?.map((directive) => + typeof directive === 'function' ? {directive} : directive, + ) || null, }; } @@ -430,15 +469,18 @@ function addDirectiveDefToUndecoratedParents(type: Type) { while (parent && parent !== objPrototype) { // Since inheritance works if the class was annotated already, we only need to add // the def if there are no annotations and the def hasn't been created already. - if (!getDirectiveDef(parent) && !getComponentDef(parent) && - shouldAddAbstractDirective(parent)) { + if ( + !getDirectiveDef(parent) && + !getComponentDef(parent) && + shouldAddAbstractDirective(parent) + ) { compileDirective(parent, null); } parent = Object.getPrototypeOf(parent); } } -function convertToR3QueryPredicate(selector: any): any|string[] { +function convertToR3QueryPredicate(selector: any): any | string[] { return typeof selector === 'string' ? splitByComma(selector) : resolveForwardRef(selector); } @@ -455,18 +497,21 @@ export function convertToR3QueryMetadata(propertyName: string, ann: Query): R3Qu }; } function extractQueriesMetadata( - type: Type, propMetadata: {[key: string]: any[]}, - isQueryAnn: (ann: any) => ann is Query): R3QueryMetadataFacade[] { + type: Type, + propMetadata: {[key: string]: any[]}, + isQueryAnn: (ann: any) => ann is Query, +): R3QueryMetadataFacade[] { const queriesMeta: R3QueryMetadataFacade[] = []; for (const field in propMetadata) { if (propMetadata.hasOwnProperty(field)) { const annotations = propMetadata[field]; - annotations.forEach(ann => { + annotations.forEach((ann) => { if (isQueryAnn(ann)) { if (!ann.selector) { throw new Error( - `Can't construct a query for the property "${field}" of ` + - `"${stringifyForError(type)}" since the query selector wasn't defined.`); + `Can't construct a query for the property "${field}" of ` + + `"${stringifyForError(type)}" since the query selector wasn't defined.`, + ); } if (annotations.some(isInputAnnotation)) { throw new Error(`Cannot combine @Input decorators with query decorators`); @@ -479,7 +524,7 @@ function extractQueriesMetadata( return queriesMeta; } -function extractExportAs(exportAs: string|undefined): string[]|null { +function extractExportAs(exportAs: string | undefined): string[] | null { return exportAs === undefined ? null : splitByComma(exportAs); } @@ -498,18 +543,24 @@ function isInputAnnotation(value: any): value is Input { } function splitByComma(value: string): string[] { - return value.split(',').map(piece => piece.trim()); + return value.split(',').map((piece) => piece.trim()); } const LIFECYCLE_HOOKS = [ - 'ngOnChanges', 'ngOnInit', 'ngOnDestroy', 'ngDoCheck', 'ngAfterViewInit', 'ngAfterViewChecked', - 'ngAfterContentInit', 'ngAfterContentChecked' + 'ngOnChanges', + 'ngOnInit', + 'ngOnDestroy', + 'ngDoCheck', + 'ngAfterViewInit', + 'ngAfterViewChecked', + 'ngAfterContentInit', + 'ngAfterContentChecked', ]; function shouldAddAbstractDirective(type: Type): boolean { const reflect = getReflect(); - if (LIFECYCLE_HOOKS.some(hookName => reflect.hasLifecycleHook(type, hookName))) { + if (LIFECYCLE_HOOKS.some((hookName) => reflect.hasLifecycleHook(type, hookName))) { return true; } @@ -522,9 +573,14 @@ function shouldAddAbstractDirective(type: Type): boolean { const current = annotations[i]; const metadataName = current.ngMetadataName; - if (isInputAnnotation(current) || isContentQuery(current) || isViewQuery(current) || - metadataName === 'Output' || metadataName === 'HostBinding' || - metadataName === 'HostListener') { + if ( + isInputAnnotation(current) || + isContentQuery(current) || + isViewQuery(current) || + metadataName === 'Output' || + metadataName === 'HostBinding' || + metadataName === 'HostListener' + ) { return true; } } diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 8be3dbf1f982b..6416025045d71 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -14,196 +14,194 @@ import * as iframe_attrs_validation from '../../sanitization/iframe_attrs_valida import * as sanitization from '../../sanitization/sanitization'; import * as r3 from '../index'; - /** * A mapping of the @angular/core API surface used in generated expressions to the actual symbols. * * This should be kept up to date with the public exports of @angular/core. */ -export const angularCoreEnv: {[name: string]: unknown} = - (() => ({ - 'ɵɵattribute': r3.ɵɵattribute, - 'ɵɵattributeInterpolate1': r3.ɵɵattributeInterpolate1, - 'ɵɵattributeInterpolate2': r3.ɵɵattributeInterpolate2, - 'ɵɵattributeInterpolate3': r3.ɵɵattributeInterpolate3, - 'ɵɵattributeInterpolate4': r3.ɵɵattributeInterpolate4, - 'ɵɵattributeInterpolate5': r3.ɵɵattributeInterpolate5, - 'ɵɵattributeInterpolate6': r3.ɵɵattributeInterpolate6, - 'ɵɵattributeInterpolate7': r3.ɵɵattributeInterpolate7, - 'ɵɵattributeInterpolate8': r3.ɵɵattributeInterpolate8, - 'ɵɵattributeInterpolateV': r3.ɵɵattributeInterpolateV, - 'ɵɵdefineComponent': r3.ɵɵdefineComponent, - 'ɵɵdefineDirective': r3.ɵɵdefineDirective, - 'ɵɵdefineInjectable': ɵɵdefineInjectable, - 'ɵɵdefineInjector': ɵɵdefineInjector, - 'ɵɵdefineNgModule': r3.ɵɵdefineNgModule, - 'ɵɵdefinePipe': r3.ɵɵdefinePipe, - 'ɵɵdirectiveInject': r3.ɵɵdirectiveInject, - 'ɵɵgetInheritedFactory': r3.ɵɵgetInheritedFactory, - 'ɵɵinject': ɵɵinject, - 'ɵɵinjectAttribute': r3.ɵɵinjectAttribute, - 'ɵɵinvalidFactory': r3.ɵɵinvalidFactory, - 'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep, - 'ɵɵtemplateRefExtractor': r3.ɵɵtemplateRefExtractor, - 'ɵɵresetView': r3.ɵɵresetView, - 'ɵɵHostDirectivesFeature': r3.ɵɵHostDirectivesFeature, - 'ɵɵNgOnChangesFeature': r3.ɵɵNgOnChangesFeature, - 'ɵɵProvidersFeature': r3.ɵɵProvidersFeature, - 'ɵɵCopyDefinitionFeature': r3.ɵɵCopyDefinitionFeature, - 'ɵɵInheritDefinitionFeature': r3.ɵɵInheritDefinitionFeature, - 'ɵɵInputTransformsFeature': r3.ɵɵInputTransformsFeature, - 'ɵɵStandaloneFeature': r3.ɵɵStandaloneFeature, - 'ɵɵnextContext': r3.ɵɵnextContext, - 'ɵɵnamespaceHTML': r3.ɵɵnamespaceHTML, - 'ɵɵnamespaceMathML': r3.ɵɵnamespaceMathML, - 'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG, - 'ɵɵenableBindings': r3.ɵɵenableBindings, - 'ɵɵdisableBindings': r3.ɵɵdisableBindings, - 'ɵɵelementStart': r3.ɵɵelementStart, - 'ɵɵelementEnd': r3.ɵɵelementEnd, - 'ɵɵelement': r3.ɵɵelement, - 'ɵɵelementContainerStart': r3.ɵɵelementContainerStart, - 'ɵɵelementContainerEnd': r3.ɵɵelementContainerEnd, - 'ɵɵelementContainer': r3.ɵɵelementContainer, - 'ɵɵpureFunction0': r3.ɵɵpureFunction0, - 'ɵɵpureFunction1': r3.ɵɵpureFunction1, - 'ɵɵpureFunction2': r3.ɵɵpureFunction2, - 'ɵɵpureFunction3': r3.ɵɵpureFunction3, - 'ɵɵpureFunction4': r3.ɵɵpureFunction4, - 'ɵɵpureFunction5': r3.ɵɵpureFunction5, - 'ɵɵpureFunction6': r3.ɵɵpureFunction6, - 'ɵɵpureFunction7': r3.ɵɵpureFunction7, - 'ɵɵpureFunction8': r3.ɵɵpureFunction8, - 'ɵɵpureFunctionV': r3.ɵɵpureFunctionV, - 'ɵɵgetCurrentView': r3.ɵɵgetCurrentView, - 'ɵɵrestoreView': r3.ɵɵrestoreView, - 'ɵɵlistener': r3.ɵɵlistener, - 'ɵɵprojection': r3.ɵɵprojection, - 'ɵɵsyntheticHostProperty': r3.ɵɵsyntheticHostProperty, - 'ɵɵsyntheticHostListener': r3.ɵɵsyntheticHostListener, - 'ɵɵpipeBind1': r3.ɵɵpipeBind1, - 'ɵɵpipeBind2': r3.ɵɵpipeBind2, - 'ɵɵpipeBind3': r3.ɵɵpipeBind3, - 'ɵɵpipeBind4': r3.ɵɵpipeBind4, - 'ɵɵpipeBindV': r3.ɵɵpipeBindV, - 'ɵɵprojectionDef': r3.ɵɵprojectionDef, - 'ɵɵhostProperty': r3.ɵɵhostProperty, - 'ɵɵproperty': r3.ɵɵproperty, - 'ɵɵpropertyInterpolate': r3.ɵɵpropertyInterpolate, - 'ɵɵpropertyInterpolate1': r3.ɵɵpropertyInterpolate1, - 'ɵɵpropertyInterpolate2': r3.ɵɵpropertyInterpolate2, - 'ɵɵpropertyInterpolate3': r3.ɵɵpropertyInterpolate3, - 'ɵɵpropertyInterpolate4': r3.ɵɵpropertyInterpolate4, - 'ɵɵpropertyInterpolate5': r3.ɵɵpropertyInterpolate5, - 'ɵɵpropertyInterpolate6': r3.ɵɵpropertyInterpolate6, - 'ɵɵpropertyInterpolate7': r3.ɵɵpropertyInterpolate7, - 'ɵɵpropertyInterpolate8': r3.ɵɵpropertyInterpolate8, - 'ɵɵpropertyInterpolateV': r3.ɵɵpropertyInterpolateV, - 'ɵɵpipe': r3.ɵɵpipe, - 'ɵɵqueryRefresh': r3.ɵɵqueryRefresh, - 'ɵɵqueryAdvance': r3.ɵɵqueryAdvance, - 'ɵɵviewQuery': r3.ɵɵviewQuery, - 'ɵɵviewQuerySignal': r3.ɵɵviewQuerySignal, - 'ɵɵloadQuery': r3.ɵɵloadQuery, - 'ɵɵcontentQuery': r3.ɵɵcontentQuery, - 'ɵɵcontentQuerySignal': r3.ɵɵcontentQuerySignal, - 'ɵɵreference': r3.ɵɵreference, - 'ɵɵclassMap': r3.ɵɵclassMap, - 'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1, - 'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2, - 'ɵɵclassMapInterpolate3': r3.ɵɵclassMapInterpolate3, - 'ɵɵclassMapInterpolate4': r3.ɵɵclassMapInterpolate4, - 'ɵɵclassMapInterpolate5': r3.ɵɵclassMapInterpolate5, - 'ɵɵclassMapInterpolate6': r3.ɵɵclassMapInterpolate6, - 'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7, - 'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8, - 'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV, - 'ɵɵstyleMap': r3.ɵɵstyleMap, - 'ɵɵstyleMapInterpolate1': r3.ɵɵstyleMapInterpolate1, - 'ɵɵstyleMapInterpolate2': r3.ɵɵstyleMapInterpolate2, - 'ɵɵstyleMapInterpolate3': r3.ɵɵstyleMapInterpolate3, - 'ɵɵstyleMapInterpolate4': r3.ɵɵstyleMapInterpolate4, - 'ɵɵstyleMapInterpolate5': r3.ɵɵstyleMapInterpolate5, - 'ɵɵstyleMapInterpolate6': r3.ɵɵstyleMapInterpolate6, - 'ɵɵstyleMapInterpolate7': r3.ɵɵstyleMapInterpolate7, - 'ɵɵstyleMapInterpolate8': r3.ɵɵstyleMapInterpolate8, - 'ɵɵstyleMapInterpolateV': r3.ɵɵstyleMapInterpolateV, - 'ɵɵstyleProp': r3.ɵɵstyleProp, - 'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1, - 'ɵɵstylePropInterpolate2': r3.ɵɵstylePropInterpolate2, - 'ɵɵstylePropInterpolate3': r3.ɵɵstylePropInterpolate3, - 'ɵɵstylePropInterpolate4': r3.ɵɵstylePropInterpolate4, - 'ɵɵstylePropInterpolate5': r3.ɵɵstylePropInterpolate5, - 'ɵɵstylePropInterpolate6': r3.ɵɵstylePropInterpolate6, - 'ɵɵstylePropInterpolate7': r3.ɵɵstylePropInterpolate7, - 'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8, - 'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV, - 'ɵɵclassProp': r3.ɵɵclassProp, - 'ɵɵadvance': r3.ɵɵadvance, - 'ɵɵtemplate': r3.ɵɵtemplate, - 'ɵɵconditional': r3.ɵɵconditional, - 'ɵɵdefer': r3.ɵɵdefer, - 'ɵɵdeferWhen': r3.ɵɵdeferWhen, - 'ɵɵdeferOnIdle': r3.ɵɵdeferOnIdle, - 'ɵɵdeferOnImmediate': r3.ɵɵdeferOnImmediate, - 'ɵɵdeferOnTimer': r3.ɵɵdeferOnTimer, - 'ɵɵdeferOnHover': r3.ɵɵdeferOnHover, - 'ɵɵdeferOnInteraction': r3.ɵɵdeferOnInteraction, - 'ɵɵdeferOnViewport': r3.ɵɵdeferOnViewport, - 'ɵɵdeferPrefetchWhen': r3.ɵɵdeferPrefetchWhen, - 'ɵɵdeferPrefetchOnIdle': r3.ɵɵdeferPrefetchOnIdle, - 'ɵɵdeferPrefetchOnImmediate': r3.ɵɵdeferPrefetchOnImmediate, - 'ɵɵdeferPrefetchOnTimer': r3.ɵɵdeferPrefetchOnTimer, - 'ɵɵdeferPrefetchOnHover': r3.ɵɵdeferPrefetchOnHover, - 'ɵɵdeferPrefetchOnInteraction': r3.ɵɵdeferPrefetchOnInteraction, - 'ɵɵdeferPrefetchOnViewport': r3.ɵɵdeferPrefetchOnViewport, - 'ɵɵdeferEnableTimerScheduling': r3.ɵɵdeferEnableTimerScheduling, - 'ɵɵrepeater': r3.ɵɵrepeater, - 'ɵɵrepeaterCreate': r3.ɵɵrepeaterCreate, - 'ɵɵrepeaterTrackByIndex': r3.ɵɵrepeaterTrackByIndex, - 'ɵɵrepeaterTrackByIdentity': r3.ɵɵrepeaterTrackByIdentity, - 'ɵɵcomponentInstance': r3.ɵɵcomponentInstance, - 'ɵɵtext': r3.ɵɵtext, - 'ɵɵtextInterpolate': r3.ɵɵtextInterpolate, - 'ɵɵtextInterpolate1': r3.ɵɵtextInterpolate1, - 'ɵɵtextInterpolate2': r3.ɵɵtextInterpolate2, - 'ɵɵtextInterpolate3': r3.ɵɵtextInterpolate3, - 'ɵɵtextInterpolate4': r3.ɵɵtextInterpolate4, - 'ɵɵtextInterpolate5': r3.ɵɵtextInterpolate5, - 'ɵɵtextInterpolate6': r3.ɵɵtextInterpolate6, - 'ɵɵtextInterpolate7': r3.ɵɵtextInterpolate7, - 'ɵɵtextInterpolate8': r3.ɵɵtextInterpolate8, - 'ɵɵtextInterpolateV': r3.ɵɵtextInterpolateV, - 'ɵɵi18n': r3.ɵɵi18n, - 'ɵɵi18nAttributes': r3.ɵɵi18nAttributes, - 'ɵɵi18nExp': r3.ɵɵi18nExp, - 'ɵɵi18nStart': r3.ɵɵi18nStart, - 'ɵɵi18nEnd': r3.ɵɵi18nEnd, - 'ɵɵi18nApply': r3.ɵɵi18nApply, - 'ɵɵi18nPostprocess': r3.ɵɵi18nPostprocess, - 'ɵɵresolveWindow': r3.ɵɵresolveWindow, - 'ɵɵresolveDocument': r3.ɵɵresolveDocument, - 'ɵɵresolveBody': r3.ɵɵresolveBody, - 'ɵɵsetComponentScope': r3.ɵɵsetComponentScope, - 'ɵɵsetNgModuleScope': r3.ɵɵsetNgModuleScope, - 'ɵɵregisterNgModuleType': registerNgModuleType, - 'ɵɵgetComponentDepsFactory': r3.ɵɵgetComponentDepsFactory, - 'ɵsetClassDebugInfo': r3.ɵsetClassDebugInfo, +export const angularCoreEnv: {[name: string]: unknown} = (() => ({ + 'ɵɵattribute': r3.ɵɵattribute, + 'ɵɵattributeInterpolate1': r3.ɵɵattributeInterpolate1, + 'ɵɵattributeInterpolate2': r3.ɵɵattributeInterpolate2, + 'ɵɵattributeInterpolate3': r3.ɵɵattributeInterpolate3, + 'ɵɵattributeInterpolate4': r3.ɵɵattributeInterpolate4, + 'ɵɵattributeInterpolate5': r3.ɵɵattributeInterpolate5, + 'ɵɵattributeInterpolate6': r3.ɵɵattributeInterpolate6, + 'ɵɵattributeInterpolate7': r3.ɵɵattributeInterpolate7, + 'ɵɵattributeInterpolate8': r3.ɵɵattributeInterpolate8, + 'ɵɵattributeInterpolateV': r3.ɵɵattributeInterpolateV, + 'ɵɵdefineComponent': r3.ɵɵdefineComponent, + 'ɵɵdefineDirective': r3.ɵɵdefineDirective, + 'ɵɵdefineInjectable': ɵɵdefineInjectable, + 'ɵɵdefineInjector': ɵɵdefineInjector, + 'ɵɵdefineNgModule': r3.ɵɵdefineNgModule, + 'ɵɵdefinePipe': r3.ɵɵdefinePipe, + 'ɵɵdirectiveInject': r3.ɵɵdirectiveInject, + 'ɵɵgetInheritedFactory': r3.ɵɵgetInheritedFactory, + 'ɵɵinject': ɵɵinject, + 'ɵɵinjectAttribute': r3.ɵɵinjectAttribute, + 'ɵɵinvalidFactory': r3.ɵɵinvalidFactory, + 'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep, + 'ɵɵtemplateRefExtractor': r3.ɵɵtemplateRefExtractor, + 'ɵɵresetView': r3.ɵɵresetView, + 'ɵɵHostDirectivesFeature': r3.ɵɵHostDirectivesFeature, + 'ɵɵNgOnChangesFeature': r3.ɵɵNgOnChangesFeature, + 'ɵɵProvidersFeature': r3.ɵɵProvidersFeature, + 'ɵɵCopyDefinitionFeature': r3.ɵɵCopyDefinitionFeature, + 'ɵɵInheritDefinitionFeature': r3.ɵɵInheritDefinitionFeature, + 'ɵɵInputTransformsFeature': r3.ɵɵInputTransformsFeature, + 'ɵɵStandaloneFeature': r3.ɵɵStandaloneFeature, + 'ɵɵnextContext': r3.ɵɵnextContext, + 'ɵɵnamespaceHTML': r3.ɵɵnamespaceHTML, + 'ɵɵnamespaceMathML': r3.ɵɵnamespaceMathML, + 'ɵɵnamespaceSVG': r3.ɵɵnamespaceSVG, + 'ɵɵenableBindings': r3.ɵɵenableBindings, + 'ɵɵdisableBindings': r3.ɵɵdisableBindings, + 'ɵɵelementStart': r3.ɵɵelementStart, + 'ɵɵelementEnd': r3.ɵɵelementEnd, + 'ɵɵelement': r3.ɵɵelement, + 'ɵɵelementContainerStart': r3.ɵɵelementContainerStart, + 'ɵɵelementContainerEnd': r3.ɵɵelementContainerEnd, + 'ɵɵelementContainer': r3.ɵɵelementContainer, + 'ɵɵpureFunction0': r3.ɵɵpureFunction0, + 'ɵɵpureFunction1': r3.ɵɵpureFunction1, + 'ɵɵpureFunction2': r3.ɵɵpureFunction2, + 'ɵɵpureFunction3': r3.ɵɵpureFunction3, + 'ɵɵpureFunction4': r3.ɵɵpureFunction4, + 'ɵɵpureFunction5': r3.ɵɵpureFunction5, + 'ɵɵpureFunction6': r3.ɵɵpureFunction6, + 'ɵɵpureFunction7': r3.ɵɵpureFunction7, + 'ɵɵpureFunction8': r3.ɵɵpureFunction8, + 'ɵɵpureFunctionV': r3.ɵɵpureFunctionV, + 'ɵɵgetCurrentView': r3.ɵɵgetCurrentView, + 'ɵɵrestoreView': r3.ɵɵrestoreView, + 'ɵɵlistener': r3.ɵɵlistener, + 'ɵɵprojection': r3.ɵɵprojection, + 'ɵɵsyntheticHostProperty': r3.ɵɵsyntheticHostProperty, + 'ɵɵsyntheticHostListener': r3.ɵɵsyntheticHostListener, + 'ɵɵpipeBind1': r3.ɵɵpipeBind1, + 'ɵɵpipeBind2': r3.ɵɵpipeBind2, + 'ɵɵpipeBind3': r3.ɵɵpipeBind3, + 'ɵɵpipeBind4': r3.ɵɵpipeBind4, + 'ɵɵpipeBindV': r3.ɵɵpipeBindV, + 'ɵɵprojectionDef': r3.ɵɵprojectionDef, + 'ɵɵhostProperty': r3.ɵɵhostProperty, + 'ɵɵproperty': r3.ɵɵproperty, + 'ɵɵpropertyInterpolate': r3.ɵɵpropertyInterpolate, + 'ɵɵpropertyInterpolate1': r3.ɵɵpropertyInterpolate1, + 'ɵɵpropertyInterpolate2': r3.ɵɵpropertyInterpolate2, + 'ɵɵpropertyInterpolate3': r3.ɵɵpropertyInterpolate3, + 'ɵɵpropertyInterpolate4': r3.ɵɵpropertyInterpolate4, + 'ɵɵpropertyInterpolate5': r3.ɵɵpropertyInterpolate5, + 'ɵɵpropertyInterpolate6': r3.ɵɵpropertyInterpolate6, + 'ɵɵpropertyInterpolate7': r3.ɵɵpropertyInterpolate7, + 'ɵɵpropertyInterpolate8': r3.ɵɵpropertyInterpolate8, + 'ɵɵpropertyInterpolateV': r3.ɵɵpropertyInterpolateV, + 'ɵɵpipe': r3.ɵɵpipe, + 'ɵɵqueryRefresh': r3.ɵɵqueryRefresh, + 'ɵɵqueryAdvance': r3.ɵɵqueryAdvance, + 'ɵɵviewQuery': r3.ɵɵviewQuery, + 'ɵɵviewQuerySignal': r3.ɵɵviewQuerySignal, + 'ɵɵloadQuery': r3.ɵɵloadQuery, + 'ɵɵcontentQuery': r3.ɵɵcontentQuery, + 'ɵɵcontentQuerySignal': r3.ɵɵcontentQuerySignal, + 'ɵɵreference': r3.ɵɵreference, + 'ɵɵclassMap': r3.ɵɵclassMap, + 'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1, + 'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2, + 'ɵɵclassMapInterpolate3': r3.ɵɵclassMapInterpolate3, + 'ɵɵclassMapInterpolate4': r3.ɵɵclassMapInterpolate4, + 'ɵɵclassMapInterpolate5': r3.ɵɵclassMapInterpolate5, + 'ɵɵclassMapInterpolate6': r3.ɵɵclassMapInterpolate6, + 'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7, + 'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8, + 'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV, + 'ɵɵstyleMap': r3.ɵɵstyleMap, + 'ɵɵstyleMapInterpolate1': r3.ɵɵstyleMapInterpolate1, + 'ɵɵstyleMapInterpolate2': r3.ɵɵstyleMapInterpolate2, + 'ɵɵstyleMapInterpolate3': r3.ɵɵstyleMapInterpolate3, + 'ɵɵstyleMapInterpolate4': r3.ɵɵstyleMapInterpolate4, + 'ɵɵstyleMapInterpolate5': r3.ɵɵstyleMapInterpolate5, + 'ɵɵstyleMapInterpolate6': r3.ɵɵstyleMapInterpolate6, + 'ɵɵstyleMapInterpolate7': r3.ɵɵstyleMapInterpolate7, + 'ɵɵstyleMapInterpolate8': r3.ɵɵstyleMapInterpolate8, + 'ɵɵstyleMapInterpolateV': r3.ɵɵstyleMapInterpolateV, + 'ɵɵstyleProp': r3.ɵɵstyleProp, + 'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1, + 'ɵɵstylePropInterpolate2': r3.ɵɵstylePropInterpolate2, + 'ɵɵstylePropInterpolate3': r3.ɵɵstylePropInterpolate3, + 'ɵɵstylePropInterpolate4': r3.ɵɵstylePropInterpolate4, + 'ɵɵstylePropInterpolate5': r3.ɵɵstylePropInterpolate5, + 'ɵɵstylePropInterpolate6': r3.ɵɵstylePropInterpolate6, + 'ɵɵstylePropInterpolate7': r3.ɵɵstylePropInterpolate7, + 'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8, + 'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV, + 'ɵɵclassProp': r3.ɵɵclassProp, + 'ɵɵadvance': r3.ɵɵadvance, + 'ɵɵtemplate': r3.ɵɵtemplate, + 'ɵɵconditional': r3.ɵɵconditional, + 'ɵɵdefer': r3.ɵɵdefer, + 'ɵɵdeferWhen': r3.ɵɵdeferWhen, + 'ɵɵdeferOnIdle': r3.ɵɵdeferOnIdle, + 'ɵɵdeferOnImmediate': r3.ɵɵdeferOnImmediate, + 'ɵɵdeferOnTimer': r3.ɵɵdeferOnTimer, + 'ɵɵdeferOnHover': r3.ɵɵdeferOnHover, + 'ɵɵdeferOnInteraction': r3.ɵɵdeferOnInteraction, + 'ɵɵdeferOnViewport': r3.ɵɵdeferOnViewport, + 'ɵɵdeferPrefetchWhen': r3.ɵɵdeferPrefetchWhen, + 'ɵɵdeferPrefetchOnIdle': r3.ɵɵdeferPrefetchOnIdle, + 'ɵɵdeferPrefetchOnImmediate': r3.ɵɵdeferPrefetchOnImmediate, + 'ɵɵdeferPrefetchOnTimer': r3.ɵɵdeferPrefetchOnTimer, + 'ɵɵdeferPrefetchOnHover': r3.ɵɵdeferPrefetchOnHover, + 'ɵɵdeferPrefetchOnInteraction': r3.ɵɵdeferPrefetchOnInteraction, + 'ɵɵdeferPrefetchOnViewport': r3.ɵɵdeferPrefetchOnViewport, + 'ɵɵdeferEnableTimerScheduling': r3.ɵɵdeferEnableTimerScheduling, + 'ɵɵrepeater': r3.ɵɵrepeater, + 'ɵɵrepeaterCreate': r3.ɵɵrepeaterCreate, + 'ɵɵrepeaterTrackByIndex': r3.ɵɵrepeaterTrackByIndex, + 'ɵɵrepeaterTrackByIdentity': r3.ɵɵrepeaterTrackByIdentity, + 'ɵɵcomponentInstance': r3.ɵɵcomponentInstance, + 'ɵɵtext': r3.ɵɵtext, + 'ɵɵtextInterpolate': r3.ɵɵtextInterpolate, + 'ɵɵtextInterpolate1': r3.ɵɵtextInterpolate1, + 'ɵɵtextInterpolate2': r3.ɵɵtextInterpolate2, + 'ɵɵtextInterpolate3': r3.ɵɵtextInterpolate3, + 'ɵɵtextInterpolate4': r3.ɵɵtextInterpolate4, + 'ɵɵtextInterpolate5': r3.ɵɵtextInterpolate5, + 'ɵɵtextInterpolate6': r3.ɵɵtextInterpolate6, + 'ɵɵtextInterpolate7': r3.ɵɵtextInterpolate7, + 'ɵɵtextInterpolate8': r3.ɵɵtextInterpolate8, + 'ɵɵtextInterpolateV': r3.ɵɵtextInterpolateV, + 'ɵɵi18n': r3.ɵɵi18n, + 'ɵɵi18nAttributes': r3.ɵɵi18nAttributes, + 'ɵɵi18nExp': r3.ɵɵi18nExp, + 'ɵɵi18nStart': r3.ɵɵi18nStart, + 'ɵɵi18nEnd': r3.ɵɵi18nEnd, + 'ɵɵi18nApply': r3.ɵɵi18nApply, + 'ɵɵi18nPostprocess': r3.ɵɵi18nPostprocess, + 'ɵɵresolveWindow': r3.ɵɵresolveWindow, + 'ɵɵresolveDocument': r3.ɵɵresolveDocument, + 'ɵɵresolveBody': r3.ɵɵresolveBody, + 'ɵɵsetComponentScope': r3.ɵɵsetComponentScope, + 'ɵɵsetNgModuleScope': r3.ɵɵsetNgModuleScope, + 'ɵɵregisterNgModuleType': registerNgModuleType, + 'ɵɵgetComponentDepsFactory': r3.ɵɵgetComponentDepsFactory, + 'ɵsetClassDebugInfo': r3.ɵsetClassDebugInfo, - 'ɵɵsanitizeHtml': sanitization.ɵɵsanitizeHtml, - 'ɵɵsanitizeStyle': sanitization.ɵɵsanitizeStyle, - 'ɵɵsanitizeResourceUrl': sanitization.ɵɵsanitizeResourceUrl, - 'ɵɵsanitizeScript': sanitization.ɵɵsanitizeScript, - 'ɵɵsanitizeUrl': sanitization.ɵɵsanitizeUrl, - 'ɵɵsanitizeUrlOrResourceUrl': sanitization.ɵɵsanitizeUrlOrResourceUrl, - 'ɵɵtrustConstantHtml': sanitization.ɵɵtrustConstantHtml, - 'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl, - 'ɵɵvalidateIframeAttribute': iframe_attrs_validation.ɵɵvalidateIframeAttribute, + 'ɵɵsanitizeHtml': sanitization.ɵɵsanitizeHtml, + 'ɵɵsanitizeStyle': sanitization.ɵɵsanitizeStyle, + 'ɵɵsanitizeResourceUrl': sanitization.ɵɵsanitizeResourceUrl, + 'ɵɵsanitizeScript': sanitization.ɵɵsanitizeScript, + 'ɵɵsanitizeUrl': sanitization.ɵɵsanitizeUrl, + 'ɵɵsanitizeUrlOrResourceUrl': sanitization.ɵɵsanitizeUrlOrResourceUrl, + 'ɵɵtrustConstantHtml': sanitization.ɵɵtrustConstantHtml, + 'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl, + 'ɵɵvalidateIframeAttribute': iframe_attrs_validation.ɵɵvalidateIframeAttribute, - 'forwardRef': forwardRef, - 'resolveForwardRef': resolveForwardRef, + 'forwardRef': forwardRef, + 'resolveForwardRef': resolveForwardRef, - 'ɵɵtwoWayProperty': r3.ɵɵtwoWayProperty, - 'ɵɵtwoWayBindingSet': r3.ɵɵtwoWayBindingSet, - 'ɵɵtwoWayListener': r3.ɵɵtwoWayListener, - }))(); + 'ɵɵtwoWayProperty': r3.ɵɵtwoWayProperty, + 'ɵɵtwoWayBindingSet': r3.ɵɵtwoWayBindingSet, + 'ɵɵtwoWayListener': r3.ɵɵtwoWayListener, +}))(); diff --git a/packages/core/src/render3/jit/jit_options.ts b/packages/core/src/render3/jit/jit_options.ts index a7a862fe6d80a..dca7edc004911 100644 --- a/packages/core/src/render3/jit/jit_options.ts +++ b/packages/core/src/render3/jit/jit_options.ts @@ -12,27 +12,29 @@ export interface JitCompilerOptions { preserveWhitespaces?: boolean; } -let jitOptions: JitCompilerOptions|null = null; +let jitOptions: JitCompilerOptions | null = null; export function setJitOptions(options: JitCompilerOptions): void { if (jitOptions !== null) { if (options.defaultEncapsulation !== jitOptions.defaultEncapsulation) { ngDevMode && - console.error( - 'Provided value for `defaultEncapsulation` can not be changed once it has been set.'); + console.error( + 'Provided value for `defaultEncapsulation` can not be changed once it has been set.', + ); return; } if (options.preserveWhitespaces !== jitOptions.preserveWhitespaces) { ngDevMode && - console.error( - 'Provided value for `preserveWhitespaces` can not be changed once it has been set.'); + console.error( + 'Provided value for `preserveWhitespaces` can not be changed once it has been set.', + ); return; } } jitOptions = options; } -export function getJitOptions(): JitCompilerOptions|null { +export function getJitOptions(): JitCompilerOptions | null { return jitOptions; } diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 370c008fbbae6..ee2d1c7f631c7 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -6,7 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, JitCompilerUsage, R3InjectorMetadataFacade} from '../../compiler/compiler_facade'; +import { + getCompilerFacade, + JitCompilerUsage, + R3InjectorMetadataFacade, +} from '../../compiler/compiler_facade'; import {resolveForwardRef} from '../../di/forward_ref'; import {NG_INJ_DEF} from '../../di/interface/defs'; import {ModuleWithProviders} from '../../di/interface/provider'; @@ -19,7 +23,14 @@ import {NgModuleDef, NgModuleTransitiveScopes, NgModuleType} from '../../metadat import {deepForEach, flatten} from '../../util/array_utils'; import {assertDefined} from '../../util/assert'; import {EMPTY_ARRAY} from '../../util/empty'; -import {GENERATED_COMP_IDS, getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef, isStandalone} from '../definition'; +import { + GENERATED_COMP_IDS, + getComponentDef, + getDirectiveDef, + getNgModuleDef, + getPipeDef, + isStandalone, +} from '../definition'; import {depsTracker, USE_RUNTIME_DEPS_TRACKER_FOR_JIT} from '../deps_tracker/deps_tracker'; import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_MOD_DEF, NG_PIPE_DEF} from '../fields'; import {ComponentDef} from '../interfaces/definition'; @@ -75,7 +86,7 @@ export function flushModuleScopingQueueAsMuchAsPossible() { * an array of declarations, it will recurse to check each declaration in that array * (which may also be arrays). */ -function isResolvedDeclaration(declaration: any[]|Type): boolean { +function isResolvedDeclaration(declaration: any[] | Type): boolean { if (Array.isArray(declaration)) { return declaration.every(isResolvedDeclaration); } @@ -108,8 +119,10 @@ export function compileNgModule(moduleType: Type, ngModule: NgModule = {}): * root. */ export function compileNgModuleDefs( - moduleType: NgModuleType, ngModule: NgModule, - allowDuplicateDeclarationsInRoot: boolean = false): void { + moduleType: NgModuleType, + ngModule: NgModule, + allowDuplicateDeclarationsInRoot: boolean = false, +): void { ngDevMode && assertDefined(moduleType, 'Required value moduleType'); ngDevMode && assertDefined(ngModule, 'Required value ngModule'); const declarations: Type[] = flatten(ngModule.declarations || EMPTY_ARRAY); @@ -123,18 +136,21 @@ export function compileNgModuleDefs( // go into an infinite loop before we've reached the point where we throw all the errors. throw new Error(`'${stringifyForError(moduleType)}' module can't import itself`); } - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'NgModule', + type: moduleType, + }); ngModuleDef = compiler.compileNgModule(angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, { type: moduleType, bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef), declarations: declarations.map(resolveForwardRef), imports: flatten(ngModule.imports || EMPTY_ARRAY) - .map(resolveForwardRef) - .map(expandModuleWithProviders), + .map(resolveForwardRef) + .map(expandModuleWithProviders), exports: flatten(ngModule.exports || EMPTY_ARRAY) - .map(resolveForwardRef) - .map(expandModuleWithProviders), + .map(resolveForwardRef) + .map(expandModuleWithProviders), schemas: ngModule.schemas ? flatten(ngModule.schemas) : null, id: ngModule.id || null, }); @@ -147,15 +163,18 @@ export function compileNgModuleDefs( } } return ngModuleDef; - } + }, }); let ngFactoryDef: any = null; Object.defineProperty(moduleType, NG_FACTORY_DEF, { get: () => { if (ngFactoryDef === null) { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'NgModule', + type: moduleType, + }); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${moduleType.name}/ɵfac.js`, { name: moduleType.name, type: moduleType, @@ -184,10 +203,16 @@ export function compileNgModuleDefs( (ngModule.exports || EMPTY_ARRAY).map(resolveForwardRef), ], }; - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); - ngInjectorDef = - compiler.compileInjector(angularCoreEnv, `ng:///${moduleType.name}/ɵinj.js`, meta); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'NgModule', + type: moduleType, + }); + ngInjectorDef = compiler.compileInjector( + angularCoreEnv, + `ng:///${moduleType.name}/ɵinj.js`, + meta, + ); } return ngInjectorDef; }, @@ -198,14 +223,17 @@ export function compileNgModuleDefs( export function generateStandaloneInDeclarationsError(type: Type, location: string) { const prefix = `Unexpected "${stringifyForError(type)}" found in the "declarations" array of the`; - const suffix = `"${stringifyForError(type)}" is marked as standalone and can't be declared ` + - 'in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?'; + const suffix = + `"${stringifyForError(type)}" is marked as standalone and can't be declared ` + + 'in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?'; return `${prefix} ${location}, ${suffix}`; } function verifySemanticsOfNgModuleDef( - moduleType: NgModuleType, allowDuplicateDeclarationsInRoot: boolean, - importingModule?: NgModuleType): void { + moduleType: NgModuleType, + allowDuplicateDeclarationsInRoot: boolean, + importingModule?: NgModuleType, +): void { if (verifiedNgModule.get(moduleType)) return; // skip verifications of standalone components, directives, and pipes @@ -217,8 +245,9 @@ function verifySemanticsOfNgModuleDef( if (importingModule) { ngModuleDef = getNgModuleDef(moduleType)!; if (!ngModuleDef) { - throw new Error(`Unexpected value '${moduleType.name}' imported by the module '${ - importingModule.name}'. Please add an @NgModule annotation.`); + throw new Error( + `Unexpected value '${moduleType.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`, + ); } } else { ngModuleDef = getNgModuleDef(moduleType, true); @@ -226,10 +255,12 @@ function verifySemanticsOfNgModuleDef( const errors: string[] = []; const declarations = maybeUnwrapFn(ngModuleDef.declarations); const imports = maybeUnwrapFn(ngModuleDef.imports); - flatten(imports).map(unwrapModuleWithProvidersImports).forEach(modOrStandaloneCmpt => { - verifySemanticsOfNgModuleImport(modOrStandaloneCmpt, moduleType); - verifySemanticsOfNgModuleDef(modOrStandaloneCmpt, false, moduleType); - }); + flatten(imports) + .map(unwrapModuleWithProvidersImports) + .forEach((modOrStandaloneCmpt) => { + verifySemanticsOfNgModuleImport(modOrStandaloneCmpt, moduleType); + verifySemanticsOfNgModuleDef(modOrStandaloneCmpt, false, moduleType); + }); const exports = maybeUnwrapFn(ngModuleDef.exports); declarations.forEach(verifyDeclarationsHaveDefinitions); declarations.forEach(verifyDirectivesHaveSelector); @@ -239,12 +270,14 @@ function verifySemanticsOfNgModuleDef( ...flatten(imports.map(computeCombinedExports)).map(resolveForwardRef), ]; exports.forEach(verifyExportsAreDeclaredOrReExported); - declarations.forEach(decl => verifyDeclarationIsUnique(decl, allowDuplicateDeclarationsInRoot)); + declarations.forEach((decl) => verifyDeclarationIsUnique(decl, allowDuplicateDeclarationsInRoot)); const ngModule = getAnnotation(moduleType, 'NgModule'); if (ngModule) { ngModule.imports && - flatten(ngModule.imports).map(unwrapModuleWithProvidersImports).forEach(mod => { + flatten(ngModule.imports) + .map(unwrapModuleWithProvidersImports) + .forEach((mod) => { verifySemanticsOfNgModuleImport(mod, moduleType); verifySemanticsOfNgModuleDef(mod, false, moduleType); }); @@ -261,8 +294,11 @@ function verifySemanticsOfNgModuleDef( type = resolveForwardRef(type); const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type); if (!def) { - errors.push(`Unexpected value '${stringifyForError(type)}' declared by the module '${ - stringifyForError(moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`); + errors.push( + `Unexpected value '${stringifyForError(type)}' declared by the module '${stringifyForError( + moduleType, + )}'. Please add a @Pipe/@Directive/@Component annotation.`, + ); } } @@ -285,15 +321,20 @@ function verifySemanticsOfNgModuleDef( function verifyExportsAreDeclaredOrReExported(type: Type) { type = resolveForwardRef(type); - const kind = getComponentDef(type) && 'component' || getDirectiveDef(type) && 'directive' || - getPipeDef(type) && 'pipe'; + const kind = + (getComponentDef(type) && 'component') || + (getDirectiveDef(type) && 'directive') || + (getPipeDef(type) && 'pipe'); if (kind) { // only checked if we are declared as Component, Directive, or Pipe // Modules don't need to be declared or imported. if (combinedDeclarations.lastIndexOf(type) === -1) { // We are exporting something which we don't explicitly declare or import. - errors.push(`Can't export ${kind} ${stringifyForError(type)} from ${ - stringifyForError(moduleType)} as it was neither declared nor imported!`); + errors.push( + `Can't export ${kind} ${stringifyForError(type)} from ${stringifyForError( + moduleType, + )} as it was neither declared nor imported!`, + ); } } } @@ -305,13 +346,16 @@ function verifySemanticsOfNgModuleDef( if (!suppressErrors) { const modules = [existingModule, moduleType].map(stringifyForError).sort(); errors.push( - `Type ${stringifyForError(type)} is part of the declarations of 2 modules: ${ - modules[0]} and ${modules[1]}! ` + + `Type ${stringifyForError(type)} is part of the declarations of 2 modules: ${ + modules[0] + } and ${modules[1]}! ` + `Please consider moving ${stringifyForError(type)} to a higher module that imports ${ - modules[0]} and ${modules[1]}. ` + - `You can also create a new NgModule that exports and includes ${ - stringifyForError( - type)} then import that NgModule in ${modules[0]} and ${modules[1]}.`); + modules[0] + } and ${modules[1]}. ` + + `You can also create a new NgModule that exports and includes ${stringifyForError( + type, + )} then import that NgModule in ${modules[0]} and ${modules[1]}.`, + ); } } else { // Mark type as having owner. @@ -323,9 +367,11 @@ function verifySemanticsOfNgModuleDef( type = resolveForwardRef(type); const existingModule = ownerNgModule.get(type); if (!existingModule && !isStandalone(type)) { - errors.push(`Component ${ - stringifyForError( - type)} is not part of any NgModule or the module has not been imported into your module.`); + errors.push( + `Component ${stringifyForError( + type, + )} is not part of any NgModule or the module has not been imported into your module.`, + ); } } @@ -338,9 +384,10 @@ function verifySemanticsOfNgModuleDef( // Note: this error should be the same as the // `NGMODULE_BOOTSTRAP_IS_STANDALONE` one in AOT compiler. errors.push( - `The \`${stringifyForError(type)}\` class is a standalone component, which can ` + + `The \`${stringifyForError(type)}\` class is a standalone component, which can ` + `not be used in the \`@NgModule.bootstrap\` array. Use the \`bootstrapApplication\` ` + - `function for bootstrap instead.`); + `function for bootstrap instead.`, + ); } } @@ -349,38 +396,43 @@ function verifySemanticsOfNgModuleDef( const directiveDef = getComponentDef(type) || getDirectiveDef(type); if (directiveDef !== null && !directiveDef.standalone) { - throw new Error(`Unexpected directive '${type.name}' imported by the module '${ - importingModule.name}'. Please add an @NgModule annotation.`); + throw new Error( + `Unexpected directive '${type.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`, + ); } const pipeDef = getPipeDef(type); if (pipeDef !== null && !pipeDef.standalone) { - throw new Error(`Unexpected pipe '${type.name}' imported by the module '${ - importingModule.name}'. Please add an @NgModule annotation.`); + throw new Error( + `Unexpected pipe '${type.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`, + ); } } } -function unwrapModuleWithProvidersImports(typeOrWithProviders: NgModuleType| - {ngModule: NgModuleType}): NgModuleType { +function unwrapModuleWithProvidersImports( + typeOrWithProviders: NgModuleType | {ngModule: NgModuleType}, +): NgModuleType { typeOrWithProviders = resolveForwardRef(typeOrWithProviders); return (typeOrWithProviders as any).ngModule || typeOrWithProviders; } -function getAnnotation(type: any, name: string): T|null { - let annotation: T|null = null; +function getAnnotation(type: any, name: string): T | null { + let annotation: T | null = null; collect(type.__annotations__); collect(type.decorators); return annotation; - function collect(annotations: any[]|null) { + function collect(annotations: any[] | null) { if (annotations) { annotations.forEach(readAnnotation); } } - function readAnnotation( - decorator: {type: {prototype: {ngMetadataName: string}, args: any[]}, args: any}): void { + function readAnnotation(decorator: { + type: {prototype: {ngMetadataName: string}; args: any[]}; + args: any; + }): void { if (!annotation) { const proto = Object.getPrototypeOf(decorator); if (proto.ngMetadataName == name) { @@ -425,15 +477,17 @@ function computeCombinedExports(type: Type): Type[] { return [type]; } - return flatten(maybeUnwrapFn(ngModuleDef.exports).map((type) => { - const ngModuleDef = getNgModuleDef(type); - if (ngModuleDef) { - verifySemanticsOfNgModuleDef(type as any as NgModuleType, false); - return computeCombinedExports(type); - } else { - return type; - } - })); + return flatten( + maybeUnwrapFn(ngModuleDef.exports).map((type) => { + const ngModuleDef = getNgModuleDef(type); + if (ngModuleDef) { + verifySemanticsOfNgModuleDef(type as any as NgModuleType, false); + return computeCombinedExports(type); + } else { + return type; + } + }), + ); } /** @@ -446,17 +500,19 @@ function setScopeOnDeclaredComponents(moduleType: Type, ngModule: NgModule) const transitiveScopes = transitiveScopesFor(moduleType); - declarations.forEach(declaration => { + declarations.forEach((declaration) => { declaration = resolveForwardRef(declaration); if (declaration.hasOwnProperty(NG_COMP_DEF)) { // A `ɵcmp` field exists - go ahead and patch the component directly. - const component = declaration as Type& {ɵcmp: ComponentDef}; + const component = declaration as Type & {ɵcmp: ComponentDef}; const componentDef = getComponentDef(component)!; patchComponentDefWithScope(componentDef, transitiveScopes); } else if ( - !declaration.hasOwnProperty(NG_DIR_DEF) && !declaration.hasOwnProperty(NG_PIPE_DEF)) { + !declaration.hasOwnProperty(NG_DIR_DEF) && + !declaration.hasOwnProperty(NG_PIPE_DEF) + ) { // Set `ngSelectorScope` for future reference when the component compilation finishes. - (declaration as Type& {ngSelectorScope?: any}).ngSelectorScope = moduleType; + (declaration as Type & {ngSelectorScope?: any}).ngSelectorScope = moduleType; } }); } @@ -466,15 +522,17 @@ function setScopeOnDeclaredComponents(moduleType: Type, ngModule: NgModule) * a given module. */ export function patchComponentDefWithScope( - componentDef: ComponentDef, transitiveScopes: NgModuleTransitiveScopes) { + componentDef: ComponentDef, + transitiveScopes: NgModuleTransitiveScopes, +) { componentDef.directiveDefs = () => - Array.from(transitiveScopes.compilation.directives) - .map( - dir => dir.hasOwnProperty(NG_COMP_DEF) ? getComponentDef(dir)! : getDirectiveDef(dir)! - ) - .filter(def => !!def); + Array.from(transitiveScopes.compilation.directives) + .map((dir) => + dir.hasOwnProperty(NG_COMP_DEF) ? getComponentDef(dir)! : getDirectiveDef(dir)!, + ) + .filter((def) => !!def); componentDef.pipeDefs = () => - Array.from(transitiveScopes.compilation.pipes).map(pipe => getPipeDef(pipe)!); + Array.from(transitiveScopes.compilation.pipes).map((pipe) => getPipeDef(pipe)!); componentDef.schemas = transitiveScopes.schemas; // Since we avoid Components/Directives/Pipes recompiling in case there are no overrides, we @@ -568,12 +626,12 @@ export function transitiveScopesForNgModule(moduleType: Type): NgModuleTra // When this module imports another, the imported module's exported directives and pipes are // added to the compilation scope of this module. const importedScope = transitiveScopesFor(imported); - importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry)); - importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry)); + importedScope.exported.directives.forEach((entry) => scopes.compilation.directives.add(entry)); + importedScope.exported.pipes.forEach((entry) => scopes.compilation.pipes.add(entry)); }); - maybeUnwrapFn(def.declarations).forEach(declared => { - const declaredWithDefs = declared as Type& { + maybeUnwrapFn(def.declarations).forEach((declared) => { + const declaredWithDefs = declared as Type & { ɵpipe?: any; }; @@ -588,7 +646,7 @@ export function transitiveScopesForNgModule(moduleType: Type): NgModuleTra }); maybeUnwrapFn(def.exports).forEach((exported: Type) => { - const exportedType = exported as Type& { + const exportedType = exported as Type & { // Components, Directives, NgModules, and Pipes can all be exported. ɵcmp?: any; ɵdir?: any; @@ -602,11 +660,11 @@ export function transitiveScopesForNgModule(moduleType: Type): NgModuleTra // When this module exports another, the exported module's exported directives and pipes are // added to both the compilation and exported scopes of this module. const exportedScope = transitiveScopesFor(exportedType); - exportedScope.exported.directives.forEach(entry => { + exportedScope.exported.directives.forEach((entry) => { scopes.compilation.directives.add(entry); scopes.exported.directives.add(entry); }); - exportedScope.exported.pipes.forEach(entry => { + exportedScope.exported.pipes.forEach((entry) => { scopes.compilation.pipes.add(entry); scopes.exported.pipes.add(entry); }); @@ -621,7 +679,7 @@ export function transitiveScopesForNgModule(moduleType: Type): NgModuleTra return scopes; } -function expandModuleWithProviders(value: Type|ModuleWithProviders<{}>): Type { +function expandModuleWithProviders(value: Type | ModuleWithProviders<{}>): Type { if (isModuleWithProviders(value)) { return value.ngModule; } diff --git a/packages/core/src/render3/jit/partial.ts b/packages/core/src/render3/jit/partial.ts index 58f132487c324..e049ddaee0522 100644 --- a/packages/core/src/render3/jit/partial.ts +++ b/packages/core/src/render3/jit/partial.ts @@ -6,7 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {FactoryTarget, getCompilerFacade, JitCompilerUsage, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade} from '../../compiler/compiler_facade'; +import { + FactoryTarget, + getCompilerFacade, + JitCompilerUsage, + R3DeclareComponentFacade, + R3DeclareDirectiveFacade, + R3DeclareFactoryFacade, + R3DeclareInjectableFacade, + R3DeclareInjectorFacade, + R3DeclareNgModuleFacade, + R3DeclarePipeFacade, +} from '../../compiler/compiler_facade'; import {Type} from '../../interface/type'; import {setClassMetadata, setClassMetadataAsync} from '../metadata'; @@ -18,10 +29,16 @@ import {angularCoreEnv} from './environment'; * @codeGenApi */ export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'directive', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'directive', + type: decl.type, + }); return compiler.compileDirectiveDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵfac.js`, + decl, + ); } /** @@ -30,12 +47,17 @@ export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown * @codeGenApi */ export function ɵɵngDeclareClassMetadata(decl: { - type: Type; decorators: any[]; + type: Type; + decorators: any[]; ctorParameters?: () => any[]; propDecorators?: {[field: string]: any}; }): void { setClassMetadata( - decl.type, decl.decorators, decl.ctorParameters ?? null, decl.propDecorators ?? null); + decl.type, + decl.decorators, + decl.ctorParameters ?? null, + decl.propDecorators ?? null, + ); } /** @@ -44,13 +66,13 @@ export function ɵɵngDeclareClassMetadata(decl: { * @codeGenApi */ export function ɵɵngDeclareClassMetadataAsync(decl: { - type: Type, - resolveDeferredDeps: () => Promise>[], + type: Type; + resolveDeferredDeps: () => Promise>[]; resolveMetadata: (...types: Type[]) => { decorators: any[]; - ctorParameters: (() => any[])|null; - propDecorators: ({[field: string]: any})|null; - }, + ctorParameters: (() => any[]) | null; + propDecorators: {[field: string]: any} | null; + }; }): void { setClassMetadataAsync(decl.type, decl.resolveDeferredDeps, (...types: Type[]) => { const meta = decl.resolveMetadata(...types); @@ -64,10 +86,16 @@ export function ɵɵngDeclareClassMetadataAsync(decl: { * @codeGenApi */ export function ɵɵngDeclareComponent(decl: R3DeclareComponentFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'component', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'component', + type: decl.type, + }); return compiler.compileComponentDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵcmp.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵcmp.js`, + decl, + ); } /** @@ -79,10 +107,13 @@ export function ɵɵngDeclareFactory(decl: R3DeclareFactoryFacade): unknown { const compiler = getCompilerFacade({ usage: JitCompilerUsage.PartialDeclaration, kind: getFactoryKind(decl.target), - type: decl.type + type: decl.type, }); return compiler.compileFactoryDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵfac.js`, + decl, + ); } function getFactoryKind(target: FactoryTarget) { @@ -106,10 +137,16 @@ function getFactoryKind(target: FactoryTarget) { * @codeGenApi */ export function ɵɵngDeclareInjectable(decl: R3DeclareInjectableFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'injectable', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'injectable', + type: decl.type, + }); return compiler.compileInjectableDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵprov.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵprov.js`, + decl, + ); } /** @@ -123,10 +160,16 @@ export {FactoryTarget} from '../../compiler/compiler_facade'; * @codeGenApi */ export function ɵɵngDeclareInjector(decl: R3DeclareInjectorFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'NgModule', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'NgModule', + type: decl.type, + }); return compiler.compileInjectorDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵinj.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵinj.js`, + decl, + ); } /** @@ -135,10 +178,16 @@ export function ɵɵngDeclareInjector(decl: R3DeclareInjectorFacade): unknown { * @codeGenApi */ export function ɵɵngDeclareNgModule(decl: R3DeclareNgModuleFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'NgModule', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'NgModule', + type: decl.type, + }); return compiler.compileNgModuleDeclaration( - angularCoreEnv, `ng:///${decl.type.name}/ɵmod.js`, decl); + angularCoreEnv, + `ng:///${decl.type.name}/ɵmod.js`, + decl, + ); } /** @@ -147,7 +196,10 @@ export function ɵɵngDeclareNgModule(decl: R3DeclareNgModuleFacade): unknown { * @codeGenApi */ export function ɵɵngDeclarePipe(decl: R3DeclarePipeFacade): unknown { - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.PartialDeclaration, kind: 'pipe', type: decl.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: 'pipe', + type: decl.type, + }); return compiler.compilePipeDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵpipe.js`, decl); } diff --git a/packages/core/src/render3/jit/pipe.ts b/packages/core/src/render3/jit/pipe.ts index c5dc68001ba69..cb244ead1bcb8 100644 --- a/packages/core/src/render3/jit/pipe.ts +++ b/packages/core/src/render3/jit/pipe.ts @@ -6,7 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, JitCompilerUsage, R3PipeMetadataFacade} from '../../compiler/compiler_facade'; +import { + getCompilerFacade, + JitCompilerUsage, + R3PipeMetadataFacade, +} from '../../compiler/compiler_facade'; import {reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; import {Pipe} from '../../metadata/directives'; @@ -22,14 +26,17 @@ export function compilePipe(type: Type, meta: Pipe): void { get: () => { if (ngFactoryDef === null) { const metadata = getPipeMetadata(type, meta); - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.Decorator, kind: 'pipe', type: metadata.type}); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'pipe', + type: metadata.type, + }); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${metadata.name}/ɵfac.js`, { name: metadata.name, type: metadata.type, typeArgumentCount: 0, deps: reflectDependencies(type), - target: compiler.FactoryTarget.Pipe + target: compiler.FactoryTarget.Pipe, }); } return ngFactoryDef; @@ -42,10 +49,16 @@ export function compilePipe(type: Type, meta: Pipe): void { get: () => { if (ngPipeDef === null) { const metadata = getPipeMetadata(type, meta); - const compiler = getCompilerFacade( - {usage: JitCompilerUsage.Decorator, kind: 'pipe', type: metadata.type}); - ngPipeDef = - compiler.compilePipe(angularCoreEnv, `ng:///${metadata.name}/ɵpipe.js`, metadata); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'pipe', + type: metadata.type, + }); + ngPipeDef = compiler.compilePipe( + angularCoreEnv, + `ng:///${metadata.name}/ɵpipe.js`, + metadata, + ); } return ngPipeDef; }, diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index b844b16c4f62f..ef3220819ac7e 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -18,7 +18,7 @@ export function isModuleWithProviders(value: any): value is ModuleWithProviders< return (value as {ngModule?: any}).ngModule !== undefined; } -export function isNgModule(value: Type): value is Type&{ɵmod: NgModuleDef} { +export function isNgModule(value: Type): value is Type & {ɵmod: NgModuleDef} { return !!getNgModuleDef(value); } @@ -45,9 +45,13 @@ export function verifyStandaloneImport(depType: Type, importingType: Ty if (isForwardRef(depType)) { depType = resolveForwardRef(depType); if (!depType) { - throw new Error(`Expected forwardRef function, imported from "${ - stringifyForError(importingType)}", to return a standalone entity or NgModule but got "${ - stringifyForError(depType) || depType}".`); + throw new Error( + `Expected forwardRef function, imported from "${stringifyForError( + importingType, + )}", to return a standalone entity or NgModule but got "${ + stringifyForError(depType) || depType + }".`, + ); } } @@ -56,21 +60,28 @@ export function verifyStandaloneImport(depType: Type, importingType: Ty if (def != null) { // if a component, directive or pipe is imported make sure that it is standalone if (!def.standalone) { - throw new Error(`The "${stringifyForError(depType)}" ${ - getDependencyTypeForError(depType)}, imported from "${ - stringifyForError( - importingType)}", is not standalone. Did you forget to add the standalone: true flag?`); + throw new Error( + `The "${stringifyForError(depType)}" ${getDependencyTypeForError( + depType, + )}, imported from "${stringifyForError( + importingType, + )}", is not standalone. Did you forget to add the standalone: true flag?`, + ); } } else { // it can be either a module with provider or an unknown (not annotated) type if (isModuleWithProviders(depType)) { - throw new Error(`A module with providers was imported from "${ - stringifyForError( - importingType)}". Modules with providers are not supported in standalone components imports.`); + throw new Error( + `A module with providers was imported from "${stringifyForError( + importingType, + )}". Modules with providers are not supported in standalone components imports.`, + ); } else { - throw new Error(`The "${stringifyForError(depType)}" type, imported from "${ - stringifyForError( - importingType)}", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?`); + throw new Error( + `The "${stringifyForError(depType)}" type, imported from "${stringifyForError( + importingType, + )}", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?`, + ); } } } diff --git a/packages/core/src/render3/list_reconciliation.ts b/packages/core/src/render3/list_reconciliation.ts index 250aab0a6c2a8..09c6509897e1d 100644 --- a/packages/core/src/render3/list_reconciliation.ts +++ b/packages/core/src/render3/list_reconciliation.ts @@ -51,8 +51,12 @@ export abstract class LiveCollection { } function valuesMatching( - liveIdx: number, liveValue: V, newIdx: number, newValue: V, - trackBy: TrackByFunction): number { + liveIdx: number, + liveValue: V, + newIdx: number, + newValue: V, + trackBy: TrackByFunction, +): number { if (liveIdx === newIdx && Object.is(liveValue, newValue)) { // matching and no value identity to update return 1; @@ -98,10 +102,12 @@ function recordDuplicateKeys(keyToIdx: Map>, key: unknown, * incoming collection; */ export function reconcile( - liveCollection: LiveCollection, newCollection: Iterable|undefined|null, - trackByFn: TrackByFunction): void { - let detachedItems: UniqueValueMultiKeyMap|undefined = undefined; - let liveKeysInTheFuture: Set|undefined = undefined; + liveCollection: LiveCollection, + newCollection: Iterable | undefined | null, + trackByFn: TrackByFunction, +): void { + let detachedItems: UniqueValueMultiKeyMap | undefined = undefined; + let liveKeysInTheFuture: Set | undefined = undefined; let liveStartIdx = 0; let liveEndIdx = liveCollection.length - 1; @@ -120,8 +126,13 @@ export function reconcile( recordDuplicateKeys(duplicateKeys!, trackByFn(liveStartIdx, newStartValue), liveStartIdx); } - const isStartMatching = - valuesMatching(liveStartIdx, liveStartValue, liveStartIdx, newStartValue, trackByFn); + const isStartMatching = valuesMatching( + liveStartIdx, + liveStartValue, + liveStartIdx, + newStartValue, + trackByFn, + ); if (isStartMatching !== 0) { if (isStartMatching < 0) { liveCollection.updateValue(liveStartIdx, newStartValue); @@ -139,8 +150,13 @@ export function reconcile( recordDuplicateKeys(duplicateKeys!, trackByFn(newEndIdx, newEndValue), newEndIdx); } - const isEndMatching = - valuesMatching(liveEndIdx, liveEndValue, newEndIdx, newEndValue, trackByFn); + const isEndMatching = valuesMatching( + liveEndIdx, + liveEndValue, + newEndIdx, + newEndValue, + trackByFn, + ); if (isEndMatching !== 0) { if (isEndMatching < 0) { liveCollection.updateValue(liveEndIdx, newEndValue); @@ -175,8 +191,12 @@ export function reconcile( // Fallback to the slow path: we need to learn more about the content of the live and new // collections. detachedItems ??= new UniqueValueMultiKeyMap(); - liveKeysInTheFuture ??= - initLiveItemsInTheFuture(liveCollection, liveStartIdx, liveEndIdx, trackByFn); + liveKeysInTheFuture ??= initLiveItemsInTheFuture( + liveCollection, + liveStartIdx, + liveEndIdx, + trackByFn, + ); // Check if I'm inserting a previously detached item: if so, attach it here if (attachPreviouslyDetached(liveCollection, detachedItems, liveStartIdx, newStartKey)) { @@ -202,10 +222,14 @@ export function reconcile( // - more items in the new collection => insert while (liveStartIdx <= newEndIdx) { createOrAttach( - liveCollection, detachedItems, trackByFn, liveStartIdx, newCollection[liveStartIdx]); + liveCollection, + detachedItems, + trackByFn, + liveStartIdx, + newCollection[liveStartIdx], + ); liveStartIdx++; } - } else if (newCollection != null) { // iterable - immediately fallback to the slow path const newCollectionIterator = newCollection[Symbol.iterator](); @@ -218,8 +242,13 @@ export function reconcile( recordDuplicateKeys(duplicateKeys!, trackByFn(liveStartIdx, newValue), liveStartIdx); } - const isStartMatching = - valuesMatching(liveStartIdx, liveValue, liveStartIdx, newValue, trackByFn); + const isStartMatching = valuesMatching( + liveStartIdx, + liveValue, + liveStartIdx, + newValue, + trackByFn, + ); if (isStartMatching !== 0) { // found a match - move on, but update value if (isStartMatching < 0) { @@ -229,8 +258,12 @@ export function reconcile( newIterationResult = newCollectionIterator.next(); } else { detachedItems ??= new UniqueValueMultiKeyMap(); - liveKeysInTheFuture ??= - initLiveItemsInTheFuture(liveCollection, liveStartIdx, liveEndIdx, trackByFn); + liveKeysInTheFuture ??= initLiveItemsInTheFuture( + liveCollection, + liveStartIdx, + liveEndIdx, + trackByFn, + ); // Check if I'm inserting a previously detached item: if so, attach it here const newKey = trackByFn(liveStartIdx, newValue); @@ -257,8 +290,12 @@ export function reconcile( // previously detached one while (!newIterationResult.done) { createOrAttach( - liveCollection, detachedItems, trackByFn, liveCollection.length, - newIterationResult.value); + liveCollection, + detachedItems, + trackByFn, + liveCollection.length, + newIterationResult.value, + ); newIterationResult = newCollectionIterator.next(); } } @@ -269,9 +306,8 @@ export function reconcile( liveCollection.destroy(liveCollection.detach(liveEndIdx--)); } - // - destroy items that were detached but never attached again. - detachedItems?.forEach(item => { + detachedItems?.forEach((item) => { liveCollection.destroy(item); }); @@ -283,17 +319,21 @@ export function reconcile( const idx = [...idxSet].sort((a, b) => a - b); for (let i = 1; i < idx.length; i++) { duplicatedKeysMsg.push( - `key "${stringifyForError(key)}" at index "${idx[i - 1]}" and "${idx[i]}"`); + `key "${stringifyForError(key)}" at index "${idx[i - 1]}" and "${idx[i]}"`, + ); } } } if (duplicatedKeysMsg.length > 0) { const message = formatRuntimeError( - RuntimeErrorCode.LOOP_TRACK_DUPLICATE_KEYS, - 'The provided track expression resulted in duplicated keys for a given collection. ' + - 'Adjust the tracking expression such that it uniquely identifies all the items in the collection. ' + - 'Duplicated keys were: \n' + duplicatedKeysMsg.join(', \n') + '.'); + RuntimeErrorCode.LOOP_TRACK_DUPLICATE_KEYS, + 'The provided track expression resulted in duplicated keys for a given collection. ' + + 'Adjust the tracking expression such that it uniquely identifies all the items in the collection. ' + + 'Duplicated keys were: \n' + + duplicatedKeysMsg.join(', \n') + + '.', + ); // tslint:disable-next-line:no-console console.warn(message); @@ -302,9 +342,11 @@ export function reconcile( } function attachPreviouslyDetached( - prevCollection: LiveCollection, - detachedItems: UniqueValueMultiKeyMap|undefined, index: number, - key: unknown): boolean { + prevCollection: LiveCollection, + detachedItems: UniqueValueMultiKeyMap | undefined, + index: number, + key: unknown, +): boolean { if (detachedItems !== undefined && detachedItems.has(key)) { prevCollection.attach(index, detachedItems.get(key)!); detachedItems.delete(key); @@ -314,9 +356,12 @@ function attachPreviouslyDetached( } function createOrAttach( - liveCollection: LiveCollection, - detachedItems: UniqueValueMultiKeyMap|undefined, - trackByFn: TrackByFunction, index: number, value: V) { + liveCollection: LiveCollection, + detachedItems: UniqueValueMultiKeyMap | undefined, + trackByFn: TrackByFunction, + index: number, + value: V, +) { if (!attachPreviouslyDetached(liveCollection, detachedItems, index, trackByFn(index, value))) { const newItem = liveCollection.create(index, value); liveCollection.attach(index, newItem); @@ -326,8 +371,11 @@ function createOrAttach( } function initLiveItemsInTheFuture( - liveCollection: LiveCollection, start: number, end: number, - trackByFn: TrackByFunction): Set { + liveCollection: LiveCollection, + start: number, + end: number, + trackByFn: TrackByFunction, +): Set { const keys = new Set(); for (let i = start; i <= end; i++) { keys.add(trackByFn(i, liveCollection.at(i))); @@ -353,7 +401,7 @@ export class UniqueValueMultiKeyMap { // A map that acts as a linked list of values - each value maps to the next value in this "linked // list" (this only works if values are unique). Allocated lazily to avoid memory consumption when // there are no duplicated values. - private _vMap: Map|undefined = undefined; + private _vMap: Map | undefined = undefined; has(key: K): boolean { return this.kvMap.has(key); @@ -373,7 +421,7 @@ export class UniqueValueMultiKeyMap { return true; } - get(key: K): V|undefined { + get(key: K): V | undefined { return this.kvMap.get(key); } @@ -381,8 +429,7 @@ export class UniqueValueMultiKeyMap { if (this.kvMap.has(key)) { let prevValue = this.kvMap.get(key)!; ngDevMode && - assertNotSame( - prevValue, value, `Detected a duplicated value ${value} for the key ${key}`); + assertNotSame(prevValue, value, `Detected a duplicated value ${value} for the key ${key}`); if (this._vMap === undefined) { this._vMap = new Map(); diff --git a/packages/core/src/render3/local_compilation.ts b/packages/core/src/render3/local_compilation.ts index c03d5abb118cf..aa07a6acc9e53 100644 --- a/packages/core/src/render3/local_compilation.ts +++ b/packages/core/src/render3/local_compilation.ts @@ -7,18 +7,24 @@ */ import {depsTracker} from './deps_tracker/deps_tracker'; -import {ComponentType, DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition'; +import { + ComponentType, + DependencyTypeList, + RawScopeInfoFromDecorator, +} from './interfaces/definition'; export function ɵɵgetComponentDepsFactory( - type: ComponentType, rawImports?: RawScopeInfoFromDecorator[]): () => DependencyTypeList { + type: ComponentType, + rawImports?: RawScopeInfoFromDecorator[], +): () => DependencyTypeList { return () => { try { return depsTracker.getComponentDependencies(type, rawImports).dependencies; } catch (e) { console.error( - `Computing dependencies in local compilation mode for the component "${ - type.name}" failed with the exception:`, - e); + `Computing dependencies in local compilation mode for the component "${type.name}" failed with the exception:`, + e, + ); throw e; } }; diff --git a/packages/core/src/render3/metadata.ts b/packages/core/src/render3/metadata.ts index 48935849f4b28..c2f9649d58d11 100644 --- a/packages/core/src/render3/metadata.ts +++ b/packages/core/src/render3/metadata.ts @@ -27,9 +27,10 @@ const ASYNC_COMPONENT_METADATA_FN = '__ngAsyncComponentMetadataFn__'; * to a function that applies component metadata after resolving defer-loadable * dependencies. Otherwise - this function returns `null`. */ -export function getAsyncClassMetadataFn(type: Type): (() => Promise>>)| - null { - const componentClass = type as any; // cast to `any`, so that we can read a monkey-patched field +export function getAsyncClassMetadataFn( + type: Type, +): (() => Promise>>) | null { + const componentClass = type as any; // cast to `any`, so that we can read a monkey-patched field return componentClass[ASYNC_COMPONENT_METADATA_FN] ?? null; } @@ -42,18 +43,20 @@ export function getAsyncClassMetadataFn(type: Type): (() => Promise, dependencyLoaderFn: () => Array>>, - metadataSetterFn: (...types: Type[]) => void): () => Promise>> { - const componentClass = type as any; // cast to `any`, so that we can monkey-patch it + type: Type, + dependencyLoaderFn: () => Array>>, + metadataSetterFn: (...types: Type[]) => void, +): () => Promise>> { + const componentClass = type as any; // cast to `any`, so that we can monkey-patch it componentClass[ASYNC_COMPONENT_METADATA_FN] = () => - Promise.all(dependencyLoaderFn()).then(dependencies => { - metadataSetterFn(...dependencies); - // Metadata is now set, reset field value to indicate that this component - // can by used/compiled synchronously. - componentClass[ASYNC_COMPONENT_METADATA_FN] = null; + Promise.all(dependencyLoaderFn()).then((dependencies) => { + metadataSetterFn(...dependencies); + // Metadata is now set, reset field value to indicate that this component + // can by used/compiled synchronously. + componentClass[ASYNC_COMPONENT_METADATA_FN] = null; - return dependencies; - }); + return dependencies; + }); return componentClass[ASYNC_COMPONENT_METADATA_FN]; } @@ -67,34 +70,37 @@ export function setClassMetadataAsync( * being tree-shaken away during production builds. */ export function setClassMetadata( - type: Type, decorators: any[]|null, ctorParameters: (() => any[])|null, - propDecorators: {[field: string]: any}|null): void { + type: Type, + decorators: any[] | null, + ctorParameters: (() => any[]) | null, + propDecorators: {[field: string]: any} | null, +): void { return noSideEffects(() => { - const clazz = type as TypeWithMetadata; + const clazz = type as TypeWithMetadata; - if (decorators !== null) { - if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) { - clazz.decorators.push(...decorators); - } else { - clazz.decorators = decorators; - } - } - if (ctorParameters !== null) { - // Rather than merging, clobber the existing parameters. If other projects exist which - // use tsickle-style annotations and reflect over them in the same way, this could - // cause issues, but that is vanishingly unlikely. - clazz.ctorParameters = ctorParameters; - } - if (propDecorators !== null) { - // The property decorator objects are merged as it is possible different fields have - // different decorator types. Decorators on individual fields are not merged, as it's - // also incredibly unlikely that a field will be decorated both with an Angular - // decorator and a non-Angular decorator that's also been downleveled. - if (clazz.hasOwnProperty('propDecorators') && clazz.propDecorators !== undefined) { - clazz.propDecorators = {...clazz.propDecorators, ...propDecorators}; - } else { - clazz.propDecorators = propDecorators; - } - } - }) as never; + if (decorators !== null) { + if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) { + clazz.decorators.push(...decorators); + } else { + clazz.decorators = decorators; + } + } + if (ctorParameters !== null) { + // Rather than merging, clobber the existing parameters. If other projects exist which + // use tsickle-style annotations and reflect over them in the same way, this could + // cause issues, but that is vanishingly unlikely. + clazz.ctorParameters = ctorParameters; + } + if (propDecorators !== null) { + // The property decorator objects are merged as it is possible different fields have + // different decorator types. Decorators on individual fields are not merged, as it's + // also incredibly unlikely that a field will be decorated both with an Angular + // decorator and a non-Angular decorator that's also been downleveled. + if (clazz.hasOwnProperty('propDecorators') && clazz.propDecorators !== undefined) { + clazz.propDecorators = {...clazz.propDecorators, ...propDecorators}; + } else { + clazz.propDecorators = propDecorators; + } + } + }) as never; } diff --git a/packages/core/src/render3/ng_module_ref.ts b/packages/core/src/render3/ng_module_ref.ts index 4bc4cb2bb881b..1adcc753a1317 100644 --- a/packages/core/src/render3/ng_module_ref.ts +++ b/packages/core/src/render3/ng_module_ref.ts @@ -12,7 +12,11 @@ import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/pr import {EnvironmentInjector, getNullInjector, R3Injector} from '../di/r3_injector'; import {Type} from '../interface/type'; import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver'; -import {InternalNgModuleRef, NgModuleFactory as viewEngine_NgModuleFactory, NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory'; +import { + InternalNgModuleRef, + NgModuleFactory as viewEngine_NgModuleFactory, + NgModuleRef as viewEngine_NgModuleRef, +} from '../linker/ng_module_factory'; import {assertDefined} from '../util/assert'; import {stringify} from '../util/stringify'; @@ -31,7 +35,9 @@ import {maybeUnwrapFn} from './util/misc_utils'; * @publicApi */ export function createNgModule( - ngModule: Type, parentInjector?: Injector): viewEngine_NgModuleRef { + ngModule: Type, + parentInjector?: Injector, +): viewEngine_NgModuleRef { return new NgModuleRef(ngModule, parentInjector ?? null, []); } @@ -48,7 +54,7 @@ export class NgModuleRef extends viewEngine_NgModuleRef implements Interna // tslint:disable-next-line:require-internal-with-underscore _r3Injector: R3Injector; override instance: T; - destroyCbs: (() => void)[]|null = []; + destroyCbs: (() => void)[] | null = []; // When bootstrapping a module we have a dependency graph that looks like this: // ApplicationRef -> ComponentFactoryResolver -> NgModuleRef. The problem is that if the @@ -57,28 +63,36 @@ export class NgModuleRef extends viewEngine_NgModuleRef implements Interna // exist yet. We work around the issue by creating the ComponentFactoryResolver ourselves // and providing it, rather than letting the injector resolve it. override readonly componentFactoryResolver: ComponentFactoryResolver = - new ComponentFactoryResolver(this); + new ComponentFactoryResolver(this); constructor( - ngModuleType: Type, public _parent: Injector|null, additionalProviders: StaticProvider[]) { + ngModuleType: Type, + public _parent: Injector | null, + additionalProviders: StaticProvider[], + ) { super(); const ngModuleDef = getNgModuleDef(ngModuleType); ngDevMode && - assertDefined( - ngModuleDef, - `NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`); + assertDefined( + ngModuleDef, + `NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`, + ); this._bootstrapComponents = maybeUnwrapFn(ngModuleDef!.bootstrap); this._r3Injector = createInjectorWithoutInjectorInstances( - ngModuleType, _parent, - [ - {provide: viewEngine_NgModuleRef, useValue: this}, { - provide: viewEngine_ComponentFactoryResolver, - useValue: this.componentFactoryResolver - }, - ...additionalProviders - ], - stringify(ngModuleType), new Set(['environment'])) as R3Injector; + ngModuleType, + _parent, + [ + {provide: viewEngine_NgModuleRef, useValue: this}, + { + provide: viewEngine_ComponentFactoryResolver, + useValue: this.componentFactoryResolver, + }, + ...additionalProviders, + ], + stringify(ngModuleType), + new Set(['environment']), + ) as R3Injector; // We need to resolve the injector types separately from the injector creation, because // the module might be trying to use this ref in its constructor for DI which will cause a @@ -95,7 +109,7 @@ export class NgModuleRef extends viewEngine_NgModuleRef implements Interna ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed'); const injector = this._r3Injector; !injector.destroyed && injector.destroy(); - this.destroyCbs!.forEach(fn => fn()); + this.destroyCbs!.forEach((fn) => fn()); this.destroyCbs = null; } override onDestroy(callback: () => void): void { @@ -109,37 +123,42 @@ export class NgModuleFactory extends viewEngine_NgModuleFactory { super(); } - override create(parentInjector: Injector|null): viewEngine_NgModuleRef { + override create(parentInjector: Injector | null): viewEngine_NgModuleRef { return new NgModuleRef(this.moduleType, parentInjector, []); } } export function createNgModuleRefWithProviders( - moduleType: Type, parentInjector: Injector|null, - additionalProviders: StaticProvider[]): InternalNgModuleRef { + moduleType: Type, + parentInjector: Injector | null, + additionalProviders: StaticProvider[], +): InternalNgModuleRef { return new NgModuleRef(moduleType, parentInjector, additionalProviders); } export class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef { override readonly injector: R3Injector; override readonly componentFactoryResolver: ComponentFactoryResolver = - new ComponentFactoryResolver(this); + new ComponentFactoryResolver(this); override readonly instance = null; constructor(config: { - providers: Array, - parent: EnvironmentInjector|null, - debugName: string|null, - runEnvironmentInitializers: boolean + providers: Array; + parent: EnvironmentInjector | null; + debugName: string | null; + runEnvironmentInitializers: boolean; }) { super(); const injector = new R3Injector( - [ - ...config.providers, - {provide: viewEngine_NgModuleRef, useValue: this}, - {provide: viewEngine_ComponentFactoryResolver, useValue: this.componentFactoryResolver}, - ], - config.parent || getNullInjector(), config.debugName, new Set(['environment'])); + [ + ...config.providers, + {provide: viewEngine_NgModuleRef, useValue: this}, + {provide: viewEngine_ComponentFactoryResolver, useValue: this.componentFactoryResolver}, + ], + config.parent || getNullInjector(), + config.debugName, + new Set(['environment']), + ); this.injector = injector; if (config.runEnvironmentInitializers) { injector.resolveInjectorInitializers(); @@ -166,9 +185,15 @@ export class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef * @publicApi */ export function createEnvironmentInjector( - providers: Array, parent: EnvironmentInjector, - debugName: string|null = null): EnvironmentInjector { - const adapter = new EnvironmentNgModuleRefAdapter( - {providers, parent, debugName, runEnvironmentInitializers: true}); + providers: Array, + parent: EnvironmentInjector, + debugName: string | null = null, +): EnvironmentInjector { + const adapter = new EnvironmentNgModuleRefAdapter({ + providers, + parent, + debugName, + runEnvironmentInitializers: true, + }); return adapter.injector; } diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index 41dbccca47ab0..a63366db8e20d 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -10,25 +10,37 @@ import {assertDefined, throwError} from '../util/assert'; import {TNode, TNodeType, toTNodeTypeAsString} from './interfaces/node'; export function assertTNodeType( - tNode: TNode|null, expectedTypes: TNodeType, message?: string): void { + tNode: TNode | null, + expectedTypes: TNodeType, + message?: string, +): void { assertDefined(tNode, 'should be called with a TNode'); if ((tNode.type & expectedTypes) === 0) { throwError( - message || - `Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${ - toTNodeTypeAsString(tNode.type)}.`); + message || + `Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${toTNodeTypeAsString( + tNode.type, + )}.`, + ); } } export function assertPureTNodeType(type: TNodeType) { - if (!(type === TNodeType.Element || // - type === TNodeType.Text || // - type === TNodeType.Container || // - type === TNodeType.ElementContainer || // - type === TNodeType.Icu || // - type === TNodeType.Projection || // - type === TNodeType.Placeholder)) { - throwError(`Expected TNodeType to have only a single type selected, but got ${ - toTNodeTypeAsString(type)}.`); + if ( + !( + type === TNodeType.Element || // + type === TNodeType.Text || // + type === TNodeType.Container || // + type === TNodeType.ElementContainer || // + type === TNodeType.Icu || // + type === TNodeType.Projection || // + type === TNodeType.Placeholder + ) + ) { + throwError( + `Expected TNodeType to have only a single type selected, but got ${toTNodeTypeAsString( + type, + )}.`, + ); } } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 0ff1c8b1eaaaf..efe09d6c77992 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -6,32 +6,90 @@ * found in the LICENSE file at https://angular.io/license */ -import {consumerDestroy, getActiveConsumer, setActiveConsumer} from '@angular/core/primitives/signals'; +import { + consumerDestroy, + getActiveConsumer, + setActiveConsumer, +} from '@angular/core/primitives/signals'; import {NotificationSource} from '../change_detection/scheduling/zoneless_scheduling'; import {hasInSkipHydrationBlockFlag} from '../hydration/skip_hydration'; import {ViewEncapsulation} from '../metadata/view'; import {RendererStyleFlags2} from '../render/api_flags'; import {addToArray, removeFromArray} from '../util/array_utils'; -import {assertDefined, assertEqual, assertFunction, assertNotReactive, assertNumber, assertString} from '../util/assert'; +import { + assertDefined, + assertEqual, + assertFunction, + assertNotReactive, + assertNumber, + assertString, +} from '../util/assert'; import {escapeCommentText} from '../util/dom'; -import {assertLContainer, assertLView, assertParentView, assertProjectionSlots, assertTNodeForLView} from './assert'; +import { + assertLContainer, + assertLView, + assertParentView, + assertProjectionSlots, + assertTNodeForLView, +} from './assert'; import {attachPatchData} from './context_discovery'; import {icuContainerIterate} from './i18n/i18n_tree_shaking'; -import {CONTAINER_HEADER_OFFSET, LContainer, LContainerFlags, MOVED_VIEWS, NATIVE} from './interfaces/container'; +import { + CONTAINER_HEADER_OFFSET, + LContainer, + LContainerFlags, + MOVED_VIEWS, + NATIVE, +} from './interfaces/container'; import {ComponentDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; import {unregisterLView} from './interfaces/lview_tracking'; -import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; +import { + TElementNode, + TIcuContainerNode, + TNode, + TNodeFlags, + TNodeType, + TProjectionNode, +} from './interfaces/node'; import {Renderer} from './interfaces/renderer'; import {RComment, RElement, RNode, RTemplate, RText} from './interfaces/renderer_dom'; import {isLContainer, isLView} from './interfaces/type_checks'; -import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, ENVIRONMENT, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, ON_DESTROY_HOOKS, PARENT, QUERIES, REACTIVE_TEMPLATE_CONSUMER, RENDERER, T_HOST, TVIEW, TView, TViewType} from './interfaces/view'; +import { + CHILD_HEAD, + CLEANUP, + DECLARATION_COMPONENT_VIEW, + DECLARATION_LCONTAINER, + DestroyHookData, + ENVIRONMENT, + FLAGS, + HookData, + HookFn, + HOST, + LView, + LViewFlags, + NEXT, + ON_DESTROY_HOOKS, + PARENT, + QUERIES, + REACTIVE_TEMPLATE_CONSUMER, + RENDERER, + T_HOST, + TVIEW, + TView, + TViewType, +} from './interfaces/view'; import {assertTNodeType} from './node_assert'; import {profiler, ProfilerEvent} from './profiler'; import {setUpAttributes} from './util/attrs_utils'; -import {getLViewParent, getNativeByTNode, unwrapRNode, updateAncestorTraversalFlagsOnAttach} from './util/view_utils'; +import { + getLViewParent, + getNativeByTNode, + unwrapRNode, + updateAncestorTraversalFlagsOnAttach, +} from './util/view_utils'; const enum WalkTNodeTreeAction { /** node create in the native environment. Run on initial creation. */ @@ -50,21 +108,23 @@ const enum WalkTNodeTreeAction { Destroy = 3, } - - /** * NOTE: for performance reasons, the possible actions are inlined within the function instead of * being passed as an argument. */ function applyToElementOrContainer( - action: WalkTNodeTreeAction, renderer: Renderer, parent: RElement|null, - lNodeToHandle: RNode|LContainer|LView, beforeNode?: RNode|null) { + action: WalkTNodeTreeAction, + renderer: Renderer, + parent: RElement | null, + lNodeToHandle: RNode | LContainer | LView, + beforeNode?: RNode | null, +) { // If this slot was allocated for a text node dynamically created by i18n, the text node itself // won't be created until i18nApply() in the update block, so this node should be skipped. // For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor" // in `i18n_spec.ts`. if (lNodeToHandle != null) { - let lContainer: LContainer|undefined; + let lContainer: LContainer | undefined; let isComponent = false; // We are expecting an RNode, but in the case of a component or LContainer the `RNode` is // wrapped in an array which needs to be unwrapped. We need to know if it is a component and if @@ -122,12 +182,14 @@ export function createCommentNode(renderer: Renderer, value: string): RComment { * @returns the element created */ export function createElementNode( - renderer: Renderer, name: string, namespace: string|null): RElement { + renderer: Renderer, + name: string, + namespace: string | null, +): RElement { ngDevMode && ngDevMode.rendererCreateElement++; return renderer.createElement(name, namespace); } - /** * Removes all DOM elements associated with a view. * @@ -159,14 +221,18 @@ export function removeViewFromDOM(tView: TView, lView: LView): void { * @param beforeNode The node before which elements should be added, if insert mode */ export function addViewToDOM( - tView: TView, parentTNode: TNode, renderer: Renderer, lView: LView, parentNativeNode: RElement, - beforeNode: RNode|null): void { + tView: TView, + parentTNode: TNode, + renderer: Renderer, + lView: LView, + parentNativeNode: RElement, + beforeNode: RNode | null, +): void { lView[HOST] = parentNativeNode; lView[T_HOST] = parentTNode; applyView(tView, lView, renderer, WalkTNodeTreeAction.Insert, parentNativeNode, beforeNode); } - /** * Detach a `LView` from the DOM by detaching its nodes. * @@ -202,7 +268,7 @@ export function destroyViewTree(rootView: LView): void { } while (lViewOrLContainer) { - let next: LView|LContainer|null = null; + let next: LView | LContainer | null = null; if (isLView(lViewOrLContainer)) { // If LView, traverse down to child. @@ -210,7 +276,7 @@ export function destroyViewTree(rootView: LView): void { } else { ngDevMode && assertLContainer(lViewOrLContainer); // If container, traverse down to its first LView. - const firstView: LView|undefined = lViewOrLContainer[CONTAINER_HEADER_OFFSET]; + const firstView: LView | undefined = lViewOrLContainer[CONTAINER_HEADER_OFFSET]; if (firstView) next = firstView; } @@ -317,9 +383,10 @@ export function trackMovedView(declarationContainer: LContainer, lView: LView) { export function detachMovedView(declarationContainer: LContainer, lView: LView) { ngDevMode && assertLContainer(declarationContainer); ngDevMode && - assertDefined( - declarationContainer[MOVED_VIEWS], - 'A projected view should belong to a non-empty projected views collection'); + assertDefined( + declarationContainer[MOVED_VIEWS], + 'A projected view should belong to a non-empty projected views collection', + ); const movedViews = declarationContainer[MOVED_VIEWS]!; const declarationViewIndex = movedViews.indexOf(lView); movedViews.splice(declarationViewIndex, 1); @@ -335,7 +402,7 @@ export function detachMovedView(declarationContainer: LContainer, lView: LView) * @param removeIndex The index of the view to detach * @returns Detached LView instance. */ -export function detachView(lContainer: LContainer, removeIndex: number): LView|undefined { +export function detachView(lContainer: LContainer, removeIndex: number): LView | undefined { if (lContainer.length <= CONTAINER_HEADER_OFFSET) return; const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex; @@ -347,7 +414,6 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView|u detachMovedView(declarationLContainer, viewToDetach); } - if (removeIndex > 0) { lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT] as LView; } @@ -491,7 +557,7 @@ function processCleanups(tView: TView, lView: LView): void { /** Calls onDestroy hooks for this view */ function executeOnDestroys(tView: TView, lView: LView): void { ngDevMode && assertNotReactive(executeOnDestroys.name); - let destroyHooks: DestroyHookData|null; + let destroyHooks: DestroyHookData | null; if (tView != null && (destroyHooks = tView.destroyHooks) != null) { for (let i = 0; i < destroyHooks.length; i += 2) { @@ -541,7 +607,7 @@ function executeOnDestroys(tView: TView, lView: LView): void { * @param tNode: `TNode` for which we wish to retrieve render parent. * @param lView: Current `LView`. */ -export function getParentRElement(tView: TView, tNode: TNode, lView: LView): RElement|null { +export function getParentRElement(tView: TView, tNode: TNode, lView: LView): RElement | null { return getClosestRElement(tView, tNode.parent, lView); } @@ -560,12 +626,15 @@ export function getParentRElement(tView: TView, tNode: TNode, lView: LView): REl * @param lView: Current `LView`. * @returns `null` if the `RElement` can't be determined at this time (no parent / projection) */ -export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView): RElement|null { - let parentTNode: TNode|null = tNode; +export function getClosestRElement( + tView: TView, + tNode: TNode | null, + lView: LView, +): RElement | null { + let parentTNode: TNode | null = tNode; // Skip over element and ICU containers as those are represented by a comment node and // can't be used as a render parent. - while (parentTNode !== null && - (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu))) { + while (parentTNode !== null && parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu)) { tNode = parentTNode; parentTNode = tNode.parent; } @@ -581,16 +650,19 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView const {componentOffset} = parentTNode; if (componentOffset > -1) { ngDevMode && assertTNodeForLView(parentTNode, lView); - const {encapsulation} = - (tView.data[parentTNode.directiveStart + componentOffset] as ComponentDef); + const {encapsulation} = tView.data[ + parentTNode.directiveStart + componentOffset + ] as ComponentDef; // We've got a parent which is an element in the current view. We just need to verify if the // parent element is not a component. Component's content nodes are not inserted immediately // because they will be projected, and so doing insert at this point would be wasteful. // Since the projection would then move it to its final destination. Note that we can't // make this assumption when using the Shadow DOM, because the native projection placeholders // ( or ) have to be in place as elements are being inserted. - if (encapsulation === ViewEncapsulation.None || - encapsulation === ViewEncapsulation.Emulated) { + if ( + encapsulation === ViewEncapsulation.None || + encapsulation === ViewEncapsulation.Emulated + ) { return null; } } @@ -604,8 +676,12 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView * This is a utility function that can be used when native nodes were determined. */ export function nativeInsertBefore( - renderer: Renderer, parent: RElement, child: RNode, beforeNode: RNode|null, - isMove: boolean): void { + renderer: Renderer, + parent: RElement, + child: RNode, + beforeNode: RNode | null, + isMove: boolean, +): void { ngDevMode && ngDevMode.rendererInsertBefore++; renderer.insertBefore(parent, child, beforeNode, isMove); } @@ -617,7 +693,12 @@ function nativeAppendChild(renderer: Renderer, parent: RElement, child: RNode): } function nativeAppendOrInsertBefore( - renderer: Renderer, parent: RElement, child: RNode, beforeNode: RNode|null, isMove: boolean) { + renderer: Renderer, + parent: RElement, + child: RNode, + beforeNode: RNode | null, + isMove: boolean, +) { if (beforeNode !== null) { nativeInsertBefore(renderer, parent, child, beforeNode, isMove); } else { @@ -627,7 +708,11 @@ function nativeAppendOrInsertBefore( /** Removes a node from the DOM given its native parent. */ function nativeRemoveChild( - renderer: Renderer, parent: RElement, child: RNode, isHostElement?: boolean): void { + renderer: Renderer, + parent: RElement, + child: RNode, + isHostElement?: boolean, +): void { renderer.removeChild(parent, child, isHostElement); } @@ -639,14 +724,14 @@ function isTemplateNode(node: RElement): node is RTemplate { /** * Returns a native parent of a given native node. */ -export function nativeParentNode(renderer: Renderer, node: RNode): RElement|null { +export function nativeParentNode(renderer: Renderer, node: RNode): RElement | null { return renderer.parentNode(node); } /** * Returns a native sibling of a given native node. */ -export function nativeNextSibling(renderer: Renderer, node: RNode): RNode|null { +export function nativeNextSibling(renderer: Renderer, node: RNode): RNode | null { return renderer.nextSibling(node); } @@ -660,12 +745,14 @@ export function nativeNextSibling(renderer: Renderer, node: RNode): RNode|null { * @param currentTNode current `TNode` (The node which we would like to insert into the DOM) * @param lView current `LView` */ -function getInsertInFrontOfRNode(parentTNode: TNode, currentTNode: TNode, lView: LView): RNode| - null { +function getInsertInFrontOfRNode( + parentTNode: TNode, + currentTNode: TNode, + lView: LView, +): RNode | null { return _getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView); } - /** * Find a node in front of which `currentTNode` should be inserted. (Does not take i18n into * account) @@ -678,7 +765,10 @@ function getInsertInFrontOfRNode(parentTNode: TNode, currentTNode: TNode, lView: * @param lView current `LView` */ export function getInsertInFrontOfRNodeWithNoI18n( - parentTNode: TNode, currentTNode: TNode, lView: LView): RNode|null { + parentTNode: TNode, + currentTNode: TNode, + lView: LView, +): RNode | null { if (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu)) { return getNativeByTNode(parentTNode, lView); } @@ -690,8 +780,11 @@ export function getInsertInFrontOfRNodeWithNoI18n( * * This function will only be set if i18n code runs. */ -let _getInsertInFrontOfRNodeWithI18n: (parentTNode: TNode, currentTNode: TNode, lView: LView) => - RNode | null = getInsertInFrontOfRNodeWithNoI18n; +let _getInsertInFrontOfRNodeWithI18n: ( + parentTNode: TNode, + currentTNode: TNode, + lView: LView, +) => RNode | null = getInsertInFrontOfRNodeWithNoI18n; /** * Tree shakable boundary for `processI18nInsertBefore` function. @@ -699,15 +792,27 @@ let _getInsertInFrontOfRNodeWithI18n: (parentTNode: TNode, currentTNode: TNode, * This function will only be set if i18n code runs. */ let _processI18nInsertBefore: ( - renderer: Renderer, childTNode: TNode, lView: LView, childRNode: RNode|RNode[], - parentRElement: RElement|null) => void; + renderer: Renderer, + childTNode: TNode, + lView: LView, + childRNode: RNode | RNode[], + parentRElement: RElement | null, +) => void; export function setI18nHandling( - getInsertInFrontOfRNodeWithI18n: (parentTNode: TNode, currentTNode: TNode, lView: LView) => - RNode | null, - processI18nInsertBefore: ( - renderer: Renderer, childTNode: TNode, lView: LView, childRNode: RNode|RNode[], - parentRElement: RElement|null) => void) { + getInsertInFrontOfRNodeWithI18n: ( + parentTNode: TNode, + currentTNode: TNode, + lView: LView, + ) => RNode | null, + processI18nInsertBefore: ( + renderer: Renderer, + childTNode: TNode, + lView: LView, + childRNode: RNode | RNode[], + parentRElement: RElement | null, + ) => void, +) { _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithI18n; _processI18nInsertBefore = processI18nInsertBefore; } @@ -721,7 +826,11 @@ export function setI18nHandling( * @param childTNode The TNode of the child element */ export function appendChild( - tView: TView, lView: LView, childRNode: RNode|RNode[], childTNode: TNode): void { + tView: TView, + lView: LView, + childRNode: RNode | RNode[], + childTNode: TNode, +): void { const parentRNode = getParentRElement(tView, childTNode, lView); const renderer = lView[RENDERER]; const parentTNode: TNode = childTNode.parent || lView[T_HOST]!; @@ -737,7 +846,7 @@ export function appendChild( } _processI18nInsertBefore !== undefined && - _processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRNode); + _processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRNode); } /** @@ -745,12 +854,13 @@ export function appendChild( * * Native nodes are returned in the order in which those appear in the native tree (DOM). */ -export function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null { +export function getFirstNativeNode(lView: LView, tNode: TNode | null): RNode | null { if (tNode !== null) { ngDevMode && - assertTNodeType( - tNode, - TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu | TNodeType.Projection); + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu | TNodeType.Projection, + ); const tNodeType = tNode.type; if (tNodeType & TNodeType.AnyRNode) { @@ -771,7 +881,7 @@ export function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null } } else if (tNodeType & TNodeType.Icu) { let nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); - let rNode: RNode|null = nextRNode(); + let rNode: RNode | null = nextRNode(); // If the ICU container has no nodes, than we use the ICU anchor as the node. return rNode || unwrapRNode(lView[tNode.index]); } else { @@ -792,7 +902,7 @@ export function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null return null; } -export function getProjectionNodes(lView: LView, tNode: TNode|null): TNode|RNode[]|null { +export function getProjectionNodes(lView: LView, tNode: TNode | null): TNode | RNode[] | null { if (tNode !== null) { const componentView = lView[DECLARATION_COMPONENT_VIEW]; const componentHost = componentView[T_HOST] as TElementNode; @@ -803,8 +913,10 @@ export function getProjectionNodes(lView: LView, tNode: TNode|null): TNode|RNode return null; } -export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode| - null { +export function getBeforeNodeForView( + viewIndexInContainer: number, + lContainer: LContainer, +): RNode | null { const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1; if (nextViewIndex < lContainer.length) { const lView = lContainer[nextViewIndex] as LView; @@ -843,20 +955,26 @@ export function clearElementContents(rElement: RElement): void { rElement.textContent = ''; } - /** * Performs the operation of `action` on the node. Typically this involves inserting or removing * nodes on the LView or projection boundary. */ function applyNodes( - renderer: Renderer, action: WalkTNodeTreeAction, tNode: TNode|null, lView: LView, - parentRElement: RElement|null, beforeNode: RNode|null, isProjection: boolean) { + renderer: Renderer, + action: WalkTNodeTreeAction, + tNode: TNode | null, + lView: LView, + parentRElement: RElement | null, + beforeNode: RNode | null, + isProjection: boolean, +) { while (tNode != null) { ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && - assertTNodeType( - tNode, - TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu); + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu, + ); const rawSlotValue = lView[tNode.index]; const tNodeType = tNode.type; if (isProjection) { @@ -871,14 +989,20 @@ function applyNodes( applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } else if (tNodeType & TNodeType.Icu) { const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); - let rNode: RNode|null; - while (rNode = nextRNode()) { + let rNode: RNode | null; + while ((rNode = nextRNode())) { applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); } applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } else if (tNodeType & TNodeType.Projection) { applyProjectionRecursive( - renderer, action, lView, tNode as TProjectionNode, parentRElement, beforeNode); + renderer, + action, + lView, + tNode as TProjectionNode, + parentRElement, + beforeNode, + ); } else { ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.Container); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); @@ -888,7 +1012,6 @@ function applyNodes( } } - /** * `applyView` performs operation on the view as specified in `action` (insert, detach, destroy) * @@ -913,14 +1036,29 @@ function applyNodes( * @param beforeNode Before which node the insertions should happen. */ function applyView( - tView: TView, lView: LView, renderer: Renderer, action: WalkTNodeTreeAction.Destroy, - parentRElement: null, beforeNode: null): void; + tView: TView, + lView: LView, + renderer: Renderer, + action: WalkTNodeTreeAction.Destroy, + parentRElement: null, + beforeNode: null, +): void; function applyView( - tView: TView, lView: LView, renderer: Renderer, action: WalkTNodeTreeAction, - parentRElement: RElement|null, beforeNode: RNode|null): void; + tView: TView, + lView: LView, + renderer: Renderer, + action: WalkTNodeTreeAction, + parentRElement: RElement | null, + beforeNode: RNode | null, +): void; function applyView( - tView: TView, lView: LView, renderer: Renderer, action: WalkTNodeTreeAction, - parentRElement: RElement|null, beforeNode: RNode|null): void { + tView: TView, + lView: LView, + renderer: Renderer, + action: WalkTNodeTreeAction, + parentRElement: RElement | null, + beforeNode: RNode | null, +): void { applyNodes(renderer, action, tView.firstChild, lView, parentRElement, beforeNode, false); } @@ -940,7 +1078,13 @@ export function applyProjection(tView: TView, lView: LView, tProjectionNode: TPr const parentTNode = tProjectionNode.parent || lView[T_HOST]!; let beforeNode = getInsertInFrontOfRNode(parentTNode, tProjectionNode, lView); applyProjectionRecursive( - renderer, WalkTNodeTreeAction.Create, lView, tProjectionNode, parentRNode, beforeNode); + renderer, + WalkTNodeTreeAction.Create, + lView, + tProjectionNode, + parentRNode, + beforeNode, + ); } /** @@ -958,12 +1102,17 @@ export function applyProjection(tView: TView, lView: LView, tProjectionNode: TPr * @param beforeNode Before which node the insertions should happen. */ function applyProjectionRecursive( - renderer: Renderer, action: WalkTNodeTreeAction, lView: LView, tProjectionNode: TProjectionNode, - parentRElement: RElement|null, beforeNode: RNode|null) { + renderer: Renderer, + action: WalkTNodeTreeAction, + lView: LView, + tProjectionNode: TProjectionNode, + parentRElement: RElement | null, + beforeNode: RNode | null, +) { const componentLView = lView[DECLARATION_COMPONENT_VIEW]; const componentNode = componentLView[T_HOST] as TElementNode; ngDevMode && - assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index'); + assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index'); const nodeToProjectOrRNodes = componentNode.projection![tProjectionNode.projection]!; if (Array.isArray(nodeToProjectOrRNodes)) { // This should not exist, it is a bit of a hack. When we bootstrap a top level node and we @@ -976,7 +1125,7 @@ function applyProjectionRecursive( applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); } } else { - let nodeToProject: TNode|null = nodeToProjectOrRNodes; + let nodeToProject: TNode | null = nodeToProjectOrRNodes; const projectedComponentLView = componentLView[PARENT] as LView; // If a parent is located within a skip hydration block, // annotate an actual node that is being projected with the same flag too. @@ -984,11 +1133,17 @@ function applyProjectionRecursive( nodeToProject.flags |= TNodeFlags.inSkipHydrationBlock; } applyNodes( - renderer, action, nodeToProject, projectedComponentLView, parentRElement, beforeNode, true); + renderer, + action, + nodeToProject, + projectedComponentLView, + parentRElement, + beforeNode, + true, + ); } } - /** * `applyContainer` performs an operation on the container and its views as specified by * `action` (insert, detach, destroy) @@ -1003,10 +1158,14 @@ function applyProjectionRecursive( * @param beforeNode Before which node the insertions should happen. */ function applyContainer( - renderer: Renderer, action: WalkTNodeTreeAction, lContainer: LContainer, - parentRElement: RElement|null, beforeNode: RNode|null|undefined) { + renderer: Renderer, + action: WalkTNodeTreeAction, + lContainer: LContainer, + parentRElement: RElement | null, + beforeNode: RNode | null | undefined, +) { ngDevMode && assertLContainer(lContainer); - const anchor = lContainer[NATIVE]; // LContainer has its own before node. + const anchor = lContainer[NATIVE]; // LContainer has its own before node. const native = unwrapRNode(lContainer); // An LContainer can be created dynamically on any node by injecting ViewContainerRef. // Asking for a ViewContainerRef on an element will result in a creation of a separate anchor @@ -1038,7 +1197,12 @@ function applyContainer( * otherwise). */ export function applyStyling( - renderer: Renderer, isClassBased: boolean, rNode: RElement, prop: string, value: any) { + renderer: Renderer, + isClassBased: boolean, + rNode: RElement, + prop: string, + value: any, +) { if (isClassBased) { // We actually want JS true/false here because any truthy value should add the class if (!value) { @@ -1049,7 +1213,7 @@ export function applyStyling( renderer.addClass(rNode, prop); } } else { - let flags = prop.indexOf('-') === -1 ? undefined : RendererStyleFlags2.DashCase as number; + let flags = prop.indexOf('-') === -1 ? undefined : (RendererStyleFlags2.DashCase as number); if (value == null /** || value === undefined */) { ngDevMode && ngDevMode.rendererRemoveStyle++; renderer.removeStyle(rNode, prop, flags); @@ -1070,7 +1234,6 @@ export function applyStyling( } } - /** * Write `cssText` to `RElement`. * @@ -1082,7 +1245,7 @@ export function applyStyling( * @param newValue The new class list to write. */ export function writeDirectStyle(renderer: Renderer, element: RElement, newValue: string) { - ngDevMode && assertString(newValue, '\'newValue\' should be a string'); + ngDevMode && assertString(newValue, "'newValue' should be a string"); renderer.setAttribute(element, 'style', newValue); ngDevMode && ngDevMode.rendererSetStyle++; } @@ -1098,7 +1261,7 @@ export function writeDirectStyle(renderer: Renderer, element: RElement, newValue * @param newValue The new class list to write. */ export function writeDirectClass(renderer: Renderer, element: RElement, newValue: string) { - ngDevMode && assertString(newValue, '\'newValue\' should be a string'); + ngDevMode && assertString(newValue, "'newValue' should be a string"); if (newValue === '') { // There are tests in `google3` which expect `element.getAttribute('class')` to be `null`. renderer.removeAttribute(element, 'class'); diff --git a/packages/core/src/render3/node_manipulation_i18n.ts b/packages/core/src/render3/node_manipulation_i18n.ts index 2db01123eb775..6eb45b8ff1103 100644 --- a/packages/core/src/render3/node_manipulation_i18n.ts +++ b/packages/core/src/render3/node_manipulation_i18n.ts @@ -15,7 +15,6 @@ import {LView} from './interfaces/view'; import {getInsertInFrontOfRNodeWithNoI18n, nativeInsertBefore} from './node_manipulation'; import {unwrapRNode} from './util/view_utils'; - /** * Find a node in front of which `currentTNode` should be inserted (takes i18n into account). * @@ -27,10 +26,14 @@ import {unwrapRNode} from './util/view_utils'; * @param lView current `LView` */ export function getInsertInFrontOfRNodeWithI18n( - parentTNode: TNode, currentTNode: TNode, lView: LView): RNode|null { + parentTNode: TNode, + currentTNode: TNode, + lView: LView, +): RNode | null { const tNodeInsertBeforeIndex = currentTNode.insertBeforeIndex; - const insertBeforeIndex = - Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex; + const insertBeforeIndex = Array.isArray(tNodeInsertBeforeIndex) + ? tNodeInsertBeforeIndex[0] + : tNodeInsertBeforeIndex; if (insertBeforeIndex === null) { return getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView); } else { @@ -39,15 +42,18 @@ export function getInsertInFrontOfRNodeWithI18n( } } - /** * Process `TNode.insertBeforeIndex` by adding i18n text nodes. * * See `TNode.insertBeforeIndex` */ export function processI18nInsertBefore( - renderer: Renderer, childTNode: TNode, lView: LView, childRNode: RNode|RNode[], - parentRElement: RElement|null): void { + renderer: Renderer, + childTNode: TNode, + lView: LView, + childRNode: RNode | RNode[], + parentRElement: RElement | null, +): void { const tNodeInsertBeforeIndex = childTNode.insertBeforeIndex; if (Array.isArray(tNodeInsertBeforeIndex)) { // An array indicates that there are i18n nodes that need to be added as children of this @@ -56,8 +62,8 @@ export function processI18nInsertBefore( // insert the `childRNode`. Additional elements are the extra nodes to be added as children of // `childRNode`. ngDevMode && assertDomNode(childRNode); - let i18nParent: RElement|null = childRNode as RElement; - let anchorRNode: RNode|null = null; + let i18nParent: RElement | null = childRNode as RElement; + let anchorRNode: RNode | null = null; if (!(childTNode.type & TNodeType.AnyRNode)) { anchorRNode = i18nParent; i18nParent = parentRElement; diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index f5031e754f169..833723dcfee17 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -28,16 +28,25 @@ const NG_TEMPLATE_SELECTOR = 'ng-template'; * addition to the `AttributeMarker.Classes`. */ function isCssClassMatching( - tNode: TNode, attrs: TAttributes, cssClassToMatch: string, isProjectionMode: boolean): boolean { + tNode: TNode, + attrs: TAttributes, + cssClassToMatch: string, + isProjectionMode: boolean, +): boolean { ngDevMode && - assertEqual( - cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.'); + assertEqual( + cssClassToMatch, + cssClassToMatch.toLowerCase(), + 'Class name expected to be lowercase.', + ); let i = 0; if (isProjectionMode) { for (; i < attrs.length && typeof attrs[i] === 'string'; i += 2) { // Search for an implicit `class` attribute and check if its value matches `cssClassToMatch`. - if (attrs[i] === 'class' && - classIndexOf((attrs[i + 1] as string).toLowerCase(), cssClassToMatch, 0) !== -1) { + if ( + attrs[i] === 'class' && + classIndexOf((attrs[i + 1] as string).toLowerCase(), cssClassToMatch, 0) !== -1 + ) { return true; } } @@ -83,9 +92,12 @@ export function isInlineTemplate(tNode: TNode): boolean { * (applicable to TNodeType.Container only). */ function hasTagAndTypeMatch( - tNode: TNode, currentSelector: string, isProjectionMode: boolean): boolean { + tNode: TNode, + currentSelector: string, + isProjectionMode: boolean, +): boolean { const tagNameToCompare = - tNode.type === TNodeType.Container && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value; + tNode.type === TNodeType.Container && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value; return currentSelector === tagNameToCompare; } @@ -99,7 +111,10 @@ function hasTagAndTypeMatch( * @returns true if node matches the selector. */ export function isNodeMatchingSelector( - tNode: TNode, selector: CssSelector, isProjectionMode: boolean): boolean { + tNode: TNode, + selector: CssSelector, + isProjectionMode: boolean, +): boolean { ngDevMode && assertDefined(selector[0], 'Selector should have a tag name'); let mode: SelectorFlags = SelectorFlags.ELEMENT; const nodeAttrs = tNode.attrs; @@ -129,9 +144,11 @@ export function isNodeMatchingSelector( if (skipToNextSelector) continue; if (mode & SelectorFlags.ELEMENT) { - mode = SelectorFlags.ATTRIBUTE | mode & SelectorFlags.NOT; - if (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode) || - current === '' && selector.length === 1) { + mode = SelectorFlags.ATTRIBUTE | (mode & SelectorFlags.NOT); + if ( + (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode)) || + (current === '' && selector.length === 1) + ) { if (isPositive(mode)) return false; skipToNextSelector = true; } @@ -142,8 +159,12 @@ export function isNodeMatchingSelector( } } else { const selectorAttrValue = selector[++i]; - const attrIndexInNode = - findAttrIndexInNode(current, nodeAttrs, isInlineTemplate(tNode), isProjectionMode); + const attrIndexInNode = findAttrIndexInNode( + current, + nodeAttrs, + isInlineTemplate(tNode), + isProjectionMode, + ); if (attrIndexInNode === -1) { if (isPositive(mode)) return false; @@ -157,9 +178,11 @@ export function isNodeMatchingSelector( nodeAttrValue = ''; } else { ngDevMode && - assertNotEqual( - nodeAttrs![attrIndexInNode], AttributeMarker.NamespaceURI, - 'We do not match directives on namespaced attributes'); + assertNotEqual( + nodeAttrs![attrIndexInNode], + AttributeMarker.NamespaceURI, + 'We do not match directives on namespaced attributes', + ); // we lowercase the attribute value to be able to match // selectors without case-sensitivity // (selectors are already in lowercase when generated) @@ -211,8 +234,11 @@ function isPositive(mode: SelectorFlags): boolean { * matching against directives. */ function findAttrIndexInNode( - name: string, attrs: TAttributes|null, isInlineTemplate: boolean, - isProjectionMode: boolean): number { + name: string, + attrs: TAttributes | null, + isInlineTemplate: boolean, + isProjectionMode: boolean, +): number { if (attrs === null) return -1; let i = 0; @@ -224,10 +250,14 @@ function findAttrIndexInNode( if (maybeAttrName === name) { return i; } else if ( - maybeAttrName === AttributeMarker.Bindings || maybeAttrName === AttributeMarker.I18n) { + maybeAttrName === AttributeMarker.Bindings || + maybeAttrName === AttributeMarker.I18n + ) { bindingsMode = true; } else if ( - maybeAttrName === AttributeMarker.Classes || maybeAttrName === AttributeMarker.Styles) { + maybeAttrName === AttributeMarker.Classes || + maybeAttrName === AttributeMarker.Styles + ) { let value = attrs[++i]; // We should skip classes here because we have a separate mechanism for // matching classes in projection mode. @@ -254,7 +284,10 @@ function findAttrIndexInNode( } export function isNodeMatchingSelectorList( - tNode: TNode, selector: CssSelectorList, isProjectionMode: boolean = false): boolean { + tNode: TNode, + selector: CssSelectorList, + isProjectionMode: boolean = false, +): boolean { for (let i = 0; i < selector.length; i++) { if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) { return true; @@ -264,7 +297,7 @@ export function isNodeMatchingSelectorList( return false; } -export function getProjectAsAttrValue(tNode: TNode): CssSelector|null { +export function getProjectAsAttrValue(tNode: TNode): CssSelector | null { const nodeAttrs = tNode.attrs; if (nodeAttrs != null) { const ngProjectAsAttrIdx = nodeAttrs.indexOf(AttributeMarker.ProjectAs); @@ -340,7 +373,7 @@ function stringifyCSSSelector(selector: CssSelector): string { if (mode & SelectorFlags.ATTRIBUTE) { const attrValue = selector[++i] as string; currentChunk += - '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']'; + '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']'; } else if (mode & SelectorFlags.CLASS) { currentChunk += '.' + valueOrMarker; } else if (mode & SelectorFlags.ELEMENT) { @@ -407,8 +440,10 @@ export function stringifyCSSSelectorList(selectorList: CssSelectorList): string * @param selector CSS selector in parsed form (in a form of array) * @returns object with `attrs` and `classes` fields that contain extracted information */ -export function extractAttrsAndClassesFromSelector(selector: CssSelector): - {attrs: string[], classes: string[]} { +export function extractAttrsAndClassesFromSelector(selector: CssSelector): { + attrs: string[]; + classes: string[]; +} { const attrs: string[] = []; const classes: string[] = []; let i = 1; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index deb89a9855f8f..79e76d5b7b73a 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -19,12 +19,16 @@ import {isHostComponentStandalone} from './instructions/element_validation'; import {PipeDef, PipeDefList} from './interfaces/definition'; import {TTextNode} from './interfaces/node'; import {CONTEXT, DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, LView, TVIEW} from './interfaces/view'; -import {pureFunction1Internal, pureFunction2Internal, pureFunction3Internal, pureFunction4Internal, pureFunctionVInternal} from './pure_function'; +import { + pureFunction1Internal, + pureFunction2Internal, + pureFunction3Internal, + pureFunction4Internal, + pureFunctionVInternal, +} from './pure_function'; import {getBindingRoot, getCurrentTNode, getLView, getTView} from './state'; import {load} from './util/view_utils'; - - /** * Create a pipe. * @@ -57,7 +61,7 @@ export function ɵɵpipe(index: number, pipeName: string): any { if (ngDevMode) { previousInjectorProfilerContext = setInjectorProfilerContext({ injector: new NodeInjector(getCurrentTNode() as TTextNode, getLView()), - token: pipeDef.type + token: pipeDef.type, }); } const previousInjectImplementation = setInjectImplementation(ɵɵdirectiveInject); @@ -85,14 +89,18 @@ export function ɵɵpipe(index: number, pipeName: string): any { * @param registry Full list of available pipes * @returns Matching PipeDef */ -function getPipeDef(name: string, registry: PipeDefList|null): PipeDef|undefined { +function getPipeDef(name: string, registry: PipeDefList | null): PipeDef | undefined { if (registry) { if (ngDevMode) { - const pipes = registry.filter(pipe => pipe.name === name); + const pipes = registry.filter((pipe) => pipe.name === name); // TODO: Throw an error in the next major if (pipes.length > 1) { - console.warn(formatRuntimeError( - RuntimeErrorCode.MULTIPLE_MATCHING_PIPES, getMultipleMatchingPipesMessage(name))); + console.warn( + formatRuntimeError( + RuntimeErrorCode.MULTIPLE_MATCHING_PIPES, + getMultipleMatchingPipesMessage(name), + ), + ); } } for (let i = registry.length - 1; i >= 0; i--) { @@ -121,10 +129,9 @@ function getMultipleMatchingPipesMessage(name: string) { const hostIsStandalone = isHostComponentStandalone(lView); const componentInfoMessage = context ? ` in the '${context.constructor.name}' component` : ''; const verifyMessage = `check ${ - hostIsStandalone ? '\'@Component.imports\' of this component' : - 'the imports of this module'}`; - const errorMessage = - `Multiple pipes match the name \`${name}\`${componentInfoMessage}. ${verifyMessage}`; + hostIsStandalone ? "'@Component.imports' of this component" : 'the imports of this module' + }`; + const errorMessage = `Multiple pipes match the name \`${name}\`${componentInfoMessage}. ${verifyMessage}`; return errorMessage; } @@ -141,10 +148,11 @@ function getPipeNotFoundErrorMessage(name: string) { const hostIsStandalone = isHostComponentStandalone(lView); const componentInfoMessage = context ? ` in the '${context.constructor.name}' component` : ''; const verifyMessage = `Verify that it is ${ - hostIsStandalone ? 'included in the \'@Component.imports\' of this component' : - 'declared or imported in this module'}`; - const errorMessage = - `The pipe '${name}' could not be found${componentInfoMessage}. ${verifyMessage}`; + hostIsStandalone + ? "included in the '@Component.imports' of this component" + : 'declared or imported in this module' + }`; + const errorMessage = `The pipe '${name}' could not be found${componentInfoMessage}. ${verifyMessage}`; return errorMessage; } @@ -164,10 +172,16 @@ export function ɵɵpipeBind1(index: number, offset: number, v1: any): any { const adjustedIndex = index + HEADER_OFFSET; const lView = getLView(); const pipeInstance = load(lView, adjustedIndex); - return isPure(lView, adjustedIndex) ? - pureFunction1Internal( - lView, getBindingRoot(), offset, pipeInstance.transform, v1, pipeInstance) : - pipeInstance.transform(v1); + return isPure(lView, adjustedIndex) + ? pureFunction1Internal( + lView, + getBindingRoot(), + offset, + pipeInstance.transform, + v1, + pipeInstance, + ) + : pipeInstance.transform(v1); } /** @@ -187,10 +201,17 @@ export function ɵɵpipeBind2(index: number, slotOffset: number, v1: any, v2: an const adjustedIndex = index + HEADER_OFFSET; const lView = getLView(); const pipeInstance = load(lView, adjustedIndex); - return isPure(lView, adjustedIndex) ? - pureFunction2Internal( - lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, pipeInstance) : - pipeInstance.transform(v1, v2); + return isPure(lView, adjustedIndex) + ? pureFunction2Internal( + lView, + getBindingRoot(), + slotOffset, + pipeInstance.transform, + v1, + v2, + pipeInstance, + ) + : pipeInstance.transform(v1, v2); } /** @@ -211,10 +232,18 @@ export function ɵɵpipeBind3(index: number, slotOffset: number, v1: any, v2: an const adjustedIndex = index + HEADER_OFFSET; const lView = getLView(); const pipeInstance = load(lView, adjustedIndex); - return isPure(lView, adjustedIndex) ? - pureFunction3Internal( - lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, v3, pipeInstance) : - pipeInstance.transform(v1, v2, v3); + return isPure(lView, adjustedIndex) + ? pureFunction3Internal( + lView, + getBindingRoot(), + slotOffset, + pipeInstance.transform, + v1, + v2, + v3, + pipeInstance, + ) + : pipeInstance.transform(v1, v2, v3); } /** @@ -233,14 +262,29 @@ export function ɵɵpipeBind3(index: number, slotOffset: number, v1: any, v2: an * @codeGenApi */ export function ɵɵpipeBind4( - index: number, slotOffset: number, v1: any, v2: any, v3: any, v4: any): any { + index: number, + slotOffset: number, + v1: any, + v2: any, + v3: any, + v4: any, +): any { const adjustedIndex = index + HEADER_OFFSET; const lView = getLView(); const pipeInstance = load(lView, adjustedIndex); - return isPure(lView, adjustedIndex) ? pureFunction4Internal( - lView, getBindingRoot(), slotOffset, - pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : - pipeInstance.transform(v1, v2, v3, v4); + return isPure(lView, adjustedIndex) + ? pureFunction4Internal( + lView, + getBindingRoot(), + slotOffset, + pipeInstance.transform, + v1, + v2, + v3, + v4, + pipeInstance, + ) + : pipeInstance.transform(v1, v2, v3, v4); } /** @@ -259,10 +303,16 @@ export function ɵɵpipeBindV(index: number, slotOffset: number, values: [any, . const adjustedIndex = index + HEADER_OFFSET; const lView = getLView(); const pipeInstance = load(lView, adjustedIndex); - return isPure(lView, adjustedIndex) ? - pureFunctionVInternal( - lView, getBindingRoot(), slotOffset, pipeInstance.transform, values, pipeInstance) : - pipeInstance.transform.apply(pipeInstance, values); + return isPure(lView, adjustedIndex) + ? pureFunctionVInternal( + lView, + getBindingRoot(), + slotOffset, + pipeInstance.transform, + values, + pipeInstance, + ) + : pipeInstance.transform.apply(pipeInstance, values); } function isPure(lView: LView, index: number): boolean { diff --git a/packages/core/src/render3/profiler.ts b/packages/core/src/render3/profiler.ts index fd908f5db07f0..b60fd56028080 100644 --- a/packages/core/src/render3/profiler.ts +++ b/packages/core/src/render3/profiler.ts @@ -64,11 +64,10 @@ export const enum ProfilerEvent { * Profiler function which the runtime will invoke before and after user code. */ export interface Profiler { - (event: ProfilerEvent, instance: {}|null, hookOrListener?: (e?: any) => any): void; + (event: ProfilerEvent, instance: {} | null, hookOrListener?: (e?: any) => any): void; } - -let profilerCallback: Profiler|null = null; +let profilerCallback: Profiler | null = null; /** * Sets the callback function which will be invoked before and after performing certain actions at @@ -80,7 +79,7 @@ let profilerCallback: Profiler|null = null; * * @param profiler function provided by the caller or null value to disable profiling. */ -export const setProfiler = (profiler: Profiler|null) => { +export const setProfiler = (profiler: Profiler | null) => { profilerCallback = profiler; }; @@ -93,8 +92,11 @@ export const setProfiler = (profiler: Profiler|null) => { * execution context * @returns */ -export const profiler: Profiler = function( - event: ProfilerEvent, instance: {}|null, hookOrListener?: (e?: any) => any) { +export const profiler: Profiler = function ( + event: ProfilerEvent, + instance: {} | null, + hookOrListener?: (e?: any) => any, +) { if (profilerCallback != null /* both `null` and `undefined` */) { profilerCallback(event, instance, hookOrListener); } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index 6db19df34d4bd..d6249a3e907c4 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -7,12 +7,18 @@ */ import {assertIndexInRange} from '../util/assert'; -import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings'; +import { + bindingUpdated, + bindingUpdated2, + bindingUpdated3, + bindingUpdated4, + getBinding, + updateBinding, +} from './bindings'; import {LView} from './interfaces/view'; import {getBindingRoot, getLView} from './state'; import {NO_CHANGE} from './tokens'; - /** * Bindings for pure functions are stored after regular bindings. * @@ -45,9 +51,9 @@ import {NO_CHANGE} from './tokens'; export function ɵɵpureFunction0(slotOffset: number, pureFn: () => T, thisArg?: any): T { const bindingIndex = getBindingRoot() + slotOffset; const lView = getLView(); - return lView[bindingIndex] === NO_CHANGE ? - updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) : - getBinding(lView, bindingIndex); + return lView[bindingIndex] === NO_CHANGE + ? updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) + : getBinding(lView, bindingIndex); } /** @@ -63,7 +69,11 @@ export function ɵɵpureFunction0(slotOffset: number, pureFn: () => T, thisAr * @codeGenApi */ export function ɵɵpureFunction1( - slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any { + slotOffset: number, + pureFn: (v: any) => any, + exp: any, + thisArg?: any, +): any { return pureFunction1Internal(getLView(), getBindingRoot(), slotOffset, pureFn, exp, thisArg); } @@ -81,10 +91,21 @@ export function ɵɵpureFunction1( * @codeGenApi */ export function ɵɵpureFunction2( - slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, - thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any) => any, + exp1: any, + exp2: any, + thisArg?: any, +): any { return pureFunction2Internal( - getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, thisArg); + getLView(), + getBindingRoot(), + slotOffset, + pureFn, + exp1, + exp2, + thisArg, + ); } /** @@ -102,10 +123,23 @@ export function ɵɵpureFunction2( * @codeGenApi */ export function ɵɵpureFunction3( - slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, - thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any) => any, + exp1: any, + exp2: any, + exp3: any, + thisArg?: any, +): any { return pureFunction3Internal( - getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, exp3, thisArg); + getLView(), + getBindingRoot(), + slotOffset, + pureFn, + exp1, + exp2, + exp3, + thisArg, + ); } /** @@ -124,10 +158,25 @@ export function ɵɵpureFunction3( * @codeGenApi */ export function ɵɵpureFunction4( - slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, - exp3: any, exp4: any, thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + thisArg?: any, +): any { return pureFunction4Internal( - getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, exp3, exp4, thisArg); + getLView(), + getBindingRoot(), + slotOffset, + pureFn, + exp1, + exp2, + exp3, + exp4, + thisArg, + ); } /** @@ -147,17 +196,27 @@ export function ɵɵpureFunction4( * @codeGenApi */ export function ɵɵpureFunction5( - slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, - exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + exp5: any, + thisArg?: any, +): any { const bindingIndex = getBindingRoot() + slotOffset; const lView = getLView(); const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4); - return bindingUpdated(lView, bindingIndex + 4, exp5) || different ? - updateBinding( - lView, bindingIndex + 5, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) : - pureFn(exp1, exp2, exp3, exp4, exp5)) : - getBinding(lView, bindingIndex + 5); + return bindingUpdated(lView, bindingIndex + 4, exp5) || different + ? updateBinding( + lView, + bindingIndex + 5, + thisArg + ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) + : pureFn(exp1, exp2, exp3, exp4, exp5), + ) + : getBinding(lView, bindingIndex + 5); } /** @@ -178,17 +237,28 @@ export function ɵɵpureFunction5( * @codeGenApi */ export function ɵɵpureFunction6( - slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, - exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + exp5: any, + exp6: any, + thisArg?: any, +): any { const bindingIndex = getBindingRoot() + slotOffset; const lView = getLView(); const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4); - return bindingUpdated2(lView, bindingIndex + 4, exp5, exp6) || different ? - updateBinding( - lView, bindingIndex + 6, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) : - pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) : - getBinding(lView, bindingIndex + 6); + return bindingUpdated2(lView, bindingIndex + 4, exp5, exp6) || different + ? updateBinding( + lView, + bindingIndex + 6, + thisArg + ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) + : pureFn(exp1, exp2, exp3, exp4, exp5, exp6), + ) + : getBinding(lView, bindingIndex + 6); } /** @@ -210,18 +280,29 @@ export function ɵɵpureFunction6( * @codeGenApi */ export function ɵɵpureFunction7( - slotOffset: number, - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, - exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + exp5: any, + exp6: any, + exp7: any, + thisArg?: any, +): any { const bindingIndex = getBindingRoot() + slotOffset; const lView = getLView(); let different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4); - return bindingUpdated3(lView, bindingIndex + 4, exp5, exp6, exp7) || different ? - updateBinding( - lView, bindingIndex + 7, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) : - pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) : - getBinding(lView, bindingIndex + 7); + return bindingUpdated3(lView, bindingIndex + 4, exp5, exp6, exp7) || different + ? updateBinding( + lView, + bindingIndex + 7, + thisArg + ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) + : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7), + ) + : getBinding(lView, bindingIndex + 7); } /** @@ -244,19 +325,30 @@ export function ɵɵpureFunction7( * @codeGenApi */ export function ɵɵpureFunction8( - slotOffset: number, - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, - exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any, - thisArg?: any): any { + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + exp5: any, + exp6: any, + exp7: any, + exp8: any, + thisArg?: any, +): any { const bindingIndex = getBindingRoot() + slotOffset; const lView = getLView(); const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4); - return bindingUpdated4(lView, bindingIndex + 4, exp5, exp6, exp7, exp8) || different ? - updateBinding( - lView, bindingIndex + 8, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) : - pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) : - getBinding(lView, bindingIndex + 8); + return bindingUpdated4(lView, bindingIndex + 4, exp5, exp6, exp7, exp8) || different + ? updateBinding( + lView, + bindingIndex + 8, + thisArg + ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) + : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8), + ) + : getBinding(lView, bindingIndex + 8); } /** @@ -275,7 +367,11 @@ export function ɵɵpureFunction8( * @codeGenApi */ export function ɵɵpureFunctionV( - slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any { + slotOffset: number, + pureFn: (...v: any[]) => any, + exps: any[], + thisArg?: any, +): any { return pureFunctionVInternal(getLView(), getBindingRoot(), slotOffset, pureFn, exps, thisArg); } @@ -305,15 +401,19 @@ function getPureFunctionReturnValue(lView: LView, returnValueIndex: number) { * @returns Updated or cached value */ export function pureFunction1Internal( - lView: LView, bindingRoot: number, slotOffset: number, pureFn: (v: any) => any, exp: any, - thisArg?: any): any { + lView: LView, + bindingRoot: number, + slotOffset: number, + pureFn: (v: any) => any, + exp: any, + thisArg?: any, +): any { const bindingIndex = bindingRoot + slotOffset; - return bindingUpdated(lView, bindingIndex, exp) ? - updateBinding(lView, bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) : - getPureFunctionReturnValue(lView, bindingIndex + 1); + return bindingUpdated(lView, bindingIndex, exp) + ? updateBinding(lView, bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) + : getPureFunctionReturnValue(lView, bindingIndex + 1); } - /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. @@ -328,14 +428,22 @@ export function pureFunction1Internal( * @returns Updated or cached value */ export function pureFunction2Internal( - lView: LView, bindingRoot: number, slotOffset: number, pureFn: (v1: any, v2: any) => any, - exp1: any, exp2: any, thisArg?: any): any { + lView: LView, + bindingRoot: number, + slotOffset: number, + pureFn: (v1: any, v2: any) => any, + exp1: any, + exp2: any, + thisArg?: any, +): any { const bindingIndex = bindingRoot + slotOffset; - return bindingUpdated2(lView, bindingIndex, exp1, exp2) ? - updateBinding( - lView, bindingIndex + 2, - thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) : - getPureFunctionReturnValue(lView, bindingIndex + 2); + return bindingUpdated2(lView, bindingIndex, exp1, exp2) + ? updateBinding( + lView, + bindingIndex + 2, + thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2), + ) + : getPureFunctionReturnValue(lView, bindingIndex + 2); } /** @@ -353,18 +461,25 @@ export function pureFunction2Internal( * @returns Updated or cached value */ export function pureFunction3Internal( - lView: LView, bindingRoot: number, slotOffset: number, - pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, - thisArg?: any): any { + lView: LView, + bindingRoot: number, + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any) => any, + exp1: any, + exp2: any, + exp3: any, + thisArg?: any, +): any { const bindingIndex = bindingRoot + slotOffset; - return bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) ? - updateBinding( - lView, bindingIndex + 3, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) : - getPureFunctionReturnValue(lView, bindingIndex + 3); + return bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) + ? updateBinding( + lView, + bindingIndex + 3, + thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3), + ) + : getPureFunctionReturnValue(lView, bindingIndex + 3); } - /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. @@ -382,15 +497,24 @@ export function pureFunction3Internal( * */ export function pureFunction4Internal( - lView: LView, bindingRoot: number, slotOffset: number, - pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, - thisArg?: any): any { + lView: LView, + bindingRoot: number, + slotOffset: number, + pureFn: (v1: any, v2: any, v3: any, v4: any) => any, + exp1: any, + exp2: any, + exp3: any, + exp4: any, + thisArg?: any, +): any { const bindingIndex = bindingRoot + slotOffset; - return bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) ? - updateBinding( - lView, bindingIndex + 4, - thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) : - getPureFunctionReturnValue(lView, bindingIndex + 4); + return bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) + ? updateBinding( + lView, + bindingIndex + 4, + thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4), + ) + : getPureFunctionReturnValue(lView, bindingIndex + 4); } /** @@ -409,13 +533,19 @@ export function pureFunction4Internal( * @returns Updated or cached value */ export function pureFunctionVInternal( - lView: LView, bindingRoot: number, slotOffset: number, pureFn: (...v: any[]) => any, - exps: any[], thisArg?: any): any { + lView: LView, + bindingRoot: number, + slotOffset: number, + pureFn: (...v: any[]) => any, + exps: any[], + thisArg?: any, +): any { let bindingIndex = bindingRoot + slotOffset; let different = false; for (let i = 0; i < exps.length; i++) { bindingUpdated(lView, bindingIndex++, exps[i]) && (different = true); } - return different ? updateBinding(lView, bindingIndex, pureFn.apply(thisArg, exps)) : - getPureFunctionReturnValue(lView, bindingIndex); + return different + ? updateBinding(lView, bindingIndex, pureFn.apply(thisArg, exps)) + : getPureFunctionReturnValue(lView, bindingIndex); } diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 7b44a51428fac..f6a820776c9c3 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -21,14 +21,20 @@ import {assertFirstCreatePass, assertLContainer} from './assert'; import {getNodeInjectable, locateDirectiveOrProvider} from './di'; import {storeCleanupWithContext} from './instructions/shared'; import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from './interfaces/container'; -import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node'; +import { + TContainerNode, + TElementContainerNode, + TElementNode, + TNode, + TNodeType, +} from './interfaces/node'; import {LQueries, LQuery, QueryFlags, TQueries, TQuery, TQueryMetadata} from './interfaces/query'; import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view'; import {assertTNodeType} from './node_assert'; import {getCurrentTNode, getLView, getTView} from './state'; class LQuery_ implements LQuery { - matches: (T|null)[]|null = null; + matches: (T | null)[] | null = null; constructor(public queryList: QueryList) {} clone(): LQuery { return new LQuery_(this.queryList); @@ -41,11 +47,11 @@ class LQuery_ implements LQuery { class LQueries_ implements LQueries { constructor(public queries: LQuery[] = []) {} - createEmbeddedView(tView: TView): LQueries|null { + createEmbeddedView(tView: TView): LQueries | null { const tQueries = tView.queries; if (tQueries !== null) { const noOfInheritedQueries = - tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length; + tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length; const viewLQueries: LQuery[] = []; // An embedded view has queries propagated from a declaration view at the beginning of the @@ -86,10 +92,12 @@ class LQueries_ implements LQueries { } export class TQueryMetadata_ implements TQueryMetadata { - public predicate: ProviderToken|string[]; + public predicate: ProviderToken | string[]; constructor( - predicate: ProviderToken|string[]|string, public flags: QueryFlags, - public read: any = null) { + predicate: ProviderToken | string[] | string, + public flags: QueryFlags, + public read: any = null, + ) { // Compiler might not be able to pre-optimize and split multiple selectors. if (typeof predicate === 'string') { this.predicate = splitQueryMultiSelectors(predicate); @@ -104,8 +112,10 @@ class TQueries_ implements TQueries { elementStart(tView: TView, tNode: TNode): void { ngDevMode && - assertFirstCreatePass( - tView, 'Queries should collect results on the first template pass only'); + assertFirstCreatePass( + tView, + 'Queries should collect results on the first template pass only', + ); for (let i = 0; i < this.queries.length; i++) { this.queries[i].elementStart(tView, tNode); } @@ -115,8 +125,8 @@ class TQueries_ implements TQueries { this.queries[i].elementEnd(tNode); } } - embeddedTView(tNode: TNode): TQueries|null { - let queriesForTemplateRef: TQuery[]|null = null; + embeddedTView(tNode: TNode): TQueries | null { + let queriesForTemplateRef: TQuery[] | null = null; for (let i = 0; i < this.length; i++) { const childQueryIndex = queriesForTemplateRef !== null ? queriesForTemplateRef.length : 0; @@ -137,8 +147,10 @@ class TQueries_ implements TQueries { template(tView: TView, tNode: TNode): void { ngDevMode && - assertFirstCreatePass( - tView, 'Queries should collect results on the first template pass only'); + assertFirstCreatePass( + tView, + 'Queries should collect results on the first template pass only', + ); for (let i = 0; i < this.queries.length; i++) { this.queries[i].template(tView, tNode); } @@ -159,7 +171,7 @@ class TQueries_ implements TQueries { } class TQuery_ implements TQuery { - matches: number[]|null = null; + matches: number[] | null = null; indexInDeclarationView = -1; crossesNgTemplate = false; @@ -177,7 +189,10 @@ class TQuery_ implements TQuery { */ private _appliesToNextNode = true; - constructor(public metadata: TQueryMetadata, nodeIndex: number = -1) { + constructor( + public metadata: TQueryMetadata, + nodeIndex: number = -1, + ) { this._declarationNodeIndex = nodeIndex; } @@ -197,7 +212,7 @@ class TQuery_ implements TQuery { this.elementStart(tView, tNode); } - embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null { + embeddedTView(tNode: TNode, childQueryIndex: number): TQuery | null { if (this.isApplyingToNode(tNode)) { this.crossesNgTemplate = true; // A marker indicating a `` element (a placeholder for query results from @@ -209,8 +224,10 @@ class TQuery_ implements TQuery { } private isApplyingToNode(tNode: TNode): boolean { - if (this._appliesToNextNode && - (this.metadata.flags & QueryFlags.descendants) !== QueryFlags.descendants) { + if ( + this._appliesToNextNode && + (this.metadata.flags & QueryFlags.descendants) !== QueryFlags.descendants + ) { const declarationNodeIdx = this._declarationNodeIndex; let parent = tNode.parent; // Determine if a given TNode is a "direct" child of a node on which a content query was @@ -223,8 +240,11 @@ class TQuery_ implements TQuery { // - : here we need // to go past `` to determine parent node (but we shouldn't traverse // up past the query's host node!). - while (parent !== null && (parent.type & TNodeType.ElementContainer) && - parent.index !== declarationNodeIdx) { + while ( + parent !== null && + parent.type & TNodeType.ElementContainer && + parent.index !== declarationNodeIdx + ) { parent = parent.parent; } return declarationNodeIdx === (parent !== null ? parent.index : -1); @@ -240,7 +260,10 @@ class TQuery_ implements TQuery { this.matchTNodeWithReadOption(tView, tNode, getIdxOfMatchingSelector(tNode, name)); // Also try matching the name to a provider since strings can be used as DI tokens too. this.matchTNodeWithReadOption( - tView, tNode, locateDirectiveOrProvider(tNode, tView, name, false, false)); + tView, + tNode, + locateDirectiveOrProvider(tNode, tView, name, false, false), + ); } } else { if ((predicate as any) === ViewEngine_TemplateRef) { @@ -249,21 +272,32 @@ class TQuery_ implements TQuery { } } else { this.matchTNodeWithReadOption( - tView, tNode, locateDirectiveOrProvider(tNode, tView, predicate, false, false)); + tView, + tNode, + locateDirectiveOrProvider(tNode, tView, predicate, false, false), + ); } } } - private matchTNodeWithReadOption(tView: TView, tNode: TNode, nodeMatchIdx: number|null): void { + private matchTNodeWithReadOption(tView: TView, tNode: TNode, nodeMatchIdx: number | null): void { if (nodeMatchIdx !== null) { const read = this.metadata.read; if (read !== null) { - if (read === ViewEngine_ElementRef || read === ViewContainerRef || - read === ViewEngine_TemplateRef && (tNode.type & TNodeType.Container)) { + if ( + read === ViewEngine_ElementRef || + read === ViewContainerRef || + (read === ViewEngine_TemplateRef && tNode.type & TNodeType.Container) + ) { this.addMatch(tNode.index, -2); } else { - const directiveOrProviderIdx = - locateDirectiveOrProvider(tNode, tView, read, false, false); + const directiveOrProviderIdx = locateDirectiveOrProvider( + tNode, + tView, + read, + false, + false, + ); if (directiveOrProviderIdx !== null) { this.addMatch(tNode.index, directiveOrProviderIdx); } @@ -291,7 +325,7 @@ class TQuery_ implements TQuery { * @param selector selector to match * @returns directive index, -1 or null if a selector didn't match any of the local names */ -function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { +function getIdxOfMatchingSelector(tNode: TNode, selector: string): number | null { const localNames = tNode.localNames; if (localNames !== null) { for (let i = 0; i < localNames.length; i += 2) { @@ -303,7 +337,6 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { return null; } - function createResultByTNodeType(tNode: TNode, currentView: LView): any { if (tNode.type & (TNodeType.AnyRNode | TNodeType.ElementContainer)) { return createElementRef(tNode, currentView); @@ -313,7 +346,6 @@ function createResultByTNodeType(tNode: TNode, currentView: LView): any { return null; } - function createResultForNode(lView: LView, tNode: TNode, matchingIdx: number, read: any): any { if (matchingIdx === -1) { // if read token and / or strategy is not specified, detect it using appropriate tNode type @@ -335,12 +367,16 @@ function createSpecialToken(lView: LView, tNode: TNode, read: any): any { } else if (read === ViewContainerRef) { ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer); return createContainerRef( - tNode as TElementNode | TContainerNode | TElementContainerNode, lView); + tNode as TElementNode | TContainerNode | TElementContainerNode, + lView, + ); } else { ngDevMode && - throwError( - `Special token to read should be one of ElementRef, TemplateRef or ViewContainerRef but got ${ - stringify(read)}.`); + throwError( + `Special token to read should be one of ElementRef, TemplateRef or ViewContainerRef but got ${stringify( + read, + )}.`, + ); } } @@ -350,12 +386,16 @@ function createSpecialToken(lView: LView, tNode: TNode, read: any): any { * doesn't change). */ function materializeViewResults( - tView: TView, lView: LView, tQuery: TQuery, queryIndex: number): T[] { + tView: TView, + lView: LView, + tQuery: TQuery, + queryIndex: number, +): T[] { const lQuery = lView[QUERIES]!.queries![queryIndex]; if (lQuery.matches === null) { const tViewData = tView.data; const tQueryMatches = tQuery.matches; - const result: Array = []; + const result: Array = []; for (let i = 0; tQueryMatches !== null && i < tQueryMatches.length; i += 2) { const matchedNodeIdx = tQueryMatches[i]; if (matchedNodeIdx < 0) { @@ -418,11 +458,9 @@ function collectQueryResults(tView: TView, lView: LView, queryIndex: number, return result; } - - export function loadQueryInternal(lView: LView, queryIndex: number): QueryList { ngDevMode && - assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query'); + assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query'); ngDevMode && assertIndexInRange(lView[QUERIES]!.queries, queryIndex); return lView[QUERIES]!.queries[queryIndex].queryList; } @@ -434,7 +472,8 @@ export function loadQueryInternal(lView: LView, queryIndex: number): QueryLis */ function createLQuery(tView: TView, lView: LView, flags: QueryFlags): number { const queryList = new QueryList( - (flags & QueryFlags.emitDistinctChangesOnly) === QueryFlags.emitDistinctChangesOnly); + (flags & QueryFlags.emitDistinctChangesOnly) === QueryFlags.emitDistinctChangesOnly, + ); storeCleanupWithContext(tView, lView, queryList, queryList.destroy); @@ -443,7 +482,10 @@ function createLQuery(tView: TView, lView: LView, flags: QueryFlags): number } export function createViewQuery( - predicate: ProviderToken|string[]|string, flags: QueryFlags, read?: any): number { + predicate: ProviderToken | string[] | string, + flags: QueryFlags, + read?: any, +): number { ngDevMode && assertNumber(flags, 'Expecting flags'); const tView = getTView(); if (tView.firstCreatePass) { @@ -457,8 +499,11 @@ export function createViewQuery( } export function createContentQuery( - directiveIndex: number, predicate: ProviderToken|string[]|string, flags: QueryFlags, - read?: ProviderToken): number { + directiveIndex: number, + predicate: ProviderToken | string[] | string, + flags: QueryFlags, + read?: ProviderToken, +): number { ngDevMode && assertNumber(flags, 'Expecting flags'); const tView = getTView(); if (tView.firstCreatePass) { @@ -475,7 +520,7 @@ export function createContentQuery( /** Splits multiple selectors in the locator. */ function splitQueryMultiSelectors(locator: string): string[] { - return locator.split(',').map(s => s.trim()); + return locator.split(',').map((s) => s.trim()); } export function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number): void { @@ -485,8 +530,9 @@ export function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: export function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) { const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []); - const lastSavedDirectiveIndex = - tViewContentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1; + const lastSavedDirectiveIndex = tViewContentQueries.length + ? tViewContentQueries[tViewContentQueries.length - 1] + : -1; if (directiveIndex !== lastSavedDirectiveIndex) { tViewContentQueries.push(tView.queries!.length - 1, directiveIndex); } @@ -505,6 +551,7 @@ export function getTQuery(tView: TView, index: number): TQuery { export function getQueryResults(lView: LView, queryIndex: number): V[] { const tView = lView[TVIEW]; const tQuery = getTQuery(tView, queryIndex); - return tQuery.crossesNgTemplate ? collectQueryResults(tView, lView, queryIndex, []) : - materializeViewResults(tView, lView, tQuery, queryIndex); + return tQuery.crossesNgTemplate + ? collectQueryResults(tView, lView, queryIndex, []) + : materializeViewResults(tView, lView, tQuery, queryIndex); } diff --git a/packages/core/src/render3/query_reactive.ts b/packages/core/src/render3/query_reactive.ts index 07c9382186093..afafee0347498 100644 --- a/packages/core/src/render3/query_reactive.ts +++ b/packages/core/src/render3/query_reactive.ts @@ -19,7 +19,7 @@ import {Signal} from './reactivity/api'; import {signal, WritableSignal} from './reactivity/signal'; import {getLView} from './state'; -interface QuerySignalNode extends ComputedNode> { +interface QuerySignalNode extends ComputedNode> { _lView?: LView; _queryIndex?: number; _queryList?: QueryList; @@ -28,7 +28,7 @@ interface QuerySignalNode extends ComputedNode> { * Stores the last seen, flattened results for a query. This is to avoid marking the signal result * computed as dirty when there was view manipulation that didn't impact final results. */ - _flatValue?: T|ReadonlyArray; + _flatValue?: T | ReadonlyArray; } /** @@ -55,8 +55,9 @@ function createQuerySignalFn(firstOnly: boolean, required: boolean) { if (required && value === undefined) { throw new RuntimeError( - RuntimeErrorCode.REQUIRED_QUERY_NO_VALUE, - ngDevMode && 'Child query result is required but no value is available.'); + RuntimeErrorCode.REQUIRED_QUERY_NO_VALUE, + ngDevMode && 'Child query result is required but no value is available.', + ); } return value; @@ -72,8 +73,10 @@ function createQuerySignalFn(firstOnly: boolean, required: boolean) { return signalFn; } -export function createSingleResultOptionalQuerySignalFn(): Signal { - return createQuerySignalFn(/* firstOnly */ true, /* required */ false) as Signal; +export function createSingleResultOptionalQuerySignalFn(): Signal { + return createQuerySignalFn(/* firstOnly */ true, /* required */ false) as Signal< + ReadT | undefined + >; } export function createSingleResultRequiredQuerySignalFn(): Signal { @@ -81,8 +84,9 @@ export function createSingleResultRequiredQuerySignalFn(): Signal } export function createMultiResultQuerySignalFn(): Signal> { - return createQuerySignalFn(/* firstOnly */ false, /* required */ false) as - Signal>; + return createQuerySignalFn(/* firstOnly */ false, /* required */ false) as Signal< + ReadonlyArray + >; } export function bindQueryToSignal(target: Signal, queryIndex: number): void { @@ -90,10 +94,10 @@ export function bindQueryToSignal(target: Signal, queryIndex: number): node._lView = getLView(); node._queryIndex = queryIndex; node._queryList = loadQueryInternal(node._lView, queryIndex); - node._queryList.onDirty(() => node._dirtyCounter.update(v => v + 1)); + node._queryList.onDirty(() => node._dirtyCounter.update((v) => v + 1)); } -function refreshSignalQuery(node: QuerySignalNode, firstOnly: boolean): V|ReadonlyArray { +function refreshSignalQuery(node: QuerySignalNode, firstOnly: boolean): V | ReadonlyArray { const lView = node._lView; const queryIndex = node._queryIndex; @@ -108,7 +112,7 @@ function refreshSignalQuery(node: QuerySignalNode, firstOnly: boolean): V| // creation pass didn't finish) and a query might have partial results, but we don't want to // return those - instead we do delay results collection until all nodes had a chance of matching // and we can present consistent, "atomic" (on a view level) results. - if (lView === undefined || queryIndex === undefined || (lView[FLAGS] & LViewFlags.CreationMode)) { + if (lView === undefined || queryIndex === undefined || lView[FLAGS] & LViewFlags.CreationMode) { return (firstOnly ? undefined : EMPTY_ARRAY) as V; } @@ -124,7 +128,7 @@ function refreshSignalQuery(node: QuerySignalNode, firstOnly: boolean): V| // QueryList in the signal-based queries (perf follow-up) const resultChanged = (queryList as any as {_changesDetected: boolean})._changesDetected; if (resultChanged || node._flatValue === undefined) { - return node._flatValue = queryList.toArray(); + return (node._flatValue = queryList.toArray()); } return node._flatValue; } diff --git a/packages/core/src/render3/queue_state_update.ts b/packages/core/src/render3/queue_state_update.ts index 7f60c721e2b9c..8262758c5a39d 100644 --- a/packages/core/src/render3/queue_state_update.ts +++ b/packages/core/src/render3/queue_state_update.ts @@ -26,7 +26,7 @@ import {internalAfterNextRender} from './after_render_hooks'; * in production. */ -export function queueStateUpdate(callback: VoidFunction, options?: {injector?: Injector;}): void { +export function queueStateUpdate(callback: VoidFunction, options?: {injector?: Injector}): void { !options && assertInInjectionContext(queueStateUpdate); const injector = options?.injector ?? inject(Injector); const appRef = injector.get(ApplicationRef); diff --git a/packages/core/src/render3/reactive_lview_consumer.ts b/packages/core/src/render3/reactive_lview_consumer.ts index aae10bc9835a3..0f18a26bbaa39 100644 --- a/packages/core/src/render3/reactive_lview_consumer.ts +++ b/packages/core/src/render3/reactive_lview_consumer.ts @@ -13,7 +13,7 @@ import {markAncestorsForTraversal} from './util/view_utils'; let freeConsumers: ReactiveLViewConsumer[] = []; export interface ReactiveLViewConsumer extends ReactiveNode { - lView: LView|null; + lView: LView | null; slot: typeof REACTIVE_TEMPLATE_CONSUMER; } @@ -28,7 +28,7 @@ export function getOrBorrowReactiveLViewConsumer(lView: LView): ReactiveLViewCon function borrowReactiveLViewConsumer(lView: LView): ReactiveLViewConsumer { const consumer: ReactiveLViewConsumer = - freeConsumers.pop() ?? Object.create(REACTIVE_LVIEW_CONSUMER_NODE); + freeConsumers.pop() ?? Object.create(REACTIVE_LVIEW_CONSUMER_NODE); consumer.lView = lView; return consumer; } @@ -42,7 +42,7 @@ export function maybeReturnReactiveLViewConsumer(consumer: ReactiveLViewConsumer freeConsumers.push(consumer); } -const REACTIVE_LVIEW_CONSUMER_NODE: Omit = { +const REACTIVE_LVIEW_CONSUMER_NODE: Omit = { ...REACTIVE_NODE, consumerIsAlwaysLive: true, consumerMarkedDirty: (node: ReactiveLViewConsumer) => { diff --git a/packages/core/src/render3/reactivity/api.ts b/packages/core/src/render3/reactivity/api.ts index 633eb235000d0..be321f4138568 100644 --- a/packages/core/src/render3/reactivity/api.ts +++ b/packages/core/src/render3/reactivity/api.ts @@ -16,7 +16,7 @@ import {SIGNAL} from '@angular/core/primitives/signals'; * * Ordinary values can be turned into `Signal`s with the `signal` function. */ -export type Signal = (() => T)&{ +export type Signal = (() => T) & { [SIGNAL]: unknown; }; diff --git a/packages/core/src/render3/reactivity/asserts.ts b/packages/core/src/render3/reactivity/asserts.ts index e6cb3d7db6b64..7c174b8f45610 100644 --- a/packages/core/src/render3/reactivity/asserts.ts +++ b/packages/core/src/render3/reactivity/asserts.ts @@ -23,9 +23,11 @@ export function assertNotInReactiveContext(debugFn: Function, extraContext?: str // from being retained in the bundle regardless of minification. if (getActiveConsumer() !== null) { throw new RuntimeError( - RuntimeErrorCode.ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT, - ngDevMode && - `${debugFn.name}() cannot be called from within a reactive context.${ - extraContext ? ` ${extraContext}` : ''}`); + RuntimeErrorCode.ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT, + ngDevMode && + `${debugFn.name}() cannot be called from within a reactive context.${ + extraContext ? ` ${extraContext}` : '' + }`, + ); } } diff --git a/packages/core/src/render3/reactivity/effect.ts b/packages/core/src/render3/reactivity/effect.ts index 34ee25f5af9a0..be05f978d2b65 100644 --- a/packages/core/src/render3/reactivity/effect.ts +++ b/packages/core/src/render3/reactivity/effect.ts @@ -23,7 +23,6 @@ import {assertNotInReactiveContext} from './asserts'; import {performanceMarkFeature} from '../../util/performance'; import {PendingTasks} from '../../pending_tasks'; - /** * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed * before the next effect run. The cleanup function makes it possible to "cancel" any work that the @@ -84,15 +83,15 @@ export abstract class EffectScheduler { */ export class ZoneAwareEffectScheduler implements EffectScheduler { private queuedEffectCount = 0; - private queues = new Map>(); + private queues = new Map>(); private readonly pendingTasks = inject(PendingTasks); - private taskId: number|null = null; + private taskId: number | null = null; scheduleEffect(handle: SchedulableEffect): void { this.enqueue(handle); if (this.taskId === null) { - const taskId = this.taskId = this.pendingTasks.add(); + const taskId = (this.taskId = this.pendingTasks.add()); queueMicrotask(() => { this.flush(); this.pendingTasks.remove(taskId); @@ -153,16 +152,22 @@ export class ZoneAwareEffectScheduler implements EffectScheduler { * available/requested. */ class EffectHandle implements EffectRef, SchedulableEffect { - unregisterOnDestroy: (() => void)|undefined; + unregisterOnDestroy: (() => void) | undefined; readonly watcher: Watch; constructor( - private scheduler: EffectScheduler, - private effectFn: (onCleanup: EffectCleanupRegisterFn) => void, - public creationZone: Zone|null, destroyRef: DestroyRef|null, private injector: Injector, - allowSignalWrites: boolean) { + private scheduler: EffectScheduler, + private effectFn: (onCleanup: EffectCleanupRegisterFn) => void, + public creationZone: Zone | null, + destroyRef: DestroyRef | null, + private injector: Injector, + allowSignalWrites: boolean, + ) { this.watcher = createWatch( - (onCleanup) => this.runEffect(onCleanup), () => this.schedule(), allowSignalWrites); + (onCleanup) => this.runEffect(onCleanup), + () => this.schedule(), + allowSignalWrites, + ); this.unregisterOnDestroy = destroyRef?.onDestroy(() => this.destroy()); } @@ -243,23 +248,29 @@ export interface CreateEffectOptions { * @developerPreview */ export function effect( - effectFn: (onCleanup: EffectCleanupRegisterFn) => void, - options?: CreateEffectOptions): EffectRef { + 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.'); + 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(APP_EFFECT_SCHEDULER), effectFn, - (typeof Zone === 'undefined') ? null : Zone.current, destroyRef, injector, - options?.allowSignalWrites ?? false); + injector.get(APP_EFFECT_SCHEDULER), + 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 @@ -269,7 +280,7 @@ export function effect( // 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| null; + const cdr = injector.get(ChangeDetectorRef, null, {optional: true}) as ViewRef | 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 diff --git a/packages/core/src/render3/reactivity/signal.ts b/packages/core/src/render3/reactivity/signal.ts index 4f55ac9ce81cd..912e8770bb9cb 100644 --- a/packages/core/src/render3/reactivity/signal.ts +++ b/packages/core/src/render3/reactivity/signal.ts @@ -6,7 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {createSignal, SIGNAL, SignalGetter, SignalNode, signalSetFn, signalUpdateFn} from '@angular/core/primitives/signals'; +import { + createSignal, + SIGNAL, + SignalGetter, + SignalNode, + signalSetFn, + signalUpdateFn, +} from '@angular/core/primitives/signals'; import {performanceMarkFeature} from '../../util/performance'; @@ -44,7 +51,7 @@ export interface WritableSignal extends Signal { * Utility function used during template type checking to extract the value from a `WritableSignal`. * @codeGenApi */ -export function ɵunwrapWritableSignal(value: T|{[ɵWRITABLE_SIGNAL]: T}): T { +export function ɵunwrapWritableSignal(value: T | {[ɵWRITABLE_SIGNAL]: T}): T { // Note: the function uses `WRITABLE_SIGNAL` as a brand instead of `WritableSignal`, // because the latter incorrectly unwraps non-signal getter functions. return null!; @@ -65,7 +72,7 @@ export interface CreateSignalOptions { */ export function signal(initialValue: T, options?: CreateSignalOptions): WritableSignal { performanceMarkFeature('NgSignals'); - const signalFn = createSignal(initialValue) as SignalGetter& WritableSignal; + const signalFn = createSignal(initialValue) as SignalGetter & WritableSignal; const node = signalFn[SIGNAL]; if (options?.equal) { node.equal = options.equal; @@ -81,7 +88,7 @@ export function signal(initialValue: T, options?: CreateSignalOptions): Wr } export function signalAsReadonlyFn(this: SignalGetter): Signal { - const node = this[SIGNAL] as SignalNode& {readonlyFn?: Signal}; + const node = this[SIGNAL] as SignalNode & {readonlyFn?: Signal}; if (node.readonlyFn === undefined) { const readonlyFn = () => this(); (readonlyFn as any)[SIGNAL] = node; diff --git a/packages/core/src/render3/scope.ts b/packages/core/src/render3/scope.ts index c9d5846fe1e73..9290fac147e13 100644 --- a/packages/core/src/render3/scope.ts +++ b/packages/core/src/render3/scope.ts @@ -14,7 +14,12 @@ import {EMPTY_ARRAY} from '../util/empty'; import {extractDefListOrFactory, getNgModuleDef} from './definition'; import {depsTracker} from './deps_tracker/deps_tracker'; -import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator, RawScopeInfoFromDecorator} from './interfaces/definition'; +import { + ComponentDef, + ComponentType, + NgModuleScopeInfoFromDecorator, + RawScopeInfoFromDecorator, +} from './interfaces/definition'; import {isModuleWithProviders} from './jit/util'; /** @@ -27,8 +32,10 @@ import {isModuleWithProviders} from './jit/util'; * @codeGenApi */ export function ɵɵsetComponentScope( - type: ComponentType, directives: Type[]|(() => Type[]), - pipes: Type[]|(() => Type[])): void { + type: ComponentType, + directives: Type[] | (() => Type[]), + pipes: Type[] | (() => Type[]), +): void { const def = type.ɵcmp as ComponentDef; def.directiveDefs = extractDefListOrFactory(directives, /* pipeDef */ false); def.pipeDefs = extractDefListOrFactory(pipes, /* pipeDef */ true); @@ -60,8 +67,9 @@ export function ɵɵsetNgModuleScope(type: any, scope: NgModuleScopeInfoFromDeco }); } -function convertToTypeArray(values: Type[]|(() => Type[])| - RawScopeInfoFromDecorator[]): Type[]|(() => Type[]) { +function convertToTypeArray( + values: Type[] | (() => Type[]) | RawScopeInfoFromDecorator[], +): Type[] | (() => Type[]) { if (typeof values === 'function') { return values; } @@ -76,5 +84,5 @@ function convertToTypeArray(values: Type[]|(() => Type[])| } function maybeUnwrapModuleWithProviders(value: any): Type { - return isModuleWithProviders(value) ? value.ngModule : value as Type; + return isModuleWithProviders(value) ? value.ngModule : (value as Type); } diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 37c5b15d8aea5..9dae40cfc4cfe 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -7,16 +7,33 @@ */ import {InjectFlags} from '../di/interface/injector'; -import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual, throwError} from '../util/assert'; +import { + assertDefined, + assertEqual, + assertGreaterThanOrEqual, + assertLessThan, + assertNotEqual, + throwError, +} from '../util/assert'; import {assertLViewOrUndefined, assertTNodeForLView, assertTNodeForTView} from './assert'; import {DirectiveDef} from './interfaces/definition'; import {TNode, TNodeType} from './interfaces/node'; -import {CONTEXT, DECLARATION_VIEW, HEADER_OFFSET, LView, OpaqueViewState, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view'; +import { + CONTEXT, + DECLARATION_VIEW, + HEADER_OFFSET, + LView, + OpaqueViewState, + T_HOST, + TData, + TVIEW, + TView, + TViewType, +} from './interfaces/view'; import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; import {getTNode, walkUpViews} from './util/view_utils'; - /** * */ @@ -33,7 +50,7 @@ interface LFrame { * * This is used to cache existing LFrames to relieve the memory pressure. */ - child: LFrame|null; + child: LFrame | null; /** * State of the current view being processed. @@ -56,7 +73,7 @@ interface LFrame { * * This is used in conjunction with `isParent`. */ - currentTNode: TNode|null; + currentTNode: TNode | null; /** * If `isParent` is: @@ -83,7 +100,7 @@ interface LFrame { * * e.g. const inner = x().$implicit; const outer = x().$implicit; */ - contextLView: LView|null; + contextLView: LView | null; /** * Store the element depth count. This is used to identify the root elements of the template @@ -96,8 +113,7 @@ interface LFrame { /** * Current namespace to be used when creating elements */ - currentNamespace: string|null; - + currentNamespace: string | null; /** * The root index from which pure function instructions should calculate their binding @@ -183,7 +199,7 @@ interface InstructionState { * * ``` */ - skipHydrationRootTNode: TNode|null; + skipHydrationRootTNode: TNode | null; } const instructionState: InstructionState = { @@ -218,7 +234,6 @@ export function specOnlyIsInstructionStateEmpty(): boolean { return instructionState.lFrame.parent === null; } - export function getElementDepthCount() { return instructionState.lFrame.elementDepthCount; } @@ -344,20 +359,18 @@ export function ɵɵrestoreView(viewToRestore: OpaqueViewState): T { return (viewToRestore as any as LView)[CONTEXT] as unknown as T; } - /** * Clears the view set in `ɵɵrestoreView` from memory. Returns the passed in * value so that it can be used as a return value of an instruction. * * @codeGenApi */ -export function ɵɵresetView(value?: T): T|undefined { +export function ɵɵresetView(value?: T): T | undefined { instructionState.lFrame.contextLView = null; return value; } - -export function getCurrentTNode(): TNode|null { +export function getCurrentTNode(): TNode | null { let currentTNode = getCurrentTNodePlaceholderOk(); while (currentTNode !== null && currentTNode.type === TNodeType.Placeholder) { currentTNode = currentTNode.parent; @@ -365,17 +378,17 @@ export function getCurrentTNode(): TNode|null { return currentTNode; } -export function getCurrentTNodePlaceholderOk(): TNode|null { +export function getCurrentTNodePlaceholderOk(): TNode | null { return instructionState.lFrame.currentTNode; } -export function getCurrentParentTNode(): TNode|null { +export function getCurrentParentTNode(): TNode | null { const lFrame = instructionState.lFrame; const currentTNode = lFrame.currentTNode; return lFrame.isParent ? currentTNode : currentTNode!.parent; } -export function setCurrentTNode(tNode: TNode|null, isParent: boolean) { +export function setCurrentTNode(tNode: TNode | null, isParent: boolean) { ngDevMode && tNode && assertTNodeForTView(tNode, instructionState.lFrame.tView); const lFrame = instructionState.lFrame; lFrame.currentTNode = tNode; @@ -429,7 +442,7 @@ export function getBindingIndex(): number { } export function setBindingIndex(value: number): number { - return instructionState.lFrame.bindingIndex = value; + return (instructionState.lFrame.bindingIndex = value); } export function nextBindingIndex(): number { @@ -463,7 +476,9 @@ export function setInI18nBlock(isInI18nBlock: boolean): void { * whose `hostBindings` are being processed. */ export function setBindingRootForHostBindings( - bindingRootIndex: number, currentDirectiveIndex: number) { + bindingRootIndex: number, + currentDirectiveIndex: number, +) { const lFrame = instructionState.lFrame; lFrame.bindingIndex = lFrame.bindingRootIndex = bindingRootIndex; setCurrentDirectiveIndex(currentDirectiveIndex); @@ -493,9 +508,9 @@ export function setCurrentDirectiveIndex(currentDirectiveIndex: number): void { * * @param tData Current `TData` where the `DirectiveDef` will be looked up at. */ -export function getCurrentDirectiveDef(tData: TData): DirectiveDef|null { +export function getCurrentDirectiveDef(tData: TData): DirectiveDef | null { const currentDirectiveIndex = instructionState.lFrame.currentDirectiveIndex; - return currentDirectiveIndex === -1 ? null : tData[currentDirectiveIndex] as DirectiveDef; + return currentDirectiveIndex === -1 ? null : (tData[currentDirectiveIndex] as DirectiveDef); } export function getCurrentQueryIndex(): number { @@ -511,7 +526,7 @@ export function setCurrentQueryIndex(value: number): void { * * @param lView an `LView` that we want to find parent `TNode` for. */ -function getDeclarationTNode(lView: LView): TNode|null { +function getDeclarationTNode(lView: LView): TNode | null { const tView = lView[TVIEW]; // Return the declaration parent for embedded views @@ -585,7 +600,7 @@ export function enterDI(lView: LView, tNode: TNode, flags: InjectFlags) { } ngDevMode && assertTNodeForLView(tNode, lView); - const lFrame = instructionState.lFrame = allocLFrame(); + const lFrame = (instructionState.lFrame = allocLFrame()); lFrame.currentTNode = tNode; lFrame.lView = lView; @@ -639,7 +654,7 @@ function allocLFrame() { return newLFrame; } -function createLFrame(parent: LFrame|null): LFrame { +function createLFrame(parent: LFrame | null): LFrame { const lFrame: LFrame = { currentTNode: null, isParent: true, @@ -657,7 +672,7 @@ function createLFrame(parent: LFrame|null): LFrame { child: null, inI18n: false, }; - parent !== null && (parent.child = lFrame); // link the new LFrame for reuse. + parent !== null && (parent.child = lFrame); // link the new LFrame for reuse. return lFrame; } @@ -709,8 +724,10 @@ export function leaveView() { } export function nextContextImpl(level: number): T { - const contextLView = instructionState.lFrame.contextLView = - walkUpViews(level, instructionState.lFrame.contextLView!); + const contextLView = (instructionState.lFrame.contextLView = walkUpViews( + level, + instructionState.lFrame.contextLView!, + )); return contextLView[CONTEXT] as unknown as T; } @@ -734,11 +751,15 @@ export function getSelectedIndex() { * run if and when the provided `index` value is different from the current selected index value.) */ export function setSelectedIndex(index: number) { - ngDevMode && index !== -1 && - assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).'); ngDevMode && - assertLessThan( - index, instructionState.lFrame.lView.length, 'Can\'t set index passed end of LView'); + index !== -1 && + assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).'); + ngDevMode && + assertLessThan( + index, + instructionState.lFrame.lView.length, + "Can't set index passed end of LView", + ); instructionState.lFrame.selectedIndex = index; } @@ -786,7 +807,7 @@ export function namespaceHTMLInternal() { instructionState.lFrame.currentNamespace = null; } -export function getNamespace(): string|null { +export function getNamespace(): string | null { return instructionState.lFrame.currentNamespace; } diff --git a/packages/core/src/render3/styling/class_differ.ts b/packages/core/src/render3/styling/class_differ.ts index 98868d8d7103b..04cac148c7f62 100644 --- a/packages/core/src/render3/styling/class_differ.ts +++ b/packages/core/src/render3/styling/class_differ.ts @@ -9,7 +9,6 @@ import {assertNotEqual} from '../../util/assert'; import {CharCode} from '../../util/char_code'; - /** * Returns an index of `classToSearch` in `className` taking token boundaries into account. * @@ -21,7 +20,10 @@ import {CharCode} from '../../util/char_code'; * @returns an index of the located class (or -1 if not found) */ export function classIndexOf( - className: string, classToSearch: string, startingIndex: number): number { + className: string, + classToSearch: string, + startingIndex: number, +): number { ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.'); let end = className.length; while (true) { @@ -30,8 +32,10 @@ export function classIndexOf( if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= CharCode.SPACE) { // Ensure that it has leading whitespace const length = classToSearch.length; - if (foundIndex + length === end || - className.charCodeAt(foundIndex + length) <= CharCode.SPACE) { + if ( + foundIndex + length === end || + className.charCodeAt(foundIndex + length) <= CharCode.SPACE + ) { // Ensure that it has trailing whitespace return foundIndex; } diff --git a/packages/core/src/render3/styling/static_styling.ts b/packages/core/src/render3/styling/static_styling.ts index 3bf5ecbded600..8ab0c16b62a1b 100644 --- a/packages/core/src/render3/styling/static_styling.ts +++ b/packages/core/src/render3/styling/static_styling.ts @@ -8,7 +8,7 @@ import {concatStringsWithSpace} from '../../util/stringify'; import {assertFirstCreatePass} from '../assert'; -import { AttributeMarker } from '../interfaces/attribute_marker'; +import {AttributeMarker} from '../interfaces/attribute_marker'; import {TAttributes, TNode} from '../interfaces/node'; import {getTView} from '../state'; @@ -24,12 +24,15 @@ import {getTView} from '../state'; * - `true` Write to `TNode.styles` / `TNode.classes` */ export function computeStaticStyling( - tNode: TNode, attrs: TAttributes|null, writeToHost: boolean): void { + tNode: TNode, + attrs: TAttributes | null, + writeToHost: boolean, +): void { ngDevMode && - assertFirstCreatePass(getTView(), 'Expecting to be called in first template pass only'); - let styles: string|null = writeToHost ? tNode.styles : null; - let classes: string|null = writeToHost ? tNode.classes : null; - let mode: AttributeMarker|0 = 0; + assertFirstCreatePass(getTView(), 'Expecting to be called in first template pass only'); + let styles: string | null = writeToHost ? tNode.styles : null; + let classes: string | null = writeToHost ? tNode.classes : null; + let mode: AttributeMarker | 0 = 0; if (attrs !== null) { for (let i = 0; i < attrs.length; i++) { const value = attrs[i]; @@ -44,6 +47,6 @@ export function computeStaticStyling( } } } - writeToHost ? tNode.styles = styles : tNode.stylesWithoutHost = styles; - writeToHost ? tNode.classes = classes : tNode.classesWithoutHost = classes; + writeToHost ? (tNode.styles = styles) : (tNode.stylesWithoutHost = styles); + writeToHost ? (tNode.classes = classes) : (tNode.classesWithoutHost = classes); } diff --git a/packages/core/src/render3/styling/style_binding_list.ts b/packages/core/src/render3/styling/style_binding_list.ts index 401d7e536f275..d5773b9d35013 100644 --- a/packages/core/src/render3/styling/style_binding_list.ts +++ b/packages/core/src/render3/styling/style_binding_list.ts @@ -10,11 +10,21 @@ import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils'; import {assertEqual, assertIndexInRange, assertNotEqual} from '../../util/assert'; import {assertFirstUpdatePass} from '../assert'; import {TNode} from '../interfaces/node'; -import {getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange, TStylingKey, TStylingKeyPrimitive, TStylingRange} from '../interfaces/styling'; +import { + getTStylingRangeNext, + getTStylingRangePrev, + setTStylingRangeNext, + setTStylingRangeNextDuplicate, + setTStylingRangePrev, + setTStylingRangePrevDuplicate, + toTStylingRange, + TStylingKey, + TStylingKeyPrimitive, + TStylingRange, +} from '../interfaces/styling'; import {TData} from '../interfaces/view'; import {getTView} from '../state'; - /** * NOTE: The word `styling` is used interchangeably as style or class styling. * @@ -191,8 +201,13 @@ let __unused_const_as_closure_does_not_like_standalone_comment_blocks__: undefin * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.) */ export function insertTStylingBinding( - tData: TData, tNode: TNode, tStylingKeyWithStatic: TStylingKey, index: number, - isHostBinding: boolean, isClassBinding: boolean): void { + tData: TData, + tNode: TNode, + tStylingKeyWithStatic: TStylingKey, + index: number, + isHostBinding: boolean, + isClassBinding: boolean, +): void { ngDevMode && assertFirstUpdatePass(getTView()); let tBindings = isClassBinding ? tNode.classBindings : tNode.styleBindings; let tmplHead = getTStylingRangePrev(tBindings); @@ -204,10 +219,12 @@ export function insertTStylingBinding( if (Array.isArray(tStylingKeyWithStatic)) { // We are case when the `TStylingKey` contains static fields as well. const staticKeyValueArray = tStylingKeyWithStatic as KeyValueArray; - tStylingKey = staticKeyValueArray[1]; // unwrap. + tStylingKey = staticKeyValueArray[1]; // unwrap. // We need to check if our key is present in the static so that we can mark it as duplicate. - if (tStylingKey === null || - keyValueArrayIndexOf(staticKeyValueArray, tStylingKey as string) > 0) { + if ( + tStylingKey === null || + keyValueArrayIndexOf(staticKeyValueArray, tStylingKey as string) > 0 + ) { // tStylingKey is present in the statics, need to mark it as duplicate. isKeyDuplicateOfStatic = true; } @@ -229,8 +246,10 @@ export function insertTStylingBinding( // binding to point to this one if (previousNode !== 0) { // We need to update the template-tail value to point to us. - tData[previousNode + 1] = - setTStylingRangeNext(tData[previousNode + 1] as TStylingRange, index); + tData[previousNode + 1] = setTStylingRangeNext( + tData[previousNode + 1] as TStylingRange, + index, + ); } // The "previous" of the template binding head should point to this host binding tData[tmplHead + 1] = setTStylingRangePrev(tData[tmplHead + 1] as TStylingRange, index); @@ -250,9 +269,11 @@ export function insertTStylingBinding( // We need to set this binding's "previous" to the current template tail tData[index + 1] = toTStylingRange(tmplTail, 0); ngDevMode && - assertEqual( - tmplHead !== 0 && tmplTail === 0, false, - 'Adding template bindings after hostBindings is not allowed.'); + assertEqual( + tmplHead !== 0 && tmplTail === 0, + false, + 'Adding template bindings after hostBindings is not allowed.', + ); if (tmplHead === 0) { tmplHead = index; } else { @@ -290,16 +311,23 @@ export function insertTStylingBinding( * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.) */ function markDuplicateOfResidualStyling( - tNode: TNode, tStylingKey: TStylingKey, tData: TData, index: number, isClassBinding: boolean) { + tNode: TNode, + tStylingKey: TStylingKey, + tData: TData, + index: number, + isClassBinding: boolean, +) { const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles; - if (residual != null /* or undefined */ && typeof tStylingKey == 'string' && - keyValueArrayIndexOf(residual, tStylingKey) >= 0) { + if ( + residual != null /* or undefined */ && + typeof tStylingKey == 'string' && + keyValueArrayIndexOf(residual, tStylingKey) >= 0 + ) { // We have duplicate in the residual so mark ourselves as duplicate. tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange); } } - /** * Marks `TStyleValue`s as duplicates if another style binding in the list has the same * `TStyleValue`. @@ -357,15 +385,16 @@ function markDuplicateOfResidualStyling( * - `false` for next (higher priority). */ function markDuplicates( - tData: TData, - tStylingKey: TStylingKeyPrimitive, - index: number, - isPrevDir: boolean, + tData: TData, + tStylingKey: TStylingKeyPrimitive, + index: number, + isPrevDir: boolean, ) { const tStylingAtIndex = tData[index + 1] as TStylingRange; const isMap = tStylingKey === null; - let cursor = - isPrevDir ? getTStylingRangePrev(tStylingAtIndex) : getTStylingRangeNext(tStylingAtIndex); + let cursor = isPrevDir + ? getTStylingRangePrev(tStylingAtIndex) + : getTStylingRangeNext(tStylingAtIndex); let foundDuplicate = false; // We keep iterating as long as we have a cursor // AND either: @@ -378,16 +407,19 @@ function markDuplicates( const tStyleRangeAtCursor = tData[cursor + 1] as TStylingRange; if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) { foundDuplicate = true; - tData[cursor + 1] = isPrevDir ? setTStylingRangeNextDuplicate(tStyleRangeAtCursor) : - setTStylingRangePrevDuplicate(tStyleRangeAtCursor); + tData[cursor + 1] = isPrevDir + ? setTStylingRangeNextDuplicate(tStyleRangeAtCursor) + : setTStylingRangePrevDuplicate(tStyleRangeAtCursor); } - cursor = isPrevDir ? getTStylingRangePrev(tStyleRangeAtCursor) : - getTStylingRangeNext(tStyleRangeAtCursor); + cursor = isPrevDir + ? getTStylingRangePrev(tStyleRangeAtCursor) + : getTStylingRangeNext(tStyleRangeAtCursor); } if (foundDuplicate) { // if we found a duplicate, than mark ourselves. - tData[index + 1] = isPrevDir ? setTStylingRangePrevDuplicate(tStylingAtIndex) : - setTStylingRangeNextDuplicate(tStylingAtIndex); + tData[index + 1] = isPrevDir + ? setTStylingRangePrevDuplicate(tStylingAtIndex) + : setTStylingRangeNextDuplicate(tStylingAtIndex); } } @@ -411,22 +443,23 @@ function markDuplicates( */ function isStylingMatch(tStylingKeyCursor: TStylingKey, tStylingKey: TStylingKeyPrimitive) { ngDevMode && - assertNotEqual( - Array.isArray(tStylingKey), true, 'Expected that \'tStylingKey\' has been unwrapped'); + assertNotEqual( + Array.isArray(tStylingKey), + true, + "Expected that 'tStylingKey' has been unwrapped", + ); if ( - tStylingKeyCursor === null || // If the cursor is `null` it means that we have map at that - // location so we must assume that we have a match. - tStylingKey == null || // If `tStylingKey` is `null` then it is a map therefor assume that it - // contains a match. - (Array.isArray(tStylingKeyCursor) ? tStylingKeyCursor[1] : tStylingKeyCursor) === - tStylingKey // If the keys match explicitly than we are a match. + tStylingKeyCursor === null || // If the cursor is `null` it means that we have map at that + // location so we must assume that we have a match. + tStylingKey == null || // If `tStylingKey` is `null` then it is a map therefor assume that it + // contains a match. + (Array.isArray(tStylingKeyCursor) ? tStylingKeyCursor[1] : tStylingKeyCursor) === tStylingKey // If the keys match explicitly than we are a match. ) { return true; } else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') { // if we did not find a match, but `tStylingKeyCursor` is `KeyValueArray` that means cursor has // statics and we need to check those as well. - return keyValueArrayIndexOf(tStylingKeyCursor, tStylingKey) >= - 0; // see if we are matching the key + return keyValueArrayIndexOf(tStylingKeyCursor, tStylingKey) >= 0; // see if we are matching the key } return false; } diff --git a/packages/core/src/render3/styling/styling_parser.ts b/packages/core/src/render3/styling/styling_parser.ts index 299a9a327773b..7b8c00e7a7972 100644 --- a/packages/core/src/render3/styling/styling_parser.ts +++ b/packages/core/src/render3/styling/styling_parser.ts @@ -103,7 +103,7 @@ export function parseClassNameNext(text: string, index: number): number { if (end === index) { return -1; } - index = parserState.keyEnd = consumeClassToken(text, parserState.key = index, end); + index = parserState.keyEnd = consumeClassToken(text, (parserState.key = index), end); return consumeWhitespace(text, index, end); } @@ -143,7 +143,7 @@ export function parseStyle(text: string): number { */ export function parseStyleNext(text: string, startIndex: number): number { const end = parserState.textEnd; - let index = parserState.key = consumeWhitespace(text, startIndex, end); + let index = (parserState.key = consumeWhitespace(text, startIndex, end)); if (end === index) { // we reached an end so just quit return -1; @@ -208,10 +208,13 @@ export function consumeClassToken(text: string, startIndex: number, endIndex: nu */ export function consumeStyleKey(text: string, startIndex: number, endIndex: number): number { let ch: number; - while (startIndex < endIndex && - ((ch = text.charCodeAt(startIndex)) === CharCode.DASH || ch === CharCode.UNDERSCORE || - ((ch & CharCode.UPPER_CASE) >= CharCode.A && (ch & CharCode.UPPER_CASE) <= CharCode.Z) || - (ch >= CharCode.ZERO && ch <= CharCode.NINE))) { + while ( + startIndex < endIndex && + ((ch = text.charCodeAt(startIndex)) === CharCode.DASH || + ch === CharCode.UNDERSCORE || + ((ch & CharCode.UPPER_CASE) >= CharCode.A && (ch & CharCode.UPPER_CASE) <= CharCode.Z) || + (ch >= CharCode.ZERO && ch <= CharCode.NINE)) + ) { startIndex++; } return startIndex; @@ -226,7 +229,11 @@ export function consumeStyleKey(text: string, startIndex: number, endIndex: numb * @returns Index after separator and surrounding whitespace. */ export function consumeSeparator( - text: string, startIndex: number, endIndex: number, separator: number): number { + text: string, + startIndex: number, + endIndex: number, + separator: number, +): number { startIndex = consumeWhitespace(text, startIndex, endIndex); if (startIndex < endIndex) { if (ngDevMode && text.charCodeAt(startIndex) !== separator) { @@ -237,7 +244,6 @@ export function consumeSeparator( return startIndex; } - /** * Consumes style value honoring `url()` and `""` text. * @@ -247,9 +253,9 @@ export function consumeSeparator( * @returns Index after last style value character. */ export function consumeStyleValue(text: string, startIndex: number, endIndex: number): number { - let ch1 = -1; // 1st previous character - let ch2 = -1; // 2nd previous character - let ch3 = -1; // 3rd previous character + let ch1 = -1; // 1st previous character + let ch2 = -1; // 2nd previous character + let ch3 = -1; // 3rd previous character let i = startIndex; let lastChIndex = i; while (i < endIndex) { @@ -259,10 +265,12 @@ export function consumeStyleValue(text: string, startIndex: number, endIndex: nu } else if (ch === CharCode.DOUBLE_QUOTE || ch === CharCode.SINGLE_QUOTE) { lastChIndex = i = consumeQuotedText(text, ch, i, endIndex); } else if ( - startIndex === - i - 4 && // We have seen only 4 characters so far "URL(" (Ignore "foo_URL()") - ch3 === CharCode.U && - ch2 === CharCode.R && ch1 === CharCode.L && ch === CharCode.OPEN_PAREN) { + startIndex === i - 4 && // We have seen only 4 characters so far "URL(" (Ignore "foo_URL()") + ch3 === CharCode.U && + ch2 === CharCode.R && + ch1 === CharCode.L && + ch === CharCode.OPEN_PAREN + ) { lastChIndex = i = consumeQuotedText(text, CharCode.CLOSE_PAREN, i, endIndex); } else if (ch > CharCode.SPACE) { // if we have a non-whitespace character then capture its location @@ -285,8 +293,12 @@ export function consumeStyleValue(text: string, startIndex: number, endIndex: nu * @returns Index after quoted characters. */ export function consumeQuotedText( - text: string, quoteCharCode: number, startIndex: number, endIndex: number): number { - let ch1 = -1; // 1st previous character + text: string, + quoteCharCode: number, + startIndex: number, + endIndex: number, +): number { + let ch1 = -1; // 1st previous character let index = startIndex; while (index < endIndex) { const ch = text.charCodeAt(index++); @@ -301,14 +313,20 @@ export function consumeQuotedText( ch1 = ch; } } - throw ngDevMode ? malformedStyleError(text, String.fromCharCode(quoteCharCode), endIndex) : - new Error(); + throw ngDevMode + ? malformedStyleError(text, String.fromCharCode(quoteCharCode), endIndex) + : new Error(); } function malformedStyleError(text: string, expecting: string, index: number): never { ngDevMode && assertEqual(typeof text === 'string', true, 'String expected here'); throw throwError( - `Malformed style at location ${index} in string '` + text.substring(0, index) + '[>>' + - text.substring(index, index + 1) + '<<]' + text.slice(index + 1) + - `'. Expecting '${expecting}'.`); + `Malformed style at location ${index} in string '` + + text.substring(0, index) + + '[>>' + + text.substring(index, index + 1) + + '<<]' + + text.slice(index + 1) + + `'. Expecting '${expecting}'.`, + ); } diff --git a/packages/core/src/render3/tokens.ts b/packages/core/src/render3/tokens.ts index 4504ffb87e448..c734831fd1cf3 100644 --- a/packages/core/src/render3/tokens.ts +++ b/packages/core/src/render3/tokens.ts @@ -13,4 +13,4 @@ export interface NO_CHANGE { /** A special value which designates that a value has not changed. */ export const NO_CHANGE: NO_CHANGE = - (typeof ngDevMode === 'undefined' || ngDevMode) ? {__brand__: 'NO_CHANGE'} : ({} as NO_CHANGE); + typeof ngDevMode === 'undefined' || ngDevMode ? {__brand__: 'NO_CHANGE'} : ({} as NO_CHANGE); diff --git a/packages/core/src/render3/util/attrs_utils.ts b/packages/core/src/render3/util/attrs_utils.ts index aaa870c2b732b..5d2a5e7804874 100644 --- a/packages/core/src/render3/util/attrs_utils.ts +++ b/packages/core/src/render3/util/attrs_utils.ts @@ -6,14 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import {CharCode} from '../../util/char_code'; -import { AttributeMarker } from '../interfaces/attribute_marker'; +import {AttributeMarker} from '../interfaces/attribute_marker'; import {TAttributes} from '../interfaces/node'; import {CssSelector} from '../interfaces/projection'; import {Renderer} from '../interfaces/renderer'; import {RElement} from '../interfaces/renderer_dom'; - - /** * Assigns all attribute values to the provided element via the inferred renderer. * @@ -90,9 +88,12 @@ export function setUpAttributes(renderer: Renderer, native: RElement, attrs: TAt * @param marker The attribute marker to test. * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`). */ -export function isNameOnlyAttributeMarker(marker: string|AttributeMarker|CssSelector) { - return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template || - marker === AttributeMarker.I18n; +export function isNameOnlyAttributeMarker(marker: string | AttributeMarker | CssSelector) { + return ( + marker === AttributeMarker.Bindings || + marker === AttributeMarker.Template || + marker === AttributeMarker.I18n + ); } export function isAnimationProp(name: string): boolean { @@ -110,7 +111,10 @@ export function isAnimationProp(name: string): boolean { * @param dst Location of where the merged `TAttributes` should end up. * @param src `TAttributes` which should be appended to `dst` */ -export function mergeHostAttrs(dst: TAttributes|null, src: TAttributes|null): TAttributes|null { +export function mergeHostAttrs( + dst: TAttributes | null, + src: TAttributes | null, +): TAttributes | null { if (src === null || src.length === 0) { // do nothing } else if (dst === null || dst.length === 0) { @@ -126,8 +130,9 @@ export function mergeHostAttrs(dst: TAttributes|null, src: TAttributes|null): TA if (srcMarker === AttributeMarker.NamespaceURI) { // Case where we need to consume `key1`, `key2`, `value` items. } else if ( - srcMarker === AttributeMarker.ImplicitAttributes || - srcMarker === AttributeMarker.Styles) { + srcMarker === AttributeMarker.ImplicitAttributes || + srcMarker === AttributeMarker.Styles + ) { // Case where we have to consume `key1` and `value` only. mergeHostAttribute(dst, srcMarker, item as string, null, src[++i] as string); } else { @@ -150,8 +155,12 @@ export function mergeHostAttrs(dst: TAttributes|null, src: TAttributes|null): TA * @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class. */ export function mergeHostAttribute( - dst: TAttributes, marker: AttributeMarker, key1: string, key2: string|null, - value: string|null): void { + dst: TAttributes, + marker: AttributeMarker, + key1: string, + key2: string | null, + value: string | null, +): void { let i = 0; // Assume that new markers will be inserted at the end. let markerInsertPosition = dst.length; diff --git a/packages/core/src/render3/util/change_detection_utils.ts b/packages/core/src/render3/util/change_detection_utils.ts index 03159cc07e505..c2af422db19b3 100644 --- a/packages/core/src/render3/util/change_detection_utils.ts +++ b/packages/core/src/render3/util/change_detection_utils.ts @@ -27,7 +27,7 @@ import {getRootComponents} from './discovery_utils'; export function applyChanges(component: {}): void { ngDevMode && assertDefined(component, 'component'); markViewDirty(getComponentViewByInstance(component), NotificationSource.DebugApplyChanges); - getRootComponents(component).forEach(rootComponent => detectChanges(rootComponent)); + getRootComponents(component).forEach((rootComponent) => detectChanges(rootComponent)); } /** diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index e777fce394caa..a1f1a8e6474d7 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -10,7 +10,13 @@ import {ChangeDetectionStrategy} from '../../change_detection/constants'; import {Injector} from '../../di/injector'; import {ViewEncapsulation} from '../../metadata/view'; import {assertLView} from '../assert'; -import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext, readPatchedLView} from '../context_discovery'; +import { + discoverLocalRefs, + getComponentAtNodeIndex, + getDirectivesAtNodeIndex, + getLContext, + readPatchedLView, +} from '../context_discovery'; import {getComponentDef, getDirectiveDef} from '../definition'; import {NodeInjector} from '../di'; import {DirectiveDef} from '../interfaces/definition'; @@ -20,8 +26,6 @@ import {CLEANUP, CONTEXT, FLAGS, LView, LViewFlags, TVIEW, TViewType} from '../i import {getRootContext} from './view_traversal_utils'; import {getLViewParent, unwrapRNode} from './view_utils'; - - /** * Retrieves the component instance associated with a given DOM element. * @@ -49,7 +53,7 @@ import {getLViewParent, unwrapRNode} from './view_utils'; * @publicApi * @globalApi ng */ -export function getComponent(element: Element): T|null { +export function getComponent(element: Element): T | null { ngDevMode && assertDomElement(element); const context = getLContext(element); if (context === null) return null; @@ -65,7 +69,6 @@ export function getComponent(element: Element): T|null { return context.component as unknown as T; } - /** * If inside an embedded view (e.g. `*ngIf` or `*ngFor`), retrieves the context of the embedded * view that the element is part of. Otherwise retrieves the instance of the component whose view @@ -78,11 +81,11 @@ export function getComponent(element: Element): T|null { * @publicApi * @globalApi ng */ -export function getContext(element: Element): T|null { +export function getContext(element: Element): T | null { assertDomElement(element); const context = getLContext(element)!; const lView = context ? context.lView : null; - return lView === null ? null : lView[CONTEXT] as T; + return lView === null ? null : (lView[CONTEXT] as T); } /** @@ -100,16 +103,16 @@ export function getContext(element: Element): T|null { * @publicApi * @globalApi ng */ -export function getOwningComponent(elementOrDir: Element|{}): T|null { +export function getOwningComponent(elementOrDir: Element | {}): T | null { const context = getLContext(elementOrDir)!; let lView = context ? context.lView : null; if (lView === null) return null; - let parent: LView|null; + let parent: LView | null; while (lView[TVIEW].type === TViewType.Embedded && (parent = getLViewParent(lView)!)) { lView = parent; } - return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as unknown as T; + return lView[FLAGS] & LViewFlags.IsRoot ? null : (lView[CONTEXT] as unknown as T); } /** @@ -123,7 +126,7 @@ export function getOwningComponent(elementOrDir: Element|{}): T|null { * @publicApi * @globalApi ng */ -export function getRootComponents(elementOrDir: Element|{}): {}[] { +export function getRootComponents(elementOrDir: Element | {}): {}[] { const lView = readPatchedLView<{}>(elementOrDir); return lView !== null ? [getRootContext(lView)] : []; } @@ -138,7 +141,7 @@ export function getRootComponents(elementOrDir: Element|{}): {}[] { * @publicApi * @globalApi ng */ -export function getInjector(elementOrDir: Element|{}): Injector { +export function getInjector(elementOrDir: Element | {}): Injector { const context = getLContext(elementOrDir)!; const lView = context ? context.lView : null; if (lView === null) return Injector.NULL; @@ -265,8 +268,9 @@ export interface ComponentDebugMetadata extends DirectiveDebugMetadata { * @publicApi * @globalApi ng */ -export function getDirectiveMetadata(directiveOrComponentInstance: any): ComponentDebugMetadata| - DirectiveDebugMetadata|null { +export function getDirectiveMetadata( + directiveOrComponentInstance: any, +): ComponentDebugMetadata | DirectiveDebugMetadata | null { const {constructor} = directiveOrComponentInstance; if (!constructor) { throw new Error('Unable to find the instance constructor'); @@ -280,8 +284,9 @@ export function getDirectiveMetadata(directiveOrComponentInstance: any): Compone inputs, outputs: componentDef.outputs, encapsulation: componentDef.encapsulation, - changeDetection: componentDef.onPush ? ChangeDetectionStrategy.OnPush : - ChangeDetectionStrategy.Default + changeDetection: componentDef.onPush + ? ChangeDetectionStrategy.OnPush + : ChangeDetectionStrategy.Default, }; } const directiveDef = getDirectiveDef(constructor); @@ -361,10 +366,9 @@ export interface Listener { /** * Type of the listener (e.g. a native DOM event or a custom @Output). */ - type: 'dom'|'output'; + type: 'dom' | 'output'; } - /** * Retrieves a list of event listeners associated with a DOM element. The list does include host * listeners, but it does not include event listeners defined outside of the Angular context @@ -407,7 +411,7 @@ export function getListeners(element: Element): Listener[] { const tCleanup = tView.cleanup; const listeners: Listener[] = []; if (tCleanup && lCleanup) { - for (let i = 0; i < tCleanup.length;) { + for (let i = 0; i < tCleanup.length; ) { const firstParam = tCleanup[i++]; const secondParam = tCleanup[i++]; if (typeof firstParam === 'string') { @@ -419,7 +423,7 @@ export function getListeners(element: Element): Listener[] { // if useCaptureOrIndx is positive number then it in unsubscribe method // if useCaptureOrIndx is negative number then it is a Subscription const type = - (typeof useCaptureOrIndx === 'boolean' || useCaptureOrIndx >= 0) ? 'dom' : 'output'; + typeof useCaptureOrIndx === 'boolean' || useCaptureOrIndx >= 0 ? 'dom' : 'output'; const useCapture = typeof useCaptureOrIndx === 'boolean' ? useCaptureOrIndx : false; if (element == listenerElement) { listeners.push({element, name, callback, useCapture, type}); @@ -442,8 +446,11 @@ function sortListeners(a: Listener, b: Listener) { * See call site for more info. */ function isDirectiveDefHack(obj: any): obj is DirectiveDef { - return obj.type !== undefined && obj.declaredInputs !== undefined && - obj.findHostDirectiveDefs !== undefined; + return ( + obj.type !== undefined && + obj.declaredInputs !== undefined && + obj.findHostDirectiveDefs !== undefined + ); } /** diff --git a/packages/core/src/render3/util/global_utils.ts b/packages/core/src/render3/util/global_utils.ts index e96704672b873..1ab5709f59588 100644 --- a/packages/core/src/render3/util/global_utils.ts +++ b/packages/core/src/render3/util/global_utils.ts @@ -12,10 +12,23 @@ import {setProfiler} from '../profiler'; import {isSignal} from '../reactivity/api'; import {applyChanges} from './change_detection_utils'; -import {getComponent, getContext, getDirectiveMetadata, getDirectives, getHostElement, getInjector, getListeners, getOwningComponent, getRootComponents} from './discovery_utils'; -import {getDependenciesFromInjectable, getInjectorMetadata, getInjectorProviders, getInjectorResolutionPath} from './injector_discovery_utils'; - - +import { + getComponent, + getContext, + getDirectiveMetadata, + getDirectives, + getHostElement, + getInjector, + getListeners, + getOwningComponent, + getRootComponents, +} from './discovery_utils'; +import { + getDependenciesFromInjectable, + getInjectorMetadata, + getInjectorProviders, + getInjectorResolutionPath, +} from './injector_discovery_utils'; /** * This file introduces series of globally accessible debug tools @@ -94,7 +107,9 @@ export type GlobalDevModeUtils = { * used from the browser console when an application is not in production. */ export function publishGlobalUtil( - name: K, fn: typeof globalUtilsFunctions[K]): void { + name: K, + fn: (typeof globalUtilsFunctions)[K], +): void { if (typeof COMPILED === 'undefined' || !COMPILED) { // Note: we can't export `ng` when using closure enhanced optimization as: // - closure declares globals itself for minified names, which sometimes clobber our `ng` global diff --git a/packages/core/src/render3/util/injector_discovery_utils.ts b/packages/core/src/render3/util/injector_discovery_utils.ts index 136937dd28a2b..135b41cf259aa 100644 --- a/packages/core/src/render3/util/injector_discovery_utils.ts +++ b/packages/core/src/render3/util/injector_discovery_utils.ts @@ -25,7 +25,12 @@ import {ChainedInjector} from '../component_ref'; import {getFrameworkDIDebugData} from '../debug/framework_injector_profiler'; import {InjectedService, ProviderRecord} from '../debug/injector_profiler'; import {getComponentDef} from '../definition'; -import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di'; +import { + getNodeInjectorLView, + getNodeInjectorTNode, + getParentInjectorLocation, + NodeInjector, +} from '../di'; import {NodeInjectorOffset} from '../interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode} from '../interfaces/node'; import {RElement} from '../interfaces/renderer_dom'; @@ -45,8 +50,9 @@ import {getNativeByTNode} from './view_utils'; * injector. */ export function getDependenciesFromInjectable( - injector: Injector, token: Type|InjectionToken): - {instance: T; dependencies: Omit[]}|undefined { + injector: Injector, + token: Type | InjectionToken, +): {instance: T; dependencies: Omit[]} | undefined { // First we check to see if the token given maps to an actual instance in the injector given. // We use `self: true` because we only want to look at the injector we were given. // We use `optional: true` because it's possible that the token we were given was never @@ -59,7 +65,7 @@ export function getDependenciesFromInjectable( const unformattedDependencies = getDependenciesForTokenInInjector(token, injector); const resolutionPath = getInjectorResolutionPath(injector); - const dependencies = unformattedDependencies.map(dep => { + const dependencies = unformattedDependencies.map((dep) => { // injectedIn contains private fields, so we omit it from the response const formattedDependency: Omit = { value: dep.value, @@ -74,7 +80,6 @@ export function getDependenciesFromInjectable( skipSelf: (InternalInjectFlags.SkipSelf & flags) === InternalInjectFlags.SkipSelf, }; - // find the injector that provided the dependency for (let i = 0; i < resolutionPath.length; i++) { const injectorToCheck = resolutionPath[i]; @@ -89,8 +94,10 @@ export function getDependenciesFromInjectable( break; } - const instance = - injectorToCheck.get(dep.token as Type, null, {self: true, optional: true}); + const instance = injectorToCheck.get(dep.token as Type, null, { + self: true, + optional: true, + }); if (instance !== null) { // if host flag is true we double check that we can get the service from the first element @@ -99,8 +106,10 @@ export function getDependenciesFromInjectable( // a router outlet. if (formattedDependency.flags.host) { const firstInjector = resolutionPath[0]; - const lookupFromFirstInjector = firstInjector.get( - dep.token as Type, null, {...formattedDependency.flags, optional: true}); + const lookupFromFirstInjector = firstInjector.get(dep.token as Type, null, { + ...formattedDependency.flags, + optional: true, + }); if (lookupFromFirstInjector !== null) { formattedDependency.providedIn = injectorToCheck; @@ -128,7 +137,9 @@ export function getDependenciesFromInjectable( } function getDependenciesForTokenInInjector( - token: Type|InjectionToken, injector: Injector): InjectedService[] { + token: Type | InjectionToken, + injector: Injector, +): InjectedService[] { const {resolverToTokenToDependencies} = getFrameworkDIDebugData(); if (!(injector instanceof NodeInjector)) { @@ -142,7 +153,7 @@ function getDependenciesForTokenInInjector( // In the NodeInjector case, all injections for every node are stored in the same lView. // We use the injectedIn field of the dependency to filter out the dependencies that // do not come from the same node as the instance we're looking at. - return dependencies.filter(dependency => { + return dependencies.filter((dependency) => { const dependencyNode = dependency.injectedIn?.tNode; if (dependencyNode === undefined) { return false; @@ -167,7 +178,7 @@ function getDependenciesForTokenInInjector( * @param injector Injector an injector instance * @returns the constructor where the `imports` array that configures this injector is located */ -function getProviderImportsContainer(injector: Injector): Type|null { +function getProviderImportsContainer(injector: Injector): Type | null { const {standaloneInjectorToComponent} = getFrameworkDIDebugData(); // standalone components configure providers through a component def, so we have to @@ -230,9 +241,10 @@ function getNodeInjectorProviders(injector: NodeInjector): ProviderRecord[] { * path * */ -function getProviderImportPaths(providerImportsContainer: Type): - Map| InjectorType)[]> { - const providerToPath = new Map| InjectorType)[]>(); +function getProviderImportPaths( + providerImportsContainer: Type, +): Map | InjectorType)[]> { + const providerToPath = new Map | InjectorType)[]>(); const visitedContainers = new Set>(); const visitor = walkProviderTreeToDiscoverImportPaths(providerToPath, visitedContainers); @@ -334,10 +346,10 @@ function getProviderImportPaths(providerImportsContainer: Type): * void */ function walkProviderTreeToDiscoverImportPaths( - providerToPath: Map| InjectorType)[]>, - visitedContainers: Set>): - (provider: SingleProvider, container: Type|InjectorType) => void { - return (provider: SingleProvider, container: Type|InjectorType) => { + providerToPath: Map | InjectorType)[]>, + visitedContainers: Set>, +): (provider: SingleProvider, container: Type | InjectorType) => void { + return (provider: SingleProvider, container: Type | InjectorType) => { // If the provider is not already in the providerToPath map, // add an entry with the provider as the key and an array containing the current container as // the value @@ -356,8 +368,9 @@ function walkProviderTreeToDiscoverImportPaths( let containerDef = getInjectorDef(container); if (!containerDef) { - const ngModule: Type|undefined = - (container as any).ngModule as Type| undefined; + const ngModule: Type | undefined = (container as any).ngModule as + | Type + | undefined; containerDef = getInjectorDef(ngModule); } @@ -373,8 +386,9 @@ function walkProviderTreeToDiscoverImportPaths( return; } - isNextStepInPath = (moduleImport as any).ngModule === lastContainerAddedToPath || - moduleImport === lastContainerAddedToPath; + isNextStepInPath = + (moduleImport as any).ngModule === lastContainerAddedToPath || + moduleImport === lastContainerAddedToPath; if (isNextStepInPath) { providerToPath.get(prov)?.unshift(container); @@ -395,7 +409,7 @@ function walkProviderTreeToDiscoverImportPaths( */ function getEnvironmentInjectorProviders(injector: EnvironmentInjector): ProviderRecord[] { const providerRecordsWithoutImportPaths = - getFrameworkDIDebugData().resolverToProviders.get(injector) ?? []; + getFrameworkDIDebugData().resolverToProviders.get(injector) ?? []; // platform injector has no provider imports container so can we skip trying to // find import paths @@ -478,8 +492,13 @@ export function getInjectorProviders(injector: Injector): ProviderRecord[] { * @returns an object containing the type and source of the given injector. If the injector metadata * cannot be determined, returns null. */ -export function getInjectorMetadata(injector: Injector): {type: 'element', source: RElement}| - {type: 'environment', source: string | null}|{type: 'null', source: null}|null { +export function getInjectorMetadata( + injector: Injector, +): + | {type: 'element'; source: RElement} + | {type: 'environment'; source: string | null} + | {type: 'null'; source: null} + | null { if (injector instanceof NodeInjector) { const lView = getNodeInjectorLView(injector); const tNode = getNodeInjectorTNode(injector)!; @@ -506,7 +525,9 @@ export function getInjectorResolutionPath(injector: Injector): Injector[] { } function getInjectorResolutionPathHelper( - injector: Injector, resolutionPath: Injector[]): Injector[] { + injector: Injector, + resolutionPath: Injector[], +): Injector[] { const parent = getInjectorParent(injector); // if getInjectorParent can't find a parent, then we've either reached the end @@ -573,12 +594,12 @@ function getInjectorResolutionPathHelper( * @param injector an Injector to get the parent of * @returns Injector the parent of the given injector */ -function getInjectorParent(injector: Injector): Injector|null { +function getInjectorParent(injector: Injector): Injector | null { if (injector instanceof R3Injector) { return injector.parent; } - let tNode: TElementNode|TContainerNode|TElementContainerNode|null; + let tNode: TElementNode | TContainerNode | TElementContainerNode | null; let lView: LView; if (injector instanceof NodeInjector) { tNode = getNodeInjectorTNode(injector); @@ -589,11 +610,14 @@ function getInjectorParent(injector: Injector): Injector|null { return injector.parentInjector; } else { throwError( - 'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector, ChainedInjector'); + 'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector, ChainedInjector', + ); } const parentLocation = getParentInjectorLocation( - tNode as TElementNode | TContainerNode | TElementContainerNode, lView); + tNode as TElementNode | TContainerNode | TElementContainerNode, + lView, + ); if (hasParentInjector(parentLocation)) { const parentInjectorIndex = getParentInjectorIndex(parentLocation); @@ -601,7 +625,9 @@ function getInjectorParent(injector: Injector): Injector|null { const parentTView = parentLView[TVIEW]; const parentTNode = parentTView.data[parentInjectorIndex + NodeInjectorOffset.TNODE] as TNode; return new NodeInjector( - parentTNode as TElementNode | TContainerNode | TElementContainerNode, parentLView); + parentTNode as TElementNode | TContainerNode | TElementContainerNode, + parentLView, + ); } else { const chainedInjector = lView[INJECTOR] as ChainedInjector; @@ -636,7 +662,7 @@ function getModuleInjectorOfNodeInjector(injector: NodeInjector): Injector { } const inj = lView[INJECTOR] as R3Injector | ChainedInjector; - const moduleInjector = (inj instanceof ChainedInjector) ? inj.parentInjector : inj.parent; + const moduleInjector = inj instanceof ChainedInjector ? inj.parentInjector : inj.parent; if (!moduleInjector) { throwError('NodeInjector must have some connection to the module injector tree'); } diff --git a/packages/core/src/render3/util/injector_utils.ts b/packages/core/src/render3/util/injector_utils.ts index ef90e52c159c2..ead2c8ba3e034 100644 --- a/packages/core/src/render3/util/injector_utils.ts +++ b/packages/core/src/render3/util/injector_utils.ts @@ -7,10 +7,13 @@ */ import {assertGreaterThan, assertNotEqual, assertNumber} from '../../util/assert'; -import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from '../interfaces/injector'; +import { + NO_PARENT_INJECTOR, + RelativeInjectorLocation, + RelativeInjectorLocationFlags, +} from '../interfaces/injector'; import {DECLARATION_VIEW, HEADER_OFFSET, LView} from '../interfaces/view'; - /// Parent Injector Utils /////////////////////////////////////////////////////////////// export function hasParentInjector(parentLocation: RelativeInjectorLocation): boolean { return parentLocation !== NO_PARENT_INJECTOR; @@ -23,7 +26,10 @@ export function getParentInjectorIndex(parentLocation: RelativeInjectorLocation) const parentInjectorIndex = parentLocation & RelativeInjectorLocationFlags.InjectorIndexMask; assertGreaterThan( - parentInjectorIndex, HEADER_OFFSET, 'Parent injector must be pointing past HEADER_OFFSET.'); + parentInjectorIndex, + HEADER_OFFSET, + 'Parent injector must be pointing past HEADER_OFFSET.', + ); } return parentLocation & RelativeInjectorLocationFlags.InjectorIndexMask; } diff --git a/packages/core/src/render3/util/misc_utils.ts b/packages/core/src/render3/util/misc_utils.ts index a7e53237297f6..ca3d6b916d4bf 100644 --- a/packages/core/src/render3/util/misc_utils.ts +++ b/packages/core/src/render3/util/misc_utils.ts @@ -15,7 +15,7 @@ import {RElement} from '../interfaces/renderer_dom'; * * @codeGenApi */ -export function ɵɵresolveWindow(element: RElement&{ownerDocument: Document}) { +export function ɵɵresolveWindow(element: RElement & {ownerDocument: Document}) { return element.ownerDocument.defaultView; } @@ -23,7 +23,7 @@ export function ɵɵresolveWindow(element: RElement&{ownerDocument: Document}) { * * @codeGenApi */ -export function ɵɵresolveDocument(element: RElement&{ownerDocument: Document}) { +export function ɵɵresolveDocument(element: RElement & {ownerDocument: Document}) { return element.ownerDocument; } @@ -31,7 +31,7 @@ export function ɵɵresolveDocument(element: RElement&{ownerDocument: Document}) * * @codeGenApi */ -export function ɵɵresolveBody(element: RElement&{ownerDocument: Document}) { +export function ɵɵresolveBody(element: RElement & {ownerDocument: Document}) { return element.ownerDocument.body; } @@ -54,7 +54,7 @@ export const INTERPOLATION_DELIMITER = `�`; /** * Unwrap a value which might be behind a closure (for forward declaration reasons). */ -export function maybeUnwrapFn(value: T|(() => T)): T { +export function maybeUnwrapFn(value: T | (() => T)): T { if (value instanceof Function) { return value(); } else { diff --git a/packages/core/src/render3/util/stringify_utils.ts b/packages/core/src/render3/util/stringify_utils.ts index 7a8ac5f4ae8bd..2868f21fd09cf 100644 --- a/packages/core/src/render3/util/stringify_utils.ts +++ b/packages/core/src/render3/util/stringify_utils.ts @@ -23,7 +23,6 @@ export function renderStringify(value: any): string { return String(value); } - /** * Used to stringify a value so that it can be displayed in an error message. * diff --git a/packages/core/src/render3/util/view_traversal_utils.ts b/packages/core/src/render3/util/view_traversal_utils.ts index 4c41eebc0cc84..1d1fbcd662e8a 100644 --- a/packages/core/src/render3/util/view_traversal_utils.ts +++ b/packages/core/src/render3/util/view_traversal_utils.ts @@ -15,14 +15,13 @@ import {CHILD_HEAD, CONTEXT, FLAGS, LView, LViewFlags, NEXT, PARENT} from '../in import {getLViewParent} from './view_utils'; - /** * Retrieve the root view from any component or `LView` by walking the parent `LView` until * reaching the root `LView`. * * @param componentOrLView any component or `LView` */ -export function getRootView(componentOrLView: LView|{}): LView { +export function getRootView(componentOrLView: LView | {}): LView { ngDevMode && assertDefined(componentOrLView, 'component'); let lView = isLView(componentOrLView) ? componentOrLView : readPatchedLView(componentOrLView)!; while (lView && !(lView[FLAGS] & LViewFlags.IsRoot)) { @@ -39,29 +38,28 @@ export function getRootView(componentOrLView: LView|{}): LView { * * @param viewOrComponent the `LView` or component to get the root context for. */ -export function getRootContext(viewOrComponent: LView|{}): T { +export function getRootContext(viewOrComponent: LView | {}): T { const rootView = getRootView(viewOrComponent); ngDevMode && - assertDefined(rootView[CONTEXT], 'Root view has no context. Perhaps it is disconnected?'); + assertDefined(rootView[CONTEXT], 'Root view has no context. Perhaps it is disconnected?'); return rootView[CONTEXT] as T; } - /** * Gets the first `LContainer` in the LView or `null` if none exists. */ -export function getFirstLContainer(lView: LView): LContainer|null { +export function getFirstLContainer(lView: LView): LContainer | null { return getNearestLContainer(lView[CHILD_HEAD]); } /** * Gets the next `LContainer` that is a sibling of the given container. */ -export function getNextLContainer(container: LContainer): LContainer|null { +export function getNextLContainer(container: LContainer): LContainer | null { return getNearestLContainer(container[NEXT]); } -function getNearestLContainer(viewOrContainer: LContainer|LView|null) { +function getNearestLContainer(viewOrContainer: LContainer | LView | null) { while (viewOrContainer !== null && !isLContainer(viewOrContainer)) { viewOrContainer = viewOrContainer[NEXT]; } diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index 817c6271b813c..eb80c455a2560 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -8,15 +8,34 @@ import {NotificationSource} from '../../change_detection/scheduling/zoneless_scheduling'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; -import {assertDefined, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan} from '../../util/assert'; +import { + assertDefined, + assertGreaterThan, + assertGreaterThanOrEqual, + assertIndexInRange, + assertLessThan, +} from '../../util/assert'; import {assertLView, assertTNode, assertTNodeForLView} from '../assert'; import {LContainer, TYPE} from '../interfaces/container'; import {TConstants, TNode} from '../interfaces/node'; import {RNode} from '../interfaces/renderer_dom'; import {isLContainer, isLView} from '../interfaces/type_checks'; -import {DECLARATION_VIEW, ENVIRONMENT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, ON_DESTROY_HOOKS, PARENT, PREORDER_HOOK_FLAGS, PreOrderHookFlags, REACTIVE_TEMPLATE_CONSUMER, TData, TView} from '../interfaces/view'; - - +import { + DECLARATION_VIEW, + ENVIRONMENT, + FLAGS, + HEADER_OFFSET, + HOST, + LView, + LViewFlags, + ON_DESTROY_HOOKS, + PARENT, + PREORDER_HOOK_FLAGS, + PreOrderHookFlags, + REACTIVE_TEMPLATE_CONSUMER, + TData, + TView, +} from '../interfaces/view'; /** * For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`) @@ -39,7 +58,7 @@ import {DECLARATION_VIEW, ENVIRONMENT, FLAGS, HEADER_OFFSET, HOST, LView, LViewF * Returns `RNode`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ -export function unwrapRNode(value: RNode|LView|LContainer): RNode { +export function unwrapRNode(value: RNode | LView | LContainer): RNode { while (Array.isArray(value)) { value = value[HOST] as any; } @@ -50,7 +69,7 @@ export function unwrapRNode(value: RNode|LView|LContainer): RNode { * Returns `LView` or `null` if not found. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ -export function unwrapLView(value: RNode|LView|LContainer): LView|null { +export function unwrapLView(value: RNode | LView | LContainer): LView | null { while (Array.isArray(value)) { // This check is same as `isLView()` but we don't call at as we don't want to call // `Array.isArray()` twice and give JITer more work for inlining. @@ -93,17 +112,16 @@ export function getNativeByTNode(tNode: TNode, lView: LView): RNode { * @param tNode * @param lView */ -export function getNativeByTNodeOrNull(tNode: TNode|null, lView: LView): RNode|null { +export function getNativeByTNodeOrNull(tNode: TNode | null, lView: LView): RNode | null { const index = tNode === null ? -1 : tNode.index; if (index !== -1) { ngDevMode && assertTNodeForLView(tNode!, lView); - const node: RNode|null = unwrapRNode(lView[index]); + const node: RNode | null = unwrapRNode(lView[index]); return node; } return null; } - // fixme(misko): The return Type should be `TNode|null` export function getTNode(tView: TView, index: number): TNode { ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode'); @@ -114,7 +132,7 @@ export function getTNode(tView: TView, index: number): TNode { } /** Retrieves a value from any `LView` or `TData`. */ -export function load(view: LView|TData, index: number): T { +export function load(view: LView | TData, index: number): T { ngDevMode && assertIndexInRange(view, index); return view[index]; } @@ -148,10 +166,16 @@ export function viewAttachedToContainer(view: LView): boolean { } /** Returns a constant from `TConstants` instance. */ -export function getConstant(consts: TConstants|null, index: null|undefined): null; -export function getConstant(consts: TConstants, index: number): T|null; -export function getConstant(consts: TConstants|null, index: number|null|undefined): T|null; -export function getConstant(consts: TConstants|null, index: number|null|undefined): T|null { +export function getConstant(consts: TConstants | null, index: null | undefined): null; +export function getConstant(consts: TConstants, index: number): T | null; +export function getConstant( + consts: TConstants | null, + index: number | null | undefined, +): T | null; +export function getConstant( + consts: TConstants | null, + index: number | null | undefined, +): T | null { if (index === null || index === undefined) return null; ngDevMode && assertIndexInRange(consts!, index); return consts![index] as unknown as T; @@ -187,9 +211,10 @@ export function markViewForRefresh(lView: LView) { export function walkUpViews(nestingLevel: number, currentView: LView): LView { while (nestingLevel > 0) { ngDevMode && - assertDefined( - currentView[DECLARATION_VIEW], - 'Declaration view should be defined if nesting level is greater than 0.'); + assertDefined( + currentView[DECLARATION_VIEW], + 'Declaration view should be defined if nesting level is greater than 0.', + ); currentView = currentView[DECLARATION_VIEW]!; nestingLevel--; } @@ -198,11 +223,11 @@ export function walkUpViews(nestingLevel: number, currentView: LView): LView { export function requiresRefreshOrTraversal(lView: LView) { return !!( - lView[FLAGS] & (LViewFlags.RefreshView | LViewFlags.HasChildViewsToRefresh) || - lView[REACTIVE_TEMPLATE_CONSUMER]?.dirty); + lView[FLAGS] & (LViewFlags.RefreshView | LViewFlags.HasChildViewsToRefresh) || + lView[REACTIVE_TEMPLATE_CONSUMER]?.dirty + ); } - /** * Updates the `HasChildViewsToRefresh` flag on the parents of the `LView` as well as the * parents above. @@ -248,7 +273,9 @@ export function markAncestorsForTraversal(lView: LView) { export function storeLViewOnDestroy(lView: LView, onDestroyCallback: () => void) { if ((lView[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed) { throw new RuntimeError( - RuntimeErrorCode.VIEW_ALREADY_DESTROYED, ngDevMode && 'View has already been destroyed.'); + RuntimeErrorCode.VIEW_ALREADY_DESTROYED, + ngDevMode && 'View has already been destroyed.', + ); } if (lView[ON_DESTROY_HOOKS] === null) { lView[ON_DESTROY_HOOKS] = []; @@ -273,7 +300,7 @@ export function removeLViewOnDestroy(lView: LView, onDestroyCallback: () => void * that LContainer, which is an LView * @param lView the lView whose parent to get */ -export function getLViewParent(lView: LView): LView|null { +export function getLViewParent(lView: LView): LView | null { ngDevMode && assertLView(lView); const parent = lView[PARENT]; return isLContainer(parent) ? parent[PARENT] : parent; diff --git a/packages/core/src/render3/view_engine_compatibility_prebound.ts b/packages/core/src/render3/view_engine_compatibility_prebound.ts index 3ad181e1e0dd0..5ce6091fad110 100644 --- a/packages/core/src/render3/view_engine_compatibility_prebound.ts +++ b/packages/core/src/render3/view_engine_compatibility_prebound.ts @@ -6,18 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ - import {createTemplateRef, TemplateRef} from '../linker/template_ref'; import {TNode} from './interfaces/node'; import {LView} from './interfaces/view'; - /** * Retrieves `TemplateRef` instance from `Injector` when a local reference is placed on the * `` element. * * @codeGenApi */ -export function ɵɵtemplateRefExtractor(tNode: TNode, lView: LView): TemplateRef|null { +export function ɵɵtemplateRefExtractor(tNode: TNode, lView: LView): TemplateRef | null { return createTemplateRef(tNode, lView); } diff --git a/packages/core/src/render3/view_manipulation.ts b/packages/core/src/render3/view_manipulation.ts index 0aeb771bde69f..8b59b4739f7f7 100644 --- a/packages/core/src/render3/view_manipulation.ts +++ b/packages/core/src/render3/view_manipulation.ts @@ -19,15 +19,36 @@ import {createLView} from './instructions/shared'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from './interfaces/container'; import {TNode} from './interfaces/node'; import {RComment, RElement} from './interfaces/renderer_dom'; -import {DECLARATION_LCONTAINER, FLAGS, HYDRATION, LView, LViewFlags, QUERIES, RENDERER, T_HOST, TVIEW} from './interfaces/view'; -import {addViewToDOM, destroyLView, detachView, getBeforeNodeForView, insertView, nativeParentNode} from './node_manipulation'; +import { + DECLARATION_LCONTAINER, + FLAGS, + HYDRATION, + LView, + LViewFlags, + QUERIES, + RENDERER, + T_HOST, + TVIEW, +} from './interfaces/view'; +import { + addViewToDOM, + destroyLView, + detachView, + getBeforeNodeForView, + insertView, + nativeParentNode, +} from './node_manipulation'; export function createAndRenderEmbeddedLView( - declarationLView: LView, templateTNode: TNode, context: T, options?: { - injector?: Injector, - embeddedViewInjector?: Injector, - dehydratedView?: DehydratedContainerView|null - }): LView { + declarationLView: LView, + templateTNode: TNode, + context: T, + options?: { + injector?: Injector; + embeddedViewInjector?: Injector; + dehydratedView?: DehydratedContainerView | null; + }, +): LView { const prevConsumer = setActiveConsumer(null); try { const embeddedTView = templateTNode.tView!; @@ -38,9 +59,18 @@ export function createAndRenderEmbeddedLView( const isSignalView = declarationLView[FLAGS] & LViewFlags.SignalView; const viewFlags = isSignalView ? LViewFlags.SignalView : LViewFlags.CheckAlways; const embeddedLView = createLView( - declarationLView, embeddedTView, context, viewFlags, null, templateTNode, null, null, - options?.injector ?? null, options?.embeddedViewInjector ?? null, - options?.dehydratedView ?? null); + declarationLView, + embeddedTView, + context, + viewFlags, + null, + templateTNode, + null, + null, + options?.injector ?? null, + options?.embeddedViewInjector ?? null, + options?.dehydratedView ?? null, + ); const declarationLContainer = declarationLView[templateTNode.index]; ngDevMode && assertLContainer(declarationLContainer); @@ -60,8 +90,10 @@ export function createAndRenderEmbeddedLView( } } -export function getLViewFromLContainer(lContainer: LContainer, index: number): LView| - undefined { +export function getLViewFromLContainer( + lContainer: LContainer, + index: number, +): LView | undefined { const adjustedIndex = CONTAINER_HEADER_OFFSET + index; // avoid reading past the array boundaries if (adjustedIndex < lContainer.length) { @@ -80,13 +112,20 @@ export function getLViewFromLContainer(lContainer: LContainer, index: number) * block (in which case view contents was re-created, thus needing insertion). */ export function shouldAddViewToDom( - tNode: TNode, dehydratedView?: DehydratedContainerView|null): boolean { - return !dehydratedView || dehydratedView.firstChild === null || - hasInSkipHydrationBlockFlag(tNode); + tNode: TNode, + dehydratedView?: DehydratedContainerView | null, +): boolean { + return ( + !dehydratedView || dehydratedView.firstChild === null || hasInSkipHydrationBlockFlag(tNode) + ); } export function addLViewToLContainer( - lContainer: LContainer, lView: LView, index: number, addToDOM = true): void { + lContainer: LContainer, + lView: LView, + index: number, + addToDOM = true, +): void { const tView = lView[TVIEW]; // Insert into the view tree so the new view can be change-detected @@ -111,8 +150,10 @@ export function addLViewToLContainer( } } -export function removeLViewFromLContainer(lContainer: LContainer, index: number): LView| - undefined { +export function removeLViewFromLContainer( + lContainer: LContainer, + index: number, +): LView | undefined { const lView = detachView(lContainer, index); if (lView !== undefined) { destroyLView(lView[TVIEW], lView); diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 2e350bbdbed27..90a794f17aa84 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -18,18 +18,31 @@ import {checkNoChangesInternal, detectChangesInternal} from './instructions/chan import {markViewDirty} from './instructions/mark_view_dirty'; import {CONTAINER_HEADER_OFFSET, VIEW_REFS} from './interfaces/container'; import {isLContainer, isRootView} from './interfaces/type_checks'; -import {CONTEXT, DECLARATION_LCONTAINER, FLAGS, LView, LViewFlags, PARENT, TVIEW} from './interfaces/view'; -import {destroyLView, detachMovedView, detachView, detachViewFromDOM, trackMovedView} from './node_manipulation'; +import { + CONTEXT, + DECLARATION_LCONTAINER, + FLAGS, + LView, + LViewFlags, + PARENT, + TVIEW, +} from './interfaces/view'; +import { + destroyLView, + detachMovedView, + detachView, + detachViewFromDOM, + trackMovedView, +} from './node_manipulation'; import {storeLViewOnDestroy, updateAncestorTraversalFlagsOnAttach} from './util/view_utils'; - // Needed due to tsickle downleveling where multiple `implements` with classes creates // multiple @extends in Closure annotations, which is illegal. This workaround fixes // the multiple @extends by making the annotation @implements instead interface ChangeDetectorRefInterface extends ChangeDetectorRef {} export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterface { - private _appRef: ViewRefTracker|null = null; + private _appRef: ViewRefTracker | null = null; private _attachedToViewContainer = false; get rootNodes(): any[] { @@ -39,26 +52,28 @@ export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterfac } constructor( - /** - * This represents `LView` associated with the component when ViewRef is a ChangeDetectorRef. - * - * When ViewRef is created for a dynamic component, this also represents the `LView` for the - * component. - * - * For a "regular" ViewRef created for an embedded view, this is the `LView` for the embedded - * view. - * - * @internal - */ - public _lView: LView, + /** + * This represents `LView` associated with the component when ViewRef is a ChangeDetectorRef. + * + * When ViewRef is created for a dynamic component, this also represents the `LView` for the + * component. + * + * For a "regular" ViewRef created for an embedded view, this is the `LView` for the embedded + * view. + * + * @internal + */ + public _lView: LView, - /** - * This represents the `LView` associated with the point where `ChangeDetectorRef` was - * requested. - * - * This may be different from `_lView` if the `_cdRefInjectingView` is an embedded view. - */ - private _cdRefInjectingView?: LView, readonly notifyErrorHandler = true) {} + /** + * This represents the `LView` associated with the point where `ChangeDetectorRef` was + * requested. + * + * This may be different from `_lView` if the `_cdRefInjectingView` is an embedded view. + */ + private _cdRefInjectingView?: LView, + readonly notifyErrorHandler = true, + ) {} get context(): T { return this._lView[CONTEXT] as unknown as T; @@ -74,7 +89,8 @@ export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterfac // Note: We have a warning message here because the `@deprecated` JSDoc will not be picked // up for assignments on the setter. We want to let users know about the deprecated usage. console.warn( - 'Angular: Replacing the `context` object of an `EmbeddedViewRef` is deprecated.'); + 'Angular: Replacing the `context` object of an `EmbeddedViewRef` is deprecated.', + ); } this._lView[CONTEXT] = value as unknown as {}; @@ -94,9 +110,11 @@ export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterfac const index = viewRefs ? viewRefs.indexOf(this) : -1; if (index > -1) { ngDevMode && - assertEqual( - index, parent.indexOf(this._lView) - CONTAINER_HEADER_OFFSET, - 'An attached view should be in the same position within its container as its ViewRef in the VIEW_REFS array.'); + assertEqual( + index, + parent.indexOf(this._lView) - CONTAINER_HEADER_OFFSET, + 'An attached view should be in the same position within its container as its ViewRef in the VIEW_REFS array.', + ); detachView(parent, index); removeFromArray(viewRefs!, index); } @@ -309,8 +327,9 @@ export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterfac attachToViewContainerRef() { if (this._appRef) { throw new RuntimeError( - RuntimeErrorCode.VIEW_ALREADY_ATTACHED, - ngDevMode && 'This view is already attached directly to the ApplicationRef!'); + RuntimeErrorCode.VIEW_ALREADY_ATTACHED, + ngDevMode && 'This view is already attached directly to the ApplicationRef!', + ); } this._attachedToViewContainer = true; } @@ -328,8 +347,9 @@ export class ViewRef implements EmbeddedViewRef, ChangeDetectorRefInterfac attachToAppRef(appRef: ViewRefTracker) { if (this._attachedToViewContainer) { throw new RuntimeError( - RuntimeErrorCode.VIEW_ALREADY_ATTACHED, - ngDevMode && 'This view is already attached to a ViewContainer!'); + RuntimeErrorCode.VIEW_ALREADY_ATTACHED, + ngDevMode && 'This view is already attached to a ViewContainer!', + ); } this._appRef = appRef; const isRoot = isRootView(this._lView); diff --git a/packages/core/src/sanitization/bypass.ts b/packages/core/src/sanitization/bypass.ts index e2209bb09ea23..53934892d44c1 100644 --- a/packages/core/src/sanitization/bypass.ts +++ b/packages/core/src/sanitization/bypass.ts @@ -58,15 +58,16 @@ export interface SafeUrl extends SafeValue {} */ export interface SafeResourceUrl extends SafeValue {} - abstract class SafeValueImpl implements SafeValue { constructor(public changingThisBreaksApplicationSecurity: string) {} abstract getTypeName(): string; toString() { - return `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}` + - ` (see ${XSS_SECURITY_URL})`; + return ( + `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}` + + ` (see ${XSS_SECURITY_URL})` + ); } } @@ -98,20 +99,28 @@ class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl { export function unwrapSafeValue(value: SafeValue): string; export function unwrapSafeValue(value: T): T; -export function unwrapSafeValue(value: T|SafeValue): T { - return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity as any as T : - value as any as T; +export function unwrapSafeValue(value: T | SafeValue): T { + return value instanceof SafeValueImpl + ? (value.changingThisBreaksApplicationSecurity as any as T) + : (value as any as T); } - export function allowSanitizationBypassAndThrow( - value: any, type: BypassType.Html): value is SafeHtml; + value: any, + type: BypassType.Html, +): value is SafeHtml; export function allowSanitizationBypassAndThrow( - value: any, type: BypassType.ResourceUrl): value is SafeResourceUrl; + value: any, + type: BypassType.ResourceUrl, +): value is SafeResourceUrl; export function allowSanitizationBypassAndThrow( - value: any, type: BypassType.Script): value is SafeScript; + value: any, + type: BypassType.Script, +): value is SafeScript; export function allowSanitizationBypassAndThrow( - value: any, type: BypassType.Style): value is SafeStyle; + value: any, + type: BypassType.Style, +): value is SafeStyle; export function allowSanitizationBypassAndThrow(value: any, type: BypassType.Url): value is SafeUrl; export function allowSanitizationBypassAndThrow(value: any, type: BypassType): boolean; export function allowSanitizationBypassAndThrow(value: any, type: BypassType): boolean { @@ -124,8 +133,8 @@ export function allowSanitizationBypassAndThrow(value: any, type: BypassType): b return actualType === type; } -export function getSanitizationBypassType(value: any): BypassType|null { - return value instanceof SafeValueImpl && value.getTypeName() as BypassType || null; +export function getSanitizationBypassType(value: any): BypassType | null { + return (value instanceof SafeValueImpl && (value.getTypeName() as BypassType)) || null; } /** diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts index dbfdb703051f2..58648b0f9e1b9 100644 --- a/packages/core/src/sanitization/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -41,47 +41,59 @@ const VOID_ELEMENTS = tagSet('area,br,col,hr,img,wbr'); // https://html.spec.whatwg.org/#optional-tags const OPTIONAL_END_TAG_BLOCK_ELEMENTS = tagSet('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'); const OPTIONAL_END_TAG_INLINE_ELEMENTS = tagSet('rp,rt'); -const OPTIONAL_END_TAG_ELEMENTS = - merge(OPTIONAL_END_TAG_INLINE_ELEMENTS, OPTIONAL_END_TAG_BLOCK_ELEMENTS); +const OPTIONAL_END_TAG_ELEMENTS = merge( + OPTIONAL_END_TAG_INLINE_ELEMENTS, + OPTIONAL_END_TAG_BLOCK_ELEMENTS, +); // Safe Block Elements - HTML5 const BLOCK_ELEMENTS = merge( - OPTIONAL_END_TAG_BLOCK_ELEMENTS, - tagSet( - 'address,article,' + - 'aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + - 'h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul')); + OPTIONAL_END_TAG_BLOCK_ELEMENTS, + tagSet( + 'address,article,' + + 'aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + + 'h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul', + ), +); // Inline Elements - HTML5 const INLINE_ELEMENTS = merge( - OPTIONAL_END_TAG_INLINE_ELEMENTS, - tagSet( - 'a,abbr,acronym,audio,b,' + - 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' + - 'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video')); - -export const VALID_ELEMENTS = - merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS); + OPTIONAL_END_TAG_INLINE_ELEMENTS, + tagSet( + 'a,abbr,acronym,audio,b,' + + 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' + + 'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video', + ), +); + +export const VALID_ELEMENTS = merge( + VOID_ELEMENTS, + BLOCK_ELEMENTS, + INLINE_ELEMENTS, + OPTIONAL_END_TAG_ELEMENTS, +); // Attributes that have href and hence need to be sanitized export const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href'); const HTML_ATTRS = tagSet( - 'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + + 'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + 'compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,' + 'ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,' + 'scope,scrolling,shape,size,sizes,span,srclang,srcset,start,summary,tabindex,target,title,translate,type,usemap,' + - 'valign,value,vspace,width'); + 'valign,value,vspace,width', +); // Accessibility attributes as per WAI-ARIA 1.1 (W3C Working Draft 14 December 2018) const ARIA_ATTRS = tagSet( - 'aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,' + + 'aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,' + 'aria-colspan,aria-controls,aria-current,aria-describedby,aria-details,aria-disabled,aria-dropeffect,' + 'aria-errormessage,aria-expanded,aria-flowto,aria-grabbed,aria-haspopup,aria-hidden,aria-invalid,' + 'aria-keyshortcuts,aria-label,aria-labelledby,aria-level,aria-live,aria-modal,aria-multiline,' + 'aria-multiselectable,aria-orientation,aria-owns,aria-placeholder,aria-posinset,aria-pressed,aria-readonly,' + 'aria-relevant,aria-required,aria-roledescription,aria-rowcount,aria-rowindex,aria-rowspan,aria-selected,' + - 'aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext'); + 'aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext', +); // NB: This currently consciously doesn't support SVG. SVG sanitization has had several security // issues in the past, so it seems safer to leave it out if possible. If support for binding SVG via @@ -207,15 +219,17 @@ class SanitizingHtmlSerializer { * accessing `.firstChild` results in an unexpected node returned. */ function isClobberedElement(parentNode: Node, childNode: Node): boolean { - return (parentNode.compareDocumentPosition(childNode) & Node.DOCUMENT_POSITION_CONTAINED_BY) !== - Node.DOCUMENT_POSITION_CONTAINED_BY; + return ( + (parentNode.compareDocumentPosition(childNode) & Node.DOCUMENT_POSITION_CONTAINED_BY) !== + Node.DOCUMENT_POSITION_CONTAINED_BY + ); } /** * Retrieves next sibling node and makes sure that there is no * clobbering of the `nextSibling` property happening. */ -function getNextSibling(node: Node): Node|null { +function getNextSibling(node: Node): Node | null { const nextSibling = node.nextSibling; // Make sure there is no `nextSibling` clobbering: navigating to // the next sibling and going back to the previous one should result @@ -230,7 +244,7 @@ function getNextSibling(node: Node): Node|null { * Retrieves first child node and makes sure that there is no * clobbering of the `firstChild` property happening. */ -function getFirstChild(node: Node): Node|null { +function getFirstChild(node: Node): Node | null { const firstChild = node.firstChild; if (firstChild && isClobberedElement(node, firstChild)) { throw clobberedElementError(firstChild); @@ -242,12 +256,13 @@ function getFirstChild(node: Node): Node|null { export function getNodeName(node: Node): string { const nodeName = node.nodeName; // If the property is clobbered, assume it is an `HTMLFormElement`. - return (typeof nodeName === 'string') ? nodeName : 'FORM'; + return typeof nodeName === 'string' ? nodeName : 'FORM'; } function clobberedElementError(node: Node) { return new Error( - `Failed to sanitize html because the element is clobbered: ${(node as Element).outerHTML}`); + `Failed to sanitize html because the element is clobbered: ${(node as Element).outerHTML}`, + ); } // Regular Expressions for parsing tags and attributes @@ -262,21 +277,18 @@ const NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g; * @param value */ function encodeEntities(value: string) { - return value.replace(/&/g, '&') - .replace( - SURROGATE_PAIR_REGEXP, - function(match: string) { - const hi = match.charCodeAt(0); - const low = match.charCodeAt(1); - return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; - }) - .replace( - NON_ALPHANUMERIC_REGEXP, - function(match: string) { - return '&#' + match.charCodeAt(0) + ';'; - }) - .replace(//g, '>'); + return value + .replace(/&/g, '&') + .replace(SURROGATE_PAIR_REGEXP, function (match: string) { + const hi = match.charCodeAt(0); + const low = match.charCodeAt(1); + return '&#' + ((hi - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000) + ';'; + }) + .replace(NON_ALPHANUMERIC_REGEXP, function (match: string) { + return '&#' + match.charCodeAt(0) + ';'; + }) + .replace(//g, '>'); } let inertBodyHelper: InertBodyHelper; @@ -285,8 +297,8 @@ let inertBodyHelper: InertBodyHelper; * Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to * the DOM in a browser environment. */ -export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): TrustedHTML|string { - let inertBodyElement: HTMLElement|null = null; +export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): TrustedHTML | string { + let inertBodyElement: HTMLElement | null = null; try { inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc); // Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime). @@ -311,7 +323,8 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): Trusted const sanitizer = new SanitizingHtmlSerializer(); const safeHtml = sanitizer.sanitizeChildren( - getTemplateContent(inertBodyElement!) as Element || inertBodyElement); + (getTemplateContent(inertBodyElement!) as Element) || inertBodyElement, + ); if ((typeof ngDevMode === 'undefined' || ngDevMode) && sanitizer.sanitizedSomething) { console.warn(`WARNING: sanitizing HTML stripped some content, see ${XSS_SECURITY_URL}`); } @@ -328,10 +341,10 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): Trusted } } -export function getTemplateContent(el: Node): Node|null { - return 'content' in (el as any /** Microsoft/TypeScript#21517 */) && isTemplateElement(el) ? - el.content : - null; +export function getTemplateContent(el: Node): Node | null { + return 'content' in (el as any) /** Microsoft/TypeScript#21517 */ && isTemplateElement(el) + ? el.content + : null; } function isTemplateElement(el: Node): el is HTMLTemplateElement { return el.nodeType === Node.ELEMENT_NODE && el.nodeName === 'TEMPLATE'; diff --git a/packages/core/src/sanitization/iframe_attrs_validation.ts b/packages/core/src/sanitization/iframe_attrs_validation.ts index 07e08eb64254f..ce0a047ae244a 100644 --- a/packages/core/src/sanitization/iframe_attrs_validation.ts +++ b/packages/core/src/sanitization/iframe_attrs_validation.ts @@ -16,7 +16,6 @@ import {getLView, getSelectedTNode} from '../render3/state'; import {getNativeByTNode} from '../render3/util/view_utils'; import {trustedHTMLFromString} from '../util/security/trusted_types'; - /** * Validation function invoked at runtime for each binding that might potentially * represent a security-sensitive attribute of an `, - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {[srcAttr]: TEST_IFRAME_URL}); - }); - - it(`should work when a security-sensitive attribute is set ` + - `as a static attribute (checking \`${securityAttr}\` and ` + - `making sure it's case-insensitive)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: ` + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {[srcAttr]: TEST_IFRAME_URL}); + }, + ); + + it( + `should work when a security-sensitive attribute is set ` + + `as a static attribute (checking \`${securityAttr}\` and ` + + `making sure it's case-insensitive)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ` `, - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {[srcAttr]: TEST_IFRAME_URL}); - }); - - it(`should error when a security-sensitive attribute is applied ` + - `using a property binding (checking \`${securityAttr}\`)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: - ``, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it(`should error when a security-sensitive attribute is applied ` + - `using a property interpolation (checking \`${securityAttr}\`)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: - ``, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it(`should error when a security-sensitive attribute is applied ` + - `using a property binding (checking \`${securityAttr}\`, making ` + - `sure it's case-insensitive)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: ` + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {[srcAttr]: TEST_IFRAME_URL}); + }, + ); + + it( + `should error when a security-sensitive attribute is applied ` + + `using a property binding (checking \`${securityAttr}\`)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ``, + }) + class IframeComp {} + + expectIframeCreationToFail(IframeComp); + }, + ); + + it( + `should error when a security-sensitive attribute is applied ` + + `using a property interpolation (checking \`${securityAttr}\`)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ``, + }) + class IframeComp {} + + expectIframeCreationToFail(IframeComp); + }, + ); + + it( + `should error when a security-sensitive attribute is applied ` + + `using a property binding (checking \`${securityAttr}\`, making ` + + `sure it's case-insensitive)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ` `, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it(`should error when a security-sensitive attribute is applied ` + - `using a property binding (checking \`${securityAttr}\`)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: ` + }) + class IframeComp {} + + expectIframeCreationToFail(IframeComp); + }, + ); + + it( + `should error when a security-sensitive attribute is applied ` + + `using a property binding (checking \`${securityAttr}\`)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ` `, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it(`should error when a security-sensitive attribute is applied ` + - `using a property binding (checking \`${securityAttr}\`, making ` + - `sure it's case-insensitive)`, - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: ` + }) + class IframeComp {} + + expectIframeCreationToFail(IframeComp); + }, + ); + + it( + `should error when a security-sensitive attribute is applied ` + + `using a property binding (checking \`${securityAttr}\`, making ` + + `sure it's case-insensitive)`, + () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ` `, - }) - class IframeComp { - } + }) + class IframeComp {} - expectIframeCreationToFail(IframeComp); - }); + expectIframeCreationToFail(IframeComp); + }, + ); it(`should allow changing \`${srcAttr}\` after initial render`, () => { @Component({ @@ -255,8 +279,9 @@ describe('iframe processing', () => { src = this.sanitizeFn(TEST_IFRAME_URL); get sanitizeFn() { - return srcAttr === 'src' ? this.sanitizer.bypassSecurityTrustResourceUrl : - this.sanitizer.bypassSecurityTrustHtml; + return srcAttr === 'src' + ? this.sanitizer.bypassSecurityTrustResourceUrl + : this.sanitizer.bypassSecurityTrustHtml; } } @@ -272,215 +297,205 @@ describe('iframe processing', () => { }); }); - it('should work when a directive sets a security-sensitive attribute as a static attribute', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'src': TEST_IFRAME_URL, - 'sandbox': '', - }, - }) - class IframeDir { - } - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive sets a security-sensitive host attribute on a non-iframe element', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'src': TEST_IFRAME_URL, - 'sandbox': '', - }, - }) - class Dir { - } - - @Component({ - standalone: true, - imports: [Dir], - selector: 'my-comp', - template: '', - }) - class NonIframeComp { - } - - const fixture = TestBed.createComponent(NonIframeComp); - fixture.detectChanges(); - - expect(fixture.nativeElement.firstChild.src).toEqual(TEST_IFRAME_URL); - }); - - - it('should work when a security-sensitive attribute on an `, - }) - class IframeComp { - visible = true; - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a security-sensitive attribute is set between `src` and `srcdoc`', - () => { - @Component({ - standalone: true, - selector: 'my-comp', - template: ``, - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive sets a security-sensitive attribute before setting `src`', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'sandbox': '', - 'src': TEST_IFRAME_URL, - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive sets an `src` and ' + - 'there was a security-sensitive attribute set in a template' + - '(directive attribute after `sandbox`)', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'src': TEST_IFRAME_URL, - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should error when a directive sets a security-sensitive attribute ' + - 'as an attribute binding (checking that it\'s case-insensitive)', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - '[attr.SANDBOX]': '\'\'', - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: ``, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it('should work when a directive sets an `src` and ' + - 'there was a security-sensitive attribute set in a template' + - '(directive attribute before `sandbox`)', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'src': TEST_IFRAME_URL, - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive sets a security-sensitive attribute and ' + - 'there was an `src` attribute set in a template' + - '(directive attribute after `src`)', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'sandbox': '', - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: ``, - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); + it('should work when a directive sets a security-sensitive attribute as a static attribute', () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'src': TEST_IFRAME_URL, + 'sandbox': '', + }, + }) + class IframeDir {} + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }); + + it('should work when a directive sets a security-sensitive host attribute on a non-iframe element', () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'src': TEST_IFRAME_URL, + 'sandbox': '', + }, + }) + class Dir {} + + @Component({ + standalone: true, + imports: [Dir], + selector: 'my-comp', + template: '', + }) + class NonIframeComp {} + + const fixture = TestBed.createComponent(NonIframeComp); + fixture.detectChanges(); + + expect(fixture.nativeElement.firstChild.src).toEqual(TEST_IFRAME_URL); + }); + + it( + 'should work when a security-sensitive attribute on an `, + }) + class IframeComp { + visible = true; + } + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it('should work when a security-sensitive attribute is set between `src` and `srcdoc`', () => { + @Component({ + standalone: true, + selector: 'my-comp', + template: ``, + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }); + + it('should work when a directive sets a security-sensitive attribute before setting `src`', () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'sandbox': '', + 'src': TEST_IFRAME_URL, + }, + }) + class IframeDir {} + + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }); + + it( + 'should work when a directive sets an `src` and ' + + 'there was a security-sensitive attribute set in a template' + + '(directive attribute after `sandbox`)', + () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'src': TEST_IFRAME_URL, + }, + }) + class IframeDir {} + + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should error when a directive sets a security-sensitive attribute ' + + "as an attribute binding (checking that it's case-insensitive)", + () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + '[attr.SANDBOX]': "''", + }, + }) + class IframeDir {} + + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: ``, + }) + class IframeComp {} + + expectIframeCreationToFail(IframeComp); + }, + ); + + it( + 'should work when a directive sets an `src` and ' + + 'there was a security-sensitive attribute set in a template' + + '(directive attribute before `sandbox`)', + () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'src': TEST_IFRAME_URL, + }, + }) + class IframeDir {} + + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should work when a directive sets a security-sensitive attribute and ' + + 'there was an `src` attribute set in a template' + + '(directive attribute after `src`)', + () => { + @Directive({ + standalone: true, + selector: '[dir]', + host: { + 'sandbox': '', + }, + }) + class IframeDir {} + + @Component({ + standalone: true, + imports: [IframeDir], + selector: 'my-comp', + template: ``, + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); it('should work when a security-sensitive attribute is set as a static attribute', () => { @Component({ @@ -490,8 +505,7 @@ describe('iframe processing', () => { `, }) - class IframeComp { - } + class IframeComp {} expectIframeToBeCreated(IframeComp, { src: TEST_IFRAME_URL, @@ -499,232 +513,233 @@ describe('iframe processing', () => { }); }); - it('should error when a security-sensitive attribute is set ' + - 'as a property binding and an `, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it('should work when a directive sets a security-sensitive attribute and ' + - 'there was an `src` attribute set in a template' + - '(directive attribute before `src`)', - () => { - @Directive({ - standalone: true, - selector: '[dir]', - host: { - 'sandbox': '', - }, - }) - class IframeDir { - } - - @Component({ - standalone: true, - imports: [IframeDir], - selector: 'my-comp', - template: ``, - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive that sets a security-sensitive attribute goes ' + - 'before the directive that sets an `src` attribute value', - () => { - @Directive({ - standalone: true, - selector: '[set-src]', - host: { - 'src': TEST_IFRAME_URL, - }, - }) - class DirThatSetsSrc { - } - - @Directive({ - standalone: true, - selector: '[set-sandbox]', - host: { - 'sandbox': '', - }, - }) - class DirThatSetsSandbox { - } - - @Component({ - standalone: true, - imports: [DirThatSetsSandbox, DirThatSetsSrc], - selector: 'my-comp', - // Important note: even though the `set-sandbox` goes after the `set-src`, - // the directive matching order (thus the order of host attributes) is - // based on the imports order, so the `sandbox` gets set first and the `src` second. - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive that sets a security-sensitive attribute has ' + - 'a host directive that sets an `src` attribute value', - () => { - @Directive({ - standalone: true, - selector: '[set-src-dir]', - host: { - 'src': TEST_IFRAME_URL, - }, - }) - class DirThatSetsSrc { - } - - @Directive({ - standalone: true, - selector: '[dir]', - hostDirectives: [DirThatSetsSrc], - host: { - 'sandbox': '', - }, - }) - class DirThatSetsSandbox { - } - - @Component({ - standalone: true, - imports: [DirThatSetsSandbox], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - it('should work when a directive that sets an `src` has ' + - 'a host directive that sets a security-sensitive attribute value', - () => { - @Directive({ - standalone: true, - selector: '[set-sandbox-dir]', - host: { - 'sandbox': '', - }, - }) - class DirThatSetsSandbox { - } - - @Directive({ - standalone: true, - selector: '[dir]', - hostDirectives: [DirThatSetsSandbox], - host: { - 'src': TEST_IFRAME_URL, - }, - }) - class DirThatSetsSrc { - } - - @Component({ - standalone: true, - imports: [DirThatSetsSrc], - selector: 'my-comp', - template: '', - }) - class IframeComp { - } - - expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); - }); - - - it('should error when creating a view that contains an `, + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should work when a directive that sets a security-sensitive attribute goes ' + + 'before the directive that sets an `src` attribute value', + () => { + @Directive({ + standalone: true, + selector: '[set-src]', + host: { + 'src': TEST_IFRAME_URL, + }, + }) + class DirThatSetsSrc {} + + @Directive({ + standalone: true, + selector: '[set-sandbox]', + host: { + 'sandbox': '', + }, + }) + class DirThatSetsSandbox {} + + @Component({ + standalone: true, + imports: [DirThatSetsSandbox, DirThatSetsSrc], + selector: 'my-comp', + // Important note: even though the `set-sandbox` goes after the `set-src`, + // the directive matching order (thus the order of host attributes) is + // based on the imports order, so the `sandbox` gets set first and the `src` second. + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should work when a directive that sets a security-sensitive attribute has ' + + 'a host directive that sets an `src` attribute value', + () => { + @Directive({ + standalone: true, + selector: '[set-src-dir]', + host: { + 'src': TEST_IFRAME_URL, + }, + }) + class DirThatSetsSrc {} + + @Directive({ + standalone: true, + selector: '[dir]', + hostDirectives: [DirThatSetsSrc], + host: { + 'sandbox': '', + }, + }) + class DirThatSetsSandbox {} + + @Component({ + standalone: true, + imports: [DirThatSetsSandbox], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should work when a directive that sets an `src` has ' + + 'a host directive that sets a security-sensitive attribute value', + () => { + @Directive({ + standalone: true, + selector: '[set-sandbox-dir]', + host: { + 'sandbox': '', + }, + }) + class DirThatSetsSandbox {} + + @Directive({ + standalone: true, + selector: '[dir]', + hostDirectives: [DirThatSetsSandbox], + host: { + 'src': TEST_IFRAME_URL, + }, + }) + class DirThatSetsSrc {} + + @Component({ + standalone: true, + imports: [DirThatSetsSrc], + selector: 'my-comp', + template: '', + }) + class IframeComp {} + + expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); + }, + ); + + it( + 'should error when creating a view that contains an `, - }) - class IframeComp { - @ViewChild('container', {read: ViewContainerRef}) container!: ViewContainerRef; - @ViewChild('template') template!: TemplateRef; + }) + class IframeComp { + @ViewChild('container', {read: ViewContainerRef}) container!: ViewContainerRef; + @ViewChild('template') template!: TemplateRef; - createEmbeddedView() { - this.container.createEmbeddedView(this.template); - } - } + createEmbeddedView() { + this.container.createEmbeddedView(this.template); + } + } - const fixture = TestBed.createComponent(IframeComp); - fixture.detectChanges(); + const fixture = TestBed.createComponent(IframeComp); + fixture.detectChanges(); - expect(() => { - fixture.componentInstance.createEmbeddedView(); - fixture.detectChanges(); - }).toThrowError(getErrorMessageRegexp()); + expect(() => { + fixture.componentInstance.createEmbeddedView(); + fixture.detectChanges(); + }).toThrowError(getErrorMessageRegexp()); - ensureNoIframePresent(fixture); - }); + ensureNoIframePresent(fixture); + }, + ); describe('i18n', () => { - it('should error when a security-sensitive attribute is set as ' + - 'a property binding on an `, - }) - class IframeComp { - } - - expectIframeCreationToFail(IframeComp); - }); - - it('should error when a security-sensitive attribute is set as ' + - 'a property binding on an `, - }) - class IframeComp { - } + }) + class IframeComp {} - expectIframeCreationToFail(IframeComp); - }); + expectIframeCreationToFail(IframeComp); + }, + ); it('should work when a security-sensitive attributes are marked for translation', () => { @Component({ @@ -735,8 +750,7 @@ describe('iframe processing', () => { `, }) - class IframeComp { - } + class IframeComp {} expectIframeToBeCreated(IframeComp, {src: TEST_IFRAME_URL}); }); diff --git a/packages/core/test/acceptance/standalone_injector_spec.ts b/packages/core/test/acceptance/standalone_injector_spec.ts index afa8e378b269d..75c0e4fd49ab2 100644 --- a/packages/core/test/acceptance/standalone_injector_spec.ts +++ b/packages/core/test/acceptance/standalone_injector_spec.ts @@ -6,7 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, createComponent, createEnvironmentInjector, EnvironmentInjector, NgModule, ViewChild, ViewContainerRef} from '@angular/core'; +import { + Component, + createComponent, + createEnvironmentInjector, + EnvironmentInjector, + NgModule, + ViewChild, + ViewContainerRef, +} from '@angular/core'; import {TestBed} from '@angular/core/testing'; describe('standalone injector', () => { @@ -18,14 +26,13 @@ describe('standalone injector', () => { } @NgModule({providers: [Service]}) - class ModuleWithAService { - } + class ModuleWithAService {} @Component({ selector: 'standalone', standalone: true, imports: [ModuleWithAService], - template: `({{service.value}})` + template: `({{service.value}})`, }) class TestComponent { constructor(readonly service: Service) {} @@ -66,27 +73,27 @@ describe('standalone injector', () => { } @NgModule({providers: [Service]}) - class ModuleWithAService { - } + class ModuleWithAService {} @Component({ selector: 'standalone', standalone: true, imports: [ModuleWithAService], - template: `{{service.value}}` + template: `{{service.value}}`, }) class DynamicComponent { constructor(readonly service: Service) {} } @Component({}) - class AppComponent { - } + class AppComponent {} const fixture = TestBed.createComponent(AppComponent); - const environmentInjector = - createEnvironmentInjector([Service], TestBed.inject(EnvironmentInjector)); + const environmentInjector = createEnvironmentInjector( + [Service], + TestBed.inject(EnvironmentInjector), + ); const componentRef = createComponent(DynamicComponent, {environmentInjector}); componentRef.changeDetectorRef.detectChanges(); diff --git a/packages/core/test/acceptance/standalone_spec.ts b/packages/core/test/acceptance/standalone_spec.ts index 4d38d46058abe..fd2885a0f4dd8 100644 --- a/packages/core/test/acceptance/standalone_spec.ts +++ b/packages/core/test/acceptance/standalone_spec.ts @@ -7,7 +7,25 @@ */ import {CommonModule, NgComponentOutlet} from '@angular/common'; -import {Component, createEnvironmentInjector, Directive, EnvironmentInjector, forwardRef, inject, Injectable, Injector, Input, isStandalone, NgModule, NO_ERRORS_SCHEMA, OnInit, Pipe, PipeTransform, ViewChild, ViewContainerRef} from '@angular/core'; +import { + Component, + createEnvironmentInjector, + Directive, + EnvironmentInjector, + forwardRef, + inject, + Injectable, + Injector, + Input, + isStandalone, + NgModule, + NO_ERRORS_SCHEMA, + OnInit, + Pipe, + PipeTransform, + ViewChild, + ViewContainerRef, +} from '@angular/core'; import {TestBed} from '@angular/core/testing'; describe('standalone components, directives, and pipes', () => { @@ -16,8 +34,7 @@ describe('standalone components, directives, and pipes', () => { standalone: true, template: 'Look at me, no NgModule!', }) - class StandaloneCmp { - } + class StandaloneCmp {} const fixture = TestBed.createComponent(StandaloneCmp); fixture.detectChanges(); @@ -28,17 +45,15 @@ describe('standalone components, directives, and pipes', () => { @Component({ selector: 'tree', standalone: true, - template: - `({{level}})`, - imports: [CommonModule] + template: `({{level}})`, + imports: [CommonModule], }) class TreeCmp { @Input() level = 0; } @Component({standalone: true, template: '', imports: [TreeCmp]}) - class StandaloneCmp { - } + class StandaloneCmp {} const fixture = TestBed.createComponent(StandaloneCmp); fixture.detectChanges(); @@ -51,51 +66,47 @@ describe('standalone components, directives, and pipes', () => { selector: 'inner-cmp', template: 'Look at me, no NgModule!', }) - class InnerCmp { - } + class InnerCmp {} @Component({ standalone: true, template: '', imports: [InnerCmp], }) - class StandaloneCmp { - } + class StandaloneCmp {} const fixture = TestBed.createComponent(StandaloneCmp); fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML) - .toEqual('Look at me, no NgModule!'); + expect(fixture.nativeElement.innerHTML).toEqual( + 'Look at me, no NgModule!', + ); }); - it('should render a standalone component with an NgModule-based dependency', () => { @Component({ selector: 'inner-cmp', template: 'Look at me, no NgModule (kinda)!', }) - class InnerCmp { - } + class InnerCmp {} @NgModule({ declarations: [InnerCmp], exports: [InnerCmp], }) - class Module { - } + class Module {} @Component({ standalone: true, template: '', imports: [Module], }) - class StandaloneCmp { - } + class StandaloneCmp {} const fixture = TestBed.createComponent(StandaloneCmp); fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML) - .toEqual('Look at me, no NgModule (kinda)!'); + expect(fixture.nativeElement.innerHTML).toEqual( + 'Look at me, no NgModule (kinda)!', + ); }); it('should allow exporting standalone components, directives, and pipes from NgModule', () => { @@ -104,18 +115,16 @@ describe('standalone components, directives, and pipes', () => { standalone: true, template: `standalone`, }) - class StandaloneCmp { - } + class StandaloneCmp {} @Directive({ selector: '[standalone-dir]', host: { '[attr.id]': '"standalone"', }, - standalone: true + standalone: true, }) - class StandaloneDir { - } + class StandaloneDir {} @Pipe({name: 'standalonePipe', standalone: true}) class StandalonePipe implements PipeTransform { @@ -128,15 +137,13 @@ describe('standalone components, directives, and pipes', () => { imports: [StandaloneCmp, StandaloneDir, StandalonePipe], exports: [StandaloneCmp, StandaloneDir, StandalonePipe], }) - class LibModule { - } + class LibModule {} @Component({ selector: 'app-cmpt', template: `{{'standalone' | standalonePipe}}`, }) - class AppComponent { - } + class AppComponent {} TestBed.configureTestingModule({ imports: [LibModule], @@ -147,27 +154,25 @@ describe('standalone components, directives, and pipes', () => { fixture.detectChanges(); expect(fixture.nativeElement.textContent).toBe('standalone|standalone'); - expect(fixture.nativeElement.querySelector('standalone-cmp').getAttribute('id')) - .toBe('standalone'); + expect(fixture.nativeElement.querySelector('standalone-cmp').getAttribute('id')).toBe( + 'standalone', + ); }); - it('should render a standalone component with dependencies and ambient providers', () => { @Component({ standalone: true, template: 'Inner', selector: 'inner-cmp', }) - class InnerCmp { - } + class InnerCmp {} class Service { value = 'Service'; } @NgModule({providers: [Service]}) - class ModuleWithAProvider { - } + class ModuleWithAProvider {} @Component({ standalone: true, @@ -189,8 +194,7 @@ describe('standalone components, directives, and pipes', () => { } @NgModule({providers: [Service]}) - class ModuleWithAProvider { - } + class ModuleWithAProvider {} @Component({ standalone: true, @@ -213,30 +217,27 @@ describe('standalone components, directives, and pipes', () => { const fixture = TestBed.createComponent(OuterCmp); fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML) - .toEqual('OuterInner(Service)Service'); + expect(fixture.nativeElement.innerHTML).toEqual( + 'OuterInner(Service)Service', + ); }); it('should correctly associate an injector with a standalone component def', () => { @Injectable() - class MyServiceA { - } + class MyServiceA {} @Injectable() - class MyServiceB { - } + class MyServiceB {} @NgModule({ providers: [MyServiceA], }) - class MyModuleA { - } + class MyModuleA {} @NgModule({ providers: [MyServiceB], }) - class MyModuleB { - } + class MyModuleB {} @Component({ selector: 'duplicate-selector', @@ -286,8 +287,7 @@ describe('standalone components, directives, and pipes', () => { } @NgModule({providers: [Service]}) - class Module { - } + class Module {} @Component({ standalone: true, @@ -317,118 +317,120 @@ describe('standalone components, directives, and pipes', () => { expect(fixture.nativeElement.textContent).toBe('Inner(Service)'); }); - it('should dynamically insert a standalone component with ambient providers override in the "left / node" injector', - () => { - class Service { - constructor(readonly value = 'Service') {} - } - - class NodeOverrideService extends Service { - constructor() { - super('NodeOverrideService'); - } - } - - class EnvOverrideService extends Service { - constructor() { - super('EnvOverrideService'); - } - } - - @NgModule({providers: [Service]}) - class Module { - } - - @Component({ - standalone: true, - template: 'Inner({{service.value}})', - selector: 'inner-cmp', - imports: [Module], - }) - class InnerCmp { - constructor(readonly service: Service) {} - } - - @Component({ - standalone: true, - template: '', - imports: [InnerCmp], - }) - class AppCmp implements OnInit { - @ViewChild('insert', {read: ViewContainerRef, static: true}) vcRef!: ViewContainerRef; - - - constructor(readonly inj: Injector, readonly envInj: EnvironmentInjector) {} - - ngOnInit(): void { - const lhsInj = Injector.create({ - providers: [{provide: Service, useClass: NodeOverrideService}], - parent: this.inj, - }); - - const rhsInj = createEnvironmentInjector( - [{provide: Service, useClass: EnvOverrideService}], this.envInj); - - this.vcRef.createComponent(InnerCmp, {injector: lhsInj, environmentInjector: rhsInj}); - } - } - - const fixture = TestBed.createComponent(AppCmp); - fixture.detectChanges(); - expect(fixture.nativeElement.textContent).toBe('Inner(NodeOverrideService)'); - }); - - it('should consult ambient providers before environment injector when inserting a component dynamically', - () => { - class Service { - constructor(readonly value = 'Service') {} - } - - class EnvOverrideService extends Service { - constructor() { - super('EnvOverrideService'); - } - } - - @NgModule({providers: [Service]}) - class Module { - } - - @Component({ - standalone: true, - template: 'Inner({{service.value}})', - selector: 'inner-cmp', - imports: [Module], - }) - class InnerCmp { - constructor(readonly service: Service) {} - } - - @Component({ - standalone: true, - template: '', - imports: [InnerCmp], - }) - class AppCmp implements OnInit { - @ViewChild('insert', {read: ViewContainerRef, static: true}) vcRef!: ViewContainerRef; - - constructor(readonly envInj: EnvironmentInjector) {} - - ngOnInit(): void { - const rhsInj = createEnvironmentInjector( - [{provide: Service, useClass: EnvOverrideService}], this.envInj); - - this.vcRef.createComponent(InnerCmp, {environmentInjector: rhsInj}); - } - } - - const fixture = TestBed.createComponent(AppCmp); - fixture.detectChanges(); - - // The Service (an ambient provider) gets injected here as the standalone injector is a child - // of the user-created environment injector. - expect(fixture.nativeElement.textContent).toBe('Inner(Service)'); - }); + it('should dynamically insert a standalone component with ambient providers override in the "left / node" injector', () => { + class Service { + constructor(readonly value = 'Service') {} + } + + class NodeOverrideService extends Service { + constructor() { + super('NodeOverrideService'); + } + } + + class EnvOverrideService extends Service { + constructor() { + super('EnvOverrideService'); + } + } + + @NgModule({providers: [Service]}) + class Module {} + + @Component({ + standalone: true, + template: 'Inner({{service.value}})', + selector: 'inner-cmp', + imports: [Module], + }) + class InnerCmp { + constructor(readonly service: Service) {} + } + + @Component({ + standalone: true, + template: '', + imports: [InnerCmp], + }) + class AppCmp implements OnInit { + @ViewChild('insert', {read: ViewContainerRef, static: true}) vcRef!: ViewContainerRef; + + constructor( + readonly inj: Injector, + readonly envInj: EnvironmentInjector, + ) {} + + ngOnInit(): void { + const lhsInj = Injector.create({ + providers: [{provide: Service, useClass: NodeOverrideService}], + parent: this.inj, + }); + + const rhsInj = createEnvironmentInjector( + [{provide: Service, useClass: EnvOverrideService}], + this.envInj, + ); + + this.vcRef.createComponent(InnerCmp, {injector: lhsInj, environmentInjector: rhsInj}); + } + } + + const fixture = TestBed.createComponent(AppCmp); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('Inner(NodeOverrideService)'); + }); + + it('should consult ambient providers before environment injector when inserting a component dynamically', () => { + class Service { + constructor(readonly value = 'Service') {} + } + + class EnvOverrideService extends Service { + constructor() { + super('EnvOverrideService'); + } + } + + @NgModule({providers: [Service]}) + class Module {} + + @Component({ + standalone: true, + template: 'Inner({{service.value}})', + selector: 'inner-cmp', + imports: [Module], + }) + class InnerCmp { + constructor(readonly service: Service) {} + } + + @Component({ + standalone: true, + template: '', + imports: [InnerCmp], + }) + class AppCmp implements OnInit { + @ViewChild('insert', {read: ViewContainerRef, static: true}) vcRef!: ViewContainerRef; + + constructor(readonly envInj: EnvironmentInjector) {} + + ngOnInit(): void { + const rhsInj = createEnvironmentInjector( + [{provide: Service, useClass: EnvOverrideService}], + this.envInj, + ); + + this.vcRef.createComponent(InnerCmp, {environmentInjector: rhsInj}); + } + } + + const fixture = TestBed.createComponent(AppCmp); + fixture.detectChanges(); + + // The Service (an ambient provider) gets injected here as the standalone injector is a child + // of the user-created environment injector. + expect(fixture.nativeElement.textContent).toBe('Inner(Service)'); + }); it('should render a recursive cycle of standalone components', () => { @Component({ @@ -437,8 +439,7 @@ describe('standalone components, directives, and pipes', () => { template: 'A', imports: [forwardRef(() => StandaloneCmpC)], }) - class StandaloneCmpA { - } + class StandaloneCmpA {} @Component({ selector: 'cmp-b', @@ -446,8 +447,7 @@ describe('standalone components, directives, and pipes', () => { template: '()B', imports: [StandaloneCmpA], }) - class StandaloneCmpB { - } + class StandaloneCmpB {} @Component({ selector: 'cmp-c', @@ -455,8 +455,7 @@ describe('standalone components, directives, and pipes', () => { template: '()C', imports: [StandaloneCmpB], }) - class StandaloneCmpC { - } + class StandaloneCmpC {} const fixture = TestBed.createComponent(StandaloneCmpC); fixture.detectChanges(); @@ -469,17 +468,15 @@ describe('standalone components, directives, and pipes', () => { } @NgModule({providers: [Service]}) - class ModuleWithAService { - } + class ModuleWithAService {} @NgModule({exports: [ModuleWithAService]}) - class ExportingModule { - } + class ExportingModule {} @Component({ selector: 'standalone', standalone: true, imports: [ExportingModule], - template: `({{service.value}})` + template: `({{service.value}})`, }) class TestComponent { constructor(readonly service: Service) {} @@ -492,8 +489,7 @@ describe('standalone components, directives, and pipes', () => { it('should support nested arrays in @Component.imports', () => { @Directive({selector: '[red]', standalone: true, host: {'[attr.red]': 'true'}}) - class RedIdDirective { - } + class RedIdDirective {} @Pipe({name: 'blue', pure: true, standalone: true}) class BluePipe implements PipeTransform { @@ -508,8 +504,7 @@ describe('standalone components, directives, and pipes', () => { template: `
{{'' | blue}}
`, imports: [[RedIdDirective, [BluePipe]]], }) - class TestComponent { - } + class TestComponent {} const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -518,8 +513,7 @@ describe('standalone components, directives, and pipes', () => { it('should support readonly arrays in @Component.imports', () => { @Directive({selector: '[red]', standalone: true, host: {'[attr.red]': 'true'}}) - class RedIdDirective { - } + class RedIdDirective {} @Pipe({name: 'blue', pure: true, standalone: true}) class BluePipe implements PipeTransform { @@ -536,8 +530,7 @@ describe('standalone components, directives, and pipes', () => { template: `
{{'' | blue}}
`, imports: [DirAndPipe], }) - class TestComponent { - } + class TestComponent {} const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -546,24 +539,19 @@ describe('standalone components, directives, and pipes', () => { it('should deduplicate declarations', () => { @Component({selector: 'test-red', standalone: true, template: 'red()'}) - class RedComponent { - } + class RedComponent {} @Component({selector: 'test-blue', template: 'blue()'}) - class BlueComponent { - } + class BlueComponent {} @NgModule({declarations: [BlueComponent], exports: [BlueComponent]}) - class BlueModule { - } + class BlueModule {} @NgModule({exports: [BlueModule]}) - class BlueAModule { - } + class BlueAModule {} @NgModule({exports: [BlueModule]}) - class BlueBModule { - } + class BlueBModule {} @Component({ selector: 'standalone', @@ -571,13 +559,13 @@ describe('standalone components, directives, and pipes', () => { template: `orange`, imports: [RedComponent, RedComponent, BlueAModule, BlueBModule], }) - class TestComponent { - } + class TestComponent {} const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML) - .toBe('red(blue(orange))'); + expect(fixture.nativeElement.innerHTML).toBe( + 'red(blue(orange))', + ); }); it('should error when forwardRef does not resolve to a truthy value', () => { @@ -587,13 +575,12 @@ describe('standalone components, directives, and pipes', () => { imports: [forwardRef(() => null)], template: '', }) - class TestComponent { - } + class TestComponent {} expect(() => { TestBed.createComponent(TestComponent); - }) - .toThrowError( - 'Expected forwardRef function, imported from "TestComponent", to return a standalone entity or NgModule but got "null".'); + }).toThrowError( + 'Expected forwardRef function, imported from "TestComponent", to return a standalone entity or NgModule but got "null".', + ); }); it('should error when a non-standalone component is imported', () => { @@ -601,8 +588,7 @@ describe('standalone components, directives, and pipes', () => { selector: 'not-a-standalone', template: '', }) - class NonStandaloneCmp { - } + class NonStandaloneCmp {} @Component({ selector: 'standalone', @@ -610,20 +596,18 @@ describe('standalone components, directives, and pipes', () => { template: '', imports: [NonStandaloneCmp], }) - class StandaloneCmp { - } + class StandaloneCmp {} expect(() => { TestBed.createComponent(StandaloneCmp); - }) - .toThrowError( - 'The "NonStandaloneCmp" component, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?'); + }).toThrowError( + 'The "NonStandaloneCmp" component, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?', + ); }); it('should error when a non-standalone directive is imported', () => { @Directive({selector: '[not-a-standalone]'}) - class NonStandaloneDirective { - } + class NonStandaloneDirective {} @Component({ selector: 'standalone', @@ -631,20 +615,18 @@ describe('standalone components, directives, and pipes', () => { template: '', imports: [NonStandaloneDirective], }) - class StandaloneCmp { - } + class StandaloneCmp {} expect(() => { TestBed.createComponent(StandaloneCmp); - }) - .toThrowError( - 'The "NonStandaloneDirective" directive, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?'); + }).toThrowError( + 'The "NonStandaloneDirective" directive, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?', + ); }); it('should error when a non-standalone pipe is imported', () => { @Pipe({name: 'not-a-standalone'}) - class NonStandalonePipe { - } + class NonStandalonePipe {} @Component({ selector: 'standalone', @@ -652,14 +634,13 @@ describe('standalone components, directives, and pipes', () => { template: '', imports: [NonStandalonePipe], }) - class StandaloneCmp { - } + class StandaloneCmp {} expect(() => { TestBed.createComponent(StandaloneCmp); - }) - .toThrowError( - 'The "NonStandalonePipe" pipe, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?'); + }).toThrowError( + 'The "NonStandalonePipe" pipe, imported from "StandaloneCmp", is not standalone. Did you forget to add the standalone: true flag?', + ); }); it('should error when an unknown type is imported', () => { @@ -671,20 +652,18 @@ describe('standalone components, directives, and pipes', () => { template: '', imports: [SthElse], }) - class StandaloneCmp { - } + class StandaloneCmp {} expect(() => { TestBed.createComponent(StandaloneCmp); - }) - .toThrowError( - 'The "SthElse" type, imported from "StandaloneCmp", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?'); + }).toThrowError( + 'The "SthElse" type, imported from "StandaloneCmp", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?', + ); }); it('should error when a module with providers is imported', () => { @NgModule() - class OtherModule { - } + class OtherModule {} @NgModule() class LibModule { @@ -700,14 +679,13 @@ describe('standalone components, directives, and pipes', () => { // are disallowed on the type level imports: [[LibModule.forComponent()]], }) - class StandaloneCmp { - } + class StandaloneCmp {} expect(() => { TestBed.createComponent(StandaloneCmp); - }) - .toThrowError( - 'A module with providers was imported from "StandaloneCmp". Modules with providers are not supported in standalone components imports.'); + }).toThrowError( + 'A module with providers was imported from "StandaloneCmp". Modules with providers are not supported in standalone components imports.', + ); }); it('should support forwardRef imports', () => { @@ -715,14 +693,12 @@ describe('standalone components, directives, and pipes', () => { selector: 'test', standalone: true, imports: [forwardRef(() => StandaloneComponent)], - template: `()` + template: `()`, }) - class TestComponent { - } + class TestComponent {} @Component({selector: 'other-standalone', standalone: true, template: `standalone component`}) - class StandaloneComponent { - } + class StandaloneComponent {} const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -734,10 +710,9 @@ describe('standalone components, directives, and pipes', () => { @Component({ standalone: true, template: '', - schemas: [NO_ERRORS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA], }) - class AppCmp { - } + class AppCmp {} const fixture = TestBed.createComponent(AppCmp); fixture.detectChanges(); @@ -746,25 +721,21 @@ describe('standalone components, directives, and pipes', () => { it('should error when schemas are specified for a non-standalone component', () => { @Component({template: '', schemas: [NO_ERRORS_SCHEMA]}) - class AppCmp { - } + class AppCmp {} expect(() => { TestBed.createComponent(AppCmp); - }) - .toThrowError( - `The 'schemas' was specified for the AppCmp but is only valid on a component that is standalone.`); + }).toThrowError( + `The 'schemas' was specified for the AppCmp but is only valid on a component that is standalone.`, + ); }); }); describe('unknown template elements', () => { const unknownElErrorRegex = (tag: string) => { - const prefix = - `'${tag}' is not a known element \\(used in the 'AppCmp' component template\\):`; - const message1 = `1. If '${ - tag}' is an Angular component, then verify that it is included in the '@Component.imports' of this component.`; - const message2 = `2. If '${ - tag}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.`; + const prefix = `'${tag}' is not a known element \\(used in the 'AppCmp' component template\\):`; + const message1 = `1. If '${tag}' is an Angular component, then verify that it is included in the '@Component.imports' of this component.`; + const message2 = `2. If '${tag}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.`; return new RegExp(`${prefix}s*\ns*${message1}s*\ns*${message2}`); }; @@ -774,8 +745,7 @@ describe('standalone components, directives, and pipes', () => { standalone: true, template: '', }) - class AppCmp { - } + class AppCmp {} TestBed.createComponent(AppCmp); @@ -789,8 +759,7 @@ describe('standalone components, directives, and pipes', () => { standalone: true, template: '', }) - class AppCmp { - } + class AppCmp {} TestBed.createComponent(AppCmp); @@ -807,31 +776,28 @@ describe('standalone components, directives, and pipes', () => { standalone: true, template: '', }) - class AppCmp { - } + class AppCmp {} TestBed.createComponent(AppCmp); expect(spy).not.toHaveBeenCalled(); }); - it('should warn the user when an unknown element is present in an instantiated embedded view', - () => { - const spy = spyOn(console, 'error'); - @Component({ - standalone: true, - template: '', - imports: [CommonModule], - }) - class AppCmp { - } - - const fixture = TestBed.createComponent(AppCmp); - fixture.detectChanges(); - - const errorRegex = unknownElErrorRegex('unknown-tag'); - expect(spy).toHaveBeenCalledOnceWith(jasmine.stringMatching(errorRegex)); - }); + it('should warn the user when an unknown element is present in an instantiated embedded view', () => { + const spy = spyOn(console, 'error'); + @Component({ + standalone: true, + template: '', + imports: [CommonModule], + }) + class AppCmp {} + + const fixture = TestBed.createComponent(AppCmp); + fixture.detectChanges(); + + const errorRegex = unknownElErrorRegex('unknown-tag'); + expect(spy).toHaveBeenCalledOnceWith(jasmine.stringMatching(errorRegex)); + }); }); /* @@ -850,12 +816,11 @@ describe('standalone components, directives, and pipes', () => { it('should allow extending a regular component and turn it into a standalone one', () => { @Component({selector: 'regular', template: 'regular: {{in}}'}) class RegularCmp { - @Input() in : string|undefined; + @Input() in: string | undefined; } @Component({selector: 'standalone', template: 'standalone: {{in}}', standalone: true}) - class StandaloneCmp extends RegularCmp { - } + class StandaloneCmp extends RegularCmp {} const fixture = TestBed.createComponent(StandaloneCmp); fixture.componentInstance.in = 'input value'; @@ -866,12 +831,11 @@ describe('standalone components, directives, and pipes', () => { it('should allow extending a regular component and turn it into a standalone one', () => { @Component({selector: 'standalone', template: 'standalone: {{in}}', standalone: true}) class StandaloneCmp { - @Input() in : string|undefined; + @Input() in: string | undefined; } @Component({selector: 'regular', template: 'regular: {{in}}'}) - class RegularCmp extends StandaloneCmp { - } + class RegularCmp extends StandaloneCmp {} const fixture = TestBed.createComponent(RegularCmp); fixture.componentInstance.in = 'input value'; @@ -885,22 +849,20 @@ describe('standalone components, directives, and pipes', () => { template: 'inner', standalone: true, }) - class InnerCmp { - } + class InnerCmp {} @Component({ selector: 'standalone', standalone: true, template: 'standalone: {{in}}; ()', - imports: [InnerCmp] + imports: [InnerCmp], }) class StandaloneCmp { - @Input() in : string|undefined; + @Input() in: string | undefined; } @Component({selector: 'regular'}) - class RegularCmp extends StandaloneCmp { - } + class RegularCmp extends StandaloneCmp {} const fixture = TestBed.createComponent(RegularCmp); fixture.componentInstance.in = 'input value'; @@ -914,48 +876,42 @@ describe('standalone components, directives, and pipes', () => { describe('isStandalone()', () => { it('should return `true` if component is standalone', () => { @Component({selector: 'standalone-cmp', standalone: true}) - class StandaloneCmp { - } + class StandaloneCmp {} expect(isStandalone(StandaloneCmp)).toBeTrue(); }); it('should return `false` if component is not standalone', () => { @Component({selector: 'standalone-cmp', standalone: false}) - class StandaloneCmp { - } + class StandaloneCmp {} expect(isStandalone(StandaloneCmp)).toBeFalse(); }); it('should return `true` if directive is standalone', () => { @Directive({selector: '[standaloneDir]', standalone: true}) - class StandAloneDirective { - } + class StandAloneDirective {} expect(isStandalone(StandAloneDirective)).toBeTrue(); }); it('should return `false` if directive is standalone', () => { @Directive({selector: '[standaloneDir]', standalone: false}) - class StandAloneDirective { - } + class StandAloneDirective {} expect(isStandalone(StandAloneDirective)).toBeFalse(); }); it('should return `true` if pipe is standalone', () => { @Pipe({name: 'standalonePipe', standalone: true}) - class StandAlonePipe { - } + class StandAlonePipe {} expect(isStandalone(StandAlonePipe)).toBeTrue(); }); it('should return `false` if pipe is standalone', () => { @Pipe({name: 'standalonePipe', standalone: false}) - class StandAlonePipe { - } + class StandAlonePipe {} expect(isStandalone(StandAlonePipe)).toBeFalse(); }); @@ -968,8 +924,7 @@ describe('standalone components, directives, and pipes', () => { it('should return `false` if the class is an NgModule', () => { @NgModule({}) - class Module { - } + class Module {} expect(isStandalone(Module)).toBeFalse(); }); @@ -981,8 +936,7 @@ describe('standalone components, directives, and pipes', () => { template: 'A', imports: [forwardRef(() => StandaloneCmpC)], }) - class StandaloneCmpA { - } + class StandaloneCmpA {} @Component({ selector: 'cmp-b', @@ -990,8 +944,7 @@ describe('standalone components, directives, and pipes', () => { template: '()B', imports: [StandaloneCmpA], }) - class StandaloneCmpB { - } + class StandaloneCmpB {} @Component({ selector: 'cmp-c', @@ -999,8 +952,7 @@ describe('standalone components, directives, and pipes', () => { template: '()C', imports: [StandaloneCmpB], }) - class StandaloneCmpC { - } + class StandaloneCmpC {} TestBed.configureTestingModule({imports: [StandaloneCmpC]}); const fixture = TestBed.createComponent(StandaloneCmpC); diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 6566c99d6eaa6..2da56a210cbbe 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -6,11 +6,26 @@ * found in the LICENSE file at https://angular.io/license */ import {CommonModule} from '@angular/common'; -import {Component, ComponentRef, Directive, ElementRef, HostBinding, Input, Renderer2, ViewChild, ViewContainerRef} from '@angular/core'; +import { + Component, + ComponentRef, + Directive, + ElementRef, + HostBinding, + Input, + Renderer2, + ViewChild, + ViewContainerRef, +} from '@angular/core'; import {bypassSanitizationTrustStyle} from '@angular/core/src/sanitization/bypass'; import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; import {TestBed} from '@angular/core/testing'; -import {getElementClasses, getElementStyles, getSortedClassName, getSortedStyle} from '@angular/core/testing/src/styling'; +import { + getElementClasses, + getElementStyles, + getSortedClassName, + getSortedStyle, +} from '@angular/core/testing/src/styling'; import {By, DomSanitizer, SafeStyle} from '@angular/platform-browser'; import {expectPerfCounters} from '@angular/private/testing'; @@ -20,8 +35,7 @@ describe('styling', () => { describe('apply in prioritization order', () => { it('should perform static bindings', () => { @Component({template: `
`}) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -35,10 +49,9 @@ describe('styling', () => { @Component({ template: `
` + [style.width.px]="100">
`, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -52,10 +65,9 @@ describe('styling', () => { it('should perform map bindings', () => { @Component({ template: `
` + [style]="{color: 'blue', width: '100px'}">
`, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -70,10 +82,9 @@ describe('styling', () => { @Component({ template: `
` + style="width: {{'100'}}px">`, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -86,24 +97,20 @@ describe('styling', () => { it('should support hostBindings', () => { @Component({ - template: - `
` + template: `
`, }) - class Cmp { - } + class Cmp {} @Directive({ selector: '[my-host-bindings-1]', - host: {'class': 'HOST_STATIC_1', 'style': 'font-family: "c1"'} + host: {'class': 'HOST_STATIC_1', 'style': 'font-family: "c1"'}, }) - class Dir1 { - } + class Dir1 {} @Directive({ selector: '[my-host-bindings-2]', - host: {'class': 'HOST_STATIC_2', 'style': 'font-family: "c2"'} + host: {'class': 'HOST_STATIC_2', 'style': 'font-family: "c2"'}, }) - class Dir2 { - } + class Dir2 {} TestBed.configureTestingModule({ declarations: [ @@ -114,7 +121,7 @@ describe('styling', () => { // Even thought component is at the end, it will still have lowest priority because // components are special that way. Cmp, - ] + ], }); const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -127,17 +134,14 @@ describe('styling', () => { it('should support hostBindings inheritance', () => { @Component({template: `
`}) - class Cmp { - } + class Cmp {} @Directive({host: {'class': 'SUPER_STATIC', 'style': 'font-family: "super";'}}) - class SuperDir { - } + class SuperDir {} @Directive({ selector: '[my-host-bindings]', - host: {'class': 'HOST_STATIC', 'style': 'font-family: "host font"'} + host: {'class': 'HOST_STATIC', 'style': 'font-family: "host font"'}, }) - class Dir extends SuperDir { - } + class Dir extends SuperDir {} TestBed.configureTestingModule({declarations: [Cmp, Dir]}); const fixture = TestBed.createComponent(Cmp); @@ -147,8 +151,9 @@ describe('styling', () => { expect(getSortedClassName(div)).toEqual('HOST_STATIC STATIC SUPER_STATIC'); // Browsers keep the '"' around the font name, but Domino removes it some we do search and // replace. Yes we could do `replace(/"/g, '')` but that fails on android. - expect(getSortedStyle(div).replace('"', '').replace('"', '')) - .toEqual('color: blue; font-family: host font;'); + expect(getSortedStyle(div).replace('"', '').replace('"', '')).toEqual( + 'color: blue; font-family: host font;', + ); }); it('should apply style properties that require quote wrapping', () => { @@ -158,10 +163,9 @@ describe('styling', () => {
- ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -180,10 +184,9 @@ describe('styling', () => { [class]="{foo: true, DELETE_MAP_A: false}" [class.bar]="true" [class.DELETE_PROP_B]="false"> - ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -200,25 +203,25 @@ describe('styling', () => { [style]="{width: '110px', height: null}" [style.color]=" 'blue' " [style.height.px]="undefined"> - ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); const styleDiv = fixture.nativeElement.querySelector('div'); - expect(getSortedStyle(styleDiv)) - .toEqual('background-color: yellow; color: blue; width: 110px;'); + expect(getSortedStyle(styleDiv)).toEqual( + 'background-color: yellow; color: blue; width: 110px;', + ); }); it('should work with ngClass/ngStyle', () => { - @Component( - {template: `
`}) - class Cmp { - } + @Component({ + template: `
`, + }) + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -230,9 +233,11 @@ describe('styling', () => { }); describe('css variables', () => { - const supportsCssVariables = typeof getComputedStyle !== 'undefined' && - typeof CSS !== 'undefined' && typeof CSS.supports !== 'undefined' && - CSS.supports('color', 'var(--fake-var)'); + const supportsCssVariables = + typeof getComputedStyle !== 'undefined' && + typeof CSS !== 'undefined' && + typeof CSS.supports !== 'undefined' && + CSS.supports('color', 'var(--fake-var)'); it('should support css variables', () => { // This test only works in browsers which support CSS variables. @@ -245,10 +250,9 @@ describe('styling', () => {
CONTENT
- ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -286,10 +290,9 @@ describe('styling', () => {
CONTENT
- ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -305,8 +308,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -319,8 +321,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -333,8 +334,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -347,8 +347,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -361,8 +360,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -375,8 +373,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -386,12 +383,10 @@ describe('styling', () => { it('should ignore a string containing spaces in a class object literal binding', () => { @Component({ - template: - `
`, + template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -404,8 +399,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -418,8 +412,7 @@ describe('styling', () => { template: `
`, standalone: true, }) - class Cmp { - } + class Cmp {} const fixture = TestBed.createComponent(Cmp); fixture.detectChanges(); @@ -433,10 +426,9 @@ describe('styling', () => { template: `
- ` + `, }) - class Cmp { - } + class Cmp {} @Directive({selector: '[dir-shadows-class-input]'}) class DirectiveShadowsClassInput { @@ -470,10 +462,9 @@ describe('styling', () => { template: `
- ` + `, }) - class Cmp { - } + class Cmp {} @Directive({selector: '[dir-shadows-class-input]', host: {'class': 'DIRECTIVE'}}) class DirectiveShadowsClassInput { @@ -506,10 +497,9 @@ describe('styling', () => { template: `
- ` + `, }) - class Cmp { - } + class Cmp {} @Directive({selector: '[dir-shadows-class-input]', host: {'style': 'color: red;'}}) class DirectiveShadowsStyleInput { @@ -537,59 +527,56 @@ describe('styling', () => { expect(divBinding.getAttribute('shadow-style')).toEqual('width: 1px; height:1px;'); }); - it('should bind [class] as input to directive when both static and falsy dynamic values are present', - () => { - @Component({ - template: ` + it('should bind [class] as input to directive when both static and falsy dynamic values are present', () => { + @Component({ + template: `
- ` - }) - class Cmp { - classBinding: any = undefined; - } - - @Directive({selector: '[dir-shadows-class-input]'}) - class DirectiveShadowsClassInput { - constructor(private elementRef: ElementRef) {} - @Input('class') - set klass(value: string) { - this.elementRef.nativeElement.setAttribute('shadow-class', value); - } - } - - TestBed.configureTestingModule({declarations: [Cmp, DirectiveShadowsClassInput]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const div = fixture.nativeElement.querySelector('div'); - expect(div.className).toEqual('s1'); - expect(div.getAttribute('shadow-class')).toEqual('s1'); - - fixture.componentInstance.classBinding = null; - fixture.detectChanges(); - expect(div.className).toEqual('s1'); - expect(div.getAttribute('shadow-class')).toEqual('s1'); - - fixture.componentInstance.classBinding = false; - fixture.detectChanges(); - expect(div.className).toEqual('s1'); - expect(div.getAttribute('shadow-class')).toEqual('s1'); - - - fixture.componentInstance.classBinding = {toString: () => 'd1'}; - fixture.detectChanges(); - expect(div.className).toEqual('s1'); - expect(div.getAttribute('shadow-class')).toEqual('s1 d1'); - }); + `, + }) + class Cmp { + classBinding: any = undefined; + } + + @Directive({selector: '[dir-shadows-class-input]'}) + class DirectiveShadowsClassInput { + constructor(private elementRef: ElementRef) {} + @Input('class') + set klass(value: string) { + this.elementRef.nativeElement.setAttribute('shadow-class', value); + } + } + + TestBed.configureTestingModule({declarations: [Cmp, DirectiveShadowsClassInput]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const div = fixture.nativeElement.querySelector('div'); + expect(div.className).toEqual('s1'); + expect(div.getAttribute('shadow-class')).toEqual('s1'); + + fixture.componentInstance.classBinding = null; + fixture.detectChanges(); + expect(div.className).toEqual('s1'); + expect(div.getAttribute('shadow-class')).toEqual('s1'); + + fixture.componentInstance.classBinding = false; + fixture.detectChanges(); + expect(div.className).toEqual('s1'); + expect(div.getAttribute('shadow-class')).toEqual('s1'); + + fixture.componentInstance.classBinding = {toString: () => 'd1'}; + fixture.detectChanges(); + expect(div.className).toEqual('s1'); + expect(div.getAttribute('shadow-class')).toEqual('s1 d1'); + }); it('should bind [style] as input to directive', () => { @Component({ template: `
- ` + `, }) - class Cmp { - } + class Cmp {} @Directive({selector: '[dir-shadows-style-input]'}) class DirectiveShadowsStyleInput { @@ -611,12 +598,11 @@ describe('styling', () => { it('should prevent circular ExpressionChangedAfterItHasBeenCheckedError on shadow inputs', () => { @Component({template: `
`}) - class Cmp { - } + class Cmp {} @Directive({selector: '[dir-shadows-class-input]'}) class DirectiveShadowsClassInput { - @Input('class') klass: string|undefined; + @Input('class') klass: string | undefined; @HostBinding('class') get hostClasses() { @@ -638,11 +624,11 @@ describe('styling', () => {
- ` + `, }) class Cmp { id = 'throw_id'; - klass: string|string[] = 'throw_klass'; + klass: string | string[] = 'throw_klass'; foo = `throw_foo`; maybeThrow(value: any) { @@ -696,44 +682,41 @@ describe('styling', () => { expectClass(span).toEqual({BAR: true, foo: true, myDir: true}); }); - it('should render inline style and class attribute values on the element before a directive is instantiated', - () => { - @Component({ - template: ` + it('should render inline style and class attribute values on the element before a directive is instantiated', () => { + @Component({ + template: `
- ` - }) - class Cmp { - } - - @Directive({selector: '[directive-expecting-styling]'}) - class DirectiveExpectingStyling { - constructor(elm: ElementRef) { - const native = elm.nativeElement; - native.setAttribute('data-captured-width', native.style.width); - native.setAttribute('data-captured-classes', native.className); - } - } - - TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const element = fixture.nativeElement.querySelector('div'); - expect(element.style.width).toEqual('200px'); - expect(element.getAttribute('data-captured-width')).toEqual('200px'); - expect(element.className.trim()).toEqual('abc xyz'); - expect(element.getAttribute('data-captured-classes')).toEqual('abc xyz'); - }); + `, + }) + class Cmp {} + + @Directive({selector: '[directive-expecting-styling]'}) + class DirectiveExpectingStyling { + constructor(elm: ElementRef) { + const native = elm.nativeElement; + native.setAttribute('data-captured-width', native.style.width); + native.setAttribute('data-captured-classes', native.className); + } + } + + TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('div'); + expect(element.style.width).toEqual('200px'); + expect(element.getAttribute('data-captured-width')).toEqual('200px'); + expect(element.className.trim()).toEqual('abc xyz'); + expect(element.getAttribute('data-captured-classes')).toEqual('abc xyz'); + }); it('should only render the same initial styling values once before a directive runs', () => { @Component({ template: `
- ` + `, }) - class Cmp { - } + class Cmp {} @Directive({selector: '[directive-expecting-styling]'}) class DirectiveExpectingStyling { @@ -753,60 +736,56 @@ describe('styling', () => { expect(element.classList.contains('abc')).toBeFalsy(); }); - it('should ensure that static classes are assigned to ng-container elements and picked up for content projection', - () => { - @Component({ - template: ` + it('should ensure that static classes are assigned to ng-container elements and picked up for content projection', () => { + @Component({ + template: ` outer inner - ` - }) - class MyApp { - } - - @Component({ - selector: 'project', - template: ` + `, + }) + class MyApp {} + + @Component({ + selector: 'project', + template: `
- ` - }) - class ProjectCmp { - } - - TestBed.configureTestingModule({declarations: [MyApp, ProjectCmp]}); - const fixture = TestBed.createComponent(MyApp); - const element = fixture.nativeElement; - fixture.detectChanges(); - - const inner = element.querySelector('.inner-area'); - expect(inner.textContent.trim()).toEqual('inner'); - const outer = element.querySelector('.outer-area'); - expect(outer.textContent.trim()).toEqual('outer'); - }); + `, + }) + class ProjectCmp {} + + TestBed.configureTestingModule({declarations: [MyApp, ProjectCmp]}); + const fixture = TestBed.createComponent(MyApp); + const element = fixture.nativeElement; + fixture.detectChanges(); + + const inner = element.querySelector('.inner-area'); + expect(inner.textContent.trim()).toEqual('inner'); + const outer = element.querySelector('.outer-area'); + expect(outer.textContent.trim()).toEqual('outer'); + }); it('should render initial styling for repeated nodes that a component host', () => { @Component({ selector: '[comp]', template: '', }) - class Comp { - } + class Comp {} @Component({ template: `

A

- ` + `, }) class App { items = [1, 2, 3]; @@ -823,8 +802,7 @@ describe('styling', () => { it('should do nothing for empty style bindings', () => { @Component({template: '
'}) - class App { - } + class App {} TestBed.configureTestingModule({declarations: [App]}); const fixture = TestBed.createComponent(App); @@ -835,8 +813,7 @@ describe('styling', () => { it('should do nothing for empty class bindings', () => { @Component({template: '
'}) - class App { - } + class App {} TestBed.configureTestingModule({declarations: [App]}); const fixture = TestBed.createComponent(App); @@ -1001,8 +978,7 @@ describe('styling', () => { } @Component({selector: 'app-comp', template: ``}) - class MyApp { - } + class MyApp {} TestBed.configureTestingModule({declarations: [MyApp, StyleDir]}); TestBed.createComponent(MyApp).detectChanges(); @@ -1041,7 +1017,7 @@ describe('styling', () => {
- ` + `, }) class Cmp { one = '1'; @@ -1073,8 +1049,16 @@ describe('styling', () => { expect(divs[8].getAttribute('class')).toBe('a1b'); expect(divs[9].getAttribute('class')).toBe('1'); - instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = - instance.seven = instance.eight = instance.nine = ''; + instance.one = + instance.two = + instance.three = + instance.four = + instance.five = + instance.six = + instance.seven = + instance.eight = + instance.nine = + ''; fixture.detectChanges(); expect(divs[0].getAttribute('class')).toBe('abcdefghij'); @@ -1102,7 +1086,7 @@ describe('styling', () => {
- ` + `, }) class Cmp { self = 'content: "self"'; @@ -1135,8 +1119,17 @@ describe('styling', () => { expect(divs[8].style.getPropertyValue('content')).toBe('"a1b"'); expect(divs[9].style.getPropertyValue('content')).toBe('"self"'); - instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = - instance.seven = instance.eight = instance.nine = instance.self = ''; + instance.one = + instance.two = + instance.three = + instance.four = + instance.five = + instance.six = + instance.seven = + instance.eight = + instance.nine = + instance.self = + ''; fixture.detectChanges(); expect(divs[0].style.getPropertyValue('content')).toBe('"abcdefghij"'); @@ -1191,10 +1184,10 @@ describe('styling', () => {
- ` + `, }) class Cmp { - singleBinding: string|null = '1337px'; + singleBinding: string | null = '1337px'; one = 1; two = 2; three = 3; @@ -1225,8 +1218,16 @@ describe('styling', () => { expect(divs[9].style.width).toBe('1337px'); instance.singleBinding = null; - instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = - instance.seven = instance.eight = instance.nine = 1; + instance.one = + instance.two = + instance.three = + instance.four = + instance.five = + instance.six = + instance.seven = + instance.eight = + instance.nine = + 1; fixture.detectChanges(); expect(divs[0].style.fontFamily).toBe('f111111111'); @@ -1262,107 +1263,105 @@ describe('styling', () => { expect(div.style.width).toBe('2667px'); }); - it('should not write to a `class` input binding in the event that there is no static class value', - () => { - let capturedClassBindingCount = 0; - let capturedClassBindingValue: string|null|undefined = undefined; - let capturedMyClassBindingCount = 0; - let capturedMyClassBindingValue: string|null|undefined = undefined; - - @Component({template: '
'}) - class Cmp { - c: any = null; - x = 'foo'; - } - - @Directive({selector: '[my-class-dir]'}) - class MyClassDir { - @Input('class') - set classVal(v: string) { - capturedClassBindingCount++; - capturedClassBindingValue = v; - } - - @Input('my-class-dir') - set myClassVal(v: string) { - capturedMyClassBindingCount++; - capturedMyClassBindingValue = v; - } - } - - TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(1); - expect(capturedClassBindingValue as any).toEqual(null); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - - fixture.componentInstance.c = 'dynamic-value'; - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(2); - expect(capturedClassBindingValue!).toEqual('dynamic-value'); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - - fixture.componentInstance.c = null; - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(3); - expect(capturedClassBindingValue as any).toEqual(null); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - - fixture.componentInstance.c = ''; - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(4); - expect(capturedClassBindingValue as any).toEqual(''); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - }); - - it('should write to [class] binding during `update` mode if there is an instantiation-level value', - () => { - let capturedClassBindingCount = 0; - let capturedClassBindingValue: string|null|undefined = undefined; - - @Component({template: '
'}) - class Cmp { - c: any = 'bar'; - } - - @Directive({selector: '[my-class-dir]'}) - class MyClassDir { - @Input('class') - set classVal(v: string) { - capturedClassBindingCount++; - capturedClassBindingValue = v; - } - } - - TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); - const fixture = TestBed.createComponent(Cmp); - expect(capturedClassBindingCount).toEqual(0); - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(1); - expect(capturedClassBindingValue as any).toEqual('bar'); - - fixture.componentInstance.c = 'dynamic-bar'; - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(2); - expect(capturedClassBindingValue!).toEqual('dynamic-bar'); - }); + it('should not write to a `class` input binding in the event that there is no static class value', () => { + let capturedClassBindingCount = 0; + let capturedClassBindingValue: string | null | undefined = undefined; + let capturedMyClassBindingCount = 0; + let capturedMyClassBindingValue: string | null | undefined = undefined; + + @Component({template: '
'}) + class Cmp { + c: any = null; + x = 'foo'; + } + + @Directive({selector: '[my-class-dir]'}) + class MyClassDir { + @Input('class') + set classVal(v: string) { + capturedClassBindingCount++; + capturedClassBindingValue = v; + } + + @Input('my-class-dir') + set myClassVal(v: string) { + capturedMyClassBindingCount++; + capturedMyClassBindingValue = v; + } + } + + TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(1); + expect(capturedClassBindingValue as any).toEqual(null); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + + fixture.componentInstance.c = 'dynamic-value'; + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(2); + expect(capturedClassBindingValue!).toEqual('dynamic-value'); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + + fixture.componentInstance.c = null; + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(3); + expect(capturedClassBindingValue as any).toEqual(null); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + + fixture.componentInstance.c = ''; + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(4); + expect(capturedClassBindingValue as any).toEqual(''); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + }); + + it('should write to [class] binding during `update` mode if there is an instantiation-level value', () => { + let capturedClassBindingCount = 0; + let capturedClassBindingValue: string | null | undefined = undefined; + + @Component({template: '
'}) + class Cmp { + c: any = 'bar'; + } + + @Directive({selector: '[my-class-dir]'}) + class MyClassDir { + @Input('class') + set classVal(v: string) { + capturedClassBindingCount++; + capturedClassBindingValue = v; + } + } + + TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); + const fixture = TestBed.createComponent(Cmp); + expect(capturedClassBindingCount).toEqual(0); + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(1); + expect(capturedClassBindingValue as any).toEqual('bar'); + + fixture.componentInstance.c = 'dynamic-bar'; + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(2); + expect(capturedClassBindingValue!).toEqual('dynamic-bar'); + }); it('should write to a `class` input binding if there is a static class value', () => { let capturedClassBindingCount = 0; - let capturedClassBindingValue: string|null = null; + let capturedClassBindingValue: string | null = null; let capturedMyClassBindingCount = 0; - let capturedMyClassBindingValue: string|null = null; + let capturedMyClassBindingValue: string | null = null; @Component({template: '
'}) class Cmp { @@ -1405,8 +1404,7 @@ describe('styling', () => { @Component({ template: ``, }) - class App { - } + class App {} TestBed.configureTestingModule({declarations: [Comp, App]}); const fixture = TestBed.createComponent(App); @@ -1426,8 +1424,7 @@ describe('styling', () => { @Component({ template: ``, }) - class App { - } + class App {} TestBed.configureTestingModule({declarations: [Comp, App]}); const fixture = TestBed.createComponent(App); @@ -1435,92 +1432,87 @@ describe('styling', () => { expect(fixture.debugElement.nativeElement.firstChild.innerHTML).toBe('static my-className'); }); - it('should write to a `class` input binding if there is a static class value and there is a binding value', - () => { - let capturedClassBindingCount = 0; - let capturedClassBindingValue: string|null = null; - let capturedMyClassBindingCount = 0; - let capturedMyClassBindingValue: string|null = null; - - @Component({template: '
'}) - class Cmp { - c: any = null; - x: any = 'foo'; - } - - @Directive({selector: '[my-class-dir]'}) - class MyClassDir { - @Input('class') - set classVal(v: string) { - capturedClassBindingCount++; - capturedClassBindingValue = v; - } - - @Input('my-class-dir') - set myClassVal(v: string) { - capturedMyClassBindingCount++; - capturedMyClassBindingValue = v; - } - } - - TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - expect(capturedClassBindingCount) - .toEqual( - 2 - // '2' is not ideal as '1' would be preferred. - // The reason for two writes is that one is for the static - // `class="static-val"` and one for `[class]="c"`. This means that - // `class="static-val"` is written during the create block which is not ideal. - // To do this correctly we would have to delay the `class="static-val"` until - // the update block, but that would be expensive since it would require that we - // would check if we possibly have this situation on every `advance()` - // instruction. We don't think this is worth it, and we are just going to live - // with this. - ); - expect(capturedClassBindingValue!).toEqual('static-val'); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - - capturedClassBindingCount = 0; - fixture.componentInstance.c = 'dynamic-val'; - fixture.detectChanges(); - - expect(capturedClassBindingCount).toEqual(1); - expect(capturedClassBindingValue!).toEqual('static-val dynamic-val'); - expect(capturedMyClassBindingCount).toEqual(1); - expect(capturedMyClassBindingValue!).toEqual('foo'); - }); - - it('should allow multiple directives to set dynamic and static classes independent of one another', - () => { - @Component({ - template: ` + it('should write to a `class` input binding if there is a static class value and there is a binding value', () => { + let capturedClassBindingCount = 0; + let capturedClassBindingValue: string | null = null; + let capturedMyClassBindingCount = 0; + let capturedMyClassBindingValue: string | null = null; + + @Component({template: '
'}) + class Cmp { + c: any = null; + x: any = 'foo'; + } + + @Directive({selector: '[my-class-dir]'}) + class MyClassDir { + @Input('class') + set classVal(v: string) { + capturedClassBindingCount++; + capturedClassBindingValue = v; + } + + @Input('my-class-dir') + set myClassVal(v: string) { + capturedMyClassBindingCount++; + capturedMyClassBindingValue = v; + } + } + + TestBed.configureTestingModule({declarations: [Cmp, MyClassDir]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual( + 2, + // '2' is not ideal as '1' would be preferred. + // The reason for two writes is that one is for the static + // `class="static-val"` and one for `[class]="c"`. This means that + // `class="static-val"` is written during the create block which is not ideal. + // To do this correctly we would have to delay the `class="static-val"` until + // the update block, but that would be expensive since it would require that we + // would check if we possibly have this situation on every `advance()` + // instruction. We don't think this is worth it, and we are just going to live + // with this. + ); + expect(capturedClassBindingValue!).toEqual('static-val'); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + + capturedClassBindingCount = 0; + fixture.componentInstance.c = 'dynamic-val'; + fixture.detectChanges(); + + expect(capturedClassBindingCount).toEqual(1); + expect(capturedClassBindingValue!).toEqual('static-val dynamic-val'); + expect(capturedMyClassBindingCount).toEqual(1); + expect(capturedMyClassBindingValue!).toEqual('foo'); + }); + + it('should allow multiple directives to set dynamic and static classes independent of one another', () => { + @Component({ + template: `
- ` - }) - class Cmp { - } + `, + }) + class Cmp {} - @Directive({selector: '[dir-one]', host: {'[class.dir-one]': 'dirOneExp'}}) - class DirOne { - dirOneExp = true; - } + @Directive({selector: '[dir-one]', host: {'[class.dir-one]': 'dirOneExp'}}) + class DirOne { + dirOneExp = true; + } - @Directive({selector: '[dir-two]', host: {'class': 'dir-two'}}) - class DirTwo { - } + @Directive({selector: '[dir-two]', host: {'class': 'dir-two'}}) + class DirTwo {} - TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); + TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); - const element = fixture.nativeElement.querySelector('div'); - expect(element.classList.contains('dir-one')).toBeTruthy(); - expect(element.classList.contains('dir-two')).toBeTruthy(); - }); + const element = fixture.nativeElement.querySelector('div'); + expect(element.classList.contains('dir-one')).toBeTruthy(); + expect(element.classList.contains('dir-two')).toBeTruthy(); + }); it('should not write empty style values to the DOM', () => { @Component({ @@ -1530,10 +1522,9 @@ describe('styling', () => { [style.--bg-color]="undefined" [style.margin]="''" [style.font-size]="' '"> - ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp]}); const fixture = TestBed.createComponent(Cmp); @@ -1549,8 +1540,7 @@ describe('styling', () => { // be found by DI when ChildDir is instantiated. it('should not overwrite other directive info when using NgClass', () => { @Directive({selector: '[test-dir]'}) - class TestDir { - } + class TestDir {} @Directive({selector: '[child-dir]'}) class ChildDir { @@ -1563,7 +1553,7 @@ describe('styling', () => {
Hello
- ` + `, }) class AppComponent { classMap = {'with-button': true}; @@ -1629,58 +1619,57 @@ describe('styling', () => { expect(classList.contains('two')).toBe(true); }); - it('should apply single property styles/classes to the element and default to any static styling values', - () => { - @Component({ - template: ` + it('should apply single property styles/classes to the element and default to any static styling values', () => { + @Component({ + template: `
- ` - }) - class Cmp { - w: string|null|undefined = '100px'; - h: string|null|undefined = '100px'; - o: string|null|undefined = '0.5'; - abc = true; - xyz = false; - } - - TestBed.configureTestingModule({declarations: [Cmp]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const element = fixture.nativeElement.querySelector('div'); - expect(element.style.width).toEqual('100px'); - expect(element.style.height).toEqual('100px'); - expect(element.style.opacity).toEqual('0.5'); - expect(element.classList.contains('abc')).toBeTruthy(); - expect(element.classList.contains('xyz')).toBeFalsy(); - - fixture.componentInstance.w = undefined; - fixture.componentInstance.h = undefined; - fixture.componentInstance.o = undefined; - fixture.componentInstance.abc = false; - fixture.componentInstance.xyz = true; - fixture.detectChanges(); - - expect(element.style.width).toEqual('200px'); - expect(element.style.height).toEqual('200px'); - expect(element.style.opacity).toBeFalsy(); - expect(element.classList.contains('abc')).toBeFalsy(); - expect(element.classList.contains('xyz')).toBeTruthy(); - - fixture.componentInstance.w = null; - fixture.componentInstance.h = null; - fixture.componentInstance.o = null; - fixture.detectChanges(); - expect(element.style.width).toBeFalsy(); - expect(element.style.height).toBeFalsy(); - expect(element.style.opacity).toBeFalsy(); - }); + `, + }) + class Cmp { + w: string | null | undefined = '100px'; + h: string | null | undefined = '100px'; + o: string | null | undefined = '0.5'; + abc = true; + xyz = false; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('div'); + expect(element.style.width).toEqual('100px'); + expect(element.style.height).toEqual('100px'); + expect(element.style.opacity).toEqual('0.5'); + expect(element.classList.contains('abc')).toBeTruthy(); + expect(element.classList.contains('xyz')).toBeFalsy(); + + fixture.componentInstance.w = undefined; + fixture.componentInstance.h = undefined; + fixture.componentInstance.o = undefined; + fixture.componentInstance.abc = false; + fixture.componentInstance.xyz = true; + fixture.detectChanges(); + + expect(element.style.width).toEqual('200px'); + expect(element.style.height).toEqual('200px'); + expect(element.style.opacity).toBeFalsy(); + expect(element.classList.contains('abc')).toBeFalsy(); + expect(element.classList.contains('xyz')).toBeTruthy(); + + fixture.componentInstance.w = null; + fixture.componentInstance.h = null; + fixture.componentInstance.o = null; + fixture.detectChanges(); + expect(element.style.width).toBeFalsy(); + expect(element.style.height).toBeFalsy(); + expect(element.style.opacity).toBeFalsy(); + }); it('should apply single style/class across the template and directive host bindings', () => { @Directive({selector: '[dir-that-sets-width]'}) @@ -1698,16 +1687,17 @@ describe('styling', () => {
- ` + `, }) class Cmp { - w0: string|null|undefined = null; - w1: string|null|undefined = null; - w2: string|null|undefined = null; + w0: string | null | undefined = null; + w1: string | null | undefined = null; + w2: string | null | undefined = null; } - TestBed.configureTestingModule( - {declarations: [Cmp, DirThatSetsWidthDirective, AnotherDirThatSetsWidthDirective]}); + TestBed.configureTestingModule({ + declarations: [Cmp, DirThatSetsWidthDirective, AnotherDirThatSetsWidthDirective], + }); const fixture = TestBed.createComponent(Cmp); fixture.componentInstance.w0 = '100px'; fixture.componentInstance.w1 = '200px'; @@ -1744,136 +1734,138 @@ describe('styling', () => { expect(element.style.width).toEqual('600px'); }); - it('should only run stylingFlush once when there are no collisions between styling properties', - () => { - @Directive({selector: '[dir-with-styling]'}) - class DirWithStyling { - @HostBinding('style.font-size') public fontSize = '100px'; - } + it('should only run stylingFlush once when there are no collisions between styling properties', () => { + @Directive({selector: '[dir-with-styling]'}) + class DirWithStyling { + @HostBinding('style.font-size') public fontSize = '100px'; + } - @Component({selector: 'comp-with-styling'}) - class CompWithStyling { - @HostBinding('style.width') public width = '900px'; + @Component({selector: 'comp-with-styling'}) + class CompWithStyling { + @HostBinding('style.width') public width = '900px'; - @HostBinding('style.height') public height = '900px'; - } + @HostBinding('style.height') public height = '900px'; + } - @Component({ - template: ` + @Component({ + template: ` ... - ` - }) - class Cmp { - opacity: string|null = '0.5'; - @ViewChild(CompWithStyling, {static: true}) compWithStyling: CompWithStyling|null = null; - @ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling|null = null; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const component = fixture.componentInstance; - const element = fixture.nativeElement.querySelector('comp-with-styling'); - - expect(element.style.opacity).toEqual('0.5'); - expect(element.style.width).toEqual('900px'); - expect(element.style.height).toEqual('900px'); - expect(element.style.fontSize).toEqual('100px'); - - // once for the template flush and again for the host bindings - expect(ngDevMode!.rendererSetStyle).toEqual(4); - ngDevModeResetPerfCounters(); - - component.opacity = '0.6'; - component.compWithStyling!.height = '100px'; - component.compWithStyling!.width = '100px'; - component.dirWithStyling!.fontSize = '50px'; - fixture.detectChanges(); - - expect(element.style.opacity).toEqual('0.6'); - expect(element.style.width).toEqual('100px'); - expect(element.style.height).toEqual('100px'); - expect(element.style.fontSize).toEqual('50px'); - - // once for the template flush and again for the host bindings - expect(ngDevMode!.rendererSetStyle).toEqual(4); - }); - - it('should combine all styling across the template, directive and component host bindings', - () => { - @Directive({selector: '[dir-with-styling]'}) - class DirWithStyling { - @HostBinding('style.color') public color = 'red'; - - @HostBinding('style.font-size') public fontSize = '100px'; - - @HostBinding('class.dir') public dirClass = true; - } - - @Component({selector: 'comp-with-styling'}) - class CompWithStyling { - @HostBinding('style.width') public width = '900px'; - - @HostBinding('style.height') public height = '900px'; - - @HostBinding('class.comp') public compClass = true; - } - - @Component({ - template: ` + `, + }) + class Cmp { + opacity: string | null = '0.5'; + @ViewChild(CompWithStyling, {static: true}) compWithStyling: CompWithStyling | null = null; + @ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling | null = null; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const component = fixture.componentInstance; + const element = fixture.nativeElement.querySelector('comp-with-styling'); + + expect(element.style.opacity).toEqual('0.5'); + expect(element.style.width).toEqual('900px'); + expect(element.style.height).toEqual('900px'); + expect(element.style.fontSize).toEqual('100px'); + + // once for the template flush and again for the host bindings + expect(ngDevMode!.rendererSetStyle).toEqual(4); + ngDevModeResetPerfCounters(); + + component.opacity = '0.6'; + component.compWithStyling!.height = '100px'; + component.compWithStyling!.width = '100px'; + component.dirWithStyling!.fontSize = '50px'; + fixture.detectChanges(); + + expect(element.style.opacity).toEqual('0.6'); + expect(element.style.width).toEqual('100px'); + expect(element.style.height).toEqual('100px'); + expect(element.style.fontSize).toEqual('50px'); + + // once for the template flush and again for the host bindings + expect(ngDevMode!.rendererSetStyle).toEqual(4); + }); + + it('should combine all styling across the template, directive and component host bindings', () => { + @Directive({selector: '[dir-with-styling]'}) + class DirWithStyling { + @HostBinding('style.color') public color = 'red'; + + @HostBinding('style.font-size') public fontSize = '100px'; + + @HostBinding('class.dir') public dirClass = true; + } + + @Component({selector: 'comp-with-styling'}) + class CompWithStyling { + @HostBinding('style.width') public width = '900px'; + + @HostBinding('style.height') public height = '900px'; + + @HostBinding('class.comp') public compClass = true; + } + + @Component({ + template: ` ... - ` - }) - class Cmp { - opacity: string|null|undefined = '0.5'; - width: string|null|undefined = 'auto'; - tplClass = true; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const element = fixture.nativeElement.querySelector('comp-with-styling'); - - expectStyle(element).toEqual({ - 'color': 'red', - 'font-size': '100px', - 'height': '900px', - 'opacity': '0.5', - 'width': 'auto', - }); - expectClass(element).toEqual({ - 'dir': true, - 'comp': true, - 'tpl': true, - }); - - fixture.componentInstance.width = undefined; - fixture.componentInstance.opacity = undefined; - fixture.componentInstance.tplClass = false; - fixture.detectChanges(); - - expectStyle(element).toEqual( - {'color': 'red', 'width': '900px', 'height': '900px', 'font-size': '100px'}); - expectClass(element).toEqual({ - 'dir': true, - 'comp': true, - }); - - fixture.componentInstance.width = null; - fixture.componentInstance.opacity = null; - fixture.detectChanges(); - - expectStyle(element).toEqual({'color': 'red', 'height': '900px', 'font-size': '100px'}); - }); + `, + }) + class Cmp { + opacity: string | null | undefined = '0.5'; + width: string | null | undefined = 'auto'; + tplClass = true; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('comp-with-styling'); + + expectStyle(element).toEqual({ + 'color': 'red', + 'font-size': '100px', + 'height': '900px', + 'opacity': '0.5', + 'width': 'auto', + }); + expectClass(element).toEqual({ + 'dir': true, + 'comp': true, + 'tpl': true, + }); + + fixture.componentInstance.width = undefined; + fixture.componentInstance.opacity = undefined; + fixture.componentInstance.tplClass = false; + fixture.detectChanges(); + + expectStyle(element).toEqual({ + 'color': 'red', + 'width': '900px', + 'height': '900px', + 'font-size': '100px', + }); + expectClass(element).toEqual({ + 'dir': true, + 'comp': true, + }); + + fixture.componentInstance.width = null; + fixture.componentInstance.opacity = null; + fixture.detectChanges(); + + expectStyle(element).toEqual({'color': 'red', 'height': '900px', 'font-size': '100px'}); + }); it('should properly apply styling across sub and super class directive host bindings', () => { @Directive({selector: '[super-class-dir]'}) @@ -1889,10 +1881,10 @@ describe('styling', () => { @Component({ template: `
- ` + `, }) class Cmp { - w3: string|null|undefined = '300px'; + w3: string | null | undefined = '300px'; } TestBed.configureTestingModule({declarations: [Cmp, SuperClassDirective, SubClassDirective]}); @@ -1918,23 +1910,22 @@ describe('styling', () => { }); }); - it('should apply map-based style and class entries', () => { @Component({template: '
'}) class Cmp { - public c: {[key: string]: any}|null = null; + public c: {[key: string]: any} | null = null; updateClasses(classes: string) { const c = this.c || (this.c = {}); - Object.keys(this.c).forEach(className => { + Object.keys(this.c).forEach((className) => { c[className] = false; }); - classes.split(/\s+/).forEach(className => { + classes.split(/\s+/).forEach((className) => { c[className] = true; }); } - public s: {[key: string]: any}|null = null; - updateStyles(prop: string, value: string|number|null) { + public s: {[key: string]: any} | null = null; + updateStyles(prop: string, value: string | number | null) { const s = this.s || (this.s = {}); Object.assign(s, {[prop]: value}); } @@ -1967,214 +1958,212 @@ describe('styling', () => { expectClass(element).toEqual({def: true}); }); - it('should resolve styling collisions across templates, directives and components for prop and map-based entries', - () => { - @Directive({selector: '[dir-that-sets-styling]'}) - class DirThatSetsStyling { - @HostBinding('style') public map: any = {color: 'red', width: '777px'}; - } + it('should resolve styling collisions across templates, directives and components for prop and map-based entries', () => { + @Directive({selector: '[dir-that-sets-styling]'}) + class DirThatSetsStyling { + @HostBinding('style') public map: any = {color: 'red', width: '777px'}; + } - @Component({ - template: ` + @Component({ + template: `
- ` - }) - class Cmp { - map: any = {width: '111px', opacity: '0.5'}; - width: string|null|undefined = '555px'; - - @ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]}); - const fixture = TestBed.createComponent(Cmp); - const comp = fixture.componentInstance; - fixture.detectChanges(); - - const element = fixture.nativeElement.querySelector('div'); - expectStyle(element).toEqual({ - 'width': '555px', - 'color': 'red', - 'font-size': '99px', - 'opacity': '0.5', - }); - - comp.width = undefined; - fixture.detectChanges(); - - expectStyle(element).toEqual({ - 'width': '111px', - 'color': 'red', - 'font-size': '99px', - 'opacity': '0.5', - }); - - comp.map = null; - fixture.detectChanges(); - - expectStyle(element).toEqual({ - 'width': '200px', - 'color': 'red', - 'font-size': '99px', - }); - - comp.dir.map = null; - fixture.detectChanges(); - - expectStyle(element).toEqual({ - 'width': '200px', - 'font-size': '99px', - }); - }); - - it('should only apply each styling property once per CD across templates, components, directives', - () => { - @Directive({selector: '[dir-that-sets-styling]', host: {'style': 'width:0px; height:0px'}}) - class DirThatSetsStyling { - @HostBinding('style') public map: any = {width: '999px', height: '999px'}; - } - - @Component({ - template: ` + `, + }) + class Cmp { + map: any = {width: '111px', opacity: '0.5'}; + width: string | null | undefined = '555px'; + + @ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]}); + const fixture = TestBed.createComponent(Cmp); + const comp = fixture.componentInstance; + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('div'); + expectStyle(element).toEqual({ + 'width': '555px', + 'color': 'red', + 'font-size': '99px', + 'opacity': '0.5', + }); + + comp.width = undefined; + fixture.detectChanges(); + + expectStyle(element).toEqual({ + 'width': '111px', + 'color': 'red', + 'font-size': '99px', + 'opacity': '0.5', + }); + + comp.map = null; + fixture.detectChanges(); + + expectStyle(element).toEqual({ + 'width': '200px', + 'color': 'red', + 'font-size': '99px', + }); + + comp.dir.map = null; + fixture.detectChanges(); + + expectStyle(element).toEqual({ + 'width': '200px', + 'font-size': '99px', + }); + }); + + it('should only apply each styling property once per CD across templates, components, directives', () => { + @Directive({selector: '[dir-that-sets-styling]', host: {'style': 'width:0px; height:0px'}}) + class DirThatSetsStyling { + @HostBinding('style') public map: any = {width: '999px', height: '999px'}; + } + + @Component({ + template: `
- ` - }) - class Cmp { - width: string|null|undefined = '111px'; - height: string|null|undefined = '111px'; - - map: any = {width: '555px', height: '555px'}; - - @ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]}); - const fixture = TestBed.createComponent(Cmp); - const comp = fixture.componentInstance; - - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - const element = fixture.nativeElement.querySelector('div'); - - assertStyleCounters(4, 0); - assertStyle(element, 'width', '111px'); - assertStyle(element, 'height', '111px'); - - comp.width = '222px'; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(1, 0); - assertStyle(element, 'width', '222px'); - assertStyle(element, 'height', '111px'); - - comp.height = '222px'; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(1, 0); - assertStyle(element, 'width', '222px'); - assertStyle(element, 'height', '222px'); - - comp.width = undefined; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(1, 0); - assertStyle(element, 'width', '555px'); - assertStyle(element, 'height', '222px'); - - comp.width = '123px'; - comp.height = '123px'; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyle(element, 'width', '123px'); - assertStyle(element, 'height', '123px'); - - comp.map = {}; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - // No change, hence no write - assertStyleCounters(0, 0); - assertStyle(element, 'width', '123px'); - assertStyle(element, 'height', '123px'); - - comp.width = undefined; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(1, 0); - assertStyle(element, 'width', '999px'); - assertStyle(element, 'height', '123px'); - - comp.dir.map = null; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - // the width is only applied once - assertStyleCounters(1, 0); - assertStyle(element, 'width', '0px'); - assertStyle(element, 'height', '123px'); - - comp.dir.map = {width: '1000px', height: '1100px', color: 'red'}; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(2, 0); - assertStyle(element, 'width', '1000px'); - assertStyle(element, 'height', '123px'); - assertStyle(element, 'color', 'red'); - - comp.height = undefined; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - // height gets applied twice and all other - // values get applied - assertStyleCounters(1, 0); - assertStyle(element, 'width', '1000px'); - assertStyle(element, 'height', '1100px'); - assertStyle(element, 'color', 'red'); - - comp.map = {color: 'blue', width: '2000px', opacity: '0.5'}; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - assertStyleCounters(3, 0); - assertStyle(element, 'width', '2000px'); - assertStyle(element, 'height', '1100px'); - assertStyle(element, 'color', 'blue'); - assertStyle(element, 'opacity', '0.5'); - - comp.map = {color: 'blue', width: '2000px'}; - ngDevModeResetPerfCounters(); - fixture.detectChanges(); - - // all four are applied because the map was altered - assertStyleCounters(0, 1); - assertStyle(element, 'width', '2000px'); - assertStyle(element, 'height', '1100px'); - assertStyle(element, 'color', 'blue'); - assertStyle(element, 'opacity', ''); - }); + `, + }) + class Cmp { + width: string | null | undefined = '111px'; + height: string | null | undefined = '111px'; + + map: any = {width: '555px', height: '555px'}; + + @ViewChild('dir', {read: DirThatSetsStyling, static: true}) dir!: DirThatSetsStyling; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]}); + const fixture = TestBed.createComponent(Cmp); + const comp = fixture.componentInstance; + + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + const element = fixture.nativeElement.querySelector('div'); + + assertStyleCounters(4, 0); + assertStyle(element, 'width', '111px'); + assertStyle(element, 'height', '111px'); + + comp.width = '222px'; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(1, 0); + assertStyle(element, 'width', '222px'); + assertStyle(element, 'height', '111px'); + + comp.height = '222px'; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(1, 0); + assertStyle(element, 'width', '222px'); + assertStyle(element, 'height', '222px'); + + comp.width = undefined; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(1, 0); + assertStyle(element, 'width', '555px'); + assertStyle(element, 'height', '222px'); + + comp.width = '123px'; + comp.height = '123px'; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyle(element, 'width', '123px'); + assertStyle(element, 'height', '123px'); + + comp.map = {}; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + // No change, hence no write + assertStyleCounters(0, 0); + assertStyle(element, 'width', '123px'); + assertStyle(element, 'height', '123px'); + + comp.width = undefined; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(1, 0); + assertStyle(element, 'width', '999px'); + assertStyle(element, 'height', '123px'); + + comp.dir.map = null; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + // the width is only applied once + assertStyleCounters(1, 0); + assertStyle(element, 'width', '0px'); + assertStyle(element, 'height', '123px'); + + comp.dir.map = {width: '1000px', height: '1100px', color: 'red'}; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(2, 0); + assertStyle(element, 'width', '1000px'); + assertStyle(element, 'height', '123px'); + assertStyle(element, 'color', 'red'); + + comp.height = undefined; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + // height gets applied twice and all other + // values get applied + assertStyleCounters(1, 0); + assertStyle(element, 'width', '1000px'); + assertStyle(element, 'height', '1100px'); + assertStyle(element, 'color', 'red'); + + comp.map = {color: 'blue', width: '2000px', opacity: '0.5'}; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + assertStyleCounters(3, 0); + assertStyle(element, 'width', '2000px'); + assertStyle(element, 'height', '1100px'); + assertStyle(element, 'color', 'blue'); + assertStyle(element, 'opacity', '0.5'); + + comp.map = {color: 'blue', width: '2000px'}; + ngDevModeResetPerfCounters(); + fixture.detectChanges(); + + // all four are applied because the map was altered + assertStyleCounters(0, 1); + assertStyle(element, 'width', '2000px'); + assertStyle(element, 'height', '1100px'); + assertStyle(element, 'color', 'blue'); + assertStyle(element, 'opacity', ''); + }); it('should not sanitize style values before writing them', () => { @Component({ template: `
- ` + `, }) class Cmp { widthExp = ''; @@ -2207,7 +2196,7 @@ describe('styling', () => { template: `
- ` + `, }) class Cmp { widthExp = ''; @@ -2228,7 +2217,7 @@ describe('styling', () => { // Prove that bindings work. comp.widthExp = '789px'; comp.styleMapExp = { - 'background-image': bypassSanitizationTrustStyle(comp.styleMapExp['background-image']) + 'background-image': bypassSanitizationTrustStyle(comp.styleMapExp['background-image']), }; fixture.detectChanges(); @@ -2241,11 +2230,11 @@ describe('styling', () => { template: `
- ` + `, }) class Cmp { - widthExp: string|number|null = ''; - heightExp: string|number|null = ''; + widthExp: string | number | null = ''; + heightExp: string | number | null = ''; } TestBed.configureTestingModule({declarations: [Cmp]}); @@ -2373,7 +2362,7 @@ describe('styling', () => { [style]="map" dir-with-styling dir-with-styling-part2>
- ` + `, }) class Cmp { map: any = null; @@ -2401,7 +2390,7 @@ describe('styling', () => { class="initial-class item-{{ item }}"> {{ item }} - ` + `, }) class Cmp { items = [1, 2, 3]; @@ -2417,7 +2406,11 @@ describe('styling', () => { } function getItemClasses(): string[] { - return getItemElements().map(e => e.className).sort().join(' ').split(' '); + return getItemElements() + .map((e) => e.className) + .sort() + .join(' ') + .split(' '); } expect(getItemElements().length).toEqual(3); @@ -2446,50 +2439,46 @@ describe('styling', () => { ]); }); - it('should create and update multiple class bindings across multiple elements in a template', - () => { - @Component({ - template: ` + it('should create and update multiple class bindings across multiple elements in a template', () => { + @Component({ + template: `
header
{{ item }}
footer
- ` - }) - class Cmp { - items = [1, 2, 3]; - } - - TestBed.configureTestingModule({declarations: [Cmp]}); - const fixture = TestBed.createComponent(Cmp); - const comp = fixture.componentInstance; - fixture.detectChanges(); - - function getItemElements(): HTMLElement[] { - return [].slice.call(fixture.nativeElement.querySelectorAll('div')); - } - - function getItemClasses(): string[] { - return getItemElements().map(e => e.className).sort().join(' ').split(' '); - } - - const header = fixture.nativeElement.querySelector('header'); - expect(header.classList.contains('header')).toBeTrue(); - - const footer = fixture.nativeElement.querySelector('footer'); - expect(footer.classList.contains('footer')).toBeTrue(); - - expect(getItemElements().length).toEqual(3); - expect(getItemClasses()).toEqual([ - 'item', - 'item-1', - 'item', - 'item-2', - 'item', - 'item-3', - ]); - }); + `, + }) + class Cmp { + items = [1, 2, 3]; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + const comp = fixture.componentInstance; + fixture.detectChanges(); + + function getItemElements(): HTMLElement[] { + return [].slice.call(fixture.nativeElement.querySelectorAll('div')); + } + + function getItemClasses(): string[] { + return getItemElements() + .map((e) => e.className) + .sort() + .join(' ') + .split(' '); + } + + const header = fixture.nativeElement.querySelector('header'); + expect(header.classList.contains('header')).toBeTrue(); + + const footer = fixture.nativeElement.querySelector('footer'); + expect(footer.classList.contains('footer')).toBeTrue(); + + expect(getItemElements().length).toEqual(3); + expect(getItemClasses()).toEqual(['item', 'item-1', 'item', 'item-2', 'item', 'item-3']); + }); it('should understand multiple directives which contain initial classes', () => { @Directive({selector: 'dir-one'}) @@ -2507,10 +2496,9 @@ describe('styling', () => {
- ` + `, }) - class Cmp { - } + class Cmp {} TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); const fixture = TestBed.createComponent(Cmp); @@ -2525,43 +2513,42 @@ describe('styling', () => { expect(div.classList.contains('initial')).toBeTruthy(); }); - it('should evaluate styling across the template directives when there are multiple elements/sources of styling', - () => { - @Directive({selector: '[one]'}) - class DirOne { - @HostBinding('class') public className = 'dir-one'; - } + it('should evaluate styling across the template directives when there are multiple elements/sources of styling', () => { + @Directive({selector: '[one]'}) + class DirOne { + @HostBinding('class') public className = 'dir-one'; + } - @Directive({selector: '[two]'}) - class DirTwo { - @HostBinding('class') public className = 'dir-two'; - } + @Directive({selector: '[two]'}) + class DirTwo { + @HostBinding('class') public className = 'dir-two'; + } - @Component({ - template: ` + @Component({ + template: `
- ` - }) - class Cmp { - w = 100; - h = 200; - c = 'red'; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const divA = fixture.nativeElement.querySelector('.a'); - const divB = fixture.nativeElement.querySelector('.b'); - const divC = fixture.nativeElement.querySelector('.c'); - - expect(divA.style.width).toEqual('100px'); - expect(divB.style.height).toEqual('200px'); - expect(divC.style.color).toEqual('red'); - }); + `, + }) + class Cmp { + w = 100; + h = 200; + c = 'red'; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const divA = fixture.nativeElement.querySelector('.a'); + const divB = fixture.nativeElement.querySelector('.b'); + const divC = fixture.nativeElement.querySelector('.c'); + + expect(divA.style.width).toEqual('100px'); + expect(divB.style.height).toEqual('200px'); + expect(divC.style.color).toEqual('red'); + }); it('should evaluate styling across the template and directives within embedded views', () => { @Directive({selector: '[some-dir-with-styling]'}) @@ -2585,7 +2572,7 @@ describe('styling', () => {

- ` + `, }) class Cmp { items: any[] = []; @@ -2614,7 +2601,7 @@ describe('styling', () => { expect(p.style['height']).toEqual('100px'); }); - it('should flush bindings even if any styling hasn\'t changed in a previous directive', () => { + it("should flush bindings even if any styling hasn't changed in a previous directive", () => { @Directive({selector: '[one]'}) class DirOne { @HostBinding('style.width') w = '100px'; @@ -2659,7 +2646,7 @@ describe('styling', () => { [style.width]="w" style.height="{{ h }}" [style.opacity]="o"> - ` + `, }) class Cmp { w: any = null; @@ -2694,7 +2681,7 @@ describe('styling', () => { @Component({ template: `
- ` + `, }) class Cmp { c: any = 'foo bar'; @@ -2715,7 +2702,7 @@ describe('styling', () => { @Component({ template: `
- ` + `, }) class Cmp { s: any = {opacity: '1'}; @@ -2739,191 +2726,188 @@ describe('styling', () => { expect(div.style.opacity).toEqual(''); }); - it('should allow detectChanges to be run in a property change that causes additional styling to be rendered', - () => { - @Component({ - selector: 'child', - template: ` + it('should allow detectChanges to be run in a property change that causes additional styling to be rendered', () => { + @Component({ + selector: 'child', + template: `
`, - }) - class ChildCmp { - readyTpl = false; + }) + class ChildCmp { + readyTpl = false; - @HostBinding('class.ready-host') readyHost = false; - } + @HostBinding('class.ready-host') readyHost = false; + } - @Component({ - selector: 'parent', - template: ` + @Component({ + selector: 'parent', + template: `

{{prop}}

`, - host: { - '[style.color]': 'color', - }, - }) - class ParentCmp { - private _prop = ''; - - @ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!; - - private child: ComponentRef = null!; - - @Input() - set prop(value: string) { - this._prop = value; - if (this.child && value === 'go') { - this.child.instance.readyHost = true; - this.child.instance.readyTpl = true; - this.child.changeDetectorRef.detectChanges(); - } - } - - get prop() { - return this._prop; - } - - ngAfterViewInit() { - this.child = this.vcr.createComponent(ChildCmp); - } - } - - @Component({ - template: ``, - }) - class App { - prop = 'a'; - } - - TestBed.configureTestingModule({declarations: [App, ParentCmp, ChildCmp]}); - const fixture = TestBed.createComponent(App); - fixture.detectChanges(false); - - let readyHost = fixture.nativeElement.querySelector('.ready-host'); - let readyChild = fixture.nativeElement.querySelector('.ready-child'); - - expect(readyHost).toBeFalsy(); - expect(readyChild).toBeFalsy(); - - fixture.componentInstance.prop = 'go'; - fixture.detectChanges(false); - - readyHost = fixture.nativeElement.querySelector('.ready-host'); - readyChild = fixture.nativeElement.querySelector('.ready-child'); - expect(readyHost).toBeTruthy(); - expect(readyChild).toBeTruthy(); - }); - - it('should allow detectChanges to be run in a hook that causes additional styling to be rendered', - () => { - @Component({ - selector: 'child', - template: ` + host: { + '[style.color]': 'color', + }, + }) + class ParentCmp { + private _prop = ''; + + @ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!; + + private child: ComponentRef = null!; + + @Input() + set prop(value: string) { + this._prop = value; + if (this.child && value === 'go') { + this.child.instance.readyHost = true; + this.child.instance.readyTpl = true; + this.child.changeDetectorRef.detectChanges(); + } + } + + get prop() { + return this._prop; + } + + ngAfterViewInit() { + this.child = this.vcr.createComponent(ChildCmp); + } + } + + @Component({ + template: ``, + }) + class App { + prop = 'a'; + } + + TestBed.configureTestingModule({declarations: [App, ParentCmp, ChildCmp]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(false); + + let readyHost = fixture.nativeElement.querySelector('.ready-host'); + let readyChild = fixture.nativeElement.querySelector('.ready-child'); + + expect(readyHost).toBeFalsy(); + expect(readyChild).toBeFalsy(); + + fixture.componentInstance.prop = 'go'; + fixture.detectChanges(false); + + readyHost = fixture.nativeElement.querySelector('.ready-host'); + readyChild = fixture.nativeElement.querySelector('.ready-child'); + expect(readyHost).toBeTruthy(); + expect(readyChild).toBeTruthy(); + }); + + it('should allow detectChanges to be run in a hook that causes additional styling to be rendered', () => { + @Component({ + selector: 'child', + template: `
`, - }) - class ChildCmp { - readyTpl = false; + }) + class ChildCmp { + readyTpl = false; - @HostBinding('class.ready-host') readyHost = false; - } + @HostBinding('class.ready-host') readyHost = false; + } - @Component({ - selector: 'parent', - template: ` + @Component({ + selector: 'parent', + template: `

{{prop}}

`, - }) - class ParentCmp { - updateChild = false; - - @ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!; - - private child: ComponentRef = null!; - - ngDoCheck() { - if (this.updateChild) { - this.child.instance.readyHost = true; - this.child.instance.readyTpl = true; - this.child.changeDetectorRef.detectChanges(); - } - } - - ngAfterViewInit() { - this.child = this.vcr.createComponent(ChildCmp); - } - } - - @Component({ - template: ``, - }) - class App { - @ViewChild('parent', {static: true}) public parent: ParentCmp|null = null; - } - - TestBed.configureTestingModule({declarations: [App, ParentCmp, ChildCmp]}); - const fixture = TestBed.createComponent(App); - fixture.detectChanges(false); - - let readyHost = fixture.nativeElement.querySelector('.ready-host'); - let readyChild = fixture.nativeElement.querySelector('.ready-child'); - expect(readyHost).toBeFalsy(); - expect(readyChild).toBeFalsy(); - - const parent = fixture.componentInstance.parent!; - parent.updateChild = true; - fixture.detectChanges(false); - - readyHost = fixture.nativeElement.querySelector('.ready-host'); - readyChild = fixture.nativeElement.querySelector('.ready-child'); - expect(readyHost).toBeTruthy(); - expect(readyChild).toBeTruthy(); - }); - - it('should allow various duplicate properties to be defined in various styling maps within the template and directive styling bindings', - () => { - @Component({ - template: ` + }) + class ParentCmp { + updateChild = false; + + @ViewChild('template', {read: ViewContainerRef}) vcr: ViewContainerRef = null!; + + private child: ComponentRef = null!; + + ngDoCheck() { + if (this.updateChild) { + this.child.instance.readyHost = true; + this.child.instance.readyTpl = true; + this.child.changeDetectorRef.detectChanges(); + } + } + + ngAfterViewInit() { + this.child = this.vcr.createComponent(ChildCmp); + } + } + + @Component({ + template: ``, + }) + class App { + @ViewChild('parent', {static: true}) public parent: ParentCmp | null = null; + } + + TestBed.configureTestingModule({declarations: [App, ParentCmp, ChildCmp]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(false); + + let readyHost = fixture.nativeElement.querySelector('.ready-host'); + let readyChild = fixture.nativeElement.querySelector('.ready-child'); + expect(readyHost).toBeFalsy(); + expect(readyChild).toBeFalsy(); + + const parent = fixture.componentInstance.parent!; + parent.updateChild = true; + fixture.detectChanges(false); + + readyHost = fixture.nativeElement.querySelector('.ready-host'); + readyChild = fixture.nativeElement.querySelector('.ready-child'); + expect(readyHost).toBeTruthy(); + expect(readyChild).toBeTruthy(); + }); + + it('should allow various duplicate properties to be defined in various styling maps within the template and directive styling bindings', () => { + @Component({ + template: `
- ` - }) - class Cmp { - h = '100px'; - w = '100px'; - s1: any = {border: '10px solid black', width: '200px'}; - s2: any = {border: '10px solid red', width: '300px'}; - } - - @Directive({selector: '[dir-with-styling]'}) - class DirectiveExpectingStyling { - @Input('dir-with-styling') @HostBinding('style') public styles: any = null; - } - - TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]}); - const fixture = TestBed.createComponent(Cmp); - fixture.detectChanges(); - - const element = fixture.nativeElement.querySelector('div'); - expect(element.style.border).toEqual('10px solid black'); - expect(element.style.width).toEqual('100px'); - expect(element.style.height).toEqual('100px'); - - fixture.componentInstance.s1 = null; - fixture.detectChanges(); - - expect(element.style.border).toEqual('10px solid red'); - expect(element.style.width).toEqual('100px'); - expect(element.style.height).toEqual('100px'); - }); + `, + }) + class Cmp { + h = '100px'; + w = '100px'; + s1: any = {border: '10px solid black', width: '200px'}; + s2: any = {border: '10px solid red', width: '300px'}; + } + + @Directive({selector: '[dir-with-styling]'}) + class DirectiveExpectingStyling { + @Input('dir-with-styling') @HostBinding('style') public styles: any = null; + } + + TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('div'); + expect(element.style.border).toEqual('10px solid black'); + expect(element.style.width).toEqual('100px'); + expect(element.style.height).toEqual('100px'); + + fixture.componentInstance.s1 = null; + fixture.detectChanges(); + + expect(element.style.border).toEqual('10px solid red'); + expect(element.style.width).toEqual('100px'); + expect(element.style.height).toEqual('100px'); + }); it('should retrieve styles set via Renderer2', () => { let dirInstance: any; @@ -2931,7 +2915,10 @@ describe('styling', () => { selector: '[dir]', }) class Dir { - constructor(public elementRef: ElementRef, public renderer: Renderer2) { + constructor( + public elementRef: ElementRef, + public renderer: Renderer2, + ) { dirInstance = this; } @@ -2943,8 +2930,7 @@ describe('styling', () => { } @Component({template: `
`}) - class App { - } + class App {} TestBed.configureTestingModule({ declarations: [App, Dir], @@ -2964,14 +2950,16 @@ describe('styling', () => { selector: '[dir]', }) class Dir { - constructor(public elementRef: ElementRef, public renderer: Renderer2) { + constructor( + public elementRef: ElementRef, + public renderer: Renderer2, + ) { dirInstance = this; } } @Component({template: `
`}) - class App { - } + class App {} TestBed.configureTestingModule({ declarations: [App, Dir], @@ -3003,7 +2991,6 @@ describe('styling', () => {
`, }) - class AppComponent { isDisabled = false; background = 'orange'; @@ -3021,93 +3008,92 @@ describe('styling', () => { expect(span.classList).not.toContain('disabled'); }); - it('should not set classes when falsy value is passed while a sanitizer from host bindings is present', - () => { - @Directive({selector: '[blockStyles]'}) - class StylesDirective { - @HostBinding('style.border') border = '1px solid red'; + it('should not set classes when falsy value is passed while a sanitizer from host bindings is present', () => { + @Directive({selector: '[blockStyles]'}) + class StylesDirective { + @HostBinding('style.border') border = '1px solid red'; - @HostBinding('style.background') background = 'white'; - } + @HostBinding('style.background') background = 'white'; + } - @Component({ - template: `
`, - }) - class AppComponent { - isDisabled = false; - } + @Component({ + template: `
`, + }) + class AppComponent { + isDisabled = false; + } - TestBed.configureTestingModule({declarations: [AppComponent, StylesDirective]}); - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); + TestBed.configureTestingModule({declarations: [AppComponent, StylesDirective]}); + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); - const div = fixture.nativeElement.querySelector('div'); - expect(div.classList.contains('disabled')).toBe(false); + const div = fixture.nativeElement.querySelector('div'); + expect(div.classList.contains('disabled')).toBe(false); - // The issue we're testing for happens after the second change detection. - fixture.detectChanges(); - expect(div.classList.contains('disabled')).toBe(false); - }); + // The issue we're testing for happens after the second change detection. + fixture.detectChanges(); + expect(div.classList.contains('disabled')).toBe(false); + }); - it('should throw an error if a prop-based style/class binding value is changed during checkNoChanges', - () => { - @Component({ - template: ` + it('should throw an error if a prop-based style/class binding value is changed during checkNoChanges', () => { + @Component({ + template: `
- ` - }) - class Cmp { - color = 'red'; - fooClass = true; - - ngAfterViewInit() { - this.color = 'blue'; - this.fooClass = false; - } - } - - TestBed.configureTestingModule({declarations: [Cmp]}); - const fixture = TestBed.createComponent(Cmp); - - expect(() => { - fixture.detectChanges(); - }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/); - }); - - it('should throw an error if a map-based style/class binding value is changed during checkNoChanges', - () => { - @Component({ - template: ` + `, + }) + class Cmp { + color = 'red'; + fooClass = true; + + ngAfterViewInit() { + this.color = 'blue'; + this.fooClass = false; + } + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + + expect(() => { + fixture.detectChanges(); + }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/); + }); + + it('should throw an error if a map-based style/class binding value is changed during checkNoChanges', () => { + @Component({ + template: `
- ` - }) - class Cmp { - style: any = 'width: 100px'; - klass: any = 'foo'; - - ngAfterViewInit() { - this.style = 'height: 200px'; - this.klass = 'bar'; - } - } - - TestBed.configureTestingModule({declarations: [Cmp]}); - const fixture = TestBed.createComponent(Cmp); - - expect(() => { - fixture.detectChanges(); - }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/); - }); + `, + }) + class Cmp { + style: any = 'width: 100px'; + klass: any = 'foo'; + + ngAfterViewInit() { + this.style = 'height: 200px'; + this.klass = 'bar'; + } + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + + expect(() => { + fixture.detectChanges(); + }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/); + }); it('should properly merge class interpolation with class-based directives', () => { - @Component( - {template: `
`}) + @Component({ + template: `
`, + }) class MyComp { one = 'one'; } - const fixture = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + const fixture = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent( + MyComp, + ); fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML).toContain('zero'); @@ -3200,8 +3186,9 @@ describe('styling', () => { exp = ''; } - const fixture = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + const fixture = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent( + MyComp, + ); fixture.detectChanges(); const div = fixture.nativeElement.querySelector('div')!; @@ -3235,41 +3222,41 @@ describe('styling', () => { constructor(private sanitizer: DomSanitizer) {} } - const fixture = - TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + const fixture = TestBed.configureTestingModule({declarations: [MyComp]}).createComponent( + MyComp, + ); fixture.detectChanges(true /* Verify that check no changes does not cause an exception */); const div: HTMLElement = fixture.nativeElement.querySelector('div'); - expect(div.style.getPropertyValue('background-image')) - .toEqual('url("https://i.imgur.com/4AiXzf8.jpg")'); + expect(div.style.getPropertyValue('background-image')).toEqual( + 'url("https://i.imgur.com/4AiXzf8.jpg")', + ); }); }); - isBrowser && it('should process
`, - styles: [ - 'div { width: 100px; }', - ] - }) - class MyComp { - } + styles: ['div { width: 100px; }'], + }) + class MyComp {} - TestBed.configureTestingModule({ - declarations: [MyComp], - }); + TestBed.configureTestingModule({ + declarations: [MyComp], + }); - const fixture = TestBed.createComponent(MyComp); - fixture.detectChanges(); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); - // `styles` array values are applied first, styles from ')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '
hi
')).toEqual( + '
hi
', + ); + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); }); it('ignores content of style elements', () => { - expect(sanitizeHtml(defaultDoc, '
hi
')) - .toEqual('
hi
'); + expect(sanitizeHtml(defaultDoc, '
hi
')).toEqual( + '
hi
', + ); expect(sanitizeHtml(defaultDoc, '')).toEqual(''); - expect(sanitizeHtml(defaultDoc, '')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/); }); @@ -182,12 +183,12 @@ describe('HTML sanitizer', () => { expect([ '<iframe>', // Double-escaped on IE - '&lt;iframe&gt;' + '&lt;iframe&gt;', ]).toContain(sanitizeHtml(defaultDoc, '