Skip to content

Commit 0ae1889

Browse files
dgp1130atscott
authored andcommitted
fix(core): run ApplicationRef.prototype.bootstrap in NgZone (#60720)
`bootstrapApplication` always creates the root component in the `NgZone`, however `ApplicationRef.prototype.bootstrap` historically did not, meaning that if users did not go out of their way to call `ngZone.run(() => appRef.bootstrap(SomeComp))`, components would not run change detection correctly. This commit updates `ApplicationRef.prototype.bootstrap` to _always_ run within `NgZone`, removing this hazard and ensuring components always run CD as expected. PR Close #60720
1 parent 5039ed0 commit 0ae1889

File tree

2 files changed

+66
-47
lines changed

2 files changed

+66
-47
lines changed

packages/core/src/application/application_ref.ts

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -510,59 +510,62 @@ export class ApplicationRef {
510510
rootSelectorOrNode?: string | any,
511511
injector: Injector = Injector.NULL,
512512
): ComponentRef<C> {
513-
profiler(ProfilerEvent.BootstrapComponentStart);
513+
const ngZone = this._injector.get(NgZone);
514+
return ngZone.run(() => {
515+
profiler(ProfilerEvent.BootstrapComponentStart);
516+
517+
(typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed);
518+
const isComponentFactory = componentOrFactory instanceof ComponentFactory;
519+
const initStatus = this._injector.get(ApplicationInitStatus);
520+
521+
if (!initStatus.done) {
522+
let errorMessage = '';
523+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
524+
const standalone = !isComponentFactory && isStandalone(componentOrFactory);
525+
errorMessage =
526+
'Cannot bootstrap as there are still asynchronous initializers running.' +
527+
(standalone
528+
? ''
529+
: ' Bootstrap components in the `ngDoBootstrap` method of the root module.');
530+
}
531+
throw new RuntimeError(RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING, errorMessage);
532+
}
514533

515-
(typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed);
516-
const isComponentFactory = componentOrFactory instanceof ComponentFactory;
517-
const initStatus = this._injector.get(ApplicationInitStatus);
534+
let componentFactory: ComponentFactory<C>;
535+
if (isComponentFactory) {
536+
componentFactory = componentOrFactory;
537+
} else {
538+
const resolver = this._injector.get(ComponentFactoryResolver);
539+
componentFactory = resolver.resolveComponentFactory(componentOrFactory)!;
540+
}
541+
this.componentTypes.push(componentFactory.componentType);
542+
543+
// Create a factory associated with the current module if it's not bound to some other
544+
const ngModule = isBoundToModule(componentFactory)
545+
? undefined
546+
: this._injector.get(NgModuleRef);
547+
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
548+
const compRef = componentFactory.create(injector, [], selectorOrNode, ngModule);
549+
const nativeElement = compRef.location.nativeElement;
550+
const testability = compRef.injector.get(TESTABILITY, null);
551+
testability?.registerApplication(nativeElement);
552+
553+
compRef.onDestroy(() => {
554+
this.detachView(compRef.hostView);
555+
remove(this.components, compRef);
556+
testability?.unregisterApplication(nativeElement);
557+
});
518558

519-
if (!initStatus.done) {
520-
let errorMessage = '';
559+
this._loadComponent(compRef);
521560
if (typeof ngDevMode === 'undefined' || ngDevMode) {
522-
const standalone = !isComponentFactory && isStandalone(componentOrFactory);
523-
errorMessage =
524-
'Cannot bootstrap as there are still asynchronous initializers running.' +
525-
(standalone
526-
? ''
527-
: ' Bootstrap components in the `ngDoBootstrap` method of the root module.');
561+
const _console = this._injector.get(Console);
562+
_console.log(`Angular is running in development mode.`);
528563
}
529-
throw new RuntimeError(RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING, errorMessage);
530-
}
531564

532-
let componentFactory: ComponentFactory<C>;
533-
if (isComponentFactory) {
534-
componentFactory = componentOrFactory;
535-
} else {
536-
const resolver = this._injector.get(ComponentFactoryResolver);
537-
componentFactory = resolver.resolveComponentFactory(componentOrFactory)!;
538-
}
539-
this.componentTypes.push(componentFactory.componentType);
540-
541-
// Create a factory associated with the current module if it's not bound to some other
542-
const ngModule = isBoundToModule(componentFactory)
543-
? undefined
544-
: this._injector.get(NgModuleRef);
545-
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
546-
const compRef = componentFactory.create(injector, [], selectorOrNode, ngModule);
547-
const nativeElement = compRef.location.nativeElement;
548-
const testability = compRef.injector.get(TESTABILITY, null);
549-
testability?.registerApplication(nativeElement);
550-
551-
compRef.onDestroy(() => {
552-
this.detachView(compRef.hostView);
553-
remove(this.components, compRef);
554-
testability?.unregisterApplication(nativeElement);
555-
});
556-
557-
this._loadComponent(compRef);
558-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
559-
const _console = this._injector.get(Console);
560-
_console.log(`Angular is running in development mode.`);
561-
}
562-
563-
profiler(ProfilerEvent.BootstrapComponentEnd, compRef);
565+
profiler(ProfilerEvent.BootstrapComponentEnd, compRef);
564566

565-
return compRef;
567+
return compRef;
568+
});
566569
}
567570

568571
/**

packages/core/test/application_ref_spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,22 @@ describe('bootstrap', () => {
256256
}),
257257
),
258258
);
259+
260+
it('runs in `NgZone`', inject([ApplicationRef], async (ref: ApplicationRef) => {
261+
@Component({
262+
selector: 'zone-comp',
263+
template: `
264+
<div>{{ name }}</div>
265+
`,
266+
})
267+
class ZoneComp {
268+
readonly inNgZone = NgZone.isInAngularZone();
269+
}
270+
271+
createRootEl('zone-comp');
272+
const comp = ref.bootstrap(ZoneComp);
273+
expect(comp.instance.inNgZone).toBeTrue();
274+
}));
259275
});
260276

261277
describe('bootstrapImpl', () => {

0 commit comments

Comments
 (0)