@@ -20,7 +20,7 @@ import {
20
20
} from '@angular/core' ;
21
21
import { DomSanitizer , SafeResourceUrl , SafeHtml } from '@angular/platform-browser' ;
22
22
import { forkJoin , Observable , of as observableOf , throwError as observableThrow } from 'rxjs' ;
23
- import { catchError , finalize , map , share , tap } from 'rxjs/operators' ;
23
+ import { catchError , finalize , map , share , tap , mapTo } from 'rxjs/operators' ;
24
24
25
25
26
26
/**
@@ -76,22 +76,17 @@ export interface IconOptions {
76
76
* @docs -private
77
77
*/
78
78
class SvgIconConfig {
79
- url : SafeResourceUrl | null ;
80
79
svgElement : SVGElement | null ;
81
80
82
- constructor ( url : SafeResourceUrl , options ?: IconOptions ) ;
83
- constructor ( svgElement : SVGElement , options ?: IconOptions ) ;
84
- constructor ( data : SafeResourceUrl | SVGElement , public options ?: IconOptions ) {
85
- // Note that we can't use `instanceof SVGElement` here,
86
- // because it'll break during server-side rendering.
87
- if ( ! ! ( data as any ) . nodeName ) {
88
- this . svgElement = data as SVGElement ;
89
- } else {
90
- this . url = data as SafeResourceUrl ;
91
- }
92
- }
81
+ constructor (
82
+ public url : SafeResourceUrl ,
83
+ public svgText : string | null ,
84
+ public options ?: IconOptions ) { }
93
85
}
94
86
87
+ /** Icon configuration whose content has already been loaded. */
88
+ type LoadedSvgIconConfig = SvgIconConfig & { svgText : string } ;
89
+
95
90
/**
96
91
* Service to register and display icons used by the `<mat-icon>` component.
97
92
* - Registers icon URLs by namespace and name.
@@ -165,7 +160,7 @@ export class MatIconRegistry implements OnDestroy {
165
160
*/
166
161
addSvgIconInNamespace ( namespace : string , iconName : string , url : SafeResourceUrl ,
167
162
options ?: IconOptions ) : this {
168
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , options ) ) ;
163
+ return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( url , null , options ) ) ;
169
164
}
170
165
171
166
/**
@@ -176,14 +171,14 @@ export class MatIconRegistry implements OnDestroy {
176
171
*/
177
172
addSvgIconLiteralInNamespace ( namespace : string , iconName : string , literal : SafeHtml ,
178
173
options ?: IconOptions ) : this {
179
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
174
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
180
175
181
- if ( ! sanitizedLiteral ) {
176
+ if ( ! cleanLiteral ) {
182
177
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
183
178
}
184
179
185
- const svgElement = this . _createSvgElementForSingleIcon ( sanitizedLiteral , options ) ;
186
- return this . _addSvgIconConfig ( namespace , iconName , new SvgIconConfig ( svgElement , options ) ) ;
180
+ return this . _addSvgIconConfig ( namespace , iconName ,
181
+ new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
187
182
}
188
183
189
184
/**
@@ -208,7 +203,7 @@ export class MatIconRegistry implements OnDestroy {
208
203
* @param url
209
204
*/
210
205
addSvgIconSetInNamespace ( namespace : string , url : SafeResourceUrl , options ?: IconOptions ) : this {
211
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , options ) ) ;
206
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( url , null , options ) ) ;
212
207
}
213
208
214
209
/**
@@ -218,14 +213,13 @@ export class MatIconRegistry implements OnDestroy {
218
213
*/
219
214
addSvgIconSetLiteralInNamespace ( namespace : string , literal : SafeHtml ,
220
215
options ?: IconOptions ) : this {
221
- const sanitizedLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
216
+ const cleanLiteral = this . _sanitizer . sanitize ( SecurityContext . HTML , literal ) ;
222
217
223
- if ( ! sanitizedLiteral ) {
218
+ if ( ! cleanLiteral ) {
224
219
throw getMatIconFailedToSanitizeLiteralError ( literal ) ;
225
220
}
226
221
227
- const svgElement = this . _svgElementFromString ( sanitizedLiteral ) ;
228
- return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( svgElement , options ) ) ;
222
+ return this . _addSvgIconSetConfig ( namespace , new SvgIconConfig ( '' , cleanLiteral , options ) ) ;
229
223
}
230
224
231
225
/**
@@ -289,7 +283,7 @@ export class MatIconRegistry implements OnDestroy {
289
283
return observableOf ( cloneSvg ( cachedIcon ) ) ;
290
284
}
291
285
292
- return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl ) ) . pipe (
286
+ return this . _loadSvgIconFromConfig ( new SvgIconConfig ( safeUrl , null ) ) . pipe (
293
287
tap ( svg => this . _cachedIconsByUrl . set ( url ! , svg ) ) ,
294
288
map ( svg => cloneSvg ( svg ) ) ,
295
289
) ;
@@ -332,15 +326,12 @@ export class MatIconRegistry implements OnDestroy {
332
326
* Returns the cached icon for a SvgIconConfig if available, or fetches it from its URL if not.
333
327
*/
334
328
private _getSvgFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
335
- if ( config . svgElement ) {
329
+ if ( config . svgText ) {
336
330
// We already have the SVG element for this icon, return a copy.
337
- return observableOf ( cloneSvg ( config . svgElement ) ) ;
331
+ return observableOf ( cloneSvg ( this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ) ) ;
338
332
} else {
339
333
// Fetch the icon from the config's URL, cache it, and return a copy.
340
- return this . _loadSvgIconFromConfig ( config ) . pipe (
341
- tap ( svg => config . svgElement = svg ) ,
342
- map ( svg => cloneSvg ( svg ) ) ,
343
- ) ;
334
+ return this . _loadSvgIconFromConfig ( config ) . pipe ( map ( svg => cloneSvg ( svg ) ) ) ;
344
335
}
345
336
}
346
337
@@ -367,11 +358,11 @@ export class MatIconRegistry implements OnDestroy {
367
358
368
359
// Not found in any cached icon sets. If there are icon sets with URLs that we haven't
369
360
// fetched, fetch them now and look for iconName in the results.
370
- const iconSetFetchRequests : Observable < SVGElement | null > [ ] = iconSetConfigs
371
- . filter ( iconSetConfig => ! iconSetConfig . svgElement )
361
+ const iconSetFetchRequests : Observable < null > [ ] = iconSetConfigs
362
+ . filter ( iconSetConfig => ! iconSetConfig . svgText )
372
363
. map ( iconSetConfig => {
373
364
return this . _loadSvgIconSetFromConfig ( iconSetConfig ) . pipe (
374
- catchError ( ( err : HttpErrorResponse ) : Observable < SVGElement | null > => {
365
+ catchError ( ( err : HttpErrorResponse ) : Observable < null > => {
375
366
const url = this . _sanitizer . sanitize ( SecurityContext . RESOURCE_URL , iconSetConfig . url ) ;
376
367
377
368
// Swallow errors fetching individual URLs so the
@@ -411,8 +402,14 @@ export class MatIconRegistry implements OnDestroy {
411
402
// Iterate backwards, so icon sets added later have precedence.
412
403
for ( let i = iconSetConfigs . length - 1 ; i >= 0 ; i -- ) {
413
404
const config = iconSetConfigs [ i ] ;
414
- if ( config . svgElement ) {
415
- const foundIcon = this . _extractSvgIconFromSet ( config . svgElement , iconName , config . options ) ;
405
+
406
+ // Parsing the icon set's text into an SVG element can be expensive. We can avoid some of
407
+ // the parsing by doing a quick check using `indexOf` to see if there's any chance for the
408
+ // icon to be in the set. This won't be 100% accurate, but it should help us avoid at least
409
+ // some of the parsing.
410
+ if ( config . svgText && config . svgText . indexOf ( iconName ) > - 1 ) {
411
+ const svg = this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) ;
412
+ const foundIcon = this . _extractSvgIconFromSet ( svg , iconName , config . options ) ;
416
413
if ( foundIcon ) {
417
414
return foundIcon ;
418
415
}
@@ -426,38 +423,22 @@ export class MatIconRegistry implements OnDestroy {
426
423
* from it.
427
424
*/
428
425
private _loadSvgIconFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
429
- return this . _fetchUrl ( config . url )
430
- . pipe ( map ( svgText => this . _createSvgElementForSingleIcon ( svgText , config . options ) ) ) ;
426
+ return this . _fetchUrl ( config . url ) . pipe (
427
+ tap ( svgText => config . svgText = svgText ) ,
428
+ map ( ( ) => this . _svgElementFromConfig ( config as LoadedSvgIconConfig ) )
429
+ ) ;
431
430
}
432
431
433
432
/**
434
- * Loads the content of the icon set URL specified in the SvgIconConfig and creates an SVG element
435
- * from it .
433
+ * Loads the content of the icon set URL specified in the
434
+ * SvgIconConfig and attaches it to the config .
436
435
*/
437
- private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < SVGElement > {
438
- // If the SVG for this icon set has already been parsed, do nothing.
439
- if ( config . svgElement ) {
440
- return observableOf ( config . svgElement ) ;
436
+ private _loadSvgIconSetFromConfig ( config : SvgIconConfig ) : Observable < null > {
437
+ if ( config . svgText ) {
438
+ return observableOf ( null ) ;
441
439
}
442
440
443
- return this . _fetchUrl ( config . url ) . pipe ( map ( svgText => {
444
- // It is possible that the icon set was parsed and cached by an earlier request, so parsing
445
- // only needs to occur if the cache is yet unset.
446
- if ( ! config . svgElement ) {
447
- config . svgElement = this . _svgElementFromString ( svgText ) ;
448
- }
449
-
450
- return config . svgElement ;
451
- } ) ) ;
452
- }
453
-
454
- /**
455
- * Creates a DOM element from the given SVG string, and adds default attributes.
456
- */
457
- private _createSvgElementForSingleIcon ( responseText : string , options ?: IconOptions ) : SVGElement {
458
- const svg = this . _svgElementFromString ( responseText ) ;
459
- this . _setSvgAttributes ( svg , options ) ;
460
- return svg ;
441
+ return this . _fetchUrl ( config . url ) . pipe ( tap ( svgText => config . svgText = svgText ) , mapTo ( null ) ) ;
461
442
}
462
443
463
444
/**
@@ -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' } ) . 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