Skip to content

Commit e76bebd

Browse files
committed
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
1 parent 88fb946 commit e76bebd

File tree

7 files changed

+54
-51
lines changed

7 files changed

+54
-51
lines changed

packages/core/src/application/create_application.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
import {Subscription} from 'rxjs';
1010

11-
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
11+
import {
12+
internalProvideZoneChangeDetection,
13+
PROVIDED_NG_ZONE,
14+
} from '../change_detection/scheduling/ng_zone_scheduling';
1215
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
1316
import {EnvironmentInjector} from '../di/r3_injector';
1417
import {ErrorHandler} from '../error_handler';
@@ -26,6 +29,7 @@ import {NgZone} from '../zone/ng_zone';
2629

2730
import {ApplicationInitStatus} from './application_init';
2831
import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref';
32+
import {PROVIDED_ZONELESS} from '../change_detection/scheduling/zoneless_scheduling';
2933

3034
/**
3135
* Internal create application API that implements the core application creation logic and optional
@@ -70,11 +74,20 @@ export function internalCreateApplication(config: {
7074
return ngZone.run(() => {
7175
envInjector.resolveInjectorInitializers();
7276
const exceptionHandler: ErrorHandler | null = envInjector.get(ErrorHandler, null);
73-
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !exceptionHandler) {
74-
throw new RuntimeError(
75-
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
76-
'No `ErrorHandler` found in the Dependency Injection tree.',
77-
);
77+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
78+
if (!exceptionHandler) {
79+
throw new RuntimeError(
80+
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
81+
'No `ErrorHandler` found in the Dependency Injection tree.',
82+
);
83+
}
84+
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
85+
throw new RuntimeError(
86+
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
87+
'Invalid change detection configuration: ' +
88+
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
89+
);
90+
}
7891
}
7992

8093
let onErrorSubscription: Subscription;

packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class NgZoneChangeDetectionScheduler {
3838
private readonly zone = inject(NgZone);
3939
private readonly changeDetectionScheduler = inject(ChangeDetectionScheduler, {optional: true});
4040
private readonly applicationRef = inject(ApplicationRef);
41-
private readonly zonelessEnabled = inject(ZONELESS_ENABLED);
4241

4342
private _onMicrotaskEmptySubscription?: Subscription;
4443

@@ -73,6 +72,7 @@ export class NgZoneChangeDetectionScheduler {
7372
*/
7473
export const PROVIDED_NG_ZONE = new InjectionToken<boolean>(
7574
typeof ngDevMode === 'undefined' || ngDevMode ? 'provideZoneChangeDetection token' : '',
75+
{factory: () => false},
7676
);
7777

7878
export function internalProvideZoneChangeDetection({
@@ -167,8 +167,9 @@ export function provideZoneChangeDetection(options?: NgZoneOptions): Environment
167167
});
168168
return makeEnvironmentProviders([
169169
typeof ngDevMode === 'undefined' || ngDevMode
170-
? [{provide: PROVIDED_NG_ZONE, useValue: true}, bothZoneAndZonelessErrorCheckProvider]
170+
? [{provide: PROVIDED_NG_ZONE, useValue: true}]
171171
: [],
172+
{provide: ZONELESS_ENABLED, useValue: false},
172173
zoneProviders,
173174
]);
174175
}
@@ -301,19 +302,3 @@ export class ZoneStablePendingTask {
301302
this.subscription.unsubscribe();
302303
}
303304
}
304-
305-
const bothZoneAndZonelessErrorCheckProvider = {
306-
provide: ENVIRONMENT_INITIALIZER,
307-
multi: true,
308-
useFactory: () => {
309-
const providedZoneless = inject(ZONELESS_ENABLED, {optional: true});
310-
if (providedZoneless) {
311-
throw new RuntimeError(
312-
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
313-
'Invalid change detection configuration: ' +
314-
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
315-
);
316-
}
317-
return () => {};
318-
},
319-
};

packages/core/src/change_detection/scheduling/zoneless_scheduling.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ export const ZONELESS_ENABLED = new InjectionToken<boolean>(
5959
{providedIn: 'root', factory: () => false},
6060
);
6161

