Skip to content

Commit 77730e9

Browse files
authored
feat(material/icon): allow fetching icons with credentials (#18896)
Adds the ability to fetch SVG icons using withCredentials. Fixes #18871
1 parent 9a0fba5 commit 77730e9

File tree

3 files changed

+28
-6
lines changed

3 files changed

+28
-6
lines changed

src/material/icon/icon-registry.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export function getMatIconFailedToSanitizeLiteralError(literal: SafeHtml): Error
6969
export interface IconOptions {
7070
/** View box to set on the icon. */
7171
viewBox?: string;
72+
73+
/** Whether or not to fetch the icon or icon set using HTTP credentials. */
74+
withCredentials?: boolean;
7275
}
7376

7477
/**
@@ -426,7 +429,7 @@ export class MatIconRegistry implements OnDestroy {
426429
* from it.
427430
*/
428431
private _loadSvgIconFromConfig(config: SvgIconConfig): Observable<SVGElement> {
429-
return this._fetchUrl(config.url)
432+
return this._fetchIcon(config)
430433
.pipe(map(svgText => this._createSvgElementForSingleIcon(svgText, config.options)));
431434
}
432435

@@ -440,7 +443,7 @@ export class MatIconRegistry implements OnDestroy {
440443
return observableOf(config.svgElement);
441444
}
442445

443-
return this._fetchUrl(config.url).pipe(map(svgText => {
446+
return this._fetchIcon(config).pipe(map(svgText => {
444447
// It is possible that the icon set was parsed and cached by an earlier request, so parsing
445448
// only needs to occur if the cache is yet unset.
446449
if (!config.svgElement) {
@@ -563,10 +566,13 @@ export class MatIconRegistry implements OnDestroy {
563566
}
564567

565568
/**
566-
* Returns an Observable which produces the string contents of the given URL. Results may be
569+
* Returns an Observable which produces the string contents of the given icon. Results may be
567570
* cached, so future calls with the same URL may not cause another HTTP request.
568571
*/
569-
private _fetchUrl(safeUrl: SafeResourceUrl | null): Observable<string> {
572+
private _fetchIcon(iconConfig: SvgIconConfig): Observable<string> {
573+
const {url: safeUrl, options} = iconConfig;
574+
const withCredentials = options?.withCredentials ?? false;
575+
570576
if (!this._httpClient) {
571577
throw getMatIconNoHttpProviderError();
572578
}
@@ -592,7 +598,7 @@ export class MatIconRegistry implements OnDestroy {
592598

593599
// TODO(jelbourn): for some reason, the `finalize` operator "loses" the generic type on the
594600
// Observable. Figure out why and fix it.
595-
const req = this._httpClient.get(url, {responseType: 'text'}).pipe(
601+
const req = this._httpClient.get(url, {responseType: 'text', withCredentials}).pipe(
596602
finalize(() => this._inProgressUrlFetches.delete(url)),
597603
share(),
598604
);

src/material/icon/icon.spec.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import {inject, async, fakeAsync, tick, TestBed} from '@angular/core/testing';
22
import {SafeResourceUrl, DomSanitizer, SafeHtml} from '@angular/platform-browser';
3-
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
3+
import {
4+
HttpClientTestingModule,
5+
HttpTestingController,
6+
TestRequest,
7+
} from '@angular/common/http/testing';
48
import {Component, ErrorHandler} from '@angular/core';
59
import {MatIconModule, MAT_ICON_LOCATION} from './index';
610
import {MatIconRegistry, getMatIconNoHttpProviderError} from './icon-registry';
@@ -189,9 +193,11 @@ describe('MatIcon', () => {
189193
it('should register icon URLs by name', fakeAsync(() => {
190194
iconRegistry.addSvgIcon('fluffy', trustUrl('cat.svg'));
191195
iconRegistry.addSvgIcon('fido', trustUrl('dog.svg'));
196+
iconRegistry.addSvgIcon('felix', trustUrl('auth-cat.svg'), {withCredentials: true});
192197

193198
const fixture = TestBed.createComponent(IconFromSvgName);
194199
let svgElement: SVGElement;
200+
let testRequest: TestRequest;
195201
const testComponent = fixture.componentInstance;
196202
const iconElement = fixture.debugElement.nativeElement.querySelector('mat-icon');
197203

@@ -215,6 +221,15 @@ describe('MatIcon', () => {
215221
svgElement = verifyAndGetSingleSvgChild(iconElement);
216222
verifyPathChildElement(svgElement, 'woof');
217223

224+
// Change icon to one that needs credentials during fetch.
225+
testComponent.iconName = 'felix';
226+
fixture.detectChanges();
227+
testRequest = http.expectOne('auth-cat.svg');
228+
expect(testRequest.request.withCredentials).toBeTrue();
229+
testRequest.flush(FAKE_SVGS.cat);
230+
svgElement = verifyAndGetSingleSvgChild(iconElement);
231+
verifyPathChildElement(svgElement, 'meow');
232+
218233
// Assert that a registered icon can be looked-up by url.
219234
iconRegistry.getSvgIconFromUrl(trustUrl('cat.svg')).subscribe(element => {
220235
verifyPathChildElement(element, 'meow');

tools/public_api_guard/material/icon.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export declare function ICON_REGISTRY_PROVIDER_FACTORY(parentRegistry: MatIconRe
1616

1717
export interface IconOptions {
1818
viewBox?: string;
19+
withCredentials?: boolean;
1920
}
2021

2122
export declare const MAT_ICON_LOCATION: InjectionToken<MatIconLocation>;

0 commit comments

Comments
 (0)