Fade effect for tabs in ui-bootstrap (Angular.JS) Fade effect for tabs in ui-bootstrap (Angular.JS) angularjs angularjs

Fade effect for tabs in ui-bootstrap (Angular.JS)


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.


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).


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.