Created
January 17, 2019 13:42
-
-
Save bennadel/1b11e8a790a7d3aa63fe05b8bfb72e7d to your computer and use it in GitHub Desktop.
Creating SVG Icon Components And SVG Icon Sprites In Angular 7.2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| :host { | |
| color: inherit ; | |
| display: inline-block ; | |
| // Let the app-icon naturally scale with the font-size. Since this is bound to the | |
| // host element, it can easily be overridden by contextual styling. | |
| height: 1em ; | |
| width: 1em ; | |
| } | |
| svg { | |
| // Let the app-icon fill color take on the same color as the contextual text. | |
| color: inherit ; | |
| fill: currentColor ; | |
| display: block ; | |
| // Scale the SVG to cover the whole app-icon container. | |
| height: 100% ; | |
| width: 100% ; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Import the core angular services. | |
| import { ChangeDetectionStrategy } from "@angular/core"; | |
| import { Component } from "@angular/core"; | |
| import { OnChanges } from "@angular/core"; | |
| // Import the application components and services. | |
| import { UniqueIDService } from "./unique-id.service"; | |
| // ----------------------------------------------------------------------------------- // | |
| // ----------------------------------------------------------------------------------- // | |
| // I define the collection of SVG icons that are pulled into this sprite (via Webpack | |
| // svg-sprite-loader). I like having them listed here individually so that icons can be | |
| // explicitly included or excluded from the build without having to remove the SVG files | |
| // from the actual file-system. This feels more maintainable. | |
| import "./svg/chevron-circle-left.svg"; | |
| import "./svg/chevron-circle-right.svg"; | |
| import "./svg/chevron-down.svg"; | |
| import "./svg/chevron-left.svg"; | |
| import "./svg/chevron-right.svg"; | |
| import "./svg/chevron-up.svg"; | |
| // ----------------------------------------------------------------------------------- // | |
| // ----------------------------------------------------------------------------------- // | |
| // CAUTION: I have no experience with ARIA configurations. I have tried my best to apply | |
| // what I read in the following post; however, please do not view the ARIA implementation | |
| // in this Icon component as "correct". I am not sure how the "app-icon" wrapper affects | |
| // the implementation details. My approach was to treat the "app-icon" like the IMG and | |
| // then "hide" the SVG itself from the device. | |
| // -- | |
| // Read More: https://www.24a11y.com/2018/accessible-svg-icons-with-inline-sprites/ | |
| @Component({ | |
| selector: "app-icon", | |
| inputs: [ "type", "title" ], | |
| host: { | |
| "[attr.title]": "ariaTitle", | |
| "[attr.aria-hidden]": "ariaHidden", | |
| "[attr.aria-labelledby]": "ariaLabelledBy", | |
| "role": "img" | |
| }, | |
| changeDetection: ChangeDetectionStrategy.OnPush, | |
| styleUrls: [ "./app-icon.component.less" ], | |
| template: | |
| ` | |
| <svg aria-hidden="true" focusable="false"> | |
| <title *ngIf="ariaTitle" [attr.id]="ariaLabelledBy"> | |
| {{ ariaTitle }} | |
| </title> | |
| <use [attr.xlink:href]="( '#app-icon-' + type )" /> | |
| </svg> | |
| ` | |
| }) | |
| export class AppIconComponent implements OnChanges { | |
| public ariaHidden: true | null; | |
| public ariaLabelledBy: string | null; | |
| public ariaTitle: string | null; | |
| public title!: string; | |
| public type!: string; | |
| private uniqueIDService: UniqueIDService; | |
| // I initialize the app-icon component. | |
| constructor( uniqueIDService: UniqueIDService ) { | |
| this.uniqueIDService = uniqueIDService; | |
| this.ariaHidden = true; | |
| this.ariaLabelledBy = null; | |
| this.ariaTitle = null; | |
| } | |
| // --- | |
| // PUBLIC METHODS. | |
| // --- | |
| // I get called when the input binding are updated. | |
| public ngOnChanges() : void { | |
| if ( this.title ) { | |
| // If a title was provided, it means that this icon is more than just a | |
| // decorative element. As such, let's try to make it more accessible to | |
| // screen-readers. | |
| this.ariaHidden = null; | |
| this.ariaLabelledBy = ( this.ariaLabelledBy || this.uniqueIDService.next() ); | |
| this.ariaTitle = this.title; | |
| } else { | |
| // If there is no title, we want to hide this icon from screen-readers. | |
| this.ariaHidden = true; | |
| this.ariaLabelledBy = null; | |
| this.ariaTitle = null; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Import the core angular services. | |
| import { Component } from "@angular/core"; | |
| // ----------------------------------------------------------------------------------- // | |
| // ----------------------------------------------------------------------------------- // | |
| @Component({ | |
| selector: "my-app", | |
| styleUrls: [ "./app.component.less" ], | |
| template: | |
| ` | |
| <div class="counter"> | |
| <button (click)="( counter = counter - 1 )" class="counter__left"> | |
| <app-icon | |
| type="chevron-circle-left" | |
| title="Decrement Counter" | |
| class="counter__icon"> | |
| </app-icon> | |
| </button> | |
| <div class="counter__value"> | |
| {{ counter }} | |
| </div> | |
| <button (click)="( counter = counter + 1 )" class="counter__right"> | |
| <app-icon | |
| type="chevron-circle-right" | |
| title="Increment Counter" | |
| class="counter__icon"> | |
| </app-icon> | |
| </button> | |
| </div> | |
| <!-- To demonstrate what the rendered HTML looks like without [title]. --> | |
| <div class="small-icons"> | |
| <app-icon type="chevron-left"></app-icon> | |
| <app-icon type="chevron-up"></app-icon> | |
| <app-icon type="chevron-right"></app-icon> | |
| <app-icon type="chevron-down"></app-icon> | |
| </div> | |
| ` | |
| }) | |
| export class AppComponent { | |
| public counter: number = 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Import the core angular services. | |
| import { Injectable } from "@angular/core"; | |
| // ----------------------------------------------------------------------------------- // | |
| // ----------------------------------------------------------------------------------- // | |
| @Injectable({ | |
| providedIn: "root" | |
| }) | |
| export class UniqueIDService { | |
| private id: number = Date.now(); | |
| // --- | |
| // PUBLIC METHODS. | |
| // --- | |
| // I get the next available unique ID. | |
| public next() : string { | |
| return( `aria-id-${ ++this.id }` ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // .... truncated for snippet .... | |
| module: { | |
| rules: [ | |
| // I build the SVG sprite for the Angular application. By using the | |
| // "include" property and the "symbolId" property, I can associate the | |
| // SVG files in one directory with a specific set of unique IDs. | |
| // -- | |
| // NOTE: If you provide multiple instances of the "svg-sprite-loader", | |
| // each instance can use a different "include" property and set of | |
| // options. These SVGs will all end-up in a single "sprite"; but, they | |
| // will each be named according to their own options. | |
| { | |
| test: /\.(svg)$/, | |
| loader: "svg-sprite-loader", | |
| include: path.join( __dirname, "app/icons/svg/" ), | |
| options: { | |
| symbolId: "app-icon-[name]" | |
| } | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment