mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(core): Update error for both zone and zoneless to be only for apps (#55813)
Developers may want to enable zoneless for all tests by default by adding the zoneless provider to `initTestEnvironment` and then temporarily disabling it for individual tests with the zone provider until they can be made zoneless compatible. PR Close #55813
This commit is contained in:
parent
88fb94693a
commit
e76bebd409
7 changed files with 54 additions and 51 deletions
|
|
@ -8,7 +8,10 @@
|
|||
|
||||
import {Subscription} from 'rxjs';
|
||||
|
||||
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {
|
||||
internalProvideZoneChangeDetection,
|
||||
PROVIDED_NG_ZONE,
|
||||
} from '../change_detection/scheduling/ng_zone_scheduling';
|
||||
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
|
||||
import {EnvironmentInjector} from '../di/r3_injector';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
|
|
@ -26,6 +29,7 @@ import {NgZone} from '../zone/ng_zone';
|
|||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref';
|
||||
import {PROVIDED_ZONELESS} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
|
||||
/**
|
||||
* Internal create application API that implements the core application creation logic and optional
|
||||
|
|
@ -70,11 +74,20 @@ export function internalCreateApplication(config: {
|
|||
return ngZone.run(() => {
|
||||
envInjector.resolveInjectorInitializers();
|
||||
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.',
|
||||
);
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
if (!exceptionHandler) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
|
||||
'No `ErrorHandler` found in the Dependency Injection tree.',
|
||||
);
|
||||
}
|
||||
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let onErrorSubscription: Subscription;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export class NgZoneChangeDetectionScheduler {
|
|||
private readonly zone = inject(NgZone);
|
||||
private readonly changeDetectionScheduler = inject(ChangeDetectionScheduler, {optional: true});
|
||||
private readonly applicationRef = inject(ApplicationRef);
|
||||
private readonly zonelessEnabled = inject(ZONELESS_ENABLED);
|
||||
|
||||
private _onMicrotaskEmptySubscription?: Subscription;
|
||||
|
||||
|
|
@ -73,6 +72,7 @@ export class NgZoneChangeDetectionScheduler {
|
|||
*/
|
||||
export const PROVIDED_NG_ZONE = new InjectionToken<boolean>(
|
||||
typeof ngDevMode === 'undefined' || ngDevMode ? 'provideZoneChangeDetection token' : '',
|
||||
{factory: () => false},
|
||||
);
|
||||
|
||||
export function internalProvideZoneChangeDetection({
|
||||
|
|
@ -167,8 +167,9 @@ export function provideZoneChangeDetection(options?: NgZoneOptions): Environment
|
|||
});
|
||||
return makeEnvironmentProviders([
|
||||
typeof ngDevMode === 'undefined' || ngDevMode
|
||||
? [{provide: PROVIDED_NG_ZONE, useValue: true}, bothZoneAndZonelessErrorCheckProvider]
|
||||
? [{provide: PROVIDED_NG_ZONE, useValue: true}]
|
||||
: [],
|
||||
{provide: ZONELESS_ENABLED, useValue: false},
|
||||
zoneProviders,
|
||||
]);
|
||||
}
|
||||
|
|
@ -301,19 +302,3 @@ export class ZoneStablePendingTask {
|
|||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
const bothZoneAndZonelessErrorCheckProvider = {
|
||||
provide: ENVIRONMENT_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory: () => {
|
||||
const providedZoneless = inject(ZONELESS_ENABLED, {optional: true});
|
||||
if (providedZoneless) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
|
||||
);
|
||||
}
|
||||
return () => {};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ export const ZONELESS_ENABLED = new InjectionToken<boolean>(
|
|||
{providedIn: 'root', factory: () => false},
|
||||
);
|
||||
|
||||
/** Token used to indicate `provideExperimentalZonelessChangeDetection` was used. */
|
||||
export const PROVIDED_ZONELESS = new InjectionToken<boolean>(
|
||||
typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless provided' : '',
|
||||
{providedIn: 'root', factory: () => false},
|
||||
);
|
||||
|
||||
export const ZONELESS_SCHEDULER_DISABLED = new InjectionToken<boolean>(
|
||||
typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
ChangeDetectionScheduler,
|
||||
NotificationSource,
|
||||
ZONELESS_ENABLED,
|
||||
PROVIDED_ZONELESS,
|
||||
ZONELESS_SCHEDULER_DISABLED,
|
||||
} from './zoneless_scheduling';
|
||||
|
||||
|
|
@ -297,5 +298,8 @@ export function provideExperimentalZonelessChangeDetection(): EnvironmentProvide
|
|||
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
|
||||
{provide: NgZone, useClass: NoopNgZone},
|
||||
{provide: ZONELESS_ENABLED, useValue: true},
|
||||
typeof ngDevMode === 'undefined' || ngDevMode
|
||||
? [{provide: PROVIDED_ZONELESS, useValue: true}]
|
||||
: [],
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,25 +95,20 @@ export class PlatformRef {
|
|||
internalProvideZoneChangeDetection({ngZoneFactory: () => ngZone, ignoreChangesOutsideZone}),
|
||||
);
|
||||
|
||||
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.',
|
||||
);
|
||||
}
|
||||
if (
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
moduleRef.injector.get(ZONELESS_ENABLED, null) &&
|
||||
options?.ngZone !== 'noop'
|
||||
) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
"`ngZone: 'noop'` must be set in `BootstrapOptions` with provideExperimentalZonelessChangeDetection.",
|
||||
);
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
if (moduleRef.injector.get(PROVIDED_NG_ZONE)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT,
|
||||
'`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.',
|
||||
);
|
||||
}
|
||||
if (moduleRef.injector.get(ZONELESS_ENABLED) && options?.ngZone !== 'noop') {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
|
||||
'Invalid change detection configuration: ' +
|
||||
"`ngZone: 'noop'` must be set in `BootstrapOptions` with provideExperimentalZonelessChangeDetection.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const exceptionHandler = moduleRef.injector.get(ErrorHandler, null);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
|||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
describe('change detection', () => {
|
||||
it('can provide zone and zoneless (last one wins like any other provider) in TestBed', () => {
|
||||
expect(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideExperimentalZonelessChangeDetection(), provideZoneChangeDetection()],
|
||||
});
|
||||
TestBed.inject(ApplicationRef);
|
||||
}).not.toThrow();
|
||||
});
|
||||
describe('embedded views', () => {
|
||||
@Directive({selector: '[viewManipulation]', exportAs: 'vm'})
|
||||
class ViewManipulation {
|
||||
|
|
|
|||
|
|
@ -64,14 +64,6 @@ describe('Angular with zoneless enabled', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('throws an error if used with zone provider', () => {
|
||||
TestBed.configureTestingModule({providers: [provideZoneChangeDetection()]});
|
||||
|
||||
expect(() => TestBed.inject(NgZone)).toThrowError(
|
||||
/NG0408: Invalid change detection configuration/,
|
||||
);
|
||||
});
|
||||
|
||||
describe('notifies scheduler', () => {
|
||||
it('contributes to application stableness', async () => {
|
||||
const val = signal('initial');
|
||||
|
|
|
|||
Loading…
Reference in a new issue