Search code examples
javascriptangularjsangularjs-directiveangularjs-compile

How to get the original attribute name before it was normalized/denormalize an attribute name?


I want to create a directive that I can use on <select> elements, to tell it to populate the <select> with a globally known list that's dynamically updated and shared among all the components in the app.

I envision using it like so:

<select ng-model="listentry" select-the-list></select>

Here's how I'm going about it so far:

.directive('selectTheList', function ($compile, ListData) {
    return {
        restrict: 'A',
        priority: 1000,
        terminal: true,
        link: function (scope, el, attributes) {
            var child = scope.$new();
            child.listData = ListData.theList;
            el.attr('ng-options', 'listItem.name for listItem in listData track by listItem.id');
            el.removeAttr('select-the-list'); /**** ATTENTION ****/
            $compile(el)(child);
        }
    };
});

That is, I assign an ng-options attribute that does what I want, based on the scope that I set up for this purpose, and then $compile it.


This works great. But note the line I commented with ATTENTION: This assumes that the user used <select select-the-list>, which then got normalized to selectTheList and used this directive. However, according to the directive docs:

Angular normalizes an element's tag and attribute name to determine which elements match which directives. We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel). However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using dash-delimited attributes on DOM elements (e.g. ng-model).

The normalization process is as follows: [... snip ...]

For example, the following forms are all equivalent and match the ngBind directive:

<div ng-controller="Controller">
  Hello <input ng-model='name'> <hr/>
  <span ng-bind="name"></span> <br/>
  <span ng:bind="name"></span> <br/>
  <span ng_bind="name"></span> <br/>
  <span data-ng-bind="name"></span> <br/>
  <span x-ng-bind="name"></span> <br/>
</div>

That is, if a user does <select select:the:list>, then the directive will be applied, element.removeAttr('select-the-list') will not work, and I'll get an infinite loop.


This may be an XY problem, which is why I provided all this context. But if this is a good way to do it - what's the best way to find the actual attribute on the element that caused my directive to be called, so I can remove it before re-compiling it?


Solution

  • The creators of angular did indeed envision the need for this. The attributes passed into your link function is not just a map, but an instance of $compile.directive.Attributes. It contains an $attr property:

    Properties

    $attr

    A map of DOM element attribute names to the normalized name. This is needed to do reverse lookup from normalized name back to actual name.

    Thus, your ATTENTION line should be:

    el.removeAttr(attributes.$attr['selectTheList']);