Toggle visibility of a ng-include depending on route

All we need is an easy explanation of the problem, so here it is.

I have the following configuration:

$routeProvider
.when('/cars', { templateUrl: 'app/cars/index.html', controller: 'CarsCtrl', reloadOnSearch: false })
.when('/bikes', { templateUrl: 'app/bikes/index.html', controller: 'BikesCtrl', reloadOnSearch: false });

and somewhere in my root index.html there is a:

<a href="#/cars" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Cars</a>
<a href="#/bikes" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Bikes</a>
<div ng-view></div>

Now, I want both views loaded and generated in the DOM at the same time, and show one of them depending on the route/URL.

Something like the following (not actual working code, just to give you an idea).

app.js:

$routeProvider
.when('/cars', { controller: 'CarsCtrl', reloadOnSearch: false })
.when('/bikes', { controller: 'BikesCtrl', reloadOnSearch: false });

root index.html:

<a href="#/cars" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Cars</a>
<a href="#/bikes" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Bikes</a>
<div ng-include="'app/cars/index.html'" ng-show="carsVisible"></div>
<div ng-include="'app/bikes/index.html'" ng-show="bikesVisible"></div>

UPDATE: I know that ng-view kind of does this, but the difference, if subtle, exists. I want the html of each view to be generated once and stay in the DOM at all times.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I created a single RouteCtrl to load all of your views via ng-include. ng-view is not used. I inlined the templates. The templates could contain their own ng-controller directives to pull in specific controllers.

<body ng-controller="RouteCtrl">
  <a href="#/cars" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Cars</a>
  <a href="#/bikes" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Bikes</a>
  <div ng-controller="RouteCtrl">
      <div ng-include="'/cars.html'"  ng-show="carsVisible"></div>
      <div ng-include="'/bikes.html'" ng-show="bikesVisible"></div>
  </div>

  <script type="text/ng-template" id="/cars.html">
     Cars template.
  </script> 
  <script type="text/ng-template" id="/bikes.html">
     Bikes template.
  </script> 

$routeProvider is still configured, but no template or controller is specified, causing the RouteCtrl to always be active. That controller listens for the $routeChangeSuccess event and manipulates the ng-show properties accordingly.

app.config(function($routeProvider) {
  $routeProvider
     .when('/cars', {} )
     .when('/bikes', {})
});

app.controller('RouteCtrl', function($scope, $route, $location) {
  $scope.$on('$routeChangeSuccess', function() {
    var path = $location.path();
    console.log(path);
    $scope.carsVisible = false;
    $scope.bikesVisible = false;
    if(path === '/cars') {
       $scope.carsVisible = true;
    } else if(path === '/bikes') {
       $scope.bikesVisible = true;
    }
  });
});

Plunker

The idea for this solution is from @Andy.

Method 2

I found another way, which I think is the simplest, quickest and most manageable:

How to set bootstrap navbar active class with Angular JS?

Which is:

Use ng-controller to run a single controller outside of the ng-view:

<div class="collapse navbar-collapse" ng-controller="HeaderController">
    <ul class="nav navbar-nav">
        <li ng-class="{ active: isActive('/')}"><a href="/" rel="nofollow noreferrer noopener">Home</a></li>
        <li ng-class="{ active: isActive('/dogs')}"><a href="/dogs" rel="nofollow noreferrer noopener">Dogs</a></li>
        <li ng-class="{ active: isActive('/cats')}"><a href="/cats" rel="nofollow noreferrer noopener">Cats</a></li>
    </ul>
</div>
<div ng-view></div>

and include in controllers.js:

function HeaderController($scope, $location) 
{ 
    $scope.isActive = function (viewLocation) { 
        return viewLocation === $location.path();
    };
}

Method 3

Instead of using ng-include, you should use ng-view;

This will display the content of either app/cars/index.html or app/bikes/index.html

<a href="#/cars" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Cars</a>
<a href="#/bikes" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Bikes</a>
<div ng-view></div>

See the Template section from http://docs.angularjs.org/tutorial/step_07

Method 4

Use a service with a show directive:

<div ng-show="myService.isActive('/')">Show this when at default route</div>
<div ng-show="myService.isActive('/cars')">Show this when at cars</div>

Service:

myApp.factory('MyService',
    function ($location) {
        return {
            isActive: function (path) {
                return (($location.path() == path));
            }
        }
    }
);

App.js:

// this is run after angular is instantiated and bootstrapped
myApp.run(function ($rootScope, myService) {
    $rootScope.breadcrumbService = MyService;
});

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply