Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 380
Tauren Avatar asked Nov 22 '14 09:11

Tauren


People also ask

Which directive definition option is used to replace the current element if true?

replace: true means that the content of the directive template will replace the element that the directive is declared on, in this case the <div myd1> tag.

Can we create custom directive in Angular True False?

Much like you create controllers and services, you can create your own directives for AngularJS to use. When AngularJS bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements.

Why we use* before ngIf?

The asterisk is "syntactic sugar". It simplifies ngIf and ngFor for both the writer and the reader. Under the hood, Angular replaces the asterisk version with a more verbose form.

What is* in structural directive?

Structural directives are responsible for the Structure and Layout of the DOM Element. It is used to hide or display the things on the DOM. Structural Directives can be easily identified using the '*'. Every Structural Directive is preceded by a '*' symbol.


2 Answers

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
    // ...
  }
})
like image 57
user2943490 Avatar answered Oct 14 '22 13:10

user2943490


It would be helpful to know how you expect the values to be merged. Does the template take priority, the element, or is some kind of merge needed?

Lacking that I can only make an assumption, the below code assumes you want to remove attributes from the template that exist on the element.

.directive('foo', function() {
    return {
        restrict: 'E',
        replace: true,
        template: function(element, attrs) {
            var template = '<div bar="{{bar}}" baz="baz"></div>';
            template = angular.element(template);
            Object.keys(attrs.$attr).forEach(function(attr) {\
                // Remove all attributes on the element from the template before returning it.
                template.removeAttr(attrs.$attr[attr]);
            });
            return template;
        },
        scope: {
          bar: '@'
        }
    };
})
like image 21
Enzey Avatar answered Oct 14 '22 11:10

Enzey