@@ -21,6 +21,8 @@ import {
21
21
InjectionToken ,
22
22
inject ,
23
23
Inject ,
24
+ OnDestroy ,
25
+ AfterViewChecked ,
24
26
} from '@angular/core' ;
25
27
import { DOCUMENT } from '@angular/common' ;
26
28
import { CanColor , CanColorCtor , mixinColor } from '@angular/material/core' ;
@@ -51,14 +53,18 @@ export const MAT_ICON_LOCATION = new InjectionToken<MatIconLocation>('mat-icon-l
51
53
* @docs -private
52
54
*/
53
55
export interface MatIconLocation {
54
- pathname : string ;
56
+ getPathname : ( ) => string ;
55
57
}
56
58
57
59
/** @docs -private */
58
60
export function MAT_ICON_LOCATION_FACTORY ( ) : MatIconLocation {
59
61
const _document = inject ( DOCUMENT ) ;
60
- const pathname = ( _document && _document . location && _document . location . pathname ) || '' ;
61
- return { pathname} ;
62
+
63
+ return {
64
+ // Note that this needs to be a function, rather than a property, because Angular
65
+ // will only resolve it once, but we want the current path on each call.
66
+ getPathname : ( ) => ( _document && _document . location && _document . location . pathname ) || ''
67
+ } ;
62
68
}
63
69
64
70
@@ -126,7 +132,9 @@ const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/;
126
132
encapsulation : ViewEncapsulation . None ,
127
133
changeDetection : ChangeDetectionStrategy . OnPush ,
128
134
} )
129
- export class MatIcon extends _MatIconMixinBase implements OnChanges , OnInit , CanColor {
135
+ export class MatIcon extends _MatIconMixinBase implements OnChanges , OnInit , AfterViewChecked ,
136
+ CanColor , OnDestroy {
137
+
130
138
/**
131
139
* Whether the icon should be inlined, automatically sizing the icon to match the font size of
132
140
* the element the icon is contained in.
@@ -162,6 +170,12 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
162
170
private _previousFontSetClass : string ;
163
171
private _previousFontIconClass : string ;
164
172
173
+ /** Keeps track of the current page path. */
174
+ private _previousPath ?: string ;
175
+
176
+ /** Keeps track of the elements and attributes that we've prefixed with the current path. */
177
+ private _elementsWithExternalReferences ?: Map < Element , { name : string , value : string } [ ] > ;
178
+
165
179
constructor (
166
180
elementRef : ElementRef < HTMLElement > ,
167
181
private _iconRegistry : MatIconRegistry ,
@@ -233,6 +247,31 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
233
247
}
234
248
}
235
249
250
+ ngAfterViewChecked ( ) {
251
+ const cachedElements = this . _elementsWithExternalReferences ;
252
+
253
+ if ( cachedElements && this . _location && cachedElements . size ) {
254
+ const newPath = this . _location . getPathname ( ) ;
255
+
256
+ // We need to check whether the URL has changed on each change detection since
257
+ // the browser doesn't have an API that will let us react on link clicks and
258
+ // we can't depend on the Angular router. The references need to be updated,
259
+ // because while most browsers don't care whether the URL is correct after
260
+ // the first render, Safari will break if the user navigates to a different
261
+ // page and the SVG isn't re-rendered.
262
+ if ( newPath !== this . _previousPath ) {
263
+ this . _previousPath = newPath ;
264
+ this . _prependPathToReferences ( newPath ) ;
265
+ }
266
+ }
267
+ }
268
+
269
+ ngOnDestroy ( ) {
270
+ if ( this . _elementsWithExternalReferences ) {
271
+ this . _elementsWithExternalReferences . clear ( ) ;
272
+ }
273
+ }
274
+
236
275
private _usingFontIcon ( ) : boolean {
237
276
return ! this . svgIcon ;
238
277
}
@@ -251,14 +290,24 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
251
290
252
291
// Note: we do this fix here, rather than the icon registry, because the
253
292
// references have to point to the URL at the time that the icon was created.
254
- this . _prependCurrentPathToReferences ( svg ) ;
293
+ if ( this . _location ) {
294
+ const path = this . _location . getPathname ( ) ;
295
+ this . _previousPath = path ;
296
+ this . _cacheChildrenWithExternalReferences ( svg ) ;
297
+ this . _prependPathToReferences ( path ) ;
298
+ }
299
+
255
300
this . _elementRef . nativeElement . appendChild ( svg ) ;
256
301
}
257
302
258
303
private _clearSvgElement ( ) {
259
304
const layoutElement : HTMLElement = this . _elementRef . nativeElement ;
260
305
let childCount = layoutElement . childNodes . length ;
261
306
307
+ if ( this . _elementsWithExternalReferences ) {
308
+ this . _elementsWithExternalReferences . clear ( ) ;
309
+ }
310
+
262
311
// Remove existing non-element child nodes and SVGs, and add the new SVG element. Note that
263
312
// we can't use innerHTML, because IE will throw if the element has a data binding.
264
313
while ( childCount -- ) {
@@ -317,24 +366,42 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
317
366
* reference. This is required because WebKit browsers require references to be prefixed with
318
367
* the current path, if the page has a `base` tag.
319
368
*/
320
- private _prependCurrentPathToReferences ( element : SVGElement ) {
321
- // @breaking -change 8.0.0 Remove this null check once `_location` parameter is required.
322
- if ( ! this . _location ) {
323
- return ;
369
+ private _prependPathToReferences ( path : string ) {
370
+ const elements = this . _elementsWithExternalReferences ;
371
+
372
+ if ( elements ) {
373
+ elements . forEach ( ( attrs , element ) => {
374
+ attrs . forEach ( attr => {
375
+ element . setAttribute ( attr . name , `url('${ path } #${ attr . value } ')` ) ;
376
+ } ) ;
377
+ } ) ;
324
378
}
379
+ }
325
380
381
+ /**
382
+ * Caches the children of an SVG element that have `url()`
383
+ * references that we need to prefix with the current path.
384
+ */
385
+ private _cacheChildrenWithExternalReferences ( element : SVGElement ) {
326
386
const elementsWithFuncIri = element . querySelectorAll ( funcIriAttributeSelector ) ;
327
- const path = this . _location . pathname ? this . _location . pathname . split ( '#' ) [ 0 ] : '' ;
387
+ const elements = this . _elementsWithExternalReferences =
388
+ this . _elementsWithExternalReferences || new Map ( ) ;
328
389
329
390
for ( let i = 0 ; i < elementsWithFuncIri . length ; i ++ ) {
330
391
funcIriAttributes . forEach ( attr => {
331
- const value = elementsWithFuncIri [ i ] . getAttribute ( attr ) ;
392
+ const elementWithReference = elementsWithFuncIri [ i ] ;
393
+ const value = elementWithReference . getAttribute ( attr ) ;
332
394
const match = value ? value . match ( funcIriPattern ) : null ;
333
395
334
396
if ( match ) {
335
- // Note the quotes inside the `url()`. They're important, because URLs pointing to named
336
- // router outlets can contain parentheses which will break if they aren't quoted.
337
- elementsWithFuncIri [ i ] . setAttribute ( attr , `url('${ path } #${ match [ 1 ] } ')` ) ;
397
+ let attributes = elements . get ( elementWithReference ) ;
398
+
399
+ if ( ! attributes ) {
400
+ attributes = [ ] ;
401
+ elements . set ( elementWithReference , attributes ) ;
402
+ }
403
+
404
+ attributes ! . push ( { name : attr , value : match [ 1 ] } ) ;
338
405
}
339
406
} ) ;
340
407
}
0 commit comments