├── .gitignore ├── README.md ├── bower.json ├── demo ├── angular-list.html └── js │ └── angular-list.js ├── dist └── angular-elements.js ├── gulpfile.js ├── package.json └── src └── angular-elements.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | bower_components -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Reactive Elements 3 | 4 | 5 | AngularJS directives as native HTML elements (web components) 6 | ============================================================= 7 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/MVC-Elements/AngularElements?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | Tiny Google [Polymer](http://polymer-project.org) or Mozilla [X-Tags](http://www.x-tags.org/) add-on which allows to use [AngularJS](https://github.com/angular/angular.js) components as [custom HTML elements](http://w3c.github.io/webcomponents/spec/custom/). Also works with a native Custom Elements implementation if present. 10 | 11 | [Demo](http://pixelscommander.com/polygon/angular-elements/demo/) 12 | 13 | Example 14 | ------- 15 | 16 | **Using component in HTML** 17 | 18 | ```html 19 | 20 | 21 | 22 | ``` 23 | 24 | **Angular directive definition** 25 | ```js 26 | angular.module('demo', []).directive('angularList', function () { 27 | return { 28 | restrict: 'E', 29 | scope: {}, 30 | template: '', 31 | link: function (scope) { 32 | scope.testMethod = function () { 33 | alert('Directive method called as node method'); 34 | } 35 | } 36 | }; 37 | }); 38 | 39 | document.registerAngular('my-angular-component', 'demo'); 40 | ``` 41 | 42 | **Find complete examples in corresponding folder.** 43 | 44 | Nesting 45 | ------- 46 | 47 | Original content of a custom element is injected to component as: 48 | 49 | ```html 50 | Hello world 51 | ``` 52 | 53 | In this case we can use "Hello world" as transclude to "my-angular-component" directive. 54 | 55 | 56 | Dependencies 57 | ------------ 58 | 59 | - [AngularJS](https://github.com/angular/angular.js) 60 | - [X-Tag core](https://github.com/x-tag/core) or [Polymer custom elements](https://github.com/Polymer/CustomElements) or native browser support for custom elements. 61 | 62 | License 63 | ------- 64 | 65 | MIT: http://mit-license.org/ 66 | 67 | Copyright 2015 Stepan Suvorov aka [stevermeister](http://github.com/stevermeister), Denis Radin aka [PixelsCommander](http://pixelscommander.com) 68 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-elements", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/MVC-Elements/angular-elements", 5 | "authors": [ 6 | "PixelsCommander " 7 | ], 8 | "description": "Allows to use AngularJS component as HTML element", 9 | "keywords": [ 10 | "angularjs", 11 | "angular", 12 | "web-components", 13 | "custom", 14 | "elements" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ], 24 | "devDependencies": { 25 | "angular": "~1.2.23" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/angular-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular list as a web-component 5 | 6 | 7 | 8 | 9 | 10 | 27 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /demo/js/angular-list.js: -------------------------------------------------------------------------------- 1 | angular.module('demo', []).directive('angularList', function () { 2 | return { 3 | restrict: 'E', 4 | scope: {}, 5 | template: '', 6 | link: function (scope) { 7 | scope.testMethod = function () { 8 | alert('Directive method called as node method'); 9 | } 10 | } 11 | }; 12 | }); 13 | 14 | document.registerAngular('angular-list', 'demo'); -------------------------------------------------------------------------------- /dist/angular-elements.js: -------------------------------------------------------------------------------- 1 | !function(w){var PROPERTY_DELIMITER_CHARACTERS=[":","-","_"],registrationFunction=(document.registerElement||document.register).bind(document);if(void 0!==registrationFunction){var registerAngular=function(e,t){var r=Object.create(HTMLElement.prototype);r.createdCallback=function(){this._content=getContentNodes(this),this.angularModuleName=t,angular.bootstrap(this.parentNode,[this.angularModuleName]),this.scope=angular.element(this).isolateScope()||angular.element(this.parentNode).scope(),extend(this,this.scope);var e=getAllProperties(this,this.attributes);extend(this.scope,e),this.scope.$digest()},r.attributeChangedCallback=function(){var e=getAllProperties(this,this.attributes);extend(this.scope,e,!0),this.scope.$digest()},registrationFunction(e,{prototype:r})};document.registerAngular=registerAngular,void 0!==w.xtag&&(w.xtag.registerAngular=registerAngular);var extend=function(e,t,r){for(var n in t)(void 0===e[n]||r===!0)&&(e[n]="function"==typeof t[n]?t[n].bind(t):t[n])},getContentNodes=function(e){for(var t=document.createElement("content");e.childNodes.length;)t.appendChild(e.childNodes[0]);return t},getAllProperties=function(e,t){for(var r={},n=0;n0&&(value=eval(matches[0].replace("{","").replace("}",""))),value},getterSetter=function(e,t,r,n){Object.defineProperty?Object.defineProperty(e,t,{get:r,set:n}):document.__defineGetter__&&(e.__defineGetter__(t,r),e.__defineSetter__(t,n)),e["get"+t]=r,e["set"+t]=n}}}(window),Function.prototype.bind||(Function.prototype.bind=function(e){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var t=Array.prototype.slice.call(arguments,1),r=this,n=function(){},i=function(){return r.apply(this instanceof n&&e?this:e,t.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,i.prototype=new n,i}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | 4 | gulp.task('default', function() { 5 | gulp.src('src/*.js') 6 | .pipe(uglify()) 7 | .pipe(gulp.dest('dist')); 8 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-elements", 3 | "version": "0.0.0", 4 | "description": "Allows to use AngularJS component as HTML element", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "./node_modules/.bin/bower install", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/MVC-Elements/angular-elements.git" 13 | }, 14 | "keywords": [ 15 | "mvc", 16 | "angularjs", 17 | "web-components" 18 | ], 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/MVC-Elements/angular-elements/issues" 23 | }, 24 | "homepage": "https://github.com/MVC-Elements/angular-elements", 25 | "devDependencies": { 26 | "gulp": "~3.8.5", 27 | "gulp-uglify": "~0.3.1", 28 | "bower": "~1.3.11" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/angular-elements.js: -------------------------------------------------------------------------------- 1 | (function (w) { 2 | 3 | var PROPERTY_DELIMITER_CHARACTERS = [':', '-', '_']; 4 | 5 | var registrationFunction = (document.registerElement || document.register).bind(document); 6 | 7 | if (registrationFunction === undefined) { 8 | return; 9 | } 10 | 11 | var registerAngular = function (elementName, moduleName) { 12 | var elementPrototype = Object.create(HTMLElement.prototype); 13 | elementPrototype.createdCallback = function () { 14 | this._content = getContentNodes(this); 15 | this.angularModuleName = moduleName; 16 | 17 | angular.bootstrap(this, [this.angularModuleName]); 18 | 19 | this.scope = angular.element(this).isolateScope() || angular.element(this.parentNode).scope(); 20 | extend(this, this.scope); 21 | 22 | var attributesObjects = getAllProperties(this, this.attributes); 23 | extend(this.scope, attributesObjects); 24 | 25 | this.scope.$digest(); 26 | }; 27 | 28 | elementPrototype.attributeChangedCallback = function () { 29 | var attributesObjects = getAllProperties(this, this.attributes); 30 | extend(this.scope, attributesObjects, true); 31 | 32 | this.scope.$digest(); 33 | } 34 | 35 | registrationFunction(elementName, { 36 | prototype: elementPrototype 37 | }); 38 | }; 39 | 40 | document.registerAngular = registerAngular; 41 | 42 | if (w.xtag !== undefined) { 43 | w.xtag.registerAngular = registerAngular; 44 | } 45 | 46 | var extend = function (extandable, extending, replace) { 47 | for (var i in extending) { 48 | if (extandable[i] === undefined || replace === true) { 49 | 50 | if (typeof extending[i] === 'function') { 51 | extandable[i] = extending[i].bind(extending); 52 | } else { 53 | extandable[i] = extending[i]; 54 | } 55 | } 56 | } 57 | }; 58 | 59 | var getContentNodes = function (el) { 60 | var fragment = document.createElement('content'); 61 | while (el.childNodes.length) { 62 | fragment.appendChild(el.childNodes[0]); 63 | } 64 | return fragment; 65 | }; 66 | 67 | var getAllProperties = function (el, attributes) { 68 | var result = {}; 69 | 70 | for (var i = 0; i < attributes.length; i++) { 71 | var attribute = attributes[i]; 72 | var propertyName = attributeNameToPropertyName(attribute.name); 73 | result[propertyName] = parseAttributeValue(attributes[i].value); 74 | } 75 | 76 | result._content = el._content; 77 | return result; 78 | }; 79 | 80 | var attributeNameToPropertyName = function (attributeName) { 81 | var result = attributeName.replace('x-', '').replace('data-', ''); 82 | var delimiterIndex = -1; 83 | 84 | while ((delimiterIndex = getNextDelimiterIndex(result)) !== -1) { 85 | result = result.slice(0, delimiterIndex) + result.charAt(delimiterIndex + 1).toUpperCase() + result.slice(delimiterIndex + 2, result.length); 86 | } 87 | 88 | return result; 89 | }; 90 | 91 | var getNextDelimiterIndex = function (string) { 92 | var result = -1; 93 | 94 | for (var i = 0; i < PROPERTY_DELIMITER_CHARACTERS.length; i++) { 95 | var char = PROPERTY_DELIMITER_CHARACTERS[i]; 96 | result = string.indexOf(char); 97 | if (result !== -1) { 98 | break; 99 | } 100 | } 101 | 102 | return result; 103 | } 104 | 105 | var parseAttributeValue = function (value) { 106 | var regexp = /\{.*?\}/g; 107 | var matches = value.match(regexp); 108 | 109 | if (matches !== null && matches !== undefined && matches.length > 0) { 110 | value = eval(matches[0].replace('{', '').replace('}', '')); 111 | } 112 | 113 | return value; 114 | }; 115 | 116 | var getterSetter = function (variableParent, variableName, getterFunction, setterFunction) { 117 | if (Object.defineProperty) { 118 | Object.defineProperty(variableParent, variableName, { 119 | get: getterFunction, 120 | set: setterFunction 121 | }); 122 | } 123 | else if (document.__defineGetter__) { 124 | variableParent.__defineGetter__(variableName, getterFunction); 125 | variableParent.__defineSetter__(variableName, setterFunction); 126 | } 127 | 128 | variableParent["get" + variableName] = getterFunction; 129 | variableParent["set" + variableName] = setterFunction; 130 | }; 131 | })(window); 132 | 133 | //Mozilla bind polyfill 134 | if (!Function.prototype.bind) { 135 | Function.prototype.bind = function (oThis) { 136 | if (typeof this !== "function") { 137 | // closest thing possible to the ECMAScript 5 internal IsCallable function 138 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 139 | } 140 | 141 | var aArgs = Array.prototype.slice.call(arguments, 1), 142 | fToBind = this, 143 | fNOP = function () { 144 | }, 145 | fBound = function () { 146 | return fToBind.apply(this instanceof fNOP && oThis 147 | ? this 148 | : oThis, 149 | aArgs.concat(Array.prototype.slice.call(arguments))); 150 | }; 151 | 152 | fNOP.prototype = this.prototype; 153 | fBound.prototype = new fNOP(); 154 | 155 | return fBound; 156 | }; 157 | } 158 | --------------------------------------------------------------------------------