Skip to content

Commit a24be22

Browse files
committed
fix(elements): initialization should run in ngZone (angular#24181)
1 parent 729c797 commit a24be22

File tree

2 files changed

+54
-4
lines changed

2 files changed

+54
-4
lines changed

packages/elements/src/component-factory-strategy.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core';
9+
import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, NgZone, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core';
1010
import {Observable, merge} from 'rxjs';
1111
import {map} from 'rxjs/operators';
1212

@@ -67,7 +67,13 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
6767
/** Set of inputs that were not initially set when the component was created. */
6868
private readonly uninitializedInputs = new Set<string>();
6969

70-
constructor(private componentFactory: ComponentFactory<any>, private injector: Injector) {}
70+
private ngZone: NgZone;
71+
private applicationRef: ApplicationRef;
72+
73+
constructor(private componentFactory: ComponentFactory<any>, private injector: Injector) {
74+
this.ngZone = this.injector.get<NgZone>(NgZone);
75+
this.applicationRef = this.injector.get<ApplicationRef>(ApplicationRef);
76+
}
7177

7278
/**
7379
* Initializes a new component if one has not yet been created and cancels any scheduled
@@ -82,7 +88,8 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
8288
}
8389

8490
if (!this.componentRef) {
85-
this.initializeComponent(element);
91+
let initializeComponentFn = () => { this.initializeComponent(element); };
92+
NgZone.isInAngularZone() ? initializeComponentFn() : this.ngZone.run(initializeComponentFn);
8693
}
8794
}
8895

packages/elements/test/component-factory-strategy_spec.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentFactory, ComponentRef, Injector, NgModuleRef, SimpleChange, SimpleChanges, Type} from '@angular/core';
9+
import {ComponentFactory, ComponentRef, Injector, NgModuleRef, NgZone, SimpleChange, SimpleChanges, Type} from '@angular/core';
1010
import {fakeAsync, tick} from '@angular/core/testing';
1111
import {Subject} from 'rxjs';
1212

@@ -20,12 +20,27 @@ describe('ComponentFactoryNgElementStrategy', () => {
2020
let injector: any;
2121
let componentRef: any;
2222
let applicationRef: any;
23+
let factoryResolver: any;
24+
let ngZone: any;
2325

2426
beforeEach(() => {
2527
factory = new FakeComponentFactory();
2628
componentRef = factory.componentRef;
2729

2830
applicationRef = jasmine.createSpyObj('applicationRef', ['attachView']);
31+
ngZone = jasmine.createSpyObj('ngZone', ['run']);
32+
ngZone.run.and.callFake((fn: any) => { return fn(); });
33+
injector = jasmine.createSpyObj('injector', ['get']);
34+
injector.get.and.callFake(function(identify: any) {
35+
const name = identify && identify.name;
36+
if (name === 'ApplicationRef') {
37+
return applicationRef;
38+
} else if (name === 'NgZone') {
39+
return ngZone;
40+
} else if (name === 'ComponentFactoryResolver') {
41+
return factoryResolver;
42+
}
43+
});
2944

3045
strategy = new ComponentNgElementStrategy(factory, injector);
3146
});
@@ -40,6 +55,34 @@ describe('ComponentFactoryNgElementStrategy', () => {
4055
expect(strategyFactory.create(injector)).toBeTruthy();
4156
});
4257

58+
describe('connected', () => {
59+
60+
it('should not run initializeComponentFn in ngZone if is already inAngularZone',
61+
fakeAsync(() => {
62+
const spy = spyOn(NgZone, 'isInAngularZone');
63+
spy.and.callFake(function() { return true; });
64+
ngZone.run.calls.reset();
65+
66+
strategy.connect(document.createElement('div'));
67+
68+
expect(ngZone.run).not.toHaveBeenCalled();
69+
ngZone.run.and.callFake(() => {});
70+
tick(16); // scheduler waits 16ms if RAF is unavailable
71+
expect(ngZone.run).not.toHaveBeenCalled();
72+
}));
73+
74+
it('should run initializeComponentFn in ngZone if is not already inAngularZone',
75+
fakeAsync(() => {
76+
const spy = spyOn(NgZone, 'isInAngularZone');
77+
spy.and.callFake(function() { return false; });
78+
ngZone.run.calls.reset();
79+
80+
strategy.connect(document.createElement('div'));
81+
82+
expect(ngZone.run).toHaveBeenCalled();
83+
}));
84+
85+
});
4386
describe('after connected', () => {
4487
beforeEach(() => {
4588
// Set up an initial value to make sure it is passed to the component

0 commit comments

Comments
 (0)