62+
/** Token used to indicate `provideExperimentalZonelessChangeDetection` was used. */
63+
export const PROVIDED_ZONELESS = new InjectionToken<boolean>(
64+
typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless provided' : '',
65+
{providedIn: 'root', factory: () => false},
66+
);
67+
6268
export const ZONELESS_SCHEDULER_DISABLED = new InjectionToken<boolean>(
6369
typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : '',
6470
);

packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ChangeDetectionScheduler,
2727
NotificationSource,
2828
ZONELESS_ENABLED,
29+
PROVIDED_ZONELESS,
2930
ZONELESS_SCHEDULER_DISABLED,
3031
} from './zoneless_scheduling';
3132

@@ -297,5 +298,8 @@ export function provideExperimentalZonelessChangeDetection(): EnvironmentProvide
297298
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
298299
{provide: NgZone, useClass: NoopNgZone},
299300
{provide: ZONELESS_ENABLED, useValue: true},
301+
typeof ngDevMode === 'undefined' || ngDevMode
302+
? [{provide: PROVIDED_ZONELESS, useValue: true}]
303+
: [],
300304
]);
301305
}

packages/core/src/platform/platform_ref.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,20 @@ export class PlatformRef {
9595
internalProvideZoneChangeDetection({ngZoneFactory: () => ngZone, ignoreChangesOutsideZone}),
9696
);
9797

98-
if (
99-
(typeof ngDevMode === 'undefined' || ngDevMode) &&
100-
moduleRef.injector.get(PROVIDED_NG_ZONE, null) !== null
101-
) {
102-
throw new RuntimeError(
103-
RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT,
104-
'`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.',
105-
);
106-
}
107-
if (
108-
(typeof ngDevMode === 'undefined' || ngDevMode) &&
109-
moduleRef.injector.get(ZONELESS_ENABLED, null) &&
110-
options?.ngZone !== 'noop'
111-
) {
112-
throw new RuntimeError(
113-
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
114-
'Invalid change detection configuration: ' +
115-
"`ngZone: 'noop'` must be set in `BootstrapOptions` with provideExperimentalZonelessChangeDetection.",
116-
);
98+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
99+
if (moduleRef.injector.get(PROVIDED_NG_ZONE)) {
100+
throw new RuntimeError(
101+
RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT,
102+
'`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.',
103+
);
104+
}
105+
if (moduleRef.injector.get(ZONELESS_ENABLED) && options?.ngZone !== 'noop') {
106+
throw new RuntimeError(
107+
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
108+
'Invalid change detection configuration: ' +
109+
"`ngZone: 'noop'` must be set in `BootstrapOptions` with provideExperimentalZonelessChangeDetection.",
110+
);
111+
}
117112
}
118113

119114
const exceptionHandler = moduleRef.injector.get(ErrorHandler, null);

packages/core/test/acceptance/change_detection_spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
4545
import {BehaviorSubject} from 'rxjs';
4646

4747
describe('change detection', () => {
48+
it('can provide zone and zoneless (last one wins like any other provider) in TestBed', () => {
49+
expect(() => {
50+
TestBed.configureTestingModule({
51+
providers: [provideExperimentalZonelessChangeDetection(), provideZoneChangeDetection()],
52+
});
53+
TestBed.inject(ApplicationRef);
54+
}).not.toThrow();
55+
});
4856
describe('embedded views', () => {
4957
@Directive({selector: '[viewManipulation]', exportAs: 'vm'})
5058
class ViewManipulation {

packages/core/test/change_detection_scheduler_spec.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,6 @@ describe('Angular with zoneless enabled', () => {
6464
});
6565
});
6666

67-
it('throws an error if used with zone provider', () => {
68-
TestBed.configureTestingModule({providers: [provideZoneChangeDetection()]});
69-
70-
expect(() => TestBed.inject(NgZone)).toThrowError(
71-
/NG0408: Invalid change detection configuration/,
72-
);
73-
});
74-
7567
describe('notifies scheduler', () => {
7668
it('contributes to application stableness', async () => {
7769
const val = signal('initial');

0 commit comments

Comments
 (0)