Created
February 10, 2018 14:24
Creating A Dynamic Favicon Service In Angular 5.2.4
This file contains 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"; | |
// Import the application components and services. | |
import { Favicons } from "./favicons"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
@Component({ | |
selector: "my-app", | |
styleUrls: [ "./app.component.less" ], | |
template: | |
` | |
<p> | |
Select the favicon to use: | |
</p> | |
<ul> | |
<li> | |
<a (click)="useFavicon( 'happy' )">Happy</a> | |
</li> | |
<li> | |
<a (click)="useFavicon( 'indifferent' )">Indifferent</a> | |
</li> | |
<li> | |
<a (click)="useFavicon( 'sad' )">Sad</a> | |
</li> | |
</ul> | |
<p> | |
<a (click)="resetFavicon()">Reset the Favicon</a> | |
</p> | |
` | |
}) | |
export class AppComponent { | |
private favicons: Favicons; | |
// I initialize the app component. | |
constructor( favicons: Favicons ) { | |
this.favicons = favicons; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I get called once after the inputs have been bound. | |
public ngOnInit() : void { | |
this.resetFavicon(); | |
} | |
// I reset the favicon to use the "default" item. | |
public resetFavicon() : void { | |
console.log( "Resetting favicon" ); | |
this.favicons.reset(); | |
} | |
// I activate the favicon with the given name. | |
public useFavicon( name: string ) : void { | |
console.log( "Activating favicon:", name ); | |
// Notice that we don't need to know anything about how the favicon is defined; | |
// not URLs, no image types - just the identifier. All of the implementation | |
// details have been defined at bootstrap time. | |
this.favicons.activate( name ); | |
} | |
} |
This file contains 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 { BrowserModule } from "@angular/platform-browser"; | |
import { NgModule } from "@angular/core"; | |
// Import the application components and services. | |
import { AppComponent } from "./app.component"; | |
import { BROWSER_FAVICONS_CONFIG } from "./favicons"; | |
import { BrowserFavicons } from "./favicons"; | |
import { Favicons } from "./favicons"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
@NgModule({ | |
bootstrap: [ | |
AppComponent | |
], | |
imports: [ | |
BrowserModule | |
], | |
declarations: [ | |
AppComponent | |
], | |
providers: [ | |
// The Favicons is an abstract class that represents the dependency-injection | |
// token and the API contract. THe BrowserFavicon is the browser-oriented | |
// implementation of the service. | |
{ | |
provide: Favicons, | |
useClass: BrowserFavicons | |
}, | |
// The BROWSER_FAVICONS_CONFIG sets up the favicon definitions for the browser- | |
// based implementation. This way, the rest of the application only needs to know | |
// the identifiers (ie, "happy", "default") - it doesn't need to know the paths | |
// or the types. This allows the favicons to be modified independently without | |
// coupling too tightly to the rest of the code. | |
{ | |
provide: BROWSER_FAVICONS_CONFIG, | |
useValue: { | |
icons: { | |
"square": { | |
type: "image/png", | |
href: "./icons/default.png", | |
isDefault: true | |
}, | |
"happy": { | |
type: "image/jpeg", | |
href: "./icons/happy.jpg" | |
}, | |
"indifferent": { | |
type: "image/png", | |
href: "./icons/indifferent.png" | |
}, | |
"sad": { | |
type: "image/jpeg", | |
href: "./icons/sad.jpg" | |
} | |
}, | |
// I determine whether or not a random token is auto-appended to the HREF | |
// values whenever an icon is injected into the document. | |
cacheBusting: true | |
} | |
} | |
] | |
}) | |
export class AppModule { | |
// ... | |
} |
This file contains 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 { Inject } from "@angular/core"; | |
import { InjectionToken } from "@angular/core"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
export interface FaviconsConfig { | |
icons: IconsConfig; | |
cacheBusting?: boolean; | |
} | |
export interface IconsConfig { | |
[ name: string ]: IconConfig; | |
} | |
export interface IconConfig { | |
type: string; | |
href: string; | |
isDefault?: boolean; | |
} | |
export var BROWSER_FAVICONS_CONFIG = new InjectionToken<FaviconsConfig>( "Favicons Configuration" ); | |
// This abstract class acts as both the interface for implementation (for any developer | |
// that wants to create an alternate implementation) and as the dependency-injection | |
// token that the rest of the application can use. | |
export abstract class Favicons { | |
abstract activate( name: string ) : void; | |
abstract reset() : void; | |
} | |
// I provide the browser-oriented implementation of the Favicons class. | |
export class BrowserFavicons implements Favicons { | |
private elementId: string; | |
private icons: IconsConfig; | |
private useCacheBusting: boolean; | |
// I initialize the Favicons service. | |
constructor( @Inject( BROWSER_FAVICONS_CONFIG ) config: FaviconsConfig ) { | |
this.elementId = "favicons-service-injected-node"; | |
this.icons = Object.assign( Object.create( null ), config.icons ); | |
this.useCacheBusting = ( config.cacheBusting || false ); | |
// Since the document may have a static favicon definition, we want to strip out | |
// any exisitng elements that are attempting to define a favicon. This way, there | |
// is only one favicon element on the page at a time. | |
this.removeExternalLinkElements(); | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I activate the favicon with the given name / identifier. | |
public activate( name: string ) : void { | |
if ( ! this.icons[ name ] ) { | |
throw( new Error( `Favicon for [ ${ name } ] not found.` ) ); | |
} | |
this.setNode( this.icons[ name ].type, this.icons[ name ].href ); | |
} | |
// I activate the default favicon (with isDefault set to True). | |
public reset() : void { | |
for ( var name of Object.keys( this.icons ) ) { | |
var icon = this.icons[ name ]; | |
if ( icon.isDefault ) { | |
this.setNode( icon.type, icon.href ); | |
return; | |
} | |
} | |
// If we made it this far, none of the favicons were flagged as default. In that | |
// case, let's just remove the favicon node altogether. | |
this.removeNode(); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I inject the favicon element into the document header. | |
private addNode( type: string, href: string ) : void { | |
var linkElement = document.createElement( "link" ); | |
linkElement.setAttribute( "id", this.elementId ); | |
linkElement.setAttribute( "rel", "icon" ); | |
linkElement.setAttribute( "type", type ); | |
linkElement.setAttribute( "href", href ); | |
document.head.appendChild( linkElement ); | |
} | |
// I return an augmented HREF value with a cache-busting query-string parameter. | |
private cacheBustHref( href: string ) : string { | |
var augmentedHref = ( href.indexOf( "?" ) === -1 ) | |
? `${ href }?faviconCacheBust=${ Date.now() }` | |
: `${ href }&faviconCacheBust=${ Date.now() }` | |
; | |
return( augmentedHref ); | |
} | |
// I remove any favicon nodes that are not controlled by this service. | |
private removeExternalLinkElements() : void { | |
var linkElements = document.querySelectorAll( "link[ rel ~= 'icon' i]" ); | |
for ( var linkElement of Array.from( linkElements ) ) { | |
linkElement.parentNode.removeChild( linkElement ); | |
} | |
} | |
// I remove the favicon node from the document header. | |
private removeNode() : void { | |
var linkElement = document.head.querySelector( "#" + this.elementId ); | |
if ( linkElement ) { | |
document.head.removeChild( linkElement ); | |
} | |
} | |
// I remove the existing favicon node and inject a new favicon node with the given | |
// element settings. | |
private setNode( type: string, href: string ) : void { | |
var augmentedHref = this.useCacheBusting | |
? this.cacheBustHref( href ) | |
: href | |
; | |
this.removeNode(); | |
this.addNode( type, augmentedHref ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment