Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle SVG sprite #32

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
[![npm version](https://badge.fury.io/js/angular-svg-icon.svg)](https://badge.fury.io/js/angular-svg-icon)

Angular SVG Icon
Angular SVG Icon
=========

The **angular-svg-icon** is an Angular 2+ service and component that provides a
means to inline SVG images to allow for them to be easily styled by CSS and
The **angular-svg-icon** is an Angular 2+ service and component that provides a
means to inline SVG images to allow for them to be easily styled by CSS and
code.

The service provides an icon registery that loads and caches a svg indexed by
its url. The component is responsible for displaying the svg. After getting the
svg from the registry it clones the `SVGElement` and the svg to the component's
The service provides an icon registery that loads and caches a svg indexed by
its url. The component is responsible for displaying the svg. After getting the
svg from the registry it clones the `SVGElement` and the svg to the component's
inner HTML.

A [working demo](http://czeckd.github.io/angular-svg-icon/demo/) shows solution
A [working demo](http://czeckd.github.io/angular-svg-icon/demo/) shows solution
in action.

## How to use?
Expand All @@ -22,7 +22,7 @@ $ npm i angular-svg-icon --save

## Integration

The **angular-svg-icon** should work as-is with webpack/angular-cli. Just add
The **angular-svg-icon** should work as-is with webpack/angular-cli. Just add
the ``AngularSvgIconModule``.

```typescript
Expand Down Expand Up @@ -52,14 +52,14 @@ constructor(private iconReg:SvgIconRegistryService) { }

The registry has two public functions: `loadSvg(string)` and `unloadSvg(string)`.

To preload a svg file into the registry:
To preload a svg file into the registry:

```typescript
{
...
this.iconReg.loadSvg('foo.svg');
}
```
```

To unload a svg from the registry.

Expand All @@ -70,22 +70,30 @@ To unload a svg from the registry.
}
```

## SVG Sprite

To avoid too many http requests to load a lot of SVG images, you can should your SVG into one unique file. Using [svg-sprite-generator](https://github.com/frexy/svg-sprite-generator), it's pretty easy.
It generates an unique file with an ID for each SVG image (ID is SVG file name).

```html
<svg-icon src="path/to/sprite.svg" symbolID="foo"></svg-icon>
```

## Background

The svg-icon is an Angular 2 component that allows for the continuation of the
AngularJS method for easily inlining SVGs explained by [Ben
Markowitz](https://www.mobomo.com/2014/09/angular-js-svg/) and others. Including
The svg-icon is an Angular 2 component that allows for the continuation of the
AngularJS method for easily inlining SVGs explained by [Ben
Markowitz](https://www.mobomo.com/2014/09/angular-js-svg/) and others. Including
the SVG source inline allows for the graphic to be easily styled by CSS.

The technique made use of ng-include to inline the svg source into the document.
Angular 2, however, drops the support of ng-include, so this is my work-around
The technique made use of ng-include to inline the svg source into the document.
Angular 2, however, drops the support of ng-include, so this is my work-around
method.

*Note:* The [icon
component](https://www.npmjs.com/package/@angular2-material/icon) from
[angular/material2](https://github.com/angular/material2) used to have a direct
means to load svg similar to this, but this functionality was removed because of
*Note:* The [icon
component](https://www.npmjs.com/package/@angular2-material/icon) from
[angular/material2](https://github.com/angular/material2) used to have a direct
means to load svg similar to this, but this functionality was removed because of
security concerns.

## License
Expand Down
58 changes: 44 additions & 14 deletions lib/svg-icon-registry.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/delay';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add additional rxjs/operators to rollup.config.js.


import { Http, Response } from '@angular/http';

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class SvgIconRegistryService {
Expand All @@ -19,28 +20,45 @@ export class SvgIconRegistryService {
constructor(private http:Http) {
}

loadSvg(url:string): Observable<SVGElement> {

loadSvg(url:string, symbolID:string = ''): Observable<SVGElement> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer symbol rather than symbolID.

const orgUrl = url;
url += `#${symbolID}`;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds # to all Urls whether or not they are using sprite symbols.

I would prefer that the original Url is kept as url and the sprite or symbol Url is changed, so something like:

const symbolUrl = url + `#${symbol}`;

// Icon is already loaded and cached (srpite or not)
if (this.iconsByUrl.has(url)) {
return Observable.of(this.iconsByUrl.get(url));
} else if (this.iconsLoadingByUrl.has(url)) {
return this.iconsLoadingByUrl.get(url);
} else {
const o = <Observable<SVGElement>> this.http.get( url )
}
// Sprite required, but sprite SVG is loading, delay loading (until sprite SVG is loaded)
else if (symbolID && this.iconsLoadingByUrl.has(orgUrl)) {
return Observable.of(true).delay(100).mergeMap(() => this.loadSvg(orgUrl, symbolID));
}
// Sprite required, sprite SVG is loaded, but sprite isn't cache yet
else if (symbolID && this.iconsByUrl.get(orgUrl)) {
return Observable.of(this.getSprite(this.iconsByUrl.get(orgUrl), symbolID));
}
// Else start load SVG file
else
{
const o = <Observable<SVGElement>> this.http.get( orgUrl )
.map( (res:Response) => {
const div = document.createElement('DIV');
div.innerHTML = res.text();
return <SVGElement>div.querySelector('svg');
const svg = <SVGElement>div.querySelector('svg');
if (symbolID) {
this.iconsByUrl.set(orgUrl, svg);
return this.getSprite(svg, symbolID);
} else {
return svg;
}
})
.do(svg => {
this.iconsByUrl.set(url, svg);
})
.finally(() => {
this.iconsLoadingByUrl.delete(url);
this.iconsLoadingByUrl.delete(orgUrl);
})
.share();

this.iconsLoadingByUrl.set(url, o);
this.iconsLoadingByUrl.set(orgUrl, o);
return o;
}
}
Expand All @@ -51,4 +69,16 @@ export class SvgIconRegistryService {
}
}

// Extract sprite
getSprite(svg: SVGElement, symbolID: string) {
const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
const spriteElement: SVGSVGElement = svg.querySelector(`#${symbolID}`);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had difficulty getting this to build without explicit casting:

		const spriteElement:SVGSVGElement = <SVGSVGElement>svg.querySelector(`#${symbolID}`);

svgElement.setAttribute('width', spriteElement.viewBox.baseVal.width + 'px');
svgElement.setAttribute('height', spriteElement.viewBox.baseVal.height + 'px');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting width and height circumvents width/height styling with CSS. So, please remove these two lines of code.

If setting width and height is desirable, then maybe an additional parameter in the component to instruct setting the viewbox width and height as the size.

svgElement.setAttribute('viewBox', spriteElement.getAttribute('viewBox'));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tab indent please.

for (let i = 0 ; i < spriteElement.childNodes.length; i++) {
svgElement.appendChild(spriteElement.childNodes[i].cloneNode(true));
}
return svgElement;
}
}