Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Josh Siok and Megan Siok
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Josh Siok ( @siok ) Megan Siok

Attributes Are Not Normalized In AngularJS 2 Beta 1

By on

If you're coming from the world of AngularJS 1.x, you probably know that the AngularJS 1.x platform normalizes attributes so that you can refer to them in a consistent manner, within your directives, no matter how the calling context provided them. As such, all of the following attributes resolve to the same normalized format:

  • data-color-me
  • data-colorMe
  • data-color_me
  • x-colorMe
  • x-color-me
  • colorMe
  • color-me
  • color:me
  • color:Me
  • color_me

In AngularJS 1.x, internally to your directive, these attributes all normalized to "attributes.colorMe" (though you could always use attribute.$attr to view the original formatting). Last night, I tripped over this divergent behavior in AngularJS 2 Beta 1. In seems that in AngularJS 2 Beta 1, attributes are not normalized - what you see is pretty much what you get.

Run this demo in my JavaScript Demos project on GitHub.

To test this, I've created a simple directive - colorMe - that accepts an input and uses it to set the host element's text color. I tried using a variety of attribute formats, some of which didn't match and some of which actually throw parser errors (I left the latter commented-out in the directive template).

<!doctype html>
<html>
<head>
	<meta charset="utf-8" />

	<title>
		Attributes Are Not Normalized In AngularJS 2 Beta 1
	</title>
</head>
<body>

	<h1>
		Attributes Are Not Normalized In AngularJS 2 Beta 1
	</h1>

	<my-app>
		Loading...
	</my-app>

	<!-- Load demo scripts. -->
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/1/es6-shim.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/1/Rx.umd.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/1/angular2-polyfills.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/1/angular2-all.umd.min.js"></script>
	<!-- AlmondJS - minimal implementation of RequireJS. -->
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/1/almond.js"></script>
	<script type="text/javascript">

		// Defer bootstrapping until all of the components have been declared.
		// --
		// NOTE: Not all components have to be required here since they will be
		// implicitly required by other components.
		requirejs(
			[ "AppComponent" ],
			function run( AppComponent ) {

				ng.platform.browser.bootstrap( AppComponent );

			}
		);


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		// I provide the root application component.
		define(
			"AppComponent",
			[ "ColorMe" ],
			function registerAppComponent( ColorMe ) {

				// Configure the App component definition.
				var AppComponent = ng.core
					.Component({
						selector: "my-app",
						directives: [ ColorMe ],
						template:
						`
							<p [colorMe]=" '#FF0099' ">
								1. Color me pink!
							</p>

							<p bind-colorMe=" '#FF0099' ">
								2. Color me pink!
							</p>

							<p bindColorMe=" '#FF0099' ">
								3. Color me pink!
							</p>

							<p data-bind-colorMe=" '#FF0099' ">
								4. Color me pink!
							</p>

							<p data-bindColorMe=" '#FF0099' ">
								5. Color me pink!
							</p>

							<p x-bind-colorMe=" '#FF0099' ">
								6. Color me pink!
							</p>

							<p data-[colorMe]=" '#FF0099' ">
								7. Color me pink!
							</p>

							<p colorMe="#FF0099">
								8. Color me pink!
							</p>

							<p data-colorMe="#FF0099">
								9. Color me pink!
							</p>

							<p color-me="#FF0099">
								10. Color me pink!
							</p>

							<p color:me="#FF0099">
								11. Color me pink!
							</p>


							<!--
								If uncommented, these two actually throw an error because
								they are trying to bind on unknown input properties:
							-->
							<!--
							<p [color-me]=" '#FF0099' ">
								Color me pink!
							</p>

							<p bind-color-me=" '#FF0099' ">
								Color me pink!
							</p>
							-->
						`
					})
					.Class({
						constructor: function AppController() {

							// ... nothing to do.

						}
					})
				;

				return( AppComponent );

			}
		);


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		// I provide a directive that sets the host text color using the colorMe property.
		define(
			"ColorMe",
			function registerColorMe() {

				// Configure the ColorMe directive definition.
				// --
				// NOTE: In this case, the directive attribute selector is also a
				// bindable property.
				var ColorMeDirective = ng.core
					.Directive({
						selector: "[colorMe]",
						inputs: [ "colorMe" ],
						host: {
							"[style.color]": "colorMe"
						}
					})
					.Class({
						constructor: function ColorMeController() {

							console.log( "ColorMe instantiated." );

						}
					})
				;

				return( ColorMeDirective );

			}
		);

	</script>

</body>
</html>

As you can see, the directive is configured to select on "[colorMe]" and bind on "colorMe". And, when we run the above code, we get the following output:

Attribute normalization in AngularJS 2 Beta 1.

As you can see, only the following attribute formats were picked up:

  • [colorMe]
  • bind-colorMe
  • data-bind-colorMe
  • data-[colorMe]
  • colorMe

... and the one's that I left commented-out would have thrown [some variation of] the following AngularJS error:

EXCEPTION: Template parse errors: Can't bind to 'color-me' since it isn't a known native property ("they are trying to bind on unknown input properties.")

Now, granted, this demo is a little ambiguous because it's not necessarily clear which is failing - the attribute matching or the input binding. But, trust me, even if I remove the input binding, the attribute selector still fails on anything but an exact match.

The one thing that was kind of confusing is the fact that these two work:

  • data-[colorMe]=" '#FF0099' "
  • data-bind-colorMe=" '#FF0099' "

... but this one fails:

  • data-colorMe="#FF0099"

It's almost like the attribute selector is more lenient when the property-binding syntax is being used but, stricter when using the attribute interpolation-binding syntax is being used. I suspect that the latter miss is actually a bug. Bug, I'm still very new to AngularJS, so don't quote me on that.

Regardless, the real take-away here is that AngularJS 2 Beta 1 does not normalize attributes in the same way that AngularJS 1.x did. And, while this is a divergent behavior, I have to say, I like it much better. The dynamic nature of attributes in AngularJS 1.x was interesting; but, it actually made certain tasks harder since you had to live up to that dynamic expectation. Now, with AngularJS 2, there is no more ambiguity.

Want to use code from this post? Check out the license.

Reader Comments

5 Comments

I think it makes sense. If you'd allow me to explain my theory:

1) [] denotes a property, this you know. Therefore, when you use it in conjunction with `data-` in `data-[colorMe]` the microdata attribute is really just `[colorMe]` when it comes to the interpreter. When you use microdata in other frameworks, it doesn't keep the `data-` portion of the attribute.

2) `[]` or `bind-` is probably shorthand for one another, or analogous to each other. My guess is that behind the scenes, Angular transforms anything in the brackets into the `bind-` format and that's where the framework picks up that you want a binding. That's why `data-bind-colorMe` works. Again, microdata attribute magic is happening.

3) I'm a bit fuzzy on `x-` support; I've never seen a popular framework utilize it, certainly not jQuery, who uses `.data` which I think is now the norm. So even with `x-bind-colorMe`, I don't think Angular strips `x-` out.

4) `data-colorMe` is again, a microdata attribute that neither has [] nor `bind-` to help the Angular interpretation, so it becomes just `colorMe` during intepretation, which is valid because...

5) `colorMe` works because it's a custom attribute and is in camelCase instead of dash-case. I would assume that Angular is picking it up and tries to interpret it as a standard attribute, realizes it isn't and is camelCased (although I'd expect one word attributes to work the same way) and thinks you want it bound, so binds it. I think it's a failsafe mechanism.

These all make sense to me, but `color-me` doesn't. I guess it plays that one safe and without explicit binding and since it's not camelCased thinks it's an attribute you want to use outside of the Angular framework.

5 Comments

@Eric,

Sorry forgot to mention, since `data-colorMe` has no implicit binding to it, I'm betting when Angular runs through the data attributes it needs some kind of binding, although custom attributes don't.

Honestly, I'd just stick with the [] brackets and make life easy ;-)

But loving these exploration posts. Clearly, or else I wouldn't post =P

15,674 Comments

@Eric,

And, here's another interesting one which I didn't look at in the post. This doesn't work (as we discussed above):

data-colorMe="#FF0099"

... but, this one DOES work:

data-colorMe="#FF0099{{ '' }}"

I asked Pascal Precht about that one and he explain that it was because the {{}} syntax is actually an implicit binding. So when you use it, as I did above, the compiler is actually transforming it to:

data-[colorMe]="( '#FF0099' + '' )"

So, the the {{}} get compiled down into expressions that are passed into the property bindings. Very interesting stuff!

15,674 Comments

@Adrian,

I believe Angular 2+ is able to do camel-case because it compiles the HTML before the browser parses it. So, it has access to the raw values. But, to be honest, that goes beyond my understanding of the "magic". I do know that when I have used data-* attribute in the past, I do access the key as an all-lowercase value:

www.bennadel.com/blog/3388-using-data-attributes-to-pass-configuration-into-an-event-plug-in-in-angular-5-1-1.htm

... as you can see, I access the property as "element.dataset.ignoremousedownoutside" even though it was defined as camelCase in the calling context.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel