I have an app that basically looks like this
<div ui-view="header">
<div ui-view="main">
<div ui-view="footer">
Now, the footer will stay the same for all different states of the app, but the header will change in some states, but also share content in a lot of the states. The only ui-view that will change across all states is ui-view="main".
Currently my $stateProvider looks like this (footer not implemented yet):
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
  $stateProvider.state('root',{
      url: '',
      abstract: true,
      views: {
        'header': {
          templateUrl: appHelper.views+'/header.html',
        },
      }
    })
    .state('root.home', {
      url: '/',
      views: {
        'header': {
          templateUrl: appHelper.views+'/header.html',
        },
        'main@': {
          templateUrl: appHelper.views+'/home.html',
          controller: 'HomeController as homeVm',
        }
      },
      resolve: {
        posts: function(dataService) {
          return dataService.getPosts();
        },
        artists: function(dataService) {
          return dataService.getArtists();
        }
      }
    })
    .state('root.artist', {
      url: '/artist/:slug',
      views: {
        'header@': {
          templateUrl: appHelper.views+'/artistHeader.html',
          controller: 'ArtistController as artistVm',
        },
        'main@': {
          templateUrl: appHelper.views+'/artist.html',
          controller: 'ArtistController as artistVm',
        }
      },
      resolve: {
        artist: function($stateParams, dataService) {
          return dataService.getArtist($stateParams.slug);    
        }
      }
    });
}]);
So far, so good. But here’s the catch. During transitions I want to animate all the ui-views as one. To make things look cohesive in my case (it’s a fade animation) I would like to put them in a wrapping div and have that div do the fade out/fade in (doing it on each different ui-view will cause all kinds of ugliness). 
What is best practice in this scenario?
Do I wrap them in a ui-view and hook that up in the $stateProvider somehow (nested ui-views?).
Are there any other ways to do it? Can I listen to $stateChange and apply classes to my wrapper like ngAnimate would with an ui-view? (fade out then wait until entirely faded out with a successful resolve before fading in).
From my googling efforts it seems ui-view is much preferred when handling animations of the transition type. 
... During transitions I want to animate all the ui-views as one. To make things look cohesive in my case (it’s a fade animation) I would like to put them in a wrapping div and have that div do the fade out/fade in (doing it on each different ui-view will cause all kinds of uglyness).
What is best practice in this scenario?
Option 1: Using event hooks $stateChangeStart and $stateChangeSuccess
This approach would have some side effects as @David mentioned in comments. So avoid it until ui-router comes up with a better way to manage state transition promise. Currently it relies on event.preventDefault() to prevent the transition from happening.
Basically, ui-router will fire your ui-view route change view-out at the same time as the incoming view-in. So what you get is the previous ui-view still visible while the next ui-view is rendered and if you're animating the view-out then there will be that transition duration of overlap between the old view and the new. You should look into holding off on rendering the new ui-view until the old one is finished animating out (before animating the new one in)
For similar reasons, wrapping them in another ui-view and hooking them up in the $stateProvider would not be wise.
Option 2:(Recommended) CSS based animations
I mostly go with CSS based transitions and you may achieve this either by
ui-view and applying animation on that class.ui-view in a <div>(if you really need this)  and apply animation on it.Either way, you would be treating all ui-views as a single entity and animating them would make things look more cohesive.
HTML:
<div ui-view="header" ></div>    
<div ui-view="main" ></div>
<div ui-view="footer" ></div>
CSS:
[ui-view].ng-enter { 
   animation: fadein 2s;
}
@keyframes fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
}
Here's a working plunkr based on your use case where, I have an abstract state root and three nested states root.home, root.artist and root.band. While the ui-view='main' gets changed across all states, ui-view='footer' doesnt change in any and ui-view='header' changes for the state 'root.artist'.
The fact that you are using css based .ng-enter and .ng-leave classes to trigger the animations, you technically overcome the issues with previous approach.
  $stateProvider
        .state('root',{
          abstract:true,
            views:{
              'header':{  //absolutely targets the 'header' view in root unnamed state.
                templateUrl:'header.html'
              },
              'footer':{  //absolutely targets the 'footer' view in root unnamed state.
                templateUrl:'footer.html'
              }
            }
        })
        .state('root.home',{
            views:{
              'main@':{
                templateUrl:'main.html'
              },
            }          
        })
        .state('root.artist',{
            views:{
              'header@':{
                templateUrl:'artist-header.html'
              },
              'main@':{
                templateUrl:'artist-main.html'
              },
            }          
        })
        .state('root.band',{
            views:{
              'main@':{
                templateUrl:'band-main.html'
              },
            }          
        })
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With