How do I negate the parameter for AngularJS ng-if directive?

beauXjames picture beauXjames · Apr 9, 2014 · Viewed 25.8k times · Source

Quick Example:

There is a routed parameter (/Home/:isLoggedIn) that equates to true or false. (/Demo/#/Home/false) and a controller property

this.loggedIn = this.routeParams.loggedIn;

I have a view (Home.html) that has two elements, each with an ng-if attribute.

<div ng-if="home.loggedIn">
    Logged In!
</div>
<div ng-if="!home.loggedIn">
    Not Logged In...
</div>

If I navigate to /Demo/#/Home/true then the first element displays and the second does not. If I navigate to /Demo/#/Home/false then the first element does not display NOR does the second one.

I would expect the !home.loggedIn parameter evaluate to true when the value of loggedIn is, in fact, false.

Any advice here?

Answer

gkalpak picture gkalpak · Apr 9, 2014

It is quite obvious that he problem has its root to the fact that routeParams.loggedIn is a string.

So the solution is quite obvious:

// Change that:
this.loggedIn = this.routeParams.loggedIn;

// To this:
this.loggedIn = this.routeParams.loggedIn === 'true';

But why the weird behaviour ?
Why work not showing anything when loggedIn is 'false' ?

Well, here is why:

The ngIf directive uses the following toBoolean() function to convert its value to boolean:

function toBoolean(value) {
  if (typeof value === 'function') {
    value = true;
  } else if (value && value.length !== 0) {
    var v = lowercase("" + value);
    value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
  } else {
    value = false;
  }
  return value;
}

If a string is passed to toBoolean() it converts it to lowercase and checks (among other things) if it equals 'false' (in which case it returns false). This is different than the default JavaScript implementation which interprets any non-empty string as true when casting to boolean.

So, let's examine the two cases for both ngIfs:

  1. loggedIn === 'true'

    ngIf1 evaluates home.loggedIn --> 'true' (string)
    ngIf1 passes this value through toBoolean()
    toBoolean('true') returns true (because it sees a string that can't match with any string considered falsy)
    ngIf1 renders its content

    ngIf2 evaluates !home.loggedIn <=> !'true' --> false (boolean)
    (this happens because any non-empty string happens to evaluate to true)
    ngIf2 passes this value through toBoolean()
    toBoolean(false) returns false
    ngIf2 does not render its content

  2. loggedIn === 'false'

    ngIf1 evaluates home.loggedIn --> 'false' (string)
    ngIf1 passes this value through toBoolean()
    toBoolean('false') returns false (because it sees a string that is considered falsy
    ngIf1 does not render its content

    ngIf2 evaluates !home.loggedIn <=> !'false' --> false (boolean)
    (this happens because any non-empty string happens to evaluate to true)
    ngIf2 passes this value through toBoolean()
    toBoolean(false) returns false
    ngIf2 does not render its content

So, this explains the "weird" behaviour (hopefully in an understandable way).