307

I need to be able to add for example "contenteditable" to elements, based on a boolean variable on scope.

Example use:

<h1 attrs="{'contenteditable=\"true\"': editMode}">{{content.title}}</h1>

Would result in contenteditable=true being added to the element if $scope.editMode was set to true. Is there some easy way to implement this ng-class like attribute behavior? I'm considering writing a directive and sharing if not.

Edit: I can see that there seems to be some similarities between my proposed attrs directive and ng-bind-attrs, but it was removed in 1.0.0.rc3, why so?

Meligy
  • 35,654
  • 11
  • 85
  • 109
Kenneth Lynne
  • 15,461
  • 12
  • 63
  • 79
  • 2
    I haven't come across anything like that. I vote do it! :D Maybe submit it to the angular project. A syntax that better matches the ng-class would be nice. `ng-attr="expression"`. – Xesued Mar 29 '13 at 02:48
  • I'm definately considering that yes! – Kenneth Lynne Mar 29 '13 at 03:00
  • 3
    This really isn't the same as ngClass or ngStyle because they control a *single* attribute and you want to control *any* attribute. I think it would be better to create a `contentEditable` directive. – Josh David Miller Mar 29 '13 at 04:27
  • @JoshDavidMiller +1 on that. I think there is a little benefit to be able to control any attribute, especially considering the complexity. You can have directives conditionally acting on a variable. – Umur Kontacı Mar 29 '13 at 14:38
  • `

    {{content.title}}

    `
    – mia Jan 08 '15 at 17:07
  • In case you can here looking for Angular 2+ then just do following e.g. – Sanjay Singh Jul 20 '18 at 21:08
  • @JoshDavidMiller: how a directive will conditionally apply? can you explain? and am asking for attribute directive. – NeverGiveUp161 Sep 11 '18 at 09:30

13 Answers13

281

I am using the following to conditionally set the class attr when ng-class can't be used (for example when styling SVG):

ng-attr-class="{{someBoolean && 'class-when-true' || 'class-when-false' }}"

The same approach should work for other attribute types.

(I think you need to be on latest unstable Angular to use ng-attr-, I'm currently on 1.1.4)

Ashley Davis
  • 9,896
  • 7
  • 69
  • 87
  • 1
    you can also use this method with things other than booleans, like checking if a variable is set to something certain. `code` class="{{varToCheck=='valueToCheck' && 'class-if-true' || 'class-if-false' }}" `code` – zonabi Sep 12 '13 at 00:50
  • 101
    Just to clarify this answer: If you prefix *any* attribute with `ng-attr-`, then the compiler will strip the prefix, and add the attribute with its **value** bound to the result of the **angular expression** from the original attribute value. – Matthias Nov 18 '13 at 21:37
  • 1
    I have published an article on working with Angular+SVG that covers many such issues. http://www.codeproject.com/Articles/709340/Implementing-a-Flowchart-with-SVG-and-AngularJS – Ashley Davis Jan 19 '14 at 07:12
  • 12
    In my opinion it's easier to read if you use a ternary operator, for which support was added in 1.1.5. That would look like: {{ someConditionalExpression ? 'class-when-true' : 'class-when-false' }} – dshap May 13 '14 at 21:50
  • 134
    the problem with this approach is that the attribute gets created regardless of the outcome. Ideally we'd like control wether or not the attribute gets created at all. – airtonix May 19 '14 at 08:29
  • NOTE: this does not work with directives that are classes, as it doesn't get re-compiled – Adam Marshall Feb 02 '15 at 16:56
  • 26
    Note that the ng-attr- stuff was removed from Angular 1.2. This ansswer is no longer valid. – Judah Gabriel Himango Mar 05 '15 at 21:20
  • 4
    You are wrong. This project https://github.com/codecapers/AngularJS-FlowChart uses Angular 1.2 and ng-attr-. Also the latest docs (Angular 1.4) still include ng-attr- – Ashley Davis Mar 07 '15 at 01:05
  • it works, yeah!!! but is extremely slow. Do not use it for ng-repeat'ed elements! – dr_leevsey Mar 16 '16 at 09:30
  • Ha, this is probably old advice now. Then it seem the best way to style SVG, but AngularJS may have changed in the meantime! There might be a better way now. – Ashley Davis Mar 17 '16 at 11:40
  • 2
    ng-attr wasn't really removed. It was just refactored. The start of the new implementation is here: https://github.com/angular/angular.js/commit/cf17c6af475eace31cf52944afd8e10d3afcf6c0 – Splaktar Sep 22 '16 at 06:16
  • Apparently it seems to be bad practice. https://docs.angularjs.org/#why-mixing-interpolation-and-expressions-is-bad-practice- – zendu Dec 06 '17 at 03:29
  • Not working for multiple attribute on select element. More info here https://stackoverflow.com/questions/43272960/why-ng-attr-cant-be-used-with-attribute-multiple – Tauri28 Apr 01 '18 at 13:00
127

You can prefix attributes with ng-attr to eval an Angular expression. When the result of the expressions undefined this removes the value from the attribute.

<a ng-attr-href="{{value || undefined}}">Hello World</a>

Will produce (when value is false)

<a ng-attr-href="{{value || undefined}}" href>Hello World</a>

So don't use false because that will produce the word "false" as the value.

<a ng-attr-href="{{value || false}}" href="false">Hello World</a>

When using this trick in a directive. The attributes for the directive will be false if they are missing a value.

For example, the above would be false.

function post($scope, $el, $attr) {
      var url = $attr['href'] || false;
      alert(url === false);
}
Kana Ki
  • 390
  • 2
  • 16
Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • 3
    I like this answer. However, I don't think if value is undefined it will hide the attribute. I might be missing something, though. So the first result would be Hello World – Roman K Nov 06 '14 at 16:51
  • 15
    @RomanK hi, the manual states that `undefined` is a special case. *"When using ngAttr, the allOrNothing flag of $interpolate is used, so if any expression in the interpolated string results in undefined, the attribute is removed and not added to the element."* – Reactgular Nov 06 '14 at 18:01
  • 9
    Just a note to aid any future readers, the 'undefined' behaviour appears to have been added in Angular 1.3. I am using 1.2.27 and currently must IE8. – Adam Marshall Feb 02 '15 at 16:55
  • 3
    To confirm some more Angular 1.2.15 will display href even if value is undefined, looks like the undefined behavior starts in Angular 1.3 as the above comment states. – Brian Ogden Mar 31 '15 at 05:49
  • It's important to note that `ng-attr-foo` will still **always** invoke the `foo` directive if it exists, even `ng-attr-foo` evaluates to `undefined`. [discussion](https://github.com/angular/angular.js/issues/14575) – hughes Mar 07 '17 at 21:32
99

I got this working by hard setting the attribute. And controlling the attribute applicability using the boolean value for the attribute.

Here is the code snippet:

<div contenteditable="{{ condition ? 'true' : 'false'}}"></div>
starball
  • 20,030
  • 7
  • 43
  • 238
jsbisht
  • 9,079
  • 7
  • 50
  • 55
  • As angular can parse the IIF expression this works perfectly for evaluating state in the current scope and assiging values based on that state. – mccainz Jul 13 '15 at 17:25
21

In the latest version of Angular (1.1.5), they have included a conditional directive called ngIf. It is different from ngShow and ngHide in that the elements aren't hidden, but not included in the DOM at all. They are very useful for components which are costly to create but aren't used:

<h1 ng-if="editMode" contenteditable=true>{{content.title}}</h1>
Brian Genisio
  • 47,787
  • 16
  • 124
  • 167
  • 34
    I believe ng-if only works on an element level, that is, you can't specify a conditional to just one attribute of an element. – Max Strater Mar 17 '14 at 21:34
  • 3
    Indeed, but you can then do `

    `

    – Byscripts Jun 16 '14 at 10:26
  • 6
    beware, however, that `ng-if` creates a new scope. – Shimon Rachlenko Jul 02 '14 at 08:59
  • 1
    @ShimonRachlenko Agreed! This can be a big source of confusion and bugs. `ng-if` creates a new scope but `ng-show` does not. This inconsistency has always been a sore spot for me. The defensive programming reaction to this is: "always bind to something that a dot in the expression" and it won't be an issue. – Brian Genisio Jul 04 '14 at 10:13
  • If you want to add an attribute that is actually a directive, this works great. Shame about the code duplication – Adam Marshall Feb 03 '15 at 10:17
  • This worked for me. I was trying to conditionally produce attributes for Bootstrap, and ng-attr would not work for me because Bootstrap finished its processing before Angular did. There may be a way to get Bootstrap to run after Angular, and this solution creates twice as much code, but it works great. – Greg Aug 10 '17 at 13:56
16

To get an attribute to show a specific value based on a boolean check, or be omitted entirely if the boolean check failed, I used the following:

ng-attr-example="{{params.type == 'test' ? 'itWasTest' : undefined }}"

Example usage:

<div ng-attr-class="{{params.type == 'test' ? 'itWasTest' : undefined }}">

Would output <div class="itWasTest"> or <div> based on the value of params.type

Glen
  • 889
  • 7
  • 13
10

<h1 ng-attr-contenteditable="{{isTrue || undefined }}">{{content.title}}</h1>

will produce when isTrue=true : <h1 contenteditable="true">{{content.title}}</h1>

and when isTrue=false : <h1>{{content.title}}</h1>

Garfi
  • 545
  • 5
  • 3
7

Regarding the accepted solution, the one posted by Ashley Davis, the method described still prints the attribute in the DOM, regardless of the fact that the value it has been assigned is undefined.

For example, on an input field setup with both an ng-model and a value attribute:

<input type="text" name="myInput" data-ng-attr-value="{{myValue}}" data-ng-model="myModel" />

Regardless of what's behind myValue, the value attribute still gets printed in the DOM, thus, interpreted. Ng-model then, becomes overridden.

A bit unpleasant, but using ng-if does the trick:

<input type="text" name="myInput" data-ng-if="value" data-ng-attr-value="{{myValue}}" data-ng-model="myModel" />
<input type="text" name="myInput" data-ng-if="!value" data-ng-model="myModel" />

I would recommend using a more detailed check inside the ng-if directives :)

