How to properly reference SVG elements in Angular 6+ when using the base tag

24th February 2017

When working on a big project, you allways learn lots of stuff. Today I learned that setting a <base href="..."> tag in your HTML-page breaks references to linearGradients or clipPaths inside inline SVG elements. It affects usage in Mozilla Firefox, Microsoft Edge and many mobile browsers.

There's lots of information on this issue on StackOverflow, and the internet in general. I didn't find a solution that worked for me in Angular 2+ though. Oleg Varaksin (How to proper use SVG gradients in AngularJS 2) wrote in his article that setting the app base programmatically in the AppModule with APP_BASE_HREF would solve this problem. And it did, but it also affected the Angular routing system, as the browser could only find the necessary scripts when loading the app from the root/homepage.

So I migrated a solution from AngularJS and at the bottom of this post, you'll also find some regular expressions for when you have to change hundreds of references (as was the case in my project).

What the references need to be able to work with the base-tag is an absolute path combined with the fragment identifier instead of the fragment identifier on it's own. For example:

<!-- What doesn't work -->
<polygon points="..." fill="url(#myGradient)" />

<!-- What fixes it -->
<polygon points="..." fill="url(/samplePage/some/random/routing#myGradient)" />

So sure, you could manually insert it where needed, but it may be a good idea to get the absolute path directly from the Angular Router. Insert this in your component:

base: string;

contructor(private router: Router){}

ngOnInit(){
  this.base = this.router.url;
}

We have the path inside our base-string. The only thing left to do, is bind it to our element.

<polygon points="..." fill="url({{ base }}#myGradient)" />

No, not like that. That doesn't work. It returns:

Unhandled Promise rejection: Template parse errors:
Can't bind to 'fill' since it isn't a known property of ':svg:polygon'

Well, how the hell am I supposed to bind it then?

Easy. Like this:

<polygon points="..." [attr.fill]="'url(' + base + '#myGradient)'" />

Et voila. Our gradient works like a charm! The same method can be used for clipPaths, filters... anything that needs referencing in SVG.

Replacing this in a giant SVG-file with thousands of lines of code can be quite exhausting... Here's some regular expressions to help you out:

Replace  ([a-z-]+)="url\((.+)\)" with  [attr.$1]="'url('+ base +'$2)'" (include the leading space for it to work as expected). This regex will change all fills, clipPaths and other attributes that use url(#id). For references like xlink:href="#id", replace  ([a-z:]+href)="#([a-zA-Z0-9_]+)" with  [attr.$1]="base + '#$2'".