@@ -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.
@@ -167,7 +162,7 @@ export class MatIconRegistry implements OnDestroy {
167
162
*/
168
163
addSvgIconInNamespace ( namespace : string , iconName : string , url : SafeResourceUrl ,
169
164
options ?: IconOptions ) : this {
170
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , options ) ) ;
165
+ return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , null , options ) ) ;
171
166
}
172
167
173
168
/**
@@ -178,14 +173,14 @@ export class MatIconRegistry implements OnDestroy {
178
173
*/
179
174
addSvgIconLiteralInNamespace ( namespace : string , iconName : string , literal : SafeHtml ,
180
175
options ?: IconOptions ) : this {
181
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
176
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
182
177
183
- if ( ! sanitizedLiteral ) {
178
+ if ( ! cleanLiteral ) {
184
179
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
185
180
}
186
181
187
- const svgElement = this . _createSvgElementForSingleIcon ( sanitizedLiteral , options ) ;
188
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( svgElement , options ) ) ;
182
+ return this . _addSvgIconConfig ( namespace , iconName ,
183
+ new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
189
184
}
190
185
191
186
/**
@@ -210,7 +205,7 @@ export class MatIconRegistry implements OnDestroy {
210
205
* @param url
211
206
*/
212
207
addSvgIconSetInNamespace ( namespace : string , url : SafeResourceUrl , options ?: IconOptions ) : this {
213
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , options ) ) ;
208
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , null , options ) ) ;
214
209
}
215
210
216
211
/**
@@ -220,14 +215,13 @@ export class MatIconRegistry implements OnDestroy {
220
215
*/
221
216
addSvgIconSetLiteralInNamespace ( namespace : string , literal : SafeHtml ,
222
217
options ?: IconOptions ) : this {
223
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
218
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
224
219
225
- if ( ! sanitizedLiteral ) {
220
+ if ( ! cleanLiteral ) {
226
221
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
227
222
}
228
223
229
- const svgElement = this . _svgElementFromString ( sanitizedLiteral ) ;
230
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( svgElement , options ) ) ;
224
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
231
225
}
232
226
233
227
/**
@@ -291,7 +285,7 @@ export class MatIconRegistry implements OnDestroy {
291
285
return observableOf ( cloneSvg ( cachedIcon ) ) ;
292
286
}
293
287
294
- return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl ) ) . pipe (
288
+ return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl , null ) ) . pipe (
295
289
tap ( svg => this . _cachedIconsByUrl . set ( url ! , svg ) ) ,
296
290
map ( svg => cloneSvg ( svg ) ) ,
297
291
) ;
@@ -334,15 +328,12 @@ export class MatIconRegistry implements OnDestroy {
334
328
* Returns the cached icon for a SvgIconConfig if available, or fetches it from its URL if not.
335
329
*/
336
330
private _getSvgFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
337
- if ( config . svgElement ) {
331
+ if ( config . svgText ) {
338
332
// We already have the SVG element for this icon, return a copy.
339
- return observableOf ( cloneSvg ( config . svgElement ) ) ;
333
+ return observableOf ( cloneSvg ( this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ) ) ;
340
334
} else {
341
335
// Fetch the icon from the config's URL, cache it, and return a copy.
342
- return this . _loadSvgIconFromConfig ( config ) . pipe (
343
- tap ( svg => config . svgElement = svg ) ,
344
- map ( svg => cloneSvg ( svg ) ) ,
345
- ) ;
336
+ return this . _loadSvgIconFromConfig ( config ) . pipe ( map ( svg => cloneSvg ( svg ) ) ) ;
346
337
}
347
338
}
348
339
@@ -369,11 +360,11 @@ export class MatIconRegistry implements OnDestroy {
369
360
370
361
// Not found in any cached icon sets. If there are icon sets with URLs that we haven't
371
362
// fetched, fetch them now and look for iconName in the results.
372
- const iconSetFetchRequests : Observable < SVGElement | null > [ ] = iconSetConfigs
373
- . filter ( iconSetConfig => ! iconSetConfig . svgElement )
363
+ const iconSetFetchRequests : Observable < string | null > [ ] = iconSetConfigs
364
+ . filter ( iconSetConfig => ! iconSetConfig . svgText )
374
365
. map ( iconSetConfig => {
375
366
return this . _loadSvgIconSetFromConfig ( iconSetConfig ) . pipe (
376
- catchError ( ( err : HttpErrorResponse ) : Observable < SVGElement | null > => {
367
+ catchError ( ( err : HttpErrorResponse ) => {
377
368
const url = this . _sanitizer . sanitize ( SecurityContext . RESOURCE_URL , iconSetConfig . url ) ;
378
369
379
370
// Swallow errors fetching individual URLs so the
@@ -408,8 +399,14 @@ export class MatIconRegistry implements OnDestroy {
408
399
// Iterate backwards, so icon sets added later have precedence.
409
400
for ( let i = iconSetConfigs . length - 1 ; i >= 0 ; i -- ) {
410
401
const config = iconSetConfigs [ i ] ;
411
- if ( config . svgElement ) {
412
- const foundIcon = this . _extractSvgIconFromSet ( config . svgElement , iconName , config . options ) ;
402
+
403
+ // Parsing the icon set's text into an SVG element can be expensive. We can avoid some of
404
+ // the parsing by doing a quick check using `indexOf` to see if there's any chance for the
405
+ // icon to be in the set. This won't be 100% accurate, but it should help us avoid at least
406
+ // some of the parsing.
407
+ if ( config . svgText && config . svgText . indexOf ( iconName ) > - 1 ) {
408
+ const svg = this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ;
409
+ const foundIcon = this . _extractSvgIconFromSet ( svg , iconName , config . options ) ;
413
410
if ( foundIcon ) {
414
411
return foundIcon ;
415
412
}
@@ -423,38 +420,22 @@ export class MatIconRegistry implements OnDestroy {
423
420
* from it.
424
421
*/
425
422
private _loadSvgIconFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
426
- return this . _fetchIcon ( config )
427
- . pipe ( map ( svgText => this . _createSvgElementForSingleIcon ( svgText , config . options ) ) ) ;
423
+ return this . _fetchIcon ( config ) . pipe (
424
+ tap ( svgText => config . svgText = svgText ) ,
425
+ map ( ( ) => this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) )
426
+ ) ;
428
427
}
429
428
430
429
/**
431
- * Loads the content of the icon set URL specified in the SvgIconConfig and creates an SVG element
432
- * from it .
430
+ * Loads the content of the icon set URL specified in the
431
+ * SvgIconConfig and attaches it to the config .
433
432
*/
434
- private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
435
- // If the SVG for this icon set has already been parsed, do nothing.
436
- if ( config . svgElement ) {
437
- return observableOf ( config . svgElement ) ;
433
+ private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < string | null > {
434
+ if ( config . svgText ) {
435
+ return observableOf ( null ) ;
438
436
}
439
437
440
- return this . _fetchIcon ( config ) . pipe ( map ( svgText => {
441
- // It is possible that the icon set was parsed and cached by an earlier request, so parsing
442
- // only needs to occur if the cache is yet unset.
443
- if ( ! config . svgElement ) {
444
- config . svgElement = this . _svgElementFromString ( svgText ) ;
445
- }
446
-
447
- return config . svgElement ;
448
- } ) ) ;
449
- }
450
-
451
- /**
452
- * Creates a DOM element from the given SVG string, and adds default attributes.
453
- */
454
- private _createSvgElementForSingleIcon ( responseText : string , options ?: IconOptions ) : SVGElement {
455
- const svg = this . _svgElementFromString ( responseText ) ;
456
- this . _setSvgAttributes ( svg , options ) ;
457
- return svg ;
438
+ return this . _fetchIcon ( config ) . pipe ( tap ( svgText => config . svgText = svgText ) ) ;
458
439
}
459
440
460
441
/**
@@ -590,8 +571,6 @@ export class MatIconRegistry implements OnDestroy {
590
571
return inProgressFetch ;
591
572
}
592
573
593
- // TODO(jelbourn): for some reason, the `finalize` operator "loses" the generic type on the
594
- // Observable. Figure out why and fix it.
595
574
const req = this . _httpClient . get ( url , { responseType : 'text' , withCredentials} ) . pipe (
596
575
finalize ( ( ) => this . _inProgressUrlFetches . delete ( url ) ) ,
597
576
share ( ) ,
@@ -628,6 +607,17 @@ export class MatIconRegistry implements OnDestroy {
628
607
629
608
return this ;
630
609
}
610
+
611
+ /** Parses a config's text into an SVG element. */
612
+ private _svgElementFromConfig ( config : LoadedSvgIconConfig ) : SVGElement {
613
+ if ( ! config . svgElement ) {
614
+ const svg = this . _svgElementFromString ( config . svgText ) ;
615
+ this . _setSvgAttributes ( svg , config . options ) ;
616
+ config . svgElement = svg ;
617
+ }
618
+
619
+ return config . svgElement ;
620
+ }
631
621
}
632
622
633
623
/** @docs -private */
0 commit comments