9

I am building an Angular application (Angular 4/5/6) and would like to use SVG sprites in my component template.

Question: Assuming I already have my SVG icon sprite generated (icons.svg), how can I get Angular to inject/import my SVG icon sprite into my component's template?

Is there a way to inject/import my SVG icon sprite into my component without having to use any 3rd party modules/directives and do it natively with Angular itself?

Background/Issue:

As discussed in this article, icons.svg file would contain all the SVG icons defined as <symbol>. Then I can render selected icons in my HTML using <use> assuming the icons.svg is injected in the DOM.

I generated SVG sprites using IcoMoon app, and saved the icons.svg into my Angular application. Below is my sample Angular component (app.component.ts) in which I am trying to inject/import the icons.svg file and trying to render the SVG icons in my HTML. However, Angular is not rending my SVG icons. I seems I am incorrectly injecting the SVG icon sprite file.

Updates:

  1. I am already aware of a similar question, SVG icon system with angular-cli, where the suggested answer was to use the Node module svg-sprite to generate a SVG sprites using the CSS mode. However, this is NOT what I am asking. I am NOT trying to generate the SVG sprites. I am trying get Angular components to be aware of my SVG sprite, icons.svg, and get it to render them in the HTML whenever I make use of them.
  2. A solution was suggested,https://stackoverflow.com/a/50460361/4988010, to generate a CSS font from the SVG sprite. I feel this is NOT a feasible solution and instead is a "workaround" to not being able to use the SVG sprite.

app.component.ts: Live example on StackBlitz: https://stackblitz.com/edit/angular-bbr2kh?file=src/app/app.component.ts

import { Component } from '@angular/core';
// import `./icons.svg`; // This import method doesn't work


