How to prevent duplicated attributes in angular directive when replace=true

angularjs, angularjs-directive

Solution

No, there isn't some nice declarative way to tell Angular how `x` attribute should be merged or manipulated when transplanted into templates.

Angular actually does a straight copy of attributes from the source to the destination element (with a few exceptions) and merges attribute values. You can see this behaviour in the `mergeTemplateAttributes` function of the Angular compiler.

Since you can't change that behaviour, you can get some control over attributes and their values with the `compile` or `link` properties of the directive definition. It most likely makes more sense for you to do attribute manipulation in the compile phase rather than the link phase, since you want these attributes to be "ready" by the time any link functions run.

You can do something like this:

.directive('foo', function() {
  return {
    // ..
    compile: compile
    // ..
  };

  function compile(tElement, tAttrs) {
    // destination element you want to manipulate attrs on
    var destEl = tElement.find(...);

    angular.forEach(tAttrs, function (value, key) {
      manipulateAttr(tElement, destEl, key);
    })

    var postLinkFn = function(scope, element, attrs) {
      // your link function
      // ...
    }

    return postLinkFn;
  }

  function manipulateAttr(src, dest, attrName) {
    // do your manipulation
    // ...
  }
})

Problem

I've found that angular directives that specify `replace: true` will copy attributes from the directive usage into the output rendered by the template. If the template contains the same attribute, both the template attribute value and the directive attribute value will be combined together in the final output. Directive usage: ``` <foo bar="one" baz="two"></foo> ``` Directive: ``` .directive('foo', function() { return { restrict: 'E', replace: true, template: '<div bar="{{bar}}" baz="baz"></div>', scope: { bar: '@' }, link: function(scope, element, attrs, parentCtrl) { scope.bar = scope.bar || 'bar'; } }; }) ``` Output: ``` <div bar="one " baz="two baz" class="ng-isolate-scope"></div> ``` The space in `bar="one "` is causing problems, as is multiple values in `baz`. Is there a way to alter this behavior? I realized I could use non-conflicting attributes in my directive and have both the template attributes and the non-conflicting attributes in the output. But I'd like to be able to use the same attribute names, and control the output of the template better. I suppose I could use a `link` method with `element.removeAttr()` and `element.attr()`. It just seems like there should be a better solution. Lastly, I realize there is talk of deprecating `remove: true`, but there are valid reasons for keeping it. In my case, I need it for directives that generate SVG tags using transclusion. See here for details: https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb

Original source