Skip to content

Commit

Permalink
refactor(core): Update error for both zone and zoneless to be only fo…
Browse files Browse the repository at this point in the history
…r apps

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.
  • Loading branch information
atscott committed May 15, 2024
1 parent 7287fef commit ccf22d6
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 47 deletions.
27 changes: 21 additions & 6 deletions packages/core/src/application/create_application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -70,11 +74,22 @@ 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.',
);
}
const providedZoneless = envInjector.get(PROVIDED_ZONELESS);
const providedZones = envInjector.get(PROVIDED_NG_ZONE);
if (providedZoneless && providedZones) {
throw new RuntimeError(
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
'Invalid change detection configuration: ' +
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
);
}
}

let onErrorSubscription: Subscription;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class NgZoneChangeDetectionScheduler {
*/
export const PROVIDED_NG_ZONE = new InjectionToken<boolean>(
typeof ngDevMode === 'undefined' || ngDevMode ? 'provideZoneChangeDetection token' : '',
{factory: () => false},
);

export function internalProvideZoneChangeDetection({
Expand All @@ -85,6 +86,7 @@ export function internalProvideZoneChangeDetection({
ngZoneFactory ??= () => new NgZone(getNgZoneOptions());
return [
{provide: NgZone, useFactory: ngZoneFactory},
{provide: ZONELESS_ENABLED, useValue: false},
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
Expand Down Expand Up @@ -165,12 +167,7 @@ export function provideZoneChangeDetection(options?: NgZoneOptions): Environment
},
ignoreChangesOutsideZone,
});
return makeEnvironmentProviders([
typeof ngDevMode === 'undefined' || ngDevMode
? [{provide: PROVIDED_NG_ZONE, useValue: true}, bothZoneAndZonelessErrorCheckProvider]
: [],
zoneProviders,
]);
return makeEnvironmentProviders([zoneProviders]);
}

/**
Expand Down Expand Up @@ -301,19 +298,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 () => {};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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' : '',
);
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ChangeDetectionScheduler,
NotificationSource,
ZONELESS_ENABLED,
PROVIDED_ZONELESS,
ZONELESS_SCHEDULER_DISABLED,
} from './zoneless_scheduling';

Expand Down Expand Up @@ -297,5 +298,6 @@ export function provideExperimentalZonelessChangeDetection(): EnvironmentProvide
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
{provide: NgZone, useClass: NoopNgZone},
{provide: ZONELESS_ENABLED, useValue: true},
{provide: PROVIDED_ZONELESS, useValue: true},
]);
}
33 changes: 14 additions & 19 deletions packages/core/src/platform/platform_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/test/acceptance/change_detection_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit ccf22d6

Please sign in to comment.