Skip to content

Commit bef4e80

Browse files
crisbetommalerba
authored andcommitted
fix(icon): cancel in-flight icon requests if the icon changes (#19303)
1 parent 8d79d73 commit bef4e80

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

src/material/icon/icon.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,42 @@ describe('MatIcon', () => {
595595
tick();
596596
}));
597597

598+
it('should cancel in-progress fetches if the icon changes', fakeAsync(() => {
599+
// Register an icon that will resolve immediately.
600+
iconRegistry.addSvgIconLiteral('fluffy', trustHtml(FAKE_SVGS.cat));
601+
602+
// Register a different icon that takes some time to resolve.
603+
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));
604+
605+
const fixture = TestBed.createComponent(IconFromSvgName);
606+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
607+
608+
// Assign the slow icon first.
609+
fixture.componentInstance.iconName = 'fido';
610+
fixture.detectChanges();
611+
612+
// Assign the quick icon while the slow one is still in-flight.
613+
fixture.componentInstance.iconName = 'fluffy';
614+
fixture.detectChanges();
615+
616+
// Expect for the in-flight request to have been cancelled.
617+
expect(http.expectOne('dog.svg').cancelled).toBe(true);
618+
619+
// Expect the last icon to have been assigned.
620+
verifyPathChildElement(verifyAndGetSingleSvgChild(iconElement), 'meow');
621+
}));
622+
623+
it('should cancel in-progress fetches if the component is destroyed', fakeAsync(() => {
624+
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));
625+
626+
const fixture = TestBed.createComponent(IconFromSvgName);
627+
fixture.componentInstance.iconName = 'fido';
628+
fixture.detectChanges();
629+
fixture.destroy();
630+
631+
expect(http.expectOne('dog.svg').cancelled).toBe(true);
632+
}));
633+
598634
});
599635

600636
describe('Icons from HTML string', () => {

src/material/icon/icon.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
ViewEncapsulation,
2828
} from '@angular/core';
2929
import {CanColor, CanColorCtor, mixinColor} from '@angular/material/core';
30+
import {Subscription} from 'rxjs';
3031
import {take} from 'rxjs/operators';
3132

3233
import {MatIconRegistry} from './icon-registry';
@@ -178,6 +179,9 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Aft
178179
/** Keeps track of the elements and attributes that we've prefixed with the current path. */
179180
private _elementsWithExternalReferences?: Map<Element, {name: string, value: string}[]>;
180181

182+
/** Subscription to the current in-progress SVG icon request. */
183+
private _currentIconFetch = Subscription.EMPTY;
184+
181185
constructor(
182186
elementRef: ElementRef<HTMLElement>, private _iconRegistry: MatIconRegistry,
183187
@Attribute('aria-hidden') ariaHidden: string,
@@ -227,10 +231,12 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Aft
227231
const svgIconChanges = changes['svgIcon'];
228232

229233
if (svgIconChanges) {
234+
this._currentIconFetch.unsubscribe();
235+
230236
if (this.svgIcon) {
231237
const [namespace, iconName] = this._splitIconName(this.svgIcon);
232238

233-
this._iconRegistry.getNamedSvgIcon(iconName, namespace)
239+
this._currentIconFetch = this._iconRegistry.getNamedSvgIcon(iconName, namespace)
234240
.pipe(take(1))
235241
.subscribe(svg => this._setSvgElement(svg), (err: Error) => {
236242
const errorMessage = `Error retrieving icon ${namespace}:${iconName}! ${err.message}`;
@@ -279,6 +285,8 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Aft
279285
}
280286

281287
ngOnDestroy() {
288+
this._currentIconFetch.unsubscribe();
289+
282290
if (this._elementsWithExternalReferences) {
283291
this._elementsWithExternalReferences.clear();
284292
}

0 commit comments

Comments
 (0)