4

This is simple to do if we don't mind blatantly violating MVC, but since I'm trying to learn to play nice with angular, I've been tearing my hair out on innocuous little things like this.

All I want is for div#thisShouldFade to fade out when a new thing is chosen, then fade in with the new data.

here's the html:

<body ng-app="MyApp">
  <div ng-controller="MyController as myCtrl">

    <select ng-model="myCtrl.currentThing" ng-options="object.name for object in myCtrl.things">
      <option value="">--choose a thing--</option>
    </select>

    <div id="thisShouldFade">{{myCtrl.currentThing.data}}</div>

  </div>
</body>

and the javascript:

  angular.module("MyApp", [])

  .controller("MyController", function(){
    this.things = [
      { name: "Thing One",   data: "This is the data for thing one" },  
      { name: "Thing Two",   data: "This is the data for thing two" },  
      { name: "Thing Three", data: "This is the data for thing three" }
    ];

    this.currentThing = null;
  })

and the plunk:

http://plnkr.co/edit/RMgEOd6nrT9lFQlslDR0?p=preview

I've tried a bunch of different approaches using ngAnimate to set classes with CSS transitions, but the fundamental problem seems to be that the model is changing instantly because it's bound to the SELECT.

Anyone have a good angular-iffic strategy? I'd prefer to leave jQuery on the sidelines. That said, here's a jQuery plunk which shows the desired effect:

http://plnkr.co/edit/SIFZI95Xy5IzgOQN0qVU?p=preview

  • Take a look a this question and the linked video. http://stackoverflow.com/questions/13083644/angularjs-animate-ng-view-transitions – isherwood Jun 10 '15 at 17:03
  • @isherwood your link and the corresponding video use a deprecated methodology. And the "updated" link, which tries to illustrate the newer class-based animations for angular 1.2, provides a hideous, very un-angular approach complete with the hard-coded setTimeouts we're all trying to avoid. – grilchgristle Jun 10 '15 at 18:23

2 Answers2

2

Here is a hacky way of getting your desired effect using transition delays and an ng-repeat. It is somewhat imperfect because there is a delay when going from no selection to selection equal to the transition delay:

angular.module("MyApp", ['ngAnimate'])

.controller("MyController", function() {
  this.things = [{
    name: "Thing One",
    data: "This is the data for thing one"
  }, {
    name: "Thing Two",
    data: "This is the data for thing two"
  }, {
    name: "Thing Three",
    data: "This is the data for thing three"
  }];

  this.currentThing = [];
})
.animate {
  position: absolute;
  transition: all 0.5s;
}
.animate.ng-enter {
  opacity: 0;
}
.animate.ng-enter-active {
  opacity: 1.0;
  transition-delay: 0.4s;
  -webkit-transition-delay: 0.4s;
}
.animate.ng-leave {
  opacity: 1.0;
}
.animate.ng-leave-active {
  opacity: 0;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.16/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.16/angular-animate.min.js"></script>

<body ng-app="MyApp">
  <div ng-controller="MyController as myCtrl">
    <select ng-model="myCtrl.currentThing[0]" ng-options="object.name for object in myCtrl.things">
      <option value="">--choose a thing--</option>
    </select>
    <div ng-repeat="thing in myCtrl.currentThing" class="animate">{{thing.data}}</div>
  </div>
</body>
dting
  • 38,604
  • 10
  • 95
  • 114
  • Outstanding! This works perfectly — I added div#defaultState at the end to account for your perceived "imperfection". Question: do you consider this "hacky" because of the transition-delay? Or because we're iterating through a 1-item (or 0-item) array? It seems like the scenario itself would inevitably lead to some form of hackery given angular's data binding, so I say BRA-VO. Thanks! – grilchgristle Jun 11 '15 at 17:41
  • Not so much the transition-delay but it feels hacky because you are using a repeat to trigger the enter and leave animation classes. No problem. – dting Jun 11 '15 at 18:09
1

Normally when you want to do something that affects the DOM you create a directive.

I made a very simple example of a directive to show how it can be done; http://jsfiddle.net/9ydqvzms/. The example expects a variable called show to be created in the controller

$scope.show = {
    data: null
};

of course this can be solved with a proper scope in the directive, or with some callback of sorts.

app.directive('optionFader', function () {
    return {
        link: function link(scope, element, attrs) {
            scope.$watch(attrs.optionFader, function (newValue, oldValue) {
                if (oldValue == newValue && newValue === undefined) return;

                var qelem = $(element);
                if (!scope.show.data) {
                    // if we have no previous value, fade in directly
                    qelem.fadeOut(0);
                    scope.show.data = newValue;
                    qelem.fadeIn(1000);
                } else {
                    qelem.fadeOut(1000, function () {
                        if (newValue) {
                            scope.show.data = newValue;
                            // we are outside of the digest cycle, so apply
                            scope.$apply();
                        }
                        qelem.fadeIn(1000);
                    });
                }
            });
        }
    };
});

Basically the directive listens for changes to some value, set to currentThing.data in the example below, and then sets another scope value, show.data, which is the actual value displayed.

You can then apply this to your HTML with an attribute

<div id="thisShouldFade" option-fader="currentThing.data">{{show.data}}</div>
Patrick
  • 17,669
  • 6
  • 70
  • 85
  • Patrick, this is a really nice illustration of angular-ified js animation. I chose DTing's answer because it uses the preferred pure CSS approach. You still get an upvote, though — I can already see several places in my project where I could end up using yours instead. – grilchgristle Jun 11 '15 at 15:47