Skip to content

Commit a797f41

Browse files
alan-agius4AndrewKushnir
authored andcommitted
fix(platform-browser): wait until animation completion before destroying renderer (#50860)
Prior to this commit, the renderer destroy method was being called before the animation complete. This is problematic when using `REMOVE_STYLES_ON_COMPONENT_DESTROY` as it causes the styles to be removed too early. This commit, updates this destroy logic to be call the render destroy once the animations complete. This has been reported internally in: - http://b/271251353#comment12 - http://b/282004950#comment5 PR Close #50860
1 parent ec1f6e3 commit a797f41

File tree

3 files changed

+39
-23
lines changed

3 files changed

+39
-23
lines changed

packages/animations/browser/src/render/animation_engine_next.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,8 @@ export class AnimationEngine {
113113
whenRenderingDone(): Promise<any> {
114114
return this._transitionEngine.whenRenderingDone();
115115
}
116+
117+
afterFlushAnimationsDone(cb: VoidFunction): void {
118+
this._transitionEngine.afterFlushAnimationsDone(cb);
119+
}
116120
}

packages/platform-browser/animations/src/animation_renderer.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,26 @@ export class AnimationRendererFactory implements RendererFactory2 {
138138
export class BaseAnimationRenderer implements Renderer2 {
139139
constructor(
140140
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine,
141-
private _onDestroy?: () => void) {
142-
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null;
143-
}
141+
private _onDestroy?: () => void) {}
144142

145143
get data() {
146144
return this.delegate.data;
147145
}
148146

149-
destroyNode: ((n: any) => void)|null;
147+
destroyNode(node: any): void {
148+
this.delegate.destroyNode?.(node);
149+
}
150150

151151
destroy(): void {
152152
this.engine.destroy(this.namespaceId, this.delegate);
153-
this.delegate.destroy();
153+
this.engine.afterFlushAnimationsDone(() => {
154+
// Call the renderer destroy method after the animations has finished as otherwise
155+
// styles will be removed too early which will cause an unstyled animation.
156+
queueMicrotask(() => {
157+
this.delegate.destroy();
158+
});
159+
});
160+
154161
this._onDestroy?.();
155162
}
156163

packages/platform-browser/test/dom/dom_renderer_spec.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,50 +144,50 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
144144
});
145145

146146
describe('should not cleanup styles of destroyed components by default', () => {
147-
it('works for components without encapsulation emulated', () => {
147+
it('works for components without encapsulation emulated', async () => {
148148
const fixture = TestBed.createComponent(SomeAppForCleanUp);
149149
const compInstance = fixture.componentInstance;
150150
compInstance.showEmulatedComponents = true;
151151

152152
fixture.detectChanges();
153153
// verify style is in DOM
154-
expect(styleCount(fixture, '.emulated')).toBe(1);
154+
expect(await styleCount(fixture, '.emulated')).toBe(1);
155155

156156
// Remove a single instance of the component.
157157
compInstance.componentOneInstanceHidden = true;
158158
fixture.detectChanges();
159159
// Verify style is still in DOM
160-
expect(styleCount(fixture, '.emulated')).toBe(1);
160+
expect(await styleCount(fixture, '.emulated')).toBe(1);
161161

162162
// Hide all instances of the component
163163
compInstance.componentTwoInstanceHidden = true;
164164
fixture.detectChanges();
165165

166166
// Verify style is still in DOM
167-
expect(styleCount(fixture, '.emulated')).toBe(1);
167+
expect(await styleCount(fixture, '.emulated')).toBe(1);
168168
});
169169

170-
it('works for components without encapsulation none', () => {
170+
it('works for components without encapsulation none', async () => {
171171
const fixture = TestBed.createComponent(SomeAppForCleanUp);
172172
const compInstance = fixture.componentInstance;
173173
compInstance.showEmulatedComponents = false;
174174

175175
fixture.detectChanges();
176176
// verify style is in DOM
177-
expect(styleCount(fixture, '.none')).toBe(1);
177+
expect(await styleCount(fixture, '.none')).toBe(1);
178178

179179
// Remove a single instance of the component.
180180
compInstance.componentOneInstanceHidden = true;
181181
fixture.detectChanges();
182182
// Verify style is still in DOM
183-
expect(styleCount(fixture, '.none')).toBe(1);
183+
expect(await styleCount(fixture, '.none')).toBe(1);
184184

185185
// Hide all instances of the component
186186
compInstance.componentTwoInstanceHidden = true;
187187
fixture.detectChanges();
188188

189189
// Verify style is still in DOM
190-
expect(styleCount(fixture, '.none')).toBe(1);
190+
expect(await styleCount(fixture, '.none')).toBe(1);
191191
});
192192
});
193193

@@ -212,56 +212,61 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
212212
});
213213
});
214214

215-
it('works for components without encapsulation emulated', () => {
215+
it('works for components without encapsulation emulated', async () => {
216216
const fixture = TestBed.createComponent(SomeAppForCleanUp);
217217
const compInstance = fixture.componentInstance;
218218
compInstance.showEmulatedComponents = true;
219-
220219
fixture.detectChanges();
221220
// verify style is in DOM
222-
expect(styleCount(fixture, '.emulated')).toBe(1);
221+
expect(await styleCount(fixture, '.emulated')).toBe(1);
223222

224223
// Remove a single instance of the component.
225224
compInstance.componentOneInstanceHidden = true;
226225
fixture.detectChanges();
227226
// Verify style is still in DOM
228-
expect(styleCount(fixture, '.emulated')).toBe(1);
227+
expect(await styleCount(fixture, '.emulated')).toBe(1);
229228

230229
// Hide all instances of the component
231230
compInstance.componentTwoInstanceHidden = true;
232231
fixture.detectChanges();
233232

234233
// Verify style is not in DOM
235-
expect(styleCount(fixture, '.emulated')).toBe(0);
234+
expect(await styleCount(fixture, '.emulated')).toBe(0);
236235
});
237236

238-
it('works for components without encapsulation none', () => {
237+
it('works for components without encapsulation none', async () => {
239238
const fixture = TestBed.createComponent(SomeAppForCleanUp);
240239
const compInstance = fixture.componentInstance;
241240
compInstance.showEmulatedComponents = false;
242241

243242
fixture.detectChanges();
244243
// verify style is in DOM
245-
expect(styleCount(fixture, '.none')).toBe(1);
244+
expect(await styleCount(fixture, '.none')).toBe(1);
246245

247246
// Remove a single instance of the component.
248247
compInstance.componentOneInstanceHidden = true;
249248
fixture.detectChanges();
250249
// Verify style is still in DOM
251-
expect(styleCount(fixture, '.none')).toBe(1);
250+
expect(await styleCount(fixture, '.none')).toBe(1);
252251

253252
// Hide all instances of the component
254253
compInstance.componentTwoInstanceHidden = true;
255254
fixture.detectChanges();
256255

257256
// Verify style is not in DOM
258-
expect(styleCount(fixture, '.emulated')).toBe(0);
257+
expect(await styleCount(fixture, '.emulated')).toBe(0);
259258
});
260259
});
261260
});
262261
}
263262

264-
function styleCount(fixture: ComponentFixture<unknown>, cssContentMatcher: string): number {
263+
async function styleCount(
264+
fixture: ComponentFixture<unknown>, cssContentMatcher: string): Promise<number> {
265+
// flush
266+
await new Promise<void>(resolve => {
267+
setTimeout(() => resolve(), 0);
268+
});
269+
265270
const html = fixture.debugElement.parent?.parent;
266271
const debugElements = html?.queryAll(By.css('style'));
267272

0 commit comments

Comments
 (0)