alexc
  • 208
  • 2
  • 11
  • With this approach, you will be repeating the same code though. – Kalyan Aug 27 '14 at 19:13
  • True, but it keeps the model declaration safe. My problem with using the accepted solution was that the value attribute ended up in the DOM, taking precedence over the model declaration. So, if the value attribute is empty but the model is not, the model will be overridden to take an empty value. – alexc Aug 29 '14 at 14:36
6

Also you can use an expression like this:

<h1 ng-attr-contenteditable="{{ editMode ? true : false }}"></h1>
Kamuran Sönecek
  • 3,293
  • 2
  • 30
  • 57
5

I actually wrote a patch to do this a few months ago (after someone asked about it in #angularjs on freenode).

It probably won't be merged, but it's very similar to ngClass: https://github.com/angular/angular.js/pull/4269

Whether it gets merged or not, the existing ng-attr-* stuff is probably suitable for your needs (as others have mentioned), although it might be a bit clunkier than the more ngClass-style functionality that you're suggesting.

chronicsalsa
  • 577
  • 6
  • 4
2

For input field validation you can do:

<input ng-model="discount" type="number" ng-attr-max="{{discountType == '%' ? 100 : undefined}}">

This will apply the attribute max to 100 only if discountType is defined as %

Nestor Britez
  • 1,218
  • 10
  • 14
1

Edit: This answer is related to Angular2+! Sorry, I missed the tag!

Original answer:

As for the very simple case when you only want to apply (or set) an attribute if a certain Input value was set, it's as easy as

<my-element [conditionalAttr]="optionalValue || false">

It's the same as:

<my-element [conditionalAttr]="optionalValue ? optionalValue : false">

(So optionalValue is applied if given otherwise the expression is false and attribute is not applied.)

Example: I had the case, where I let apply a color but also arbitrary styles, where the color attribute didn't work as it was already set (even if the @Input() color wasn't given):

@Component({
  selector: "rb-icon",
  styleUrls: ["icon.component.scss"],
  template: "<span class="ic-{{icon}}" [style.color]="color==color" [ngStyle]="styleObj" ></span>",
})
export class IconComponent {
   @Input() icon: string;
   @Input() color: string;
   @Input() styles: string;

   private styleObj: object;
...
}

So, "style.color" was only set, when the color attribute was there, otherwise the color attribute in the "styles" string could be used.

Of course, this could also be achieved with

[style.color]="color" 

and

@Input color: (string | boolean) = false;
Zaphoid
  • 2,360
  • 1
  • 18
  • 19
1

Was able to get this working:

ng-attr-aria-current="{{::item.isSelected==true ? 'page' : undefined}}"

The nice thing here is that if item.isSelected is false then the attribute simply isn't rendered.

Richard Strickland
  • 1,771
  • 17
  • 8
0

Just in case you need solution for Angular 2 then its simple, use property binding like below, e.g. you want to make input read only conditionally, then add in square braces the attrbute followed by = sign and expression.

<input [readonly]="mode=='VIEW'"> 
Sanjay Singh
  • 957
  • 10
  • 8