mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(core): Add stability debugging utility
This commit adds a utility method to debug why the application has not stabilized after a set period of time (9 seconds, or `hydrationTimeout-1`). fixes #52912
This commit is contained in:
committed by
Andrew Kushnir
parent
244b54c9bf
commit
99ad18a4ee
@@ -174,6 +174,24 @@ Keep in mind that adding the `ngSkipHydration` attribute to your root applicatio
|
||||
|
||||
Application stability is an important part of the hydration process. Hydration and any post-hydration processes only occur once the application has reported stability. There are a number of ways that stability can be delayed. Examples include setting timeouts and intervals, unresolved promises, and pending microtasks. In those cases, you may encounter the [Application remains unstable](errors/NG0506) error, which indicates that your app has not yet reached the stable state after 10 seconds. If you're finding that your application is not hydrating right away, take a look at what is impacting application stability and refactor to avoid causing these delays.
|
||||
|
||||
### Debugging Application Stability
|
||||
|
||||
The `provideStabilityDebugging` utility helps identify why your application fails to stabilize. This utility is provided by default in dev mode when using `provideClientHydration`. You can also add it manually to the application providers for use in production bundles or when using SSR without hydration, for example. The feature logs information to the console if the application takes longer than expected to stabilize.
|
||||
|
||||
```typescript
|
||||
import {provideStabilityDebugging} from '@angular/core';
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
import 'zone.js/plugins/task-tracking'; // Use if you have Zone.js with `provideZoneChangeDetection`
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideStabilityDebugging()],
|
||||
});
|
||||
```
|
||||
|
||||
When enabled, the utility logs pending tasks (`PendingTasks`) to the console. If your application uses Zone.js, you can also import `zone.js/plugins/task-tracking` to see which macrotasks are keeping the Angular Zone from stabilizing. This plugin provides the stack trace of the macrotask creation, effectively helping you identify the source of the delay.
|
||||
|
||||
IMPORTANT: Angular does not remove the zone.js task tracking plugin or this utility from production bundles. Use them only for temporary debugging of stability issues during development, including for optimized production builds.
|
||||
|
||||
## I18N
|
||||
|
||||
HELPFUL: By default, Angular will skip hydration for components that use i18n blocks, effectively re-rendering those components from scratch.
|
||||
|
||||
@@ -1493,6 +1493,9 @@ export type Provider = TypeProvider | ValueProvider | ClassProvider | Constructo
|
||||
// @public
|
||||
export type ProviderToken<T> = Type<T> | AbstractType<T> | InjectionToken<T>;
|
||||
|
||||
// @public
|
||||
export function provideStabilityDebugging(): EnvironmentProviders;
|
||||
|
||||
// @public
|
||||
export function provideZoneChangeDetection(options?: NgZoneOptions): EnvironmentProviders;
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
|
||||
export const DEBUG_TASK_TRACKER = new InjectionToken<DebugTaskTracker>(
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode ? 'DEBUG_TASK_TRACKER' : '',
|
||||
);
|
||||
export interface DebugTaskTracker {
|
||||
add(taskId: number): void;
|
||||
remove(taskId: number): void;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {EnvironmentProviders, inject, makeEnvironmentProviders} from '../di';
|
||||
import {NgZone} from '../zone';
|
||||
import {provideAppInitializer} from './application_init';
|
||||
import {ApplicationRef} from './application_ref';
|
||||
import {APPLICATION_IS_STABLE_TIMEOUT} from '../hydration/api';
|
||||
import {DEBUG_TASK_TRACKER, DebugTaskTracker} from './stability_debug';
|
||||
|
||||
const STABILITY_WARNING_THRESHOLD = APPLICATION_IS_STABLE_TIMEOUT - 1_000;
|
||||
|
||||
class DebugTaskTrackerImpl implements DebugTaskTracker {
|
||||
readonly openTasks = new Map<number, Error>();
|
||||
|
||||
add(taskId: number): void {
|
||||
this.openTasks.set(taskId, new Error('Task stack tracking error'));
|
||||
}
|
||||
|
||||
remove(taskId: number): void {
|
||||
this.openTasks.delete(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an application initializer that will log information about what tasks are keeping
|
||||
* the application from stabilizing if the application does not stabilize within 9 seconds.
|
||||
*
|
||||
* The logged information includes the stack of the tasks preventing stability. This stack can be traced
|
||||
* back to the source in the application code.
|
||||
*
|
||||
* If you are using Zone.js, it is recommended that you also temporarily import "zone.js/plugins/task-tracking".
|
||||
* This Zone.js plugin provides additional information about which macrotasks are scheduled in the Angular Zone
|
||||
* and keeping the Zone from stabilizing.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ```ts
|
||||
* import 'zone.js/plugins/task-tracking';
|
||||
*
|
||||
* bootstrapApplication(AppComponent, {providers: [provideStabilityDebugging()]});
|
||||
* ```
|
||||
*
|
||||
* IMPORTANT: Neither the zone.js task tracking plugin nor this utility are removed from production bundles.
|
||||
* They are intended for temporary use while debugging stability issues during development, including for
|
||||
* optimized production builds.
|
||||
*
|
||||
* @publicApi 21.1
|
||||
*/
|
||||
export function provideStabilityDebugging(): EnvironmentProviders {
|
||||
const taskTracker = new DebugTaskTrackerImpl();
|
||||
const {openTasks} = taskTracker;
|
||||
return makeEnvironmentProviders([
|
||||
{
|
||||
provide: DEBUG_TASK_TRACKER,
|
||||
useValue: taskTracker,
|
||||
},
|
||||
provideAppInitializer(() => {
|
||||
if (typeof ngDevMode === 'undefined' || !ngDevMode) {
|
||||
console.warn(
|
||||
'Stability debugging untility was provided in production mode. ' +
|
||||
'This will cause debug code to be included in production bundles. ' +
|
||||
'If this is intentional because you are debugging stability issues in a production environment, you can ignore this warning.',
|
||||
);
|
||||
}
|
||||
const ngZone = inject(NgZone);
|
||||
const applicationRef = inject(ApplicationRef);
|
||||
|
||||
// From TaskTrackingZone:
|
||||
// https://github.com/angular/angular/blob/ae0c59028a2f393ea5716bf222db2c38e7a3989f/packages/zone.js/lib/zone-spec/task-tracking.ts#L46
|
||||
let _taskTrackingZone: {macroTasks: Array<{creationLocation: Error}>} | null = null;
|
||||
if (typeof Zone !== 'undefined') {
|
||||
ngZone.run(() => {
|
||||
_taskTrackingZone = Zone.current.get('TaskTrackingZone');
|
||||
});
|
||||
}
|
||||
ngZone.runOutsideAngular(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.debug(
|
||||
`---- Application did not stabilize within ${STABILITY_WARNING_THRESHOLD / 1000} seconds ----`,
|
||||
);
|
||||
if (typeof Zone !== 'undefined' && !_taskTrackingZone) {
|
||||
console.info(
|
||||
'Zone.js is present but no TaskTrackingZone found. To enable better debugging of tasks in the Angular Zone, ' +
|
||||
'import "zone.js/plugins/task-tracking" in your application.',
|
||||
);
|
||||
}
|
||||
if (_taskTrackingZone?.macroTasks?.length) {
|
||||
console.group('Macrotasks keeping Angular Zone unstable:');
|
||||
for (const t of _taskTrackingZone?.macroTasks ?? []) {
|
||||
console.debug(t.creationLocation.stack);
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
console.group('PendingTasks keeping application unstable:');
|
||||
for (const error of openTasks.values()) {
|
||||
console.debug(error.stack);
|
||||
}
|
||||
console.groupEnd();
|
||||
}, STABILITY_WARNING_THRESHOLD);
|
||||
|
||||
applicationRef.whenStable().then(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -54,6 +54,7 @@ export {
|
||||
ANIMATION_MODULE_TYPE,
|
||||
CSP_NONCE,
|
||||
} from './application/application_tokens';
|
||||
export {provideStabilityDebugging} from './application/stability_debug_impl';
|
||||
export {
|
||||
APP_INITIALIZER,
|
||||
ApplicationInitStatus,
|
||||
|
||||
@@ -83,7 +83,7 @@ let isIncrementalHydrationRuntimeSupportEnabled = false;
|
||||
* Defines a period of time that Angular waits for the `ApplicationRef.isStable` to emit `true`.
|
||||
* If there was no event with the `true` value during this time, Angular reports a warning.
|
||||
*/
|
||||
const APPLICATION_IS_STABLE_TIMEOUT = 10_000;
|
||||
export const APPLICATION_IS_STABLE_TIMEOUT = 10_000;
|
||||
|
||||
/**
|
||||
* Brings the necessary hydration code in tree-shakable manner.
|
||||
|
||||
@@ -10,6 +10,8 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||
|
||||
import {ɵɵdefineInjectable} from './di/interface/defs';
|
||||
import {OnDestroy} from './change_detection/lifecycle_hooks';
|
||||
import {DEBUG_TASK_TRACKER} from './application/stability_debug';
|
||||
import {inject} from './di';
|
||||
|
||||
/**
|
||||
* Internal implementation of the pending tasks service.
|
||||
@@ -19,8 +21,8 @@ export class PendingTasksInternal implements OnDestroy {
|
||||
private taskId = 0;
|
||||
private pendingTasks = new Set<number>();
|
||||
private destroyed = false;
|
||||
|
||||
private pendingTask = new BehaviorSubject<boolean>(false);
|
||||
private debugTaskTracker = inject(DEBUG_TASK_TRACKER, {optional: true});
|
||||
|
||||
get hasPendingTasks(): boolean {
|
||||
// Accessing the value of a closed `BehaviorSubject` throws an error.
|
||||
@@ -50,6 +52,7 @@ export class PendingTasksInternal implements OnDestroy {
|
||||
}
|
||||
const taskId = this.taskId++;
|
||||
this.pendingTasks.add(taskId);
|
||||
this.debugTaskTracker?.add(taskId);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@@ -59,6 +62,7 @@ export class PendingTasksInternal implements OnDestroy {
|
||||
|
||||
remove(taskId: number): void {
|
||||
this.pendingTasks.delete(taskId);
|
||||
this.debugTaskTracker?.remove(taskId);
|
||||
if (this.pendingTasks.size === 0 && this.hasPendingTasks) {
|
||||
this.pendingTask.next(false);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"ANIMATION_PREFIX",
|
||||
"ANIMATION_QUEUE",
|
||||
"ANY_STATE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -66,6 +67,7 @@
|
||||
"ComponentRef2",
|
||||
"ConsumerObserver",
|
||||
"DASH_CASE_REGEXP",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -226,6 +228,7 @@
|
||||
"SHARED_ANIMATION_PROVIDERS",
|
||||
"SIGNAL",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"STAR_CLASSNAME",
|
||||
"STAR_SELECTOR",
|
||||
"SUBSTITUTION_EXPR_END",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -42,6 +43,7 @@
|
||||
"ComponentRef",
|
||||
"ComponentRef2",
|
||||
"ConsumerObserver",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -174,6 +176,7 @@
|
||||
"SIGNAL",
|
||||
"SIGNAL_NODE",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"Sanitizer",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_INITIALIZER",
|
||||
@@ -93,6 +94,7 @@
|
||||
"ComponentRef",
|
||||
"ComponentRef2",
|
||||
"ConsumerObserver",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -218,6 +220,7 @@
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"SSR_BLOCK_STATE",
|
||||
"SSR_UNIQUE_ID",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"Sanitizer",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -62,6 +63,7 @@
|
||||
"ConsumerObserver",
|
||||
"ControlContainer",
|
||||
"ControlEvent",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -229,6 +231,7 @@
|
||||
"SIGNAL",
|
||||
"SIGNAL_NODE",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"SafeValueImpl",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -63,6 +64,7 @@
|
||||
"ConsumerObserver",
|
||||
"ControlContainer",
|
||||
"ControlEvent",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -224,6 +226,7 @@
|
||||
"SIGNAL",
|
||||
"SIGNAL_NODE",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"SafeValueImpl",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ALLOWED_METHODS",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -50,6 +51,7 @@
|
||||
"ComponentRef",
|
||||
"ComponentRef2",
|
||||
"ConsumerObserver",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -202,6 +204,7 @@
|
||||
"SKIP_HYDRATION_ATTR_NAME",
|
||||
"SKIP_HYDRATION_ATTR_NAME_LOWER_CASE",
|
||||
"SSR_CONTENT_INTEGRITY_MARKER",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"STATUS",
|
||||
"STATUS_TEXT",
|
||||
"SVG_NAMESPACE",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BASE_HREF",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
@@ -63,6 +64,7 @@
|
||||
"ComponentRef2",
|
||||
"Console",
|
||||
"ConsumerObserver",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -270,6 +272,7 @@
|
||||
"SIGNAL",
|
||||
"SIGNAL_NODE",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"SafeValueImpl",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AFTER_RENDER_SEQUENCES_TO_ADD",
|
||||
"ANIMATIONS",
|
||||
"ANIMATION_QUEUE",
|
||||
"APPLICATION_IS_STABLE_TIMEOUT",
|
||||
"APP_BOOTSTRAP_LISTENER",
|
||||
"APP_ID",
|
||||
"APP_ID_ATTRIBUTE_NAME",
|
||||
@@ -42,6 +43,7 @@
|
||||
"ComponentRef",
|
||||
"ComponentRef2",
|
||||
"ConsumerObserver",
|
||||
"DEBUG_TASK_TRACKER",
|
||||
"DECLARATION_COMPONENT_VIEW",
|
||||
"DECLARATION_LCONTAINER",
|
||||
"DECLARATION_VIEW",
|
||||
@@ -165,6 +167,7 @@
|
||||
"SCHEDULE_IN_ROOT_ZONE_DEFAULT",
|
||||
"SIGNAL",
|
||||
"SIMPLE_CHANGES_STORE",
|
||||
"STABILITY_WARNING_THRESHOLD",
|
||||
"SVG_NAMESPACE",
|
||||
"SafeSubscriber",
|
||||
"Sanitizer",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ApplicationInitStatus, PendingTasks, provideZoneChangeDetection} from '../../src/core';
|
||||
import {provideStabilityDebugging} from '../../src/application/stability_debug_impl';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
|
||||
describe('provideStabilityDebugging', () => {
|
||||
let consoleWarnSpy: jasmine.Spy;
|
||||
let consoleDebugSpy: jasmine.Spy;
|
||||
let consoleInfoSpy: jasmine.Spy;
|
||||
let consoleGroupEndSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(global, 'Error').and.returnValue({stack: 'fake stack trace', name: 'Error', message: ''});
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
consoleDebugSpy = spyOn(console, 'debug');
|
||||
consoleInfoSpy = spyOn(console, 'info');
|
||||
consoleGroupEndSpy = spyOn(console, 'groupEnd');
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideStabilityDebugging(), provideZoneChangeDetection()],
|
||||
});
|
||||
});
|
||||
|
||||
function runInitializers() {
|
||||
(TestBed.inject(ApplicationInitStatus) as any).runInitializers();
|
||||
}
|
||||
|
||||
it('should log pending tasks if application does not stabilize', fakeAsync(() => {
|
||||
const pendingTasks = TestBed.inject(PendingTasks);
|
||||
|
||||
// Prevent stability
|
||||
const removeTask = pendingTasks.add();
|
||||
|
||||
runInitializers();
|
||||
|
||||
tick(10_000);
|
||||
|
||||
expect(consoleDebugSpy.calls.first().args[0]).toMatch(/Application did not stabilize/);
|
||||
expect(consoleDebugSpy.calls.all()[1].args[0]).toMatch(/fake stack trace/);
|
||||
|
||||
removeTask();
|
||||
}));
|
||||
|
||||
it('should not log if application stabilizes within 9 seconds', fakeAsync(() => {
|
||||
const pendingTasks = TestBed.inject(PendingTasks);
|
||||
|
||||
// Prevent stability
|
||||
const removeTask = pendingTasks.add();
|
||||
|
||||
runInitializers();
|
||||
|
||||
tick(5000);
|
||||
removeTask(); // Stabilize
|
||||
tick(4000); // Reach 9000 total
|
||||
|
||||
expect(consoleDebugSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
ɵZONELESS_ENABLED as ZONELESS_ENABLED,
|
||||
ɵwithIncrementalHydration,
|
||||
ɵIS_ENABLED_BLOCKING_INITIAL_NAVIGATION as IS_ENABLED_BLOCKING_INITIAL_NAVIGATION,
|
||||
provideStabilityDebugging,
|
||||
} from '@angular/core';
|
||||
import {RuntimeErrorCode} from './errors';
|
||||
|
||||
@@ -255,6 +256,7 @@ export function provideClientHydration(
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode
|
||||
? provideEnabledBlockingInitialNavigationDetector()
|
||||
: [],
|
||||
typeof ngDevMode !== 'undefined' && ngDevMode ? provideStabilityDebugging() : [],
|
||||
withDomHydration(),
|
||||
featuresKind.has(HydrationFeatureKind.NoHttpTransferCache) || hasHttpTransferCacheOptions
|
||||
? []
|
||||
|
||||
Reference in New Issue
Block a user