Skip to content

Instantly share code, notes, and snippets.

@AviFix
Forked from bennadel/app.component.ts
Created May 24, 2020 16:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AviFix/61685a439018ea4ca03b1f71bcc6c246 to your computer and use it in GitHub Desktop.
Save AviFix/61685a439018ea4ca03b1f71bcc6c246 to your computer and use it in GitHub Desktop.
Creating A Dynamic Favicon Service In Angular 5.2.4
// 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 );
}
}
// 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 {
// ...
}
// 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