Skip to content

Commit 677be58

Browse files
authored
fix(icon): cancel in-flight icon requests if the icon changes (#19303)
1 parent 485352e commit 677be58

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
@@ -610,6 +610,42 @@ describe('MatIcon', () => {
610610
tick();
611611
}));
612612

613+
it('should cancel in-progress fetches if the icon changes', fakeAsync(() => {
614+
// Register an icon that will resolve immediately.
615+
iconRegistry.addSvgIconLiteral('fluffy', trustHtml(FAKE_SVGS.cat));
616+
617+
// Register a different icon that takes some time to resolve.
618+
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));
619+
620+
const fixture = TestBed.createComponent(IconFromSvgName);
621+
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
622+
623+
// Assign the slow icon first.
624+
fixture.componentInstance.iconName = 'fido';
625+
fixture.detectChanges();
626+
627+
// Assign the quick icon while the slow one is still in-flight.
628+
fixture.componentInstance.iconName = 'fluffy';
629+
fixture.detectChanges();
630+
631+
// Expect for the in-flight request to have been cancelled.
632+
expect(http.expectOne('dog.svg').cancelled).toBe(true);
633+
634+
// Expect the last icon to have been assigned.
635+
verifyPathChildElement(verifyAndGetSingleSvgChild(iconElement), 'meow');
636+
}));
637+
638+
it('should cancel in-progress fetches if the component is destroyed', fakeAsync(() => {
639+
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));
640+
641+
const fixture = TestBed.createComponent(IconFromSvgName);
642+
fixture.componentInstance.iconName = 'fido';
643+
fixture.detectChanges();
644+
fixture.destroy();
645+
646+
expect(http.expectOne('dog.svg').cancelled).toBe(true);
647+
}));
648+
613649
});
614650

615651
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)