26

I'm trying to trigger a transition bound to a boolean property, but this doesn't seem to fire.

Here is a cut down version of my animation trigger

trigger(
  'trueFalseAnimation', [
    transition('* => true', [
      style({backgroundColor: '#00f7ad'}),
      animate('2500ms', style({backgroundColor: '#fff'}))
    ]),
    transition('* => false', [
      style({backgroundColor: '#ff0000'}),
      animate('2500ms', style({backgroundColor: '#fff'}))
    ])
  ]
)

HTML:

<div [@trueFalseAnimation]="model.someProperty">Content here</div>

To test:

ngOnInit() {

    setTimeout(() => {
        this.model.someProperty = true;
        setTimeOut(() => {
            this.model.someProperty = false;
        }, 5000);    
    }, 1000)
}

The trigger never happens when the someProperty changes.

As a quick test I changed the trigger to use a string and it works

trigger(
      'trueFalseAnimation', [
        transition('* => Success', [
          style({backgroundColor: '#00f7ad'}),
          animate('2500ms', style({backgroundColor: '#fff'}))
        ]),
        transition('* => Failed', [
          style({backgroundColor: '#ff0000'}),
          animate('2500ms', style({backgroundColor: '#fff'}))
        ])
      ]
    )

To test:

ngOnInit() {

    setTimeout(() => {
        this.model.someProperty = "Success";
        setTimeOut(() => {
            this.model.someProperty = "Failed";
        }, 5000);    
    }, 1000)
}

The second example works just fine

My questions are

  1. Are boolean supported as triggers?
  2. If yes to #1 what am I doing wrong?
Steven Yates
  • 2,400
  • 3
  • 30
  • 58

3 Answers3

29
  1. It seems not. I saw that an an issue (12337) has already been raised for this, but there has been no update so far.
  2. One other alternative is to use 1 and 0 instead of true and false.

See the plunker from here.

trigger('isVisibleChanged', [
      state('true' , style({ opacity: 1, transform: 'scale(1.0)' })),
      state('false', style({ opacity: 0, transform: 'scale(0.0)'  })),
      transition('1 => 0', animate('300ms')),
      transition('0 => 1', animate('900ms'))
])
ScottL
  • 1,755
  • 10
  • 7
  • 33
    For those seeing this answer in 2017: Since Angular 4.0.1, you will need to use `state('1', ...` and `state('0', ...)` instead of `state('true', ...)` and `state('false', ...)` – user3587412 Apr 13 '17 at 07:42
  • 1
    `state('0',...)` I spent hours to get that damn thing to work. thanks for the hint! – wodzu Apr 13 '17 at 13:41
  • I had the same issue with using hyphens in my state name. Changing the state name to camelCase fixed my issue. – Cole Garstin Jun 16 '17 at 14:29
  • I was lucky enough to stumbled upon [this pull request](https://github.com/angular/angular/pull/15311) so I saved myself a couple of hours of banging my head into a wall. – Nico Van Belle Sep 06 '17 at 07:51
  • There is a change on its way in angular 5.0.0-rc.2 https://github.com/angular/angular/blob/master/CHANGELOG.md#500-rc2-2017-10-12 – philipooo Oct 17 '17 at 12:05
  • 2
    Greetings from 2019! This has been fixed. [boolean-value-matching](https://angular.io/api/animations/transition#boolean-value-matching) – Josh Leslie May 27 '19 at 21:53
3

I'm having the same issue. Not sure if boolean are supported as triggers, but the workaround I found was to define a string property with a getter to return the boolean value as string. Something like this:

get somePropertyStr():string {
    return this.someProperty.toString();
}

Then you should bind your animation to that somePropertyStr property.

Once again, this is an ugly workaround, best thing would be able to use the boolean value.

OvSleep
  • 298
  • 1
  • 2
  • 8
1

Edit: As some other answers allude to, the behavior has changed. If you're interested here is the relevant Angular source code:

const TRUE_BOOLEAN_VALUES = new Set<string>(['true', '1']);
const FALSE_BOOLEAN_VALUES = new Set<string>(['false', '0']);

function makeLambdaFromStates(lhs: string, rhs: string): TransitionMatcherFn {
  const LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs);
  const RHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(rhs) || FALSE_BOOLEAN_VALUES.has(rhs);

  return (fromState: any, toState: any): boolean => {
    let lhsMatch = lhs == ANY_STATE || lhs == fromState;
    let rhsMatch = rhs == ANY_STATE || rhs == toState;

    if (!lhsMatch && LHS_MATCH_BOOLEAN && typeof fromState === 'boolean') {
      lhsMatch = fromState ? TRUE_BOOLEAN_VALUES.has(lhs) : FALSE_BOOLEAN_VALUES.has(lhs);
    }
    if (!rhsMatch && RHS_MATCH_BOOLEAN && typeof toState === 'boolean') {
      rhsMatch = toState ? TRUE_BOOLEAN_VALUES.has(rhs) : FALSE_BOOLEAN_VALUES.has(rhs);
    }

    return lhsMatch && rhsMatch;
  };
}

It's a little tricky to follow unless you're debugging it yourself but basically the important thing to notice is that fromState and toState are the values (when there is a change) that you set on the animation in the template: eg. [@animation]="animationState"

So you can see they explicitly allow them to be booleans (typeof fromState === 'boolean').

The reason I got caught out (and found my way back to my own old answer) is that Angular's AnimationEvent defines them as strings:

  /**
   * The name of the state from which the animation is triggered.
   */
  fromState: string;
  /**
   * The name of the state in which the animation completes.
   */
  toState: string;

So therefore on completion of an animation where your state is set to a boolean (eg. true / false and NOT 'true' / 'false' you'll run into a typing issue.

The actual value of event.fromState may be true so

  • event.fromState == true gives a compiler error ('string' and 'boolean' have no type overlap)
  • event.fromState == 'true' will NEVER be true (since the value is actually true)

So you'd need to either use:

 if (<any>(event.toState) == true)

OR

 if ((event.toState as string | boolean) == true)

Old answer:

A state is defined as being a string, so we need to stick to that.

The simplest - but ickiest - way based on your code is this

<div [@trueFalseAnimation]="model.someProperty?.toString()">Content here</div>

But this is pretty awful, so this is probably better

<div [@trueFalseAnimation]="model.someProperty ? 'active' : 'inactive'">Content here</div>
<div [@trueFalseAnimation]="model.someProperty ? 'visible' : 'hidden'">Content here</div>
<div [@trueFalseAnimation]="model.someProperty ? 'up' : 'down'">Content here</div>
<div [@trueFalseAnimation]="model.someProperty ? 'left' : 'right'">Content here</div>

The best advice here would be to use a state that corresponds to what it really means. What does true and false really mean in this context anyway?

I considered making a pipe to convert a boolean, but the only benefit of that would be to make sure you're being consistent with your state strings.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689