15

How can I add fade animation to a tabset using angular-ui-bootstrap?

For example, given the following code:

<tabset>
    <tab heading="Tab1">Some content</tab>
    <tab heading="Tab2">Other content</tab>
</tabset>

I would like the content of the tabs to fade when switching between them. I tried to add thefade class to the tab tags (similar to how you would do it with the bootstrap3 js file), but it didn't work.

Many thanks!

urish
  • 8,943
  • 8
  • 54
  • 75
  • Try reading documentation for $animate - maybe it will give you some idea. – lort Oct 22 '13 at 07:15
  • I will... I hoped there is some quick solution that I overlooked, as with bootstrap3 js it's pretty easy to do – urish Oct 23 '13 at 07:08

3 Answers3

30

Since tabset use ng-class to control "active" tab, which allow us to define fade effect with angular animation by setting opacity=0 when "active" class is removed/attached.

First, you need to load ngAnimate module by including angular-animate.js and set up dependency.

Add to your <head>:

<script src="https://code.angularjs.org/1.2.24/angular-animate.js"></script>

Set module dependency:

angular.module("myApp", ["ui.bootstrap", "ngAnimate"]);

Now add animation class to your tabset.

<tabset class="tab-animation">
    <tab heading="Tab1">Some content</tab>
    <tab heading="Tab2">Other content</tab>
</tabset>

Put following code into your css file:

/* set reference point */
.tab-animation > .tab-content {
    position: relative;
}

/* set animate effect */
.tab-animation > .tab-content > .tab-pane{
    transition: 0.2s linear opacity;
}

/* overwrite display: none and remove from document flow */
.tab-animation > .tab-content > .tab-pane.active-remove {
    position: absolute;
    top: 0;
    width: 100%;
    display: block;
}

/* opacity=0 when removing "active" class */
.tab-animation > .tab-content > .tab-pane.active-remove-active {
    opacity: 0;
}

/* opacity=0 when adding "active" class */
.tab-animation > .tab-content > .tab-pane.active-add {
    opacity: 0;
}

That's all. You can check the demo on Plunker.

Also take a look at ngAnimate doc.

eight04
  • 416
  • 4
  • 7
2

I ended up patching the ui-bootstrap file. I'm still a noob with AngularJS, so please forgive the lingo. This is an unconventional hack, and needs to be refactored with ng-animate, but it works.

Open ui-bootstrap-tpls-0.10.0.js and look for the 'tab' directive :

    .directive('tab', ['$parse', function($parse) {
    return {
    require: '^tabset',
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/tabs/tab.html',
    transclude: true,
    scope: {
    id:'@', // PATCH : GETTING TAB 'id' ATTRIBUTE
    heading: '@',
    onSelect: '&select', //This callback is called in contentHeadingTransclude
                      //once it inserts the tab's content into the dom
    onDeselect: '&deselect'
    },
    // ...

Notice the extra code for retrieving the id attribute value (via transclusion, I guess).



A few lines below, look for :

     scope.$watch('active', function(active) {

and patch it like so :

          scope.$watch('active', function(active) {
      // Note this watcher also initializes and assigns scope.active to the
      // attrs.active expression.
      setActive(scope.$parent, active);

      if (active) {
        tabsetCtrl.select(scope);
        scope.onSelect();

        tab_id = attrs.id;
        $(".tab_pane_"+tab_id).hide(); // HIDE AT FIRST, SO IT CAN ACTUALLY FADE IN
        $(".tab_pane_"+tab_id).fadeIn(1000); // JQUERY TARGETING BY CLASS

      } else {
        scope.onDeselect();

        tab_id = attrs.id;
        $(".tab_pane_"+tab_id).hide(); // JQUERY TARGETING BY CLASS
      }

    });



A few lines below, look for :

    scope.select = function() {

and add inside :

    $(".tab-pane").hide();

so all tab panes hide properly at first.



Then, look for :

angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { ...

and add the css class to the tab-pane element in the corresponding template, like so :

angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/tabs/tabset.html",
"\n" +
"<div class=\"tabbable\">\n" +
"  <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
"  <div class=\"tab-content\">\n" +
"    <div class=\"tab-pane tab_pane_{{tab.id}}\" \n" + // CLASS NAME IS DYNAMIC
"         ng-repeat=\"tab in tabs\" \n" +
"         ng-class=\"{active: tab.active}\"\n" + 
"         tab-content-transclude=\"tab\">\n" +
"    </div>\n" +
"  </div>\n" +
"</div>\n" +
"");
}]);





Once the ui-bootstrap .js file is modified, you must edit your view template (where you fetch the tabs) and declare the 'id' attribute :

    <!-- TABS -->
    <tabset justified="true">
        <tab ng-repeat="tab in tabs" heading="{{tab.title}}" id="{{tab.id}}" >
            // ... TAB CONTENT



You should get the basic concept, currently it's not very elegant (to put it mildly). But it works.


In case you wonder how my tabs got id's, well, I injected them in my controller :

                        Tab1 = {
                        id:1,
                         'ShortDescription': ShortDescription, 
                         'FullDescription': FullDescription, 
                         'TabContent': TabContent1, 
                        title: "ProductTabTitleDefault1", 
                        // active:true
                    };

                    Tab2 = {
                        id:2,
                         'ShortDescription': ShortDescription, 
                         'FullDescription': FullDescription, 
                         'TabContent': TabContent1, 
                        title: "ProductTabTitleDefault2", 
                        // active:true
                    };


                    $rootScope.tabs = { 
                        'Tab1': Tab1, 
                        'Tab2': Tab2, 
                        };

Of course this is mockup data, but assuming your tabs and their content are dynamic, you can use a counter, and maybe use another key instead of "id" (but you'll have to change the rest accordingly).

Christian Bonato
  • 1,253
  • 11
  • 26
1

I have an alternative solution to @user3413125's well-written solution above. It uses @keyframes to achieve a cross-fade, rather than a fade out followed by a fade in. See demo on Plunker

Here is the fade-in portion of the CSS (fade-out is similar):

.tab-animation > .tab-content > .tab-pane.active-add {
    animation: 1s fade-in;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

The keyframe technique is taken from the AngularJs tutorial 14 - look for "CSS Keyframe Animations: Animating ngView", about halfway down the page.

Martin Randall
  • 308
  • 2
  • 9