Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
AngularJS 1.x service that constructs and registers Custom Elements in the DOM
// This depends on a custom element polyfill for browsers
// other than chrome and opera - 9/1/14
angular.module('CustomElemFactory', [])
.service('customElem', [
// hash of registered element types
// that Angular knows about
// note that this would NOT include CEs registered from
// elsewhere
var customElements = {};
var noop = function(){};
// generic custom element registration function
var registerElem = function(name, protoName, callbacks, members){
// if the element is already registered, exit
// since the DOM will throw errors otherwise
if(customElements[name]) return customElements[name];
// special properties and methods all elems of this type get
members = members || {};
// must be an existing DOM element interface
protoName = protoName || $window.HTMLElement;
// create and populate what will become the custom element
// prototype
var CustomElemProto = Object.create(protoName.prototype, members);
// these are the "current as of 9/14" names of the custom
// element lifecycle callbacks
// createdCallback executes upon instantiation
CustomElemProto.createdCallback = callbacks.createdCallback || noop;
// attachedCallback is queued when the element is added to the DOM
CustomElemProto.attachedCallback = callbacks.attachedCallback || noop;
// detachedCallback is queued when the element is removed from the DOM
CustomElemProto.detachedCallback = callbacks.detachedCallback || noop;
// attributeChangedCallback is queued when something adds/alters an attr
CustomElemProto.attributeChangedCallback = callbacks.attributeChangedCallback || noop;
// call the new DOM API method to make this element
// a first class citizen of the DOM
var CustomElem = $window.document.registerElement( name, {
prototype: CustomElemProto
// add the newly registered element to the tracking hash
customElements[name] = CustomElem;
return CustomElem;
return registerElem;
angular.module('uiComponents.smartButton', ['CustomElemFactory'])
// An example of how an AngularJS 1.x "component" directive
// might be defined including the Angular independent
// custom element config that is exported to the DOM
.directive('smartButton', [
'customElem', function(customElem){
return {
template: tpl,
transclude: true,
// create an isolate (component) scope
scope: {},
// must restrict directive matching to custom element name
restrict: 'E',
// must not overwrite the custom element markup
replace: false,
// housing the custom element config code in a directive
// controller may or may not be the ideal place to store
// the custom element config code depending on situation
controller: function($scope, $element, $attrs, $window){
// custom elements must include hyphens
// in the real world we'd use a namespace of 3 letters
var elemName = 'smart-button';
// this will be a descendent of <button>
var parent = $window.HTMLButtonElement;
// define the element's lifecycle logic
var callbacks = {
createdCallback: function(){
// this is where most of the instance
// config would occur
attachedCallback: function(){
//alert('attached to DOM')
detachedCallback: function(){
//test in dev tools by deleting a node
//alert('detached from DOM')
attributeChangedCallback: function(attr, oldVal, newVal){
//console.warn(attr, oldVal, newVal)
// add any special class props and methods
var members = {
testMember: {
get: function() { return "foo"; },
enumerable: true,
configurable: true
// invoke the Custom Element factory service
var SmartButton = customElem(elemName, parent, callbacks, members);
// rest of directive definition
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment