Danger14 Danger14 - 5 months ago 30
Javascript Question

How to make a list and grid view toggle switch control that loads in partials in AngularJS?

I am new to AngularJS and I've been unable to find specific tutorials of a list and grid view toggle switch buttons that loads in two different HTML partials. Read official

ng-include
,
ng-switch
official docs and searched SO. Unfortunately, we don't want to use UI-router.

Is loading in two partials (
list.html
and
grid.html
) the correct Angular way of coding this?

grid view
list view

The most relevant help I've found are these:

1.http://tutorialzine.com/2013/08/learn-angularjs-5-examples (Example #5)

There was an insightful comment on Example #5


Nice simple examples - well done.
The last example that switches between grid and list views is not very
efficient since it creates both options and the shows/hides one. A
simpler/better approach would be using a single ul with repeater and
ng-switch and then enabling the alternate list elements using
ng-switch-case. - Johan


2.http://www.adobe.com/devnet/html5/articles/getting-started-with-angularjs.html

3.create a single html view for multiple partial views in angularjs

4.Conditional ng-include in angularjs




My HTML code

<div class="col-xs-6" ng-controller="ToggleDisplayCtrl">
<div class="btn-group select-format-container" ng-switch on="selected">
<button ng-switch-when="true" ng-click="toggleGrid()" type="button" class="btn btn-primary" ng-model="formatChoice" ng-disabled="">grid</button>
<button ng-switch-when="false" ng-click="toggleList()" type="button" class="btn btn-primary" ng-model="formatChoice" ng-disabled="">list</button>
</div>

<div ng-include src="formatChoice.url" scope="" onload=""></div>

</div><!-- col-xs-6 END ToggleDisplayCtrl-->


My directive code

'use strict';

var app = angular.module('tempApp');

app.controller('ToggleDisplayCtrl', function($scope) {
$scope.formatChoices =
[ { name: 'grid', url: 'partials/grid.html'},
{ name: 'list', url: 'partials/list.html'} ];

$scope.selected = true;
$scope.toggleGrid = function() {
if(selected) {
return "partials/grid.html";
}
return "main.html";
};
$scope.toggleList = function() {
if(selected) {
return "partials/list.html";
}
return "main.html";

};
});

Answer

You should define application controller's scope property that will hold URL of template for ng-include, and bind this property to your directive's scope. Make sure to use isolated scope in your directive in order to avoid side effects while modifying directive's scope. Here is an example of doing it (see comments in the code):

JavaScript

angular.module('app',['ngRoute']).
  config(['$routeProvider', function($routeProvider){
    $routeProvider.
      when('/main', {
        controller: 'appController',
        templateUrl: 'main.html'
      }).
      otherwise({
        redirectTo: '/main'
      });
  }]).
  controller('appController', ['$scope', function($scope){
    $scope.view = 'list.html'; // <- default template used for ng-include
    $scope.data = [{
      text: '1'
    }, {
      text: '2'
    }, {
      text: '3'
    }, {
      text: '4'
    }, {
      text: '5'
    }, {
      text: '6'
    }];
  }]).
  directive('appView', function() {
    return {
      scope: {
        view: '=appView' // <= link view property of directive's scope to some property in the parent scope (scope of appController) specified in app-view attribute of root element of directive
      },
      replace: true,
      template: '<nav class="navbar navbar-default">' +
                    '<div class="container">' +
                      '<ul class="nav navbar-nav navbar-right">' +
                        '<li ng-repeat="v in views" ng-bind="v.name" ng-class="v.icon" ng-click="switchView(v)"></li>' +
                      '</ul>' +
                    '</div>' +
                '</nav>',
      link: function(scope, el, attr) {
        scope.views = [{
          name: 'List',
          template: 'list.html',
          icon: 'btn btn-default navbar-btn glyphicon glyphicon-th-list'
        }, {
          name: 'Grid',
          template: 'grid.html',
          icon: 'btn btn-default navbar-btn glyphicon glyphicon-th'
        }];
      },
      controller: ['$scope', function($scope){
        $scope.switchView = function(view) {
          $scope.view = view.template; // <- modify parent scope view
        }
      }]
    }
  });

Main page of application (index.html)

<html ng-app="app">
  ...
  <body ng-view=""></body>
</html>

Route template(main.html)

<header app-view="view"></header>
<section ng-include="view"></section>

List view template (list.html)

<div class="container">
  <div class="row">
    <div class="col-md-12 col-sm-12 panel panel-default" ng-repeat="item in data">
      <div class="panel-body">{{item.text}}</div>
    </div>
  </div>
</div>

Grid view template (grid.html)

<div class="container">
  <div class="row">
    <div class="col-md-4 col-sm-6 panel panel-default" ng-repeat="item in data">
      <div class="panel-body">{{item.text}}</div>
    </div>
  </div>
</div>

Plunker: http://plnkr.co/edit/uWw7NuPG0I161mHXZg2r?p=preview

Bonus: grid is responsive, just play a bit with a window size

Another Option:

As you have probably noticed, grid.html and list.html are very similar, so if you have only these two options you may decide not to use ng-include with separate switchable views at all, but place content view directly into your route's template and just switch classes used in panels using ng-class directive which can switch classes when view is changed.

Route template(main.html)

<header app-view="view"></header>
<section>
  <div class="container">
    <div class="row">
      <div ng-class="{'col-md-4': view === 'grid.html', 'col-md-12': view === 'list.html', 'panel':true, 'panel-default':true}" ng-repeat="item in data">
        <div class="panel-body">{{item.text}}</div>
      </div>
    </div>
  </div>
</section>
Comments