Skip to content

Commit

Permalink
fix(icon): cancel in-flight icon requests if the icon changes (#19303)
Browse files Browse the repository at this point in the history
  • Loading branch information
crisbeto authored and mmalerba committed May 15, 2020
1 parent 8d79d73 commit bef4e80
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 1 deletion.
36 changes: 36 additions & 0 deletions src/material/icon/icon.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,42 @@ describe('MatIcon', () => {
tick();
}));

it('should cancel in-progress fetches if the icon changes', fakeAsync(() => {
// Register an icon that will resolve immediately.
iconRegistry.addSvgIconLiteral('fluffy', trustHtml(FAKE_SVGS.cat));

// Register a different icon that takes some time to resolve.
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));

const fixture = TestBed.createComponent(IconFromSvgName);
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');

// Assign the slow icon first.
fixture.componentInstance.iconName = 'fido';
fixture.detectChanges();

// Assign the quick icon while the slow one is still in-flight.
fixture.componentInstance.iconName = 'fluffy';
fixture.detectChanges();

// Expect for the in-flight request to have been cancelled.
expect(http.expectOne('dog.svg').cancelled).toBe(true);

// Expect the last icon to have been assigned.
verifyPathChildElement(verifyAndGetSingleSvgChild(iconElement), 'meow');
}));

it('should cancel in-progress fetches if the component is destroyed', fakeAsync(() => {
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));

const fixture = TestBed.createComponent(IconFromSvgName);
fixture.componentInstance.iconName = 'fido';
fixture.detectChanges();
fixture.destroy();

expect(http.expectOne('dog.svg').cancelled).toBe(true);
}));

});

describe('Icons from HTML string', () => {
Expand Down
10 changes: 9 additions & 1 deletion src/material/icon/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {CanColor, CanColorCtor, mixinColor} from '@angular/material/core';
import {Subscription} from 'rxjs';
import {take} from 'rxjs/operators';

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

/** Subscription to the current in-progress SVG icon request. */
private _currentIconFetch = Subscription.EMPTY;

constructor(
elementRef: ElementRef<HTMLElement>, private _iconRegistry: MatIconRegistry,
@Attribute('aria-hidden') ariaHidden: string,
Expand Down Expand Up @@ -227,10 +231,12 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Aft
const svgIconChanges = changes['svgIcon'];

if (svgIconChanges) {
this._currentIconFetch.unsubscribe();

if (this.svgIcon) {
const [namespace, iconName] = this._splitIconName(this.svgIcon);

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

ngOnDestroy() {
this._currentIconFetch.unsubscribe();

if (this._elementsWithExternalReferences) {
this._elementsWithExternalReferences.clear();
}
Expand Down

0 comments on commit bef4e80

Please sign in to comment.