Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
AngularJS v1 Meta Service
// ***********************************************************************************
// meta-service.js
'use strict';
/**
* Meta Service
*
* The Meta Service allows you to abstract a lot of business logic
* that goes into setting meta & link tags for your templates.
* Easily set page title, description, canonical, og, twitter, etc. tags.
*
* NOTE: Pre-populated definitions exist for the developers convenience,
* i.e. rss, stylesheet, favicon, etc. This allows the developer to
* only provide the dynamic aspects of the tag, like `href`, without
* needing to manually set a `type` for example.
*
* Instead of:
* MetaService.set([{rel: 'alternate', type: "application/rss+xml", title: 'Site RSS', href: 'http://feeds.feedburner.com/sitename/blog'}]);
*
* you can just do:
* MetaService.set([{
* definition: 'rss', href: 'http://feeds.feedburner.com/sitename/blog'
* }]);
*
* EXAMPLE:
*
* # in your js controller, etc.
* MetaService.set([
* {title: title},
* {name: 'description', content: description.toString()},
* {'http-equiv': 'Content-Type', content: 'text/html; charset=utf-8'},
* {definition: 'rss', href: 'http://feeds.feedburner.com/sitename/blog'},
* {definition: 'favicon', href: '/alternate-favicon.png'},
* {rel: 'stylesheet', href: '/css/hello.css'}
* ]);
*
* @ngInject
*/
function MetaService($rootScope, $window) {
var MetaService = {},
dictionary = {}, // default values for known meta & link tags
page = {}, // local store for current page values (title, etc)
links = [], // local store for current link tags (canonical, etc)
metas = []; // local store for current meta tags (description, etc)
// initialize an empty objects on the root scope
$rootScope.page = {};
$rootScope.metas = [];
$rootScope.links = [];
// put defaults here so users don't need to.
// Values will get extended later
dictionary = {
stylesheet: {
rel: 'stylesheet',
type: 'text/css',
media: 'all'
},
favicon: {
rel: 'shortcut icon',
type: 'image/x-icon'
},
rss: {
rel: 'alternate',
type: 'application/rss+xml',
title: 'RSS'
}
};
/**
* Set's any & all provided meta/link/page values
*
* @param {array} tags array of objects containing meta/link/page tag info
* @return {void}
*/
MetaService.set = function(tags) {
var _this = this;
if ($window.Object.keys(tags).length === 0) {
return;
}
angular.forEach(tags, function(tag, key) {
var definition = {},
pos = null,
nameAttr = null,
contentAttr = null,
linkTag = (tag.rel && tag.href) ? true : false;
// handle any unique and special tags
if ($window.Object.keys(tag).length === 1 && tag.title) {
_this.title = tag.title;
return;
}
if ($window.Object.keys(tag).length === 1 && tag.canonical) {
_this.canonical = tag.canonical;
return;
}
// check if there's a known tag definition you can extend
if (definition = dictionary[tag.definition || tag[nameAttr]]) {
tag = angular.extend(definition, tag);
}
// figure out proper attributes for the tag
// meta tags can have either 'name', 'http-equiv' or
// 'property' as their "name" attribute
metaName = tag['http-equiv'] ? 'http-equiv' : (tag.property ? 'property' : 'name');
nameAttr = tag.rel ? 'rel' : metaName;
contentAttr = tag.href ? 'href' : 'content';
linkTag = (tag.rel && tag.href) ? true : false;
// add tag to page
if (linkTag) {
// do nothing if same tag exists already
pos = links.map(function(obj) {
if (obj[nameAttr] === tag[nameAttr]) {
return obj[contentAttr];
}
}).indexOf(tag[contentAttr]);
if (pos !== -1) { return; } // tag exists already
links.push(tag);
$rootScope.links.push(tag);
} else {
// do nothing if same tag exists already
pos = metas.map(function(obj) {
if (obj[nameAttr] === tag[nameAttr]) {
return obj[contentAttr];
}
}).indexOf(tag[contentAttr]);
if (pos !== -1) { return; } // tag exists already
metas.push(tag);
$rootScope.metas.push(tag);
}
});
};
/**
* Getter and Setter for page title
*/
Object.defineProperty(MetaService, 'title', {
get: function() {
return page.title;
},
set: function(title) {
page.title = title;
$rootScope.page.title = title;
}
});
/**
* Getter and Setter for canonical link
*/
Object.defineProperty(MetaService, 'canonical', {
get: function() {
var pos = links.map(function(link) {
return link['rel'];
}).indexOf('canonical');
return links[pos]['href'];
},
set: function(canonical) {
var tag = {
'rel': 'canonical',
'href': canonical
};
links.push(tag);
$rootScope.links.push(tag);
}
});
return MetaService;
};
angular
.module('app.meta', [])
.service('MetaService', MetaService);
// ***********************************************************************************
// app-controller.js
'use strict';
/**
* Application Controller
*
* @ngInject
*/
function AppCtrl($rootScope, $log, $state, MetaService, configuration) {
$rootScope.$on('$stateChangeSuccess', function(event, state, params, from, fromParams) {
// set canonical based on the new state url & param values
MetaService.canonical = configuration.DOMAIN_HOST + $state.href(state, params);
});
}
angular
.module('app')
.controller('AppCtrl', AppCtrl);
// ***********************************************************************************
// Example updates from random controller
// Update meta tags
MetaService.set([
{title: 'Title goes here'},
{name: 'description', content: 'Description goes here'},
{'http-equiv': 'refresh', content: '15'},
{'http-equiv': 'Content-Type', content: 'text/html; charset=utf-8'},
{definition: 'rss', title: 'Site Name - RSS', href: 'http://feeds.feedburner.com/sitename/blog'},
{definition: 'favicon', href: '/alternate-favicon.png'},
{rel: 'stylesheet', href: '/css/hello.css'}
]);
// ***********************************************************************************
// index.html
<!doctype html>
<head>
<title data-ng-bind-template="{{ page.title }} - Site Name">Site Name</title>
<meta ng-attr-name="{{ meta.name }}" ng-attr-property="{{ meta.property }}" ng-attr-http-equiv="{{ meta['http-equiv'] }}" ng-attr-content="{{ meta.content }}" ng-attr-charset="{{ meta.charset }}" ng-attr-data-ng-repeat="meta in metas">
<link ng-attr-rel="{{ link.rel }}" ng-attr-href="{{ link.href }}" ng-attr-type="{{ link.type }}" ng-attr-media="{{ link.media }}" ng-attr-data-ng-repeat="link in links">
</head>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.