How can I fade out/in a view based on a SELECT change?

628 views Asked by At

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

2

There are 2 answers

2
dting On BEST ANSWER

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>

1
Patrick On

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>