@@ -79,22 +79,17 @@ export interface IconOptions {
79
79
* @docs -private
80
80
*/
81
81
class SvgIconConfig {
82
- url : SafeResourceUrl | null ;
83
82
svgElement : SVGElement | null ;
84
83
85
- constructor ( url : SafeResourceUrl , options ?: IconOptions ) ;
86
- constructor ( svgElement : SVGElement , options ?: IconOptions ) ;
87
- constructor ( data : SafeResourceUrl | SVGElement , public options ?: IconOptions ) {
88
- // Note that we can't use `instanceof SVGElement` here,
89
- // because it'll break during server-side rendering.
90
- if ( ! ! ( data as any ) . nodeName ) {
91
- this . svgElement = data as SVGElement ;
92
- } else {
93
- this . url = data as SafeResourceUrl ;
94
- }
95
- }
84
+ constructor (
85
+ public url : SafeResourceUrl ,
86
+ public svgText : string | null ,
87
+ public options ?: IconOptions ) { }
96
88
}
97
89
90
+ /** Icon configuration whose content has already been loaded. */
91
+ type LoadedSvgIconConfig = SvgIconConfig & { svgText : string } ;
92
+
98
93
/**
99
94
* Service to register and display icons used by the `<mat-icon>` component.
100
95
* - Registers icon URLs by namespace and name.
@@ -168,7 +163,7 @@ export class MatIconRegistry implements OnDestroy {
168
163
*/
169
164
addSvgIconInNamespace ( namespace : string , iconName : string , url : SafeResourceUrl ,
170
165
options ?: IconOptions ) : this {
171
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , options ) ) ;
166
+ return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , null , options ) ) ;
172
167
}
173
168
174
169
/**
@@ -179,14 +174,14 @@ export class MatIconRegistry implements OnDestroy {
179
174
*/
180
175
addSvgIconLiteralInNamespace ( namespace : string , iconName : string , literal : SafeHtml ,
181
176
options ?: IconOptions ) : this {
182
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
177
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
183
178
184
- if ( ! sanitizedLiteral ) {
179
+ if ( ! cleanLiteral ) {
185
180
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
186
181
}
187
182
188
- const svgElement = this . _createSvgElementForSingleIcon ( sanitizedLiteral , options ) ;
189
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( svgElement , options ) ) ;
183
+ return this . _addSvgIconConfig ( namespace , iconName ,
184
+ new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
190
185
}
191
186
192
187
/**
@@ -211,7 +206,7 @@ export class MatIconRegistry implements OnDestroy {
211
206
* @param url
212
207
*/
213
208
addSvgIconSetInNamespace ( namespace : string , url : SafeResourceUrl , options ?: IconOptions ) : this {
214
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , options ) ) ;
209
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , null , options ) ) ;
215
210
}
216
211
217
212
/**
@@ -221,14 +216,13 @@ export class MatIconRegistry implements OnDestroy {
221
216
*/
222
217
addSvgIconSetLiteralInNamespace ( namespace : string , literal : SafeHtml ,
223
218
options ?: IconOptions ) : this {
224
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
219
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
225
220
226
- if ( ! sanitizedLiteral ) {
221
+ if ( ! cleanLiteral ) {
227
222
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
228
223
}
229
224
230
- const svgElement = this . _svgElementFromString ( sanitizedLiteral ) ;
231
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( svgElement , options ) ) ;
225
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
232
226
}
233
227
234
228
/**
@@ -292,7 +286,7 @@ export class MatIconRegistry implements OnDestroy {
292
286
return observableOf ( cloneSvg ( cachedIcon ) ) ;
293
287
}
294
288
295
- return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl ) ) . pipe (
289
+ return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl , null ) ) . pipe (
296
290
tap ( svg => this . _cachedIconsByUrl . set ( url ! , svg ) ) ,
297
291
map ( svg => cloneSvg ( svg ) ) ,
298
292
) ;
@@ -335,15 +329,12 @@ export class MatIconRegistry implements OnDestroy {
335
329
* Returns the cached icon for a SvgIconConfig if available, or fetches it from its URL if not.
336
330
*/
337
331
private _getSvgFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
338
- if ( config . svgElement ) {
332
+ if ( config . svgText ) {
339
333
// We already have the SVG element for this icon, return a copy.
340
- return observableOf ( cloneSvg ( config . svgElement ) ) ;
334
+ return observableOf ( cloneSvg ( this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ) ) ;
341
335
} else {
342
336
// Fetch the icon from the config's URL, cache it, and return a copy.
343
- return this . _loadSvgIconFromConfig ( config ) . pipe (
344
- tap ( svg => config . svgElement = svg ) ,
345
- map ( svg => cloneSvg ( svg ) ) ,
346
- ) ;
337
+ return this . _loadSvgIconFromConfig ( config ) . pipe ( map ( svg => cloneSvg ( svg ) ) ) ;
347
338
}
348
339
}
349
340
@@ -370,11 +361,11 @@ export class MatIconRegistry implements OnDestroy {
370
361
371
362
// Not found in any cached icon sets. If there are icon sets with URLs that we haven't
372
363
// fetched, fetch them now and look for iconName in the results.
373
- const iconSetFetchRequests : Observable < SVGElement | null > [ ] = iconSetConfigs
374
- . filter ( iconSetConfig => ! iconSetConfig . svgElement )
364
+ const iconSetFetchRequests : Observable < string | null > [ ] = iconSetConfigs
365
+ . filter ( iconSetConfig => ! iconSetConfig . svgText )
375
366
. map ( iconSetConfig => {
376
367
return this . _loadSvgIconSetFromConfig ( iconSetConfig ) . pipe (
377
- catchError ( ( err : HttpErrorResponse ) : Observable < SVGElement | null > => {
368
+ catchError ( ( err : HttpErrorResponse ) => {
378
369
const url = this . _sanitizer . sanitize ( SecurityContext . RESOURCE_URL , iconSetConfig . url ) ;
379
370
380
371
// Swallow errors fetching individual URLs so the
@@ -414,8 +405,14 @@ export class MatIconRegistry implements OnDestroy {
414
405
// Iterate backwards, so icon sets added later have precedence.
415
406
for ( let i = iconSetConfigs . length - 1 ; i >= 0 ; i -- ) {
416
407
const config = iconSetConfigs [ i ] ;
417
- if ( config . svgElement ) {
418
- const foundIcon = this . _extractSvgIconFromSet ( config . svgElement , iconName , config . options ) ;
408
+
409
+ // Parsing the icon set's text into an SVG element can be expensive. We can avoid some of
410
+ // the parsing by doing a quick check using `indexOf` to see if there's any chance for the
411
+ // icon to be in the set. This won't be 100% accurate, but it should help us avoid at least
412
+ // some of the parsing.
413
+ if ( config . svgText && config . svgText . indexOf ( iconName ) > - 1 ) {
414
+ const svg = this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ;
415
+ const foundIcon = this . _extractSvgIconFromSet ( svg , iconName , config . options ) ;
419
416
if ( foundIcon ) {
420
417
return foundIcon ;
421
418
}
@@ -429,38 +426,22 @@ export class MatIconRegistry implements OnDestroy {
429
426
* from it.
430
427
*/
431
428
private _loadSvgIconFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
432
- return this . _fetchIcon ( config )
433
- . pipe ( map ( svgText => this . _createSvgElementForSingleIcon ( svgText , config . options ) ) ) ;
429
+ return this . _fetchIcon ( config ) . pipe (
430
+ tap ( svgText => config . svgText = svgText ) ,
431
+ map ( ( ) => this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) )
432
+ ) ;
434
433
}
435
434
436
435
/**
437
- * Loads the content of the icon set URL specified in the SvgIconConfig and creates an SVG element
438
- * from it .
436
+ * Loads the content of the icon set URL specified in the
437
+ * SvgIconConfig and attaches it to the config .
439
438
*/
440
- private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
441
- // If the SVG for this icon set has already been parsed, do nothing.
442
- if ( config . svgElement ) {
443
- return observableOf ( config . svgElement ) ;
439
+ private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < string | null > {
440
+ if ( config . svgText ) {
441
+ return observableOf ( null ) ;
444
442
}
445
443
446
- return this . _fetchIcon ( config ) . pipe ( map ( svgText => {
447
- // It is possible that the icon set was parsed and cached by an earlier request, so parsing
448
- // only needs to occur if the cache is yet unset.
449
- if ( ! config . svgElement ) {
450
- config . svgElement = this . _svgElementFromString ( svgText ) ;
451
- }
452
-
453
- return config . svgElement ;
454
- } ) ) ;
455
- }
456
-
457
- /**
458
- * Creates a DOM element from the given SVG string, and adds default attributes.
459
- */
460
- private _createSvgElementForSingleIcon ( responseText : string , options ?: IconOptions ) : SVGElement {
461
- const svg = this . _svgElementFromString ( responseText ) ;
462
- this . _setSvgAttributes ( svg , options ) ;
463
- return svg ;
444
+ return this . _fetchIcon ( config ) . pipe ( tap ( svgText => config . svgText = svgText ) ) ;
464
445
}
465
446
466
447
/**
@@ -596,8 +577,6 @@ export class MatIconRegistry implements OnDestroy {
596
577
return inProgressFetch ;
597
578
}
598
579
599
- // TODO(jelbourn): for some reason, the `finalize` operator "loses" the generic type on the
600
- // Observable. Figure out why and fix it.
601
580
const req = this . _httpClient . get ( url , { responseType : 'text' , withCredentials} ) . pipe (
602
581
finalize ( ( ) => this . _inProgressUrlFetches . delete ( url ) ) ,
603
582
share ( ) ,
@@ -634,6 +613,17 @@ export class MatIconRegistry implements OnDestroy {
634
613
635
614
return this ;
636
615
}
616
+
617
+ /** Parses a config's text into an SVG element. */
618
+ private _svgElementFromConfig ( config : LoadedSvgIconConfig ) : SVGElement {
619
+ if ( ! config . svgElement ) {
620
+ const svg = this . _svgElementFromString ( config . svgText ) ;
621
+ this . _setSvgAttributes ( svg , config . options ) ;
622
+ config . svgElement = svg ;
623
+ }
624
+
625
+ return config . svgElement ;
626
+ }
637
627
}
638
628
639
629
/** @docs -private */
0 commit comments