|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
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'; |
10 | 10 | import {merge, Observable, ReplaySubject} from 'rxjs';
|
11 | 11 | import {map, switchMap} from 'rxjs/operators';
|
12 | 12 |
|
@@ -73,78 +73,94 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
|
73 | 73 | */
|
74 | 74 | private readonly unchangedInputs = new Set<string>();
|
75 | 75 |
|
| 76 | + /** Service for setting zone context. */ |
| 77 | + private readonly ngZone = this.injector.get<NgZone>(NgZone); |
| 78 | + |
| 79 | + /** The zone the element was created in or `null` if Zone.js is not loaded. */ |
| 80 | + private readonly elementZone = |
| 81 | + (typeof Zone === 'undefined') ? null : this.ngZone.run(() => Zone.current); |
| 82 | + |
76 | 83 | constructor(private componentFactory: ComponentFactory<any>, private injector: Injector) {}
|
77 | 84 |
|
78 | 85 | /**
|
79 | 86 | * Initializes a new component if one has not yet been created and cancels any scheduled
|
80 | 87 | * destruction.
|
81 | 88 | */
|
82 | 89 | connect(element: HTMLElement) {
|
83 |
| - // If the element is marked to be destroyed, cancel the task since the component was reconnected |
84 |
| - if (this.scheduledDestroyFn !== null) { |
85 |
| - this.scheduledDestroyFn(); |
86 |
| - this.scheduledDestroyFn = null; |
87 |
| - return; |
88 |
| - } |
| 90 | + this.runInZone(() => { |
| 91 | + // If the element is marked to be destroyed, cancel the task since the component was |
| 92 | + // reconnected |
| 93 | + if (this.scheduledDestroyFn !== null) { |
| 94 | + this.scheduledDestroyFn(); |
| 95 | + this.scheduledDestroyFn = null; |
| 96 | + return; |
| 97 | + } |
89 | 98 |
|
90 |
| - if (this.componentRef === null) { |
91 |
| - this.initializeComponent(element); |
92 |
| - } |
| 99 | + if (this.componentRef === null) { |
| 100 | + this.initializeComponent(element); |
| 101 | + } |
| 102 | + }); |
93 | 103 | }
|
94 | 104 |
|
95 | 105 | /**
|
96 | 106 | * Schedules the component to be destroyed after some small delay in case the element is just
|
97 | 107 | * being moved across the DOM.
|
98 | 108 | */
|
99 | 109 | disconnect() {
|
100 |
| - // Return if there is no componentRef or the component is already scheduled for destruction |
101 |
| - if (this.componentRef === null || this.scheduledDestroyFn !== null) { |
102 |
| - return; |
103 |
| - } |
104 |
| - |
105 |
| - // Schedule the component to be destroyed after a small timeout in case it is being |
106 |
| - // moved elsewhere in the DOM |
107 |
| - this.scheduledDestroyFn = scheduler.schedule(() => { |
108 |
| - if (this.componentRef !== null) { |
109 |
| - this.componentRef.destroy(); |
110 |
| - this.componentRef = null; |
| 110 | + this.runInZone(() => { |
| 111 | + // Return if there is no componentRef or the component is already scheduled for destruction |
| 112 | + if (this.componentRef === null || this.scheduledDestroyFn !== null) { |
| 113 | + return; |
111 | 114 | }
|
112 |
| - }, DESTROY_DELAY); |
| 115 | + |
| 116 | + // Schedule the component to be destroyed after a small timeout in case it is being |
| 117 | + // moved elsewhere in the DOM |
| 118 | + this.scheduledDestroyFn = scheduler.schedule(() => { |
| 119 | + if (this.componentRef !== null) { |
| 120 | + this.componentRef.destroy(); |
| 121 | + this.componentRef = null; |
| 122 | + } |
| 123 | + }, DESTROY_DELAY); |
| 124 | + }); |
113 | 125 | }
|
114 | 126 |
|
115 | 127 | /**
|
116 | 128 | * Returns the component property value. If the component has not yet been created, the value is
|
117 | 129 | * retrieved from the cached initialization values.
|
118 | 130 | */
|
119 | 131 | getInputValue(property: string): any {
|
120 |
| - if (this.componentRef === null) { |
121 |
| - return this.initialInputValues.get(property); |
122 |
| - } |
| 132 | + return this.runInZone(() => { |
| 133 | + if (this.componentRef === null) { |
| 134 | + return this.initialInputValues.get(property); |
| 135 | + } |
123 | 136 |
|
124 |
| - return this.componentRef.instance[property]; |
| 137 | + return this.componentRef.instance[property]; |
| 138 | + }); |
125 | 139 | }
|
126 | 140 |
|
127 | 141 | /**
|
128 | 142 | * Sets the input value for the property. If the component has not yet been created, the value is
|
129 | 143 | * cached and set when the component is created.
|
130 | 144 | */
|
131 | 145 | setInputValue(property: string, value: any): void {
|
132 |
| - if (this.componentRef === null) { |
133 |
| - this.initialInputValues.set(property, value); |
134 |
| - return; |
135 |
| - } |
| 146 | + this.runInZone(() => { |
| 147 | + if (this.componentRef === null) { |
| 148 | + this.initialInputValues.set(property, value); |
| 149 | + return; |
| 150 | + } |
136 | 151 |
|
137 |
| - // Ignore the value if it is strictly equal to the current value, except if it is `undefined` |
138 |
| - // and this is the first change to the value (because an explicit `undefined` _is_ strictly |
139 |
| - // equal to not having a value set at all, but we still need to record this as a change). |
140 |
| - if (strictEquals(value, this.getInputValue(property)) && |
141 |
| - !((value === undefined) && this.unchangedInputs.has(property))) { |
142 |
| - return; |
143 |
| - } |
| 152 | + // Ignore the value if it is strictly equal to the current value, except if it is `undefined` |
| 153 | + // and this is the first change to the value (because an explicit `undefined` _is_ strictly |
| 154 | + // equal to not having a value set at all, but we still need to record this as a change). |
| 155 | + if (strictEquals(value, this.getInputValue(property)) && |
| 156 | + !((value === undefined) && this.unchangedInputs.has(property))) { |
| 157 | + return; |
| 158 | + } |
144 | 159 |
|
145 |
| - this.recordInputChange(property, value); |
146 |
| - this.componentRef.instance[property] = value; |
147 |
| - this.scheduleDetectChanges(); |
| 160 | + this.recordInputChange(property, value); |
| 161 | + this.componentRef.instance[property] = value; |
| 162 | + this.scheduleDetectChanges(); |
| 163 | + }); |
148 | 164 | }
|
149 | 165 |
|
150 | 166 | /**
|
@@ -264,4 +280,9 @@ export class ComponentNgElementStrategy implements NgElementStrategy {
|
264 | 280 | this.callNgOnChanges(this.componentRef);
|
265 | 281 | this.componentRef.changeDetectorRef.detectChanges();
|
266 | 282 | }
|
| 283 | + |
| 284 | + /** Runs in the angular zone, if present. */ |
| 285 | + private runInZone(fn: () => unknown) { |
| 286 | + return (this.elementZone && Zone.current !== this.elementZone) ? this.ngZone.run(fn) : fn(); |
| 287 | + } |
267 | 288 | }
|
0 commit comments