@Component({
  selector: 'my-app',
  template: `

   <!-- This import method doesn't work -->
   <!-- <script src="./icons.svg"></script>  -->

  <p>
  Hello this is a sample Angular 6 component.
  </p>

  <p>Testing to see if SVG icon sprite import works. See below if icons appear. </p>
  <p>Icon (home): <svg class="icon icon-home"><use xlink:href="#icon-home"></use></svg> </p>
  <p>Icon (rocket): <svg class="icon icon-rocket"><use xlink:href="#icon-rocket"></use></svg> </p>
  <p>Icon (wifi): <svg class="icon icon-connection"><use xlink:href="#icon-connection"></use></svg>

  <!-- This import method doesn't work -->
  <!-- <p>Icon (home): <svg class="icon icon-home"><use xlink:href="./icons.svg#icon-home"></use></svg> </p>-->

   </p>
  `,
  styles: [`

  .icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
  }

  `]
})
export class AppComponent {
}
Zythyr
  • 1,142
  • 4
  • 20
  • 33
  • Create a my-icon component that takes a reference to your sprite sheet for @input. Check this... [Stackblitz](https://stackblitz.com/edit/angular-svg-sprite?file=src%2Fapp%2Fmy-icon%2Fmy-icon.component.html) – wrob Jan 16 '19 at 02:01

5 Answers5

3

I think your root path is where you are having problems. In your code you are telling angular to look in app but the browser sees this as https://your-site.com./icons

If you move your icon file under your /assets folder, then it should be available already because the src\assets folder is already included in angular.json, the "assets" collection tells Angular where to look.

<svg class="icon">
   <use xlink:href="/assets/icons.svg#icon-rocket"></use> // Notice root path not "./"
</svg>

If you would like your files to be served from another directory all you need to do is add a path in your angular.json:

…
"assets": [
   "src/favicon.ico",
   "src/assets",
   "src/your-dir"
],
…

Then in your code

<svg class="icon">
   <use xlink:href="/your-dir/icons.svg#icon-rocket"></use>
</svg>

I wouldn't suggest adding /src/app as an asset path, this would basically open up your entire app for serving files, making your entire directory accessible.

I forked your example and updated here

Andy Braham
  • 9,594
  • 4
  • 48
  • 56
  • If I am using icons from SVG sprite on multiple screens, then it will be a separate network call for the sprite to render individual icon, isn't it? Is there a way to restrict this call to once on application boot up? – parthsw Aug 05 '19 at 13:26
  • @parthsw If you want to load all the sprites on load, I would keep all the sprites in one file [this article](https://medium.com/@rubenvermeulen/using-an-svg-sprite-icon-system-in-angular-9d4056357b60) explains it fairly well, in short you add all the sprites as symbols in one svg file then use it like `` – Andy Braham Aug 06 '19 at 11:06
3

I solved this issue using angular material icon.

Angular material injects the SVG by id and without shadow-dom.

first install angular material:

"@angular/material": "^7.3.7", // change version according to your angular version 

Import to your module:

MatIconModule

Then declare your SVG sprite in the app.component or service..

    constructor(
        private matIconRegistry: MatIconRegistry,
        private domSanitizer: DomSanitizer) { 
matIconRegistry.addSvgIconSet(this.domSanitizer.bypassSecurityTrustResourceUrl(`assets/sprite.svg`));
        }

And last, In Html use angular material icon:

<mat-icon svgIcon="svg-id"></mat-icon> // or use as directive <div svgIcon="svg-id"></div>

Read more here

Asaf Hananel
  • 7,092
  • 4
  • 24
  • 24
  • Great solution which has the additional major benefit of allowing styling of an SVG from a remote URL, which is not possible otherwise. – wlf Jan 19 '22 at 05:37
0

you can convert your svg file to font by https://icomoon.io/app/#/select, upload your svg file and select it,click on the Generate Font then click the download button, download font file and choose font folder and put it in a folder in your project, enter image description here

import font file into css files

@font-face {
   font-family: 'icomoon';
   src:  url('fonts/icomoon.eot?31svmk');
   src:  url('fonts/icomoon.eot?31svmk#iefix') format('embedded-opentype'),
   url('fonts/icomoon.ttf?31svmk') format('truetype'),
   url('fonts/icomoon.woff?31svmk') format('woff'),
   url('fonts/icomoon.svg?31svmk#icomoon') format('svg');
  font-weight: normal;
  font-style: normal;
}

declear your class like this

.icon-home:before {
  content: "\ea77";
}
.icon-conection:before {
  content: "\ea78";
}
.icon-rocket:before {
 content: "\ea79";
}

and use it in your html

  <p>Icon (rocket): <i class="icon-rocket"></i> </p>

The solution was to add the icons in the project, but if you want to use the svg code in the project, it's best to add the loader to it.

If you have used the webpack:

  npm install svg-inline-loader --save-dev

Simply add configuration object to module.loaders like this.

{
    test: /\.svg$/,
    loader: 'svg-inline-loader'
}

more info

  • 1
    Its great to know that I can convert my SVG sprites to fonts, however, I don't think this is feasible solution. Why can't I directly use the SVG sprites itself? Why do I have to circumvent around the main issue? – Zythyr May 22 '18 at 06:07
  • I already had this problem and I had to use this solution, this method fixes your problem. Unless you have another reason to use svg in Angular – Alireza Varmaghani May 22 '18 at 07:04
  • If you use WebPack, you may be able to fix your problem by adding a loader , I add the explanation in the above text. i hope it can help you – Alireza Varmaghani May 22 '18 at 07:27
  • I don't know understand why we have to use a "CSS/Font" workaround instead of being able to directly use SVG. Is this a bug in Angular? Is Angular not flexibility enough to support basic features such as being able to load external files? – Zythyr May 22 '18 at 17:51
  • @AlirezaVarmaghani while creating production build it's directly adding file to the root of it (svgs and fonts). It should directly imported into CSS itself. – Abhishek Jul 26 '18 at 06:28
0

I struck a similar issue and solved using a similar technique to some of the answers given. Using this as needed with optional height and width setting ...

<app-icon icon="filter" width="15" height="15"></app-icon>

or

<app-icon icon="filter"></app-icon>

The component has only two icons (all I am presently using) in icon.component.svg:

<!-- https://icons.getbootstrap.com/ -->
<div [ngSwitch]="icon">
    <svg *ngSwitchCase="'filter'" xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" fill="currentColor" viewBox="0 0 16 16">
        <path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
    </svg>
    <svg *ngSwitchCase="'tags'" xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" fill="currentColor" viewBox="0 0 16 16">
        <path d="M3 2v4.586l7 7L14.586 9l-7-7H3zM2 2a1 1 0 0 1 1-1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 2 6.586V2z"/>
        <path d="M5.5 5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0 1a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM1 7.086a1 1 0 0 0 .293.707L8.75 15.25l-.043.043a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 0 7.586V3a1 1 0 0 1 1-1v5.086z"/>
    </svg>
</div>

and icon.component.ts:

import { Component, Input, OnInit } from '@angular/core';

@Component({
    selector: 'app-icon',
    templateUrl: './icon.component.svg',
    styleUrls: []
})
export class IconComponent implements OnInit{
    @Input() icon: any
    @Input() width?: any
    @Input() height?: any

    ngOnInit(): void {
        if (typeof this.width != 'string') {
            this.width = '20'
        }
        if (typeof this.height != 'string') {
            this.height = '20'
        }
    }
}

No need to include an entire icon library set, with reasonably simple reuse within the application.

jwal
  • 630
  • 6
  • 16
-1

I was in your same boat today with designers pushing back about the svg font. I also did not want to move the icon def out of the node_modules because it would change over time. I came up with a solution.

You need to first create an angular icon component, then do something like this:

import { Component, OnInit, Input } from '@angular/core';
declare const require;
@Component({
  selector: 'my-icon',
  templateUrl: './my-icon.component.html',
  styleUrls: ['./my-icon.component.scss']
})
export class IconComponent implements OnInit {

  constructor() { }

  @Input() icon: string = "";
  @Input() x: string = "0";
  @Input() y: string = "0";
  @Input() fillColor: string = "black";
  @Input() strokeColor: string = "black";
  @Input() height: string = "";
  @Input() width: string = "";
  private baseUrl: string = require("../../../../../node_modules/path/to/defs.svg") + "#beginning-of-iconset-";

  svgUrl: string = "";

  ngOnInit() {
  }

  ngAfterViewInit() {
    setTimeout(()=>{
      this.svgUrl = this.baseUrl + this.icon;
    },0);

  }

}

And then, in the html:

<svg [attr.height]="height" [attr.width]="width" xmlns="http://www.w3.org/2000/svg">
  <use [attr.href]="svgUrl" [attr.x]="x" [attr.y]="y" [attr.fill]="fillColor" [attr.stroke]="strokeColor" />
</svg>

I'm still working on scaling the solution as the width and height attributes don't work as expected. You can also expand on the inputs as I know I will.

Hope that helps.

dcatoday
  • 21
  • 6