├── .gitignore ├── .gitmodules ├── Gruntfile.js ├── README.md ├── bower.json ├── dist ├── angular-custom-element-nopolyfill.min.js └── angular-custom-element.min.js ├── examples ├── example-config.js ├── example-directive.js ├── example-markup.html ├── navbar.html └── navbar │ ├── lib │ ├── angular-sanitize.min.js │ ├── bootstrap.min.css │ ├── jquery-2.1.0.min.js │ └── ui-bootstrap-collapse.js │ └── src │ ├── Dropdown.js │ ├── MenuItem.js │ ├── Navbar.css │ ├── Navbar.js │ └── aceComponents.js ├── lib ├── angular.min.js ├── angular.min.js.map └── document-register-element.js ├── package.json ├── src └── angular-custom-element.js └── test ├── angular-custom-elementSpec.js └── karma.conf.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/CustomElements"] 2 | path = lib/CustomElements 3 | url = https://github.com/WebReflection/document-register-element.git 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.loadNpmTasks('grunt-karma'); 4 | //grunt.loadNpmTasks('grunt-ngdocs'); 5 | grunt.loadNpmTasks('grunt-conventional-changelog'); 6 | grunt.loadNpmTasks('grunt-contrib-uglify'); 7 | grunt.loadNpmTasks('grunt-contrib-concat'); 8 | grunt.loadNpmTasks('grunt-contrib-copy'); 9 | //sgrunt.loadNpmTasks('grunt-contrib-watch'); 10 | 11 | grunt.initConfig({ 12 | // external library versions 13 | ngversion: '1.2.25', 14 | libs: [], 15 | dist: 'dist', 16 | filename: 'angular-custom-element', 17 | pkg: grunt.file.readJSON('package.json'), 18 | srcDir: 'src/', 19 | buildDir: '<%= dist %>', 20 | meta: { 21 | modules: 'angular.module("uiComponents", [<%= srcModules %>]);', 22 | all: '<%= meta.srcModules %>', 23 | banner: ['/*', 24 | ' * <%= pkg.name %>', 25 | ' * <%= pkg.repository.url %>\n', 26 | ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>', 27 | ' * License: <%= pkg.license %>', 28 | ' */\n'].join('\n') 29 | }, 30 | karma: { 31 | options: { 32 | configFile: 'test/karma.conf.js', 33 | autoWatch: false, 34 | browsers: ['PhantomJS'] 35 | }, 36 | unit: { 37 | singleRun: true, 38 | reporters: 'dots' 39 | }, 40 | chrome: { 41 | autoWatch: true, 42 | browsers: ['Chrome'] 43 | } 44 | }, 45 | uglify: { 46 | options: { 47 | banner: '<%= meta.banner %>' 48 | //sourceMap: true, 49 | //mangle: false 50 | }, 51 | dist: { 52 | src: ['src/angular-custom-element.js'], 53 | dest: '<%= dist %>/<%= filename %>.min.js' 54 | } 55 | }, 56 | copy: { 57 | dist: { 58 | src: ['<%= dist %>/<%= filename %>.min.js'], 59 | dest: '<%= dist %>/<%= filename %>-nopolyfill.min.js' 60 | } 61 | }, 62 | concat: { 63 | distMin: { 64 | options: {}, 65 | //src filled in by build task 66 | src: ['lib/document-register-element.js', '<%= uglify.dist.dest %>'], 67 | dest: '<%= dist %>/<%= filename %>.min.js' 68 | } 69 | } 70 | }); 71 | 72 | grunt.registerTask('default', ['uglify:dist', 'copy:dist', 'concat:distMin']); 73 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Custom Element 2 | 3 | **Upgrade your AngularJS 1.x.x component directives to Web Components (W3C) Custom Elements!** 4 | 5 | Why wait for AngularJS 2.0 to start writing Angular code for the **W3C Web Components** specifications? With just a tiny, 2kb, Custom Element polyfill plus this provider you can define, export, import, and use **Custom Elements** within your AngularJS 1.x.x app or component now. Your AngularJS element directives can now be real, bonafide Custom Element directives. The element properties are seemlesly bound to your directive's $scope, so changes from outside Angular will be immediately reflected in your Angular bindings. 6 | 7 | 8 | ## Table of Contents 9 | 10 | - [Getting and Installing](#getting-and-installing) 11 | - [Prerequisites](#prerequisites) 12 | - [API Documentation](#api-documentation) 13 | - [Injecting AngularCustomElement into your app](#injecting-angularcustomelement-into-your-app) 14 | - [Defining Custom Elements](#defining-custom-elements) 15 | - [Options for the Custom Element config object](#options-for-the-custom-element-config-object) 16 | - [Enabling Custom Element binding in the directive](#enabling-custom-element-binding-in-the-directive) 17 | - [Template bindings](#template-bindings) 18 | - [Directive Definition Guidelines](#directive-definition-guidelines) 19 | - [Contributing](#contributing) 20 | - [FAQs and Opinions](#faqs-and-opinions) 21 | - [What is AngularCustomElement?](#what-is-angularcustomelement) 22 | - [Where can it be used?](#where-can-it-be-used) 23 | - [When can I use it?](#when-can-i-use-it) 24 | - [Why would I want to use it?](#why-would-i-want-to-use-it) 25 | - [How does it work?](#how-does-it-work) 26 | - [Who is responsible for this?](#who-is-responsible-for-this) 27 | - [How is this different from Polymer or X-Tags?](#how-is-this-different-from-polymer-or-x-tags-1) 28 | - [What about :unresolved?](#what-about-unresolved) 29 | - [Do I have to use the included polyfill?](#do-i-have-to-use-the-included-polyfill) 30 | - [Release Notes](#release-notes) 31 | - [License](#license) 32 | 33 | 34 | ## Getting and Installing 35 | 36 | You just need to load `angular-custom-element.min.js` after Angular.js and before your app or component modules. The file is available via: 37 | 38 | * Bower: `$> bower install angular-custom-element` 39 | * fork or clone this repo 40 | * just copy [angular-custom-element.min.js from here](https://raw.githubusercontent.com/dgs700/angular-custom-element/master/dist/angular-custom-element.min.js) 41 | * NPM: coming soon 42 | 43 | ## Prerequisites 44 | 45 | To get the most value out of AngularCustomElement its helpful to have good knowledge of the Custom Element specification and the DOM in general. You should also have at least an intermediate level of experience with AngularJS directives and familiarity with the architectures and patterns used in UI component development. The defacto Custom Elements tutorial can be found here: 46 | - Custom Elements 47 | defining new elements in HTML 48 | 49 | Comprehensive information on creating UI component directives can be found here: 50 | - [Web Component Architecture & Development with AngularJS](https://leanpub.com/web-component-development-with-angularjs/read) 51 | 52 | ## API Documentation 53 | 54 | * Also see the code in the usage examples directory for inline docs. It's written so the documentation is self-explanitory and you can cut and paste the code into your app to get started. 55 | 56 | 57 | #### Injecting AngularCustomElement into your app 58 | 59 | 1) Include your element directive and Custom Element provider as dependencies. 60 | ````javascript 61 | var app = angular.module('MyApp',['myComponents.elementDirectives', 'customElements']); 62 | ```` 63 | 64 | #### Defining Custom Elements 65 | 66 | 2) Inject $customElementsProvider into a config block. 67 | ````javascript 68 | app.config(['$customElementsProvider', function ($customElementsProvider) { 69 | ```` 70 | 71 | **The `.register()` method:** 72 | 73 | 3) Call the .register() function with a tag name (including namespace plus dash plus name) 74 | and the custom element config object (very similar to X-Tag config). You will also need 75 | to define matching element directives, i.e. "tagName1"... 76 | ````javascript 77 | $customElementsProvider.register('tag-name1', { elemConfigObj1 }) 78 | .register('tag-name2', { elemConfigObj2 }) 79 | .register('tag-name3', { elemConfigObj3 }); 80 | ```` 81 | 82 | **The `.registerCollection()` method:** 83 | 84 | If you have several custom elements to register, and/or you prefer to maintain the element definition code 85 | in a location seperate from your App.config() block, then you can use `.registerCollection([definitions])` to 86 | register a group of custom elements in the config block. 87 | 88 | `.registerCollection()` takes as its argument an array of custom element definition objects each with a 89 | name string and a definition config object in the same format as would be passed the `.register()` function. They 90 | should be keyed as `name:` and `definition:`. 91 | ````javascript 92 | var elementDefinitions = [ 93 | { 94 | name: "el-one", 95 | definition: {...} 96 | }, 97 | { 98 | name: "el-two", 99 | definition: {...} 100 | }, 101 | { 102 | name: "el-three", 103 | definition: {...} 104 | } 105 | ]; 106 | 107 | $customElementsProvider.registerCollection( elementDefinitions ); 108 | ```` 109 | 110 | #### Options for the Custom Element config object 111 | 112 | The format and options are similar to X-Tags, but there are some differences. Also keep in mind 113 | that the context of any code placed in the element config object executes *outside of* AngularJS, so 114 | it should be VanillaJS and framework agnostic. 115 | 116 | **parent: element prototype** (optional) is the element prototype we wish to inherit from. 117 | It defaults to HTMLElement. You may inherit from standard HTML elements or other custom elements. 118 | ````javascript 119 | parent: HTMLButtonElement, 120 | ```` 121 | All Custom Elements created with this module can be retrieved from a global registry map. To create 122 | a Custom Element that inherits from another just reference the parent like this: 123 | ````javascript 124 | register('smarter-button', { 125 | parent: window.registeredElements['smart-button'] 126 | ... 127 | ```` 128 | 129 | **extends: tag name** (optional) will include all of the parent's properties, but 130 | the `` tag syntax must appear in the DOM. 131 | In this situation a matching element directive will not work. The yet-to-be-coded 132 | work around will likely be a matching tag name directive that wraps the real element, 133 | Or you can just use the real tag in the template along with the directive replace:true option for now. 134 | Either way, we want the HTML monkeys to be able to use `` syntax for 135 | declarativeness and simplicity. 136 | ````javascript 137 | extends: 'button', 138 | ```` 139 | 140 | The **properties** object contains definitions for the element's instance (or constructor) 141 | properties. 142 | ````javascript 143 | properties: { 144 | ```` 145 | 146 | The object key (propertyNameOne) becomes a property name on the element 147 | ````javascript 148 | propertyNameOne: { 149 | ```` 150 | 151 | Include a **get** function (optional) if any calculations or adjustments to the value are needed 152 | The syntax is the same as an ES5 Object property getter. 153 | ````javascript 154 | get: function(){ 155 | // do any value calculations 156 | return valueVar; 157 | }, 158 | ```` 159 | Include a **set** function (optional) if any value adjustments are needed, or other actions 160 | that need to happen upon a change. Unlike the ES5 syntax, this function *must return the property value*. 161 | ````javascript 162 | set: function(val){ 163 | // do any value calculations 164 | val = val + 'X'; 165 | return val; 166 | }, 167 | ```` 168 | 169 | The `value: intialValue` (optional) option may be used to set the default or initial 170 | value of a property. 171 | 172 | NOTE: a) This may be used along with a get and/or set function option which differs from 173 | actual ES5 property definitions where that is not allowed 174 | 175 | NOTE: b) Any initial value provided by a matching attribute will take priority. 176 | ````javascript 177 | value: 'I am a new property', 178 | ```` 179 | 180 | Include an **attribute object** (optional) with an attribute **name** (optional) to bind the property 181 | value to an attribute value. Using `attribute: {}` will default to the property name lowercased. 182 | If the initial markup includes an attribute value, it will be auto assigned to the 183 | property value, so you can initialize custom element props with attribute string values. 184 | 185 | Include **boolean: true** (defaults to false) in order to have the attribute behave as a 186 | boolean such as "checked" or "selected". **Note** that the behavior is not the same as the actual *value*. 187 | To set a boolean property/attribute's initial value to true, you must also include 188 | `value: true` as well. 189 | 190 | Note: DO NOT serialize large data objects into attribute strings in order to set the 191 | initial value, use some kind of pointer to another property or data store instead. 192 | ````javascript 193 | attribute: { 194 | name: 'property-one', 195 | boolean: true // default is false 196 | } 197 | }, 198 | ```` 199 | 200 | **readOnly** set to **true** creates a read only property (defaults to false). You must also 201 | include an initial **value** option and no matching attribute (it is ignored). 202 | ````javascript 203 | readOnly: true 204 | } 205 | }, 206 | ```` 207 | 208 | The **callbacks** object follows the W3C Custom Elements spec except that the names are 209 | shortened similar to X-Tags and Polymer configs. This object and any callback functions are 210 | optional. The callbacks execute in the context of the element instance so `this` may be used to 211 | reference the element instance and its properties. 212 | 213 | **Best Practice:** To ensure that your components are portable, shareable, and reusable, the component 214 | (element) should have NO knowledge of anything outside its boundaries. This means you should not have any 215 | direct references to any application or global vars in your callbacks. Any initialization values 216 | should be *injected* into the element. Use attributes for primatives and pointers to anything else, or have a 217 | "container" component or application inject any necessary instantiation data or logic. 218 | ````javascript 219 | callbacks: { 220 | ```` 221 | 222 | The **created** callback is called upon custom element instantiation which is effectively the element 223 | constructor function (for the custom parts). This happens when the browser finds a tag on load parse, 224 | or elemement is created programatically including from a template. Any special initialization tasks that 225 | you would typically have in a "constructor function" would go here. 226 | ````javascript 227 | created: function(){ 228 | // include any special logic 229 | // console.log('created') 230 | }, 231 | ```` 232 | 233 | The **attached** callback is called when the element is inserted into the DOM. Any tasks that 234 | require the DOM to be in place would go here. 235 | ````javascript 236 | attached: function(){ 237 | //console.log('I am now in the DOM') 238 | }, 239 | ```` 240 | 241 | The **detached** callback is called when the element is removed from the DOM. Any cleanup such as 242 | destruction of event bindings would go here. 243 | ````javascript 244 | detached: function (){ 245 | // include any cleanup logic 246 | //console.log('detached') 247 | }, 248 | ```` 249 | 250 | The **attributeChanged** callback is called upon any attribute change including any set programatically 251 | during element instantiation (but not if the elem already exists in markup). 252 | ````javascript 253 | attributeChanged: function(attrName, oldVal, newVal){ 254 | //console.log('attributeChanged', attrName, oldVal, newVal) 255 | } 256 | }, 257 | ```` 258 | 259 | The **members** object contains any Custom Element Prototype Methods and Properties. These would be 260 | akin to class members in which every element instance has access to the same function or value. 261 | ````javascript 262 | members: { 263 | ```` 264 | 265 | Typically any component logic would be placed in functions here. There are no ES5 object options. Just include a name and function. 266 | ````javascript 267 | elementMethodName: function(args){ 268 | // logic available to any element instance of this type 269 | // by calling elem.elementProtoMethod(args) 270 | // All of your custom element 271 | }, 272 | ```` 273 | 274 | Prototype properties - in the RARE case where a property needs to be accessable by ALL element instances of 275 | this type, define it here. Any data binding to directive $scope requires an explicit event 276 | listener attached to the document (see below). 277 | One example use case might be a re-themeing of all elements during a page app lifecycle. 278 | The attribute option is not available for these. All other options are the same as in the 279 | properties object. 280 | ````javascript 281 | memberPropName: { 282 | // same as element property 283 | get: function(val){ 284 | return val; 285 | }, 286 | // same as element property 287 | set: function(val){ 288 | val = val + 'X'; 289 | return val; 290 | }, 291 | // same as element property 292 | value: "blah blah", 293 | // same as element property 294 | // most prototype properties if needed would ideally be readOnly 295 | readOnly: true 296 | } 297 | } 298 | ```` 299 | 300 | 301 | #### Enabling Custom Element binding in the directive 302 | 303 | **Inject the Custom Element service into the directive** 304 | ````javascript 305 | angular.module( 'myComponents.tagName1', ['customElements'] ) 306 | .directive( 'tagName1', [ 307 | '$customElements', function($customElements){ ... 308 | ```` 309 | 310 | **Call $watchElement( scope, element, [true (no bindings)] )** in your directive link or controller function: 311 | 312 | This takes care of binding all custom properties 313 | to the directive's $scope including triggering a $digest() when 314 | any custom property is changed from outside of Angular. Two or more 315 | frameworks can share the same custom elements and no boilerplate! 316 | 317 | After this line you can enjoy the full power of AngularJS' framework tools 318 | when interacting with your Custom Element. You can have normal bindings 319 | in your templates and controllers: `$scope.el.propertyName` or `{{el.propertyName}}`. 320 | ````javascript 321 | $customElements.$watchElement( $scope, $element, [noBindings] ); 322 | ```` 323 | Performance edge case: Under the hood, $watchElement binds a callback function to each 324 | of the custom element's property setters that calls scope.$digest() asynchronously after 325 | a change. This is what enables the Custom Element API to be framework independent and 326 | shareable between AngularJS and other frameworks, toolkits, etc. 327 | 328 | However, there is no easy way to tell where the change came from- the element directive or 329 | something outside of Angular's scope including a container element or another framework. This 330 | means that changes coming from *within AngularJS* automatically result in a $digest() call, and then 331 | another $digest() occurs when the setter callback is invoked. If this module is used 332 | in a very large AngularJS app that has more than 1-2 thousand active bindings, and nothing else in the page 333 | outside AngularJS code could possibly cause custom element prop mutations, 334 | then **pass `true` for no bindings** as the 3rd argument. 335 | This will prevent unnecessary double $digest() calls if there is a noticable lag in UI responsiveness. If it's 336 | possible to gleen where a change originated from the `prop:changed` event object (see below) you can 337 | issue an *asynchronous* `$scope.$digest()` call in the callback function to update the bindings. 338 | 339 | **Custom Element instance and prototype property change events:** 340 | 341 | You can bind to a property change event on the element. 342 | Since all prop changes generate a change event 343 | other frameworks in the page can import and interact with 344 | the same component. 345 | ````javascript 346 | $element.on( 'prop:changed', function(evt){ 347 | $log.log(evt.detail); 348 | $scope.$emit(evt.detail); 349 | // $timeout(function(){$scope.$digest()}, 0); 350 | }); 351 | ```` 352 | 353 | You can bind to a Custom Elemement prototype property change event if needed 354 | for something that affects all elem intances such as a theme change. 355 | 356 | NOTE: that these bindings must be specifically destroyed on 357 | the $destroy event for the directive to avoid memory leaks 358 | ````javascript 359 | $document.on( 'member:changed', function(evt){ 360 | if(evt.detail.propName == 'a prototype prop we need to watch'){ 361 | // do stuff 362 | $log.log(evt.detail); 363 | $scope.$emit(evt.detail); 364 | } 365 | }); 366 | ```` 367 | 368 | **Utility functions** 369 | 370 | The **.info( $element )** function returns the original custom element config 371 | object for debugging purposes. 372 | ````javascript 373 | var info = $customElements.info($element); 374 | ```` 375 | 376 | **Attempt to bind to a foreign Custom Element** i.e. something generated by X-Tags or Polymer. 377 | Binding is currently limited to attribute values unless the element 378 | broadcasts property change events like those above. 379 | 380 | The $scope and $element params are required. If you know that the custom element dispatches 381 | an event and its name (`evtName` below) upon property changes then complete binding can be achieved. 382 | Otherwise attribute names are the fallback. *This function is even more experimental than the rest 383 | of this module ;-) 384 | ````javascript 385 | $customElements.$importElement($scope, $element, [array of attr names], evtName); 386 | ```` 387 | Note that if you control the element configuration source code for X-Tags, or even 388 | Polymer elements, then complete integration with matching AngularJS directives is possible with 389 | about 10 extra lines of code. (examples coming soon). 390 | 391 | #### `.unresolved` class for FOUC prevention 392 | 393 | If the custom tag markup includes an **unresolved** class, 394 | `` it will automatically be removed when the element is 395 | upgraded. It can be used to match something like an `.unresolved: display none;` CSS rule. 396 | See the FAQ regarding the `:unresolved` pseudo class below. 397 | 398 | #### Directive Definition Guidelines 399 | 400 | The plan for AngularJS 2.0 Component Directives (based on the current design docs) is to 401 | simplify the directive definition object. Component Directives will automatically have: 402 | 403 | * An isolate (component) scope, `scope: {}` 404 | * Matching restricted to tag names, `restrict: 'E'` 405 | * Templates appended to the tag (vs replacing the tag), `replace: false` 406 | 407 | An isolate scope is a must for the proper encapsulation of a component. Otherwise it loses 408 | portability, reusability, etc. Appended templates are necessary since deleting 409 | the custom element tag defeats the entire purpose of using one. It also improves 410 | declarativeness and allows other frameworks in the page to use the custom element. 411 | 412 | Matching only via element name in AngularJS 1.x.x is recommended in most cases. The gray area 413 | would be Custom Elements that extend existing tags and therefore must use the tag name of the 414 | extended element with an `is="custom-tagname"` attribute, i.e. ``. 415 | 416 | There's no best practice for how to handle this in Angular. The syntax proposed by the W3C is much less 417 | declarative for "extended" tags. Hopefully that will change, but for now, one suggested 418 | solution would be to create a skeleton custom element that acts as a wrapper and proxy for 419 | the extended element to the associated directive, and have the extended element as the template 420 | for the wrapper element. 421 | 422 | Your directive definition will need a link and/or controller function in which to invoke the service 423 | command that data-binds element properties: `$watchElement(scope, element)`. For simple, stand-alone 424 | components you should be able to invoke this anywhere. But if you have a "complex" or "container" component 425 | element that has bindings and interactions with child components, the safest place to invoke $watchElement 426 | would be in an actual `postLink: function(scope elem){...}` function block. postLink is invoked after 427 | the full creation and insertion of all children elements. 428 | 429 | To learn more about building component directives with AngularJS plus component best practices, check out my 430 | book: [Web Component Architecture and Development with AngularJS](https://leanpub.com/web-component-development-with-angularjs). It is free to download or read online while it is still being completed. 431 | 432 | 433 | #### Template bindings 434 | 435 | Binding to custom element properties and functions couldn't be more simple. After `$watchElement()` is 436 | invoked which attaches **el** for the element instance reference to the $scope object, any custom 437 | property or method can be bound in the template. Note that html5 *standard properties* cannot be data-bound 438 | in your templates, only the the props you define can. 439 | 440 | ````html 441 | 442 | 443 | {{ el.bttnText }} 444 | 445 | ```` 446 | 447 | 448 | ## Contributing 449 | 450 | If you like the ideas behind this module, PLEASE help by forking, using, identifying bugs and 451 | functionality that is either missing or could be improved, testing, contributing code for tests, 452 | bug fixes, and feature improvements. 453 | 454 | Please use the [issue tracker for this repo](https://github.com/dgs700/angular-custom-element/issues) for bugs and suggestions. 455 | 456 | 457 | ## FAQs and Opinions 458 | 459 | #### What is AngularCustomElement? 460 | 461 | **AngularJS Custom Element** is an **Angular provider** that allows you to define and register W3C spec custom elements in an application config block. It is also an **Angular service** meant to be injected into your matching element directive that auto binds the element's custom properties, and attributes to directive scope. You can access these properties via `$scope.el.propertyName`, or just `el.propertyName` in your template bindings. 462 | 463 | There is a lot of code boilerplate involved in Custom Element definitions, and even more when it comes time to integrate the element with AngularJS's data-binding. One of the goals of this provider is to reduce that down to just a little bit of configuration and convention, and keep everything as simple, minimalist, performant, and compatible as possible- just like the rest of AngularJS. 464 | 465 | This module is focused exclusively on Custom Elements because their APIs are the integration point for AngularJS and any other app framework. Other Web Components APIs, including Shadow DOM, HTML Imports, and Template tags are beyond this scope because their usage is essentially independent of any framework internals and/or the polyfills aren't suitable for current use in widescale production code for one reason or another. For those wishing to use Shadow DOM and template tags within your AngularJS HTML templates and JavaScript, there is nothing in this module that would prevent you from doing so. 466 | 467 | Instead of 2-way data-binding, you can now have **3-way data-binding**. 468 | 469 | #### Where can it be used? 470 | 471 | **All modern browsers** including IE 9+, and any existing or yet to be coded Angular element directives. 472 | 473 | #### Why would I want to use it? 474 | 475 | Because Custom Element APIs, which are essentially HTML element attributes, properties, methods and events, are becoming the common interface through which web components, applications, toolkits and even different frameworks interact. Reusable UI components will no longer be restricted to the scope of a particular framework, and components will inherit logic and data directly from other components or elements. By moving component specific data and logic out of the controller and onto the element, code shelf-life will become much longer. 476 | 477 | Unlike the other Web Component polyfills such as Shadow DOM, the Custom Element registration polyfill is very small, simple and reasonably performant/stable meaning the risk of use in large scale, production web applications now is very low. One of the goals of this small add-on is to build upon the polyfill in a manner that can be used to enhance any Angular element directive. Additional goals and opinions of ths module are: 478 | 479 | * provide a **simple element config API**, very similar to X-Tags 480 | * provide an even **simpler service API** (just one line of code in your directive link or controller fn) 481 | * work across **all modern browsers** (IE9+) 482 | * be suitable for production grade, consumer facing code (unlike Polymer) 483 | * be **performant and small** (9kb including polyfill dependency) 484 | * **export Custom Elements** that can be shared, consumed, and bound by other data-binding frameworks 485 | * provide **support for importing** and binding to Custom Elements from other sources 486 | * do one thing, and do it well 487 | * help component developers write **longer lasting code** 488 | * help developers to start getting a feel for the **web development APIs of the near future** 489 | * attempt to reflect, where possible, the decisions about how component directives will function in the AngularJS 2.0 design docs 490 | * be a **community** driven project 491 | 492 | #### When can I use it? 493 | 494 | ~~From now until AngularJS 2.0 is in widescale production. AngularJS 2.0 Component Directives will replace this functionality. It seems this lib was already deprecated before it was released~~ 495 | As long as AngularJS 1.x.x is around which may be quite a while given that AngularJS 2.x is effectively a different framework with no simple upgrade path from 1.x.x 496 | 497 | #### How does it work? 498 | 499 | As long as a browser has DOM mutation observer capability, the Custom Element API can be easily shimmed. The one exception is the new css pseudo **:unresolved**, but FOUC can be easily prevented in other ways. Chrome already supports the API natively, and Mozilla will shortly. 500 | 501 | The other task is triggering a $digest cycle for element properties that are mutated from outside Angular. Neither Object.observe or DOM Mutation Observers work with element properties due to certain, potential performance reasons. However, because we can define element properties using ES5 Object property setters and getters, we can invoke callback functions that include an injected $scope.$digest() and trigger custom change events whenever the property setter is called during a mutation. Any data-binding framework, not just Angular, can use these hooks and events to bind to the Custom Element. 502 | 503 | #### Who is responsible for this? 504 | 505 | Myself and anyone who wants to help with testing across browsers and suggestion and/or code to help improve. There are so many DOM peculiarities and weird use-case situations that it is impossible for one person to conceive of comprehensive test coverage or anticipate every edge-case bug. 506 | 507 | #### How is this different from Polymer or X-Tags? 508 | 509 | Polymer and X-Tags are both great projects and have been invaluable for introducing the web development community to the upcoming [Web Components](http://www.w3.org/wiki/WebComponents/) standards. At the core, the Custom Elements API is exactly the same. But unlike the Polymer Framework, this module only provides Custom Element integration because Custom Elements are the only standard that can be safely polyfilled across current browsers (including IE 9+) in a manner acceptable for production level code in terms of performance and risk. Polymer also uses ~~Shadow DOM~~, `