Paginated AngularJS posts application











up vote
2
down vote

favorite












I have made a small application that displays a posts JSON in the form of cards, with the help of AngularJS and Twitter Bootstrap 4.



The application has a pagination and there are about 100 posts displayed on each page.






var root = 'https://jsonplaceholder.typicode.com';

// Create an Angular module named "postsApp"
var app = angular.module("postsApp", );

// Create controller for the "postsApp" module
app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
var url = root + "/posts";
$scope.postList = ;
$scope.search = "";
$scope.filterList = function() {
var oldList = $scope.postList || ;
$scope.postList = $filter('filter')($scope.posts, $scope.search);
if (oldList.length != $scope.postList.length) {
$scope.pageNum = 1;
$scope.startAt = 0;
};
$scope.itemsCount = $scope.postList.length;
$scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
};
$http.get(url)
.then(function(data) {
// posts arary
$scope.posts = data.data;
$scope.filterList();

// Paginate
$scope.pageNum = 1;
$scope.perPage = 24;
$scope.startAt = 0;
$scope.filterList();

$scope.currentPage = function(index) {
$scope.pageNum = index + 1;
$scope.startAt = index * $scope.perPage;
};

$scope.prevPage = function() {
if ($scope.pageNum > 1) {
$scope.pageNum = $scope.pageNum - 1;
$scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
}
};

$scope.nextPage = function() {
if ($scope.pageNum < $scope.pageMax) {
$scope.pageNum = $scope.pageNum + 1;
$scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
}
};
});
}]);

.posts-grid {
margin-top: 25px;
display: flex;
flex-wrap: wrap;
}

.posts-grid>[class*='col-'] {
display: flex;
flex-direction: column;
margin-bottom: 25px;
}

.posts-grid .post {
background: #fff;
border-top: 1px solid #d5d5d5;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
}

.posts-grid .text {
padding: 8px;
}

.posts-grid .card-title {
font-size: 1.25rem;
margin-bottom: 8px;
text-transform: capitalize;
}

.posts-grid .read-more {
padding: 0 8px 8px 8px;
}

.posts-grid .text-muted {
margin-bottom: 8px;
}

.posts-grid .thumbnail img {
display: block;
width: 100%;
height: auto;
}

.posts-grid p {
text-align: justify;
}

.posts-grid .post {
flex-grow: 1;
display: flex;
flex-direction: column;
}

.posts-grid .read-more {
margin-top: auto;
}

.pagination>li>a,
.pagination>li>a:hover,
.pagination>li>span {
color: #585858;
line-height: 1;
padding: 6px 12px;
text-decoration: none;
}

.pagination>.active>a,
.pagination>.active>span,
.pagination>.active>a:hover,
.pagination>.active>span:hover,
.pagination>.active>a:focus,
.pagination>.active>span:focus {
background-color: #007bff;
border-color: #2b7c2b;
color: #fff;
}

@media (max-width: 767px) {
.container {
max-width: 100%;
}
}

@media (max-width: 575px) {
.container {
max-width: 100%;
padding-left: 0;
padding-right: 0;
}
.posts-grid>[class*='col-'] {
padding-left: 5px;
padding-right: 5px;
}
}

<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

<nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
<!-- Brand -->
<a class="navbar-brand" href="#">My Blog</a>
<!-- Toggler/collapsibe Button -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>

<!-- Navbar links -->
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link active" href="#">Contacts</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About us</a>
</li>
<li class="nav-item">
<a class="nav-link btn btn-outline-primary" href="#">Login</a>
</li>
</ul>
</div>
</nav>

<div data-ng-app="postsApp">
<div class="container" data-ng-controller="postsCtrl">
<div class="row">
<div class="col-sm-9 mx-auto">
<div class="form-group search-box mt-3 px-3">
<input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
</div>
</div>
</div>
<div class="posts-grid" ng-if="postList.length > 0">
<div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
<div class="post">
<div class="thumbnail">
<img src="//lorempixel.com/450/300" />
</div>
<div class="text">
<h3 class="card-title">{{post.title}}</h3>
<p class="text-muted">{{post.body}}</p>
</div>
<div class="read-more">
<a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
</div>
</div>
</div>
</div>
<p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
<div ng-if="pageMax > 1">
<ul class="pagination pagination-sm justify-content-center">
<li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
<li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
<a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
</li>
<li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
</ul>
</div>
</div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
<script>
$(document).ready(function($) {
$('.pagination > li > a').click(function() {
$("html, body").animate({
scrollTop: 0
}, 500);
return false;
});
});
</script>





A few questions ware born in my mind recently and I did not find the answer, hence my topic here:




  1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?

  2. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?

  3. How would you optimize this application, on the front end?










share|improve this question




























    up vote
    2
    down vote

    favorite












    I have made a small application that displays a posts JSON in the form of cards, with the help of AngularJS and Twitter Bootstrap 4.



    The application has a pagination and there are about 100 posts displayed on each page.






    var root = 'https://jsonplaceholder.typicode.com';

    // Create an Angular module named "postsApp"
    var app = angular.module("postsApp", );

    // Create controller for the "postsApp" module
    app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
    var url = root + "/posts";
    $scope.postList = ;
    $scope.search = "";
    $scope.filterList = function() {
    var oldList = $scope.postList || ;
    $scope.postList = $filter('filter')($scope.posts, $scope.search);
    if (oldList.length != $scope.postList.length) {
    $scope.pageNum = 1;
    $scope.startAt = 0;
    };
    $scope.itemsCount = $scope.postList.length;
    $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
    };
    $http.get(url)
    .then(function(data) {
    // posts arary
    $scope.posts = data.data;
    $scope.filterList();

    // Paginate
    $scope.pageNum = 1;
    $scope.perPage = 24;
    $scope.startAt = 0;
    $scope.filterList();

    $scope.currentPage = function(index) {
    $scope.pageNum = index + 1;
    $scope.startAt = index * $scope.perPage;
    };

    $scope.prevPage = function() {
    if ($scope.pageNum > 1) {
    $scope.pageNum = $scope.pageNum - 1;
    $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
    }
    };

    $scope.nextPage = function() {
    if ($scope.pageNum < $scope.pageMax) {
    $scope.pageNum = $scope.pageNum + 1;
    $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
    }
    };
    });
    }]);

    .posts-grid {
    margin-top: 25px;
    display: flex;
    flex-wrap: wrap;
    }

    .posts-grid>[class*='col-'] {
    display: flex;
    flex-direction: column;
    margin-bottom: 25px;
    }

    .posts-grid .post {
    background: #fff;
    border-top: 1px solid #d5d5d5;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
    }

    .posts-grid .text {
    padding: 8px;
    }

    .posts-grid .card-title {
    font-size: 1.25rem;
    margin-bottom: 8px;
    text-transform: capitalize;
    }

    .posts-grid .read-more {
    padding: 0 8px 8px 8px;
    }

    .posts-grid .text-muted {
    margin-bottom: 8px;
    }

    .posts-grid .thumbnail img {
    display: block;
    width: 100%;
    height: auto;
    }

    .posts-grid p {
    text-align: justify;
    }

    .posts-grid .post {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    }

    .posts-grid .read-more {
    margin-top: auto;
    }

    .pagination>li>a,
    .pagination>li>a:hover,
    .pagination>li>span {
    color: #585858;
    line-height: 1;
    padding: 6px 12px;
    text-decoration: none;
    }

    .pagination>.active>a,
    .pagination>.active>span,
    .pagination>.active>a:hover,
    .pagination>.active>span:hover,
    .pagination>.active>a:focus,
    .pagination>.active>span:focus {
    background-color: #007bff;
    border-color: #2b7c2b;
    color: #fff;
    }

    @media (max-width: 767px) {
    .container {
    max-width: 100%;
    }
    }

    @media (max-width: 575px) {
    .container {
    max-width: 100%;
    padding-left: 0;
    padding-right: 0;
    }
    .posts-grid>[class*='col-'] {
    padding-left: 5px;
    padding-right: 5px;
    }
    }

    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

    <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
    <!-- Brand -->
    <a class="navbar-brand" href="#">My Blog</a>
    <!-- Toggler/collapsibe Button -->
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
    </button>

    <!-- Navbar links -->
    <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav ml-auto">
    <li class="nav-item">
    <a class="nav-link active" href="#">Contacts</a>
    </li>
    <li class="nav-item">
    <a class="nav-link" href="#">About us</a>
    </li>
    <li class="nav-item">
    <a class="nav-link btn btn-outline-primary" href="#">Login</a>
    </li>
    </ul>
    </div>
    </nav>

    <div data-ng-app="postsApp">
    <div class="container" data-ng-controller="postsCtrl">
    <div class="row">
    <div class="col-sm-9 mx-auto">
    <div class="form-group search-box mt-3 px-3">
    <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
    </div>
    </div>
    </div>
    <div class="posts-grid" ng-if="postList.length > 0">
    <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
    <div class="post">
    <div class="thumbnail">
    <img src="//lorempixel.com/450/300" />
    </div>
    <div class="text">
    <h3 class="card-title">{{post.title}}</h3>
    <p class="text-muted">{{post.body}}</p>
    </div>
    <div class="read-more">
    <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
    </div>
    </div>
    </div>
    </div>
    <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
    <div ng-if="pageMax > 1">
    <ul class="pagination pagination-sm justify-content-center">
    <li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
    <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
    <a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
    </li>
    <li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
    </ul>
    </div>
    </div>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
    <script>
    $(document).ready(function($) {
    $('.pagination > li > a').click(function() {
    $("html, body").animate({
    scrollTop: 0
    }, 500);
    return false;
    });
    });
    </script>





    A few questions ware born in my mind recently and I did not find the answer, hence my topic here:




    1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?

    2. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?

    3. How would you optimize this application, on the front end?










    share|improve this question


























      up vote
      2
      down vote

      favorite









      up vote
      2
      down vote

      favorite











      I have made a small application that displays a posts JSON in the form of cards, with the help of AngularJS and Twitter Bootstrap 4.



      The application has a pagination and there are about 100 posts displayed on each page.






      var root = 'https://jsonplaceholder.typicode.com';

      // Create an Angular module named "postsApp"
      var app = angular.module("postsApp", );

      // Create controller for the "postsApp" module
      app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
      var url = root + "/posts";
      $scope.postList = ;
      $scope.search = "";
      $scope.filterList = function() {
      var oldList = $scope.postList || ;
      $scope.postList = $filter('filter')($scope.posts, $scope.search);
      if (oldList.length != $scope.postList.length) {
      $scope.pageNum = 1;
      $scope.startAt = 0;
      };
      $scope.itemsCount = $scope.postList.length;
      $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
      };
      $http.get(url)
      .then(function(data) {
      // posts arary
      $scope.posts = data.data;
      $scope.filterList();

      // Paginate
      $scope.pageNum = 1;
      $scope.perPage = 24;
      $scope.startAt = 0;
      $scope.filterList();

      $scope.currentPage = function(index) {
      $scope.pageNum = index + 1;
      $scope.startAt = index * $scope.perPage;
      };

      $scope.prevPage = function() {
      if ($scope.pageNum > 1) {
      $scope.pageNum = $scope.pageNum - 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };

      $scope.nextPage = function() {
      if ($scope.pageNum < $scope.pageMax) {
      $scope.pageNum = $scope.pageNum + 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };
      });
      }]);

      .posts-grid {
      margin-top: 25px;
      display: flex;
      flex-wrap: wrap;
      }

      .posts-grid>[class*='col-'] {
      display: flex;
      flex-direction: column;
      margin-bottom: 25px;
      }

      .posts-grid .post {
      background: #fff;
      border-top: 1px solid #d5d5d5;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
      }

      .posts-grid .text {
      padding: 8px;
      }

      .posts-grid .card-title {
      font-size: 1.25rem;
      margin-bottom: 8px;
      text-transform: capitalize;
      }

      .posts-grid .read-more {
      padding: 0 8px 8px 8px;
      }

      .posts-grid .text-muted {
      margin-bottom: 8px;
      }

      .posts-grid .thumbnail img {
      display: block;
      width: 100%;
      height: auto;
      }

      .posts-grid p {
      text-align: justify;
      }

      .posts-grid .post {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      }

      .posts-grid .read-more {
      margin-top: auto;
      }

      .pagination>li>a,
      .pagination>li>a:hover,
      .pagination>li>span {
      color: #585858;
      line-height: 1;
      padding: 6px 12px;
      text-decoration: none;
      }

      .pagination>.active>a,
      .pagination>.active>span,
      .pagination>.active>a:hover,
      .pagination>.active>span:hover,
      .pagination>.active>a:focus,
      .pagination>.active>span:focus {
      background-color: #007bff;
      border-color: #2b7c2b;
      color: #fff;
      }

      @media (max-width: 767px) {
      .container {
      max-width: 100%;
      }
      }

      @media (max-width: 575px) {
      .container {
      max-width: 100%;
      padding-left: 0;
      padding-right: 0;
      }
      .posts-grid>[class*='col-'] {
      padding-left: 5px;
      padding-right: 5px;
      }
      }

      <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
      <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

      <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
      <!-- Brand -->
      <a class="navbar-brand" href="#">My Blog</a>
      <!-- Toggler/collapsibe Button -->
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
      <span class="navbar-toggler-icon"></span>
      </button>

      <!-- Navbar links -->
      <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="navbar-nav ml-auto">
      <li class="nav-item">
      <a class="nav-link active" href="#">Contacts</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">About us</a>
      </li>
      <li class="nav-item">
      <a class="nav-link btn btn-outline-primary" href="#">Login</a>
      </li>
      </ul>
      </div>
      </nav>

      <div data-ng-app="postsApp">
      <div class="container" data-ng-controller="postsCtrl">
      <div class="row">
      <div class="col-sm-9 mx-auto">
      <div class="form-group search-box mt-3 px-3">
      <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
      </div>
      </div>
      </div>
      <div class="posts-grid" ng-if="postList.length > 0">
      <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
      <div class="post">
      <div class="thumbnail">
      <img src="//lorempixel.com/450/300" />
      </div>
      <div class="text">
      <h3 class="card-title">{{post.title}}</h3>
      <p class="text-muted">{{post.body}}</p>
      </div>
      <div class="read-more">
      <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
      </div>
      </div>
      </div>
      </div>
      <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
      <div ng-if="pageMax > 1">
      <ul class="pagination pagination-sm justify-content-center">
      <li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
      <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
      <a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
      </li>
      <li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
      </ul>
      </div>
      </div>
      </div>

      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
      <script>
      $(document).ready(function($) {
      $('.pagination > li > a').click(function() {
      $("html, body").animate({
      scrollTop: 0
      }, 500);
      return false;
      });
      });
      </script>





      A few questions ware born in my mind recently and I did not find the answer, hence my topic here:




      1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?

      2. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?

      3. How would you optimize this application, on the front end?










      share|improve this question















      I have made a small application that displays a posts JSON in the form of cards, with the help of AngularJS and Twitter Bootstrap 4.



      The application has a pagination and there are about 100 posts displayed on each page.






      var root = 'https://jsonplaceholder.typicode.com';

      // Create an Angular module named "postsApp"
      var app = angular.module("postsApp", );

      // Create controller for the "postsApp" module
      app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
      var url = root + "/posts";
      $scope.postList = ;
      $scope.search = "";
      $scope.filterList = function() {
      var oldList = $scope.postList || ;
      $scope.postList = $filter('filter')($scope.posts, $scope.search);
      if (oldList.length != $scope.postList.length) {
      $scope.pageNum = 1;
      $scope.startAt = 0;
      };
      $scope.itemsCount = $scope.postList.length;
      $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
      };
      $http.get(url)
      .then(function(data) {
      // posts arary
      $scope.posts = data.data;
      $scope.filterList();

      // Paginate
      $scope.pageNum = 1;
      $scope.perPage = 24;
      $scope.startAt = 0;
      $scope.filterList();

      $scope.currentPage = function(index) {
      $scope.pageNum = index + 1;
      $scope.startAt = index * $scope.perPage;
      };

      $scope.prevPage = function() {
      if ($scope.pageNum > 1) {
      $scope.pageNum = $scope.pageNum - 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };

      $scope.nextPage = function() {
      if ($scope.pageNum < $scope.pageMax) {
      $scope.pageNum = $scope.pageNum + 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };
      });
      }]);

      .posts-grid {
      margin-top: 25px;
      display: flex;
      flex-wrap: wrap;
      }

      .posts-grid>[class*='col-'] {
      display: flex;
      flex-direction: column;
      margin-bottom: 25px;
      }

      .posts-grid .post {
      background: #fff;
      border-top: 1px solid #d5d5d5;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
      }

      .posts-grid .text {
      padding: 8px;
      }

      .posts-grid .card-title {
      font-size: 1.25rem;
      margin-bottom: 8px;
      text-transform: capitalize;
      }

      .posts-grid .read-more {
      padding: 0 8px 8px 8px;
      }

      .posts-grid .text-muted {
      margin-bottom: 8px;
      }

      .posts-grid .thumbnail img {
      display: block;
      width: 100%;
      height: auto;
      }

      .posts-grid p {
      text-align: justify;
      }

      .posts-grid .post {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      }

      .posts-grid .read-more {
      margin-top: auto;
      }

      .pagination>li>a,
      .pagination>li>a:hover,
      .pagination>li>span {
      color: #585858;
      line-height: 1;
      padding: 6px 12px;
      text-decoration: none;
      }

      .pagination>.active>a,
      .pagination>.active>span,
      .pagination>.active>a:hover,
      .pagination>.active>span:hover,
      .pagination>.active>a:focus,
      .pagination>.active>span:focus {
      background-color: #007bff;
      border-color: #2b7c2b;
      color: #fff;
      }

      @media (max-width: 767px) {
      .container {
      max-width: 100%;
      }
      }

      @media (max-width: 575px) {
      .container {
      max-width: 100%;
      padding-left: 0;
      padding-right: 0;
      }
      .posts-grid>[class*='col-'] {
      padding-left: 5px;
      padding-right: 5px;
      }
      }

      <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
      <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

      <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
      <!-- Brand -->
      <a class="navbar-brand" href="#">My Blog</a>
      <!-- Toggler/collapsibe Button -->
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
      <span class="navbar-toggler-icon"></span>
      </button>

      <!-- Navbar links -->
      <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="navbar-nav ml-auto">
      <li class="nav-item">
      <a class="nav-link active" href="#">Contacts</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">About us</a>
      </li>
      <li class="nav-item">
      <a class="nav-link btn btn-outline-primary" href="#">Login</a>
      </li>
      </ul>
      </div>
      </nav>

      <div data-ng-app="postsApp">
      <div class="container" data-ng-controller="postsCtrl">
      <div class="row">
      <div class="col-sm-9 mx-auto">
      <div class="form-group search-box mt-3 px-3">
      <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
      </div>
      </div>
      </div>
      <div class="posts-grid" ng-if="postList.length > 0">
      <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
      <div class="post">
      <div class="thumbnail">
      <img src="//lorempixel.com/450/300" />
      </div>
      <div class="text">
      <h3 class="card-title">{{post.title}}</h3>
      <p class="text-muted">{{post.body}}</p>
      </div>
      <div class="read-more">
      <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
      </div>
      </div>
      </div>
      </div>
      <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
      <div ng-if="pageMax > 1">
      <ul class="pagination pagination-sm justify-content-center">
      <li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
      <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
      <a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
      </li>
      <li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
      </ul>
      </div>
      </div>
      </div>

      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
      <script>
      $(document).ready(function($) {
      $('.pagination > li > a').click(function() {
      $("html, body").animate({
      scrollTop: 0
      }, 500);
      return false;
      });
      });
      </script>





      A few questions ware born in my mind recently and I did not find the answer, hence my topic here:




      1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?

      2. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?

      3. How would you optimize this application, on the front end?






      var root = 'https://jsonplaceholder.typicode.com';

      // Create an Angular module named "postsApp"
      var app = angular.module("postsApp", );

      // Create controller for the "postsApp" module
      app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
      var url = root + "/posts";
      $scope.postList = ;
      $scope.search = "";
      $scope.filterList = function() {
      var oldList = $scope.postList || ;
      $scope.postList = $filter('filter')($scope.posts, $scope.search);
      if (oldList.length != $scope.postList.length) {
      $scope.pageNum = 1;
      $scope.startAt = 0;
      };
      $scope.itemsCount = $scope.postList.length;
      $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
      };
      $http.get(url)
      .then(function(data) {
      // posts arary
      $scope.posts = data.data;
      $scope.filterList();

      // Paginate
      $scope.pageNum = 1;
      $scope.perPage = 24;
      $scope.startAt = 0;
      $scope.filterList();

      $scope.currentPage = function(index) {
      $scope.pageNum = index + 1;
      $scope.startAt = index * $scope.perPage;
      };

      $scope.prevPage = function() {
      if ($scope.pageNum > 1) {
      $scope.pageNum = $scope.pageNum - 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };

      $scope.nextPage = function() {
      if ($scope.pageNum < $scope.pageMax) {
      $scope.pageNum = $scope.pageNum + 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };
      });
      }]);

      .posts-grid {
      margin-top: 25px;
      display: flex;
      flex-wrap: wrap;
      }

      .posts-grid>[class*='col-'] {
      display: flex;
      flex-direction: column;
      margin-bottom: 25px;
      }

      .posts-grid .post {
      background: #fff;
      border-top: 1px solid #d5d5d5;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
      }

      .posts-grid .text {
      padding: 8px;
      }

      .posts-grid .card-title {
      font-size: 1.25rem;
      margin-bottom: 8px;
      text-transform: capitalize;
      }

      .posts-grid .read-more {
      padding: 0 8px 8px 8px;
      }

      .posts-grid .text-muted {
      margin-bottom: 8px;
      }

      .posts-grid .thumbnail img {
      display: block;
      width: 100%;
      height: auto;
      }

      .posts-grid p {
      text-align: justify;
      }

      .posts-grid .post {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      }

      .posts-grid .read-more {
      margin-top: auto;
      }

      .pagination>li>a,
      .pagination>li>a:hover,
      .pagination>li>span {
      color: #585858;
      line-height: 1;
      padding: 6px 12px;
      text-decoration: none;
      }

      .pagination>.active>a,
      .pagination>.active>span,
      .pagination>.active>a:hover,
      .pagination>.active>span:hover,
      .pagination>.active>a:focus,
      .pagination>.active>span:focus {
      background-color: #007bff;
      border-color: #2b7c2b;
      color: #fff;
      }

      @media (max-width: 767px) {
      .container {
      max-width: 100%;
      }
      }

      @media (max-width: 575px) {
      .container {
      max-width: 100%;
      padding-left: 0;
      padding-right: 0;
      }
      .posts-grid>[class*='col-'] {
      padding-left: 5px;
      padding-right: 5px;
      }
      }

      <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
      <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

      <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
      <!-- Brand -->
      <a class="navbar-brand" href="#">My Blog</a>
      <!-- Toggler/collapsibe Button -->
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
      <span class="navbar-toggler-icon"></span>
      </button>

      <!-- Navbar links -->
      <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="navbar-nav ml-auto">
      <li class="nav-item">
      <a class="nav-link active" href="#">Contacts</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">About us</a>
      </li>
      <li class="nav-item">
      <a class="nav-link btn btn-outline-primary" href="#">Login</a>
      </li>
      </ul>
      </div>
      </nav>

      <div data-ng-app="postsApp">
      <div class="container" data-ng-controller="postsCtrl">
      <div class="row">
      <div class="col-sm-9 mx-auto">
      <div class="form-group search-box mt-3 px-3">
      <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
      </div>
      </div>
      </div>
      <div class="posts-grid" ng-if="postList.length > 0">
      <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
      <div class="post">
      <div class="thumbnail">
      <img src="//lorempixel.com/450/300" />
      </div>
      <div class="text">
      <h3 class="card-title">{{post.title}}</h3>
      <p class="text-muted">{{post.body}}</p>
      </div>
      <div class="read-more">
      <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
      </div>
      </div>
      </div>
      </div>
      <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
      <div ng-if="pageMax > 1">
      <ul class="pagination pagination-sm justify-content-center">
      <li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
      <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
      <a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
      </li>
      <li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
      </ul>
      </div>
      </div>
      </div>

      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
      <script>
      $(document).ready(function($) {
      $('.pagination > li > a').click(function() {
      $("html, body").animate({
      scrollTop: 0
      }, 500);
      return false;
      });
      });
      </script>





      var root = 'https://jsonplaceholder.typicode.com';

      // Create an Angular module named "postsApp"
      var app = angular.module("postsApp", );

      // Create controller for the "postsApp" module
      app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
      var url = root + "/posts";
      $scope.postList = ;
      $scope.search = "";
      $scope.filterList = function() {
      var oldList = $scope.postList || ;
      $scope.postList = $filter('filter')($scope.posts, $scope.search);
      if (oldList.length != $scope.postList.length) {
      $scope.pageNum = 1;
      $scope.startAt = 0;
      };
      $scope.itemsCount = $scope.postList.length;
      $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
      };
      $http.get(url)
      .then(function(data) {
      // posts arary
      $scope.posts = data.data;
      $scope.filterList();

      // Paginate
      $scope.pageNum = 1;
      $scope.perPage = 24;
      $scope.startAt = 0;
      $scope.filterList();

      $scope.currentPage = function(index) {
      $scope.pageNum = index + 1;
      $scope.startAt = index * $scope.perPage;
      };

      $scope.prevPage = function() {
      if ($scope.pageNum > 1) {
      $scope.pageNum = $scope.pageNum - 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };

      $scope.nextPage = function() {
      if ($scope.pageNum < $scope.pageMax) {
      $scope.pageNum = $scope.pageNum + 1;
      $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
      }
      };
      });
      }]);

      .posts-grid {
      margin-top: 25px;
      display: flex;
      flex-wrap: wrap;
      }

      .posts-grid>[class*='col-'] {
      display: flex;
      flex-direction: column;
      margin-bottom: 25px;
      }

      .posts-grid .post {
      background: #fff;
      border-top: 1px solid #d5d5d5;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
      }

      .posts-grid .text {
      padding: 8px;
      }

      .posts-grid .card-title {
      font-size: 1.25rem;
      margin-bottom: 8px;
      text-transform: capitalize;
      }

      .posts-grid .read-more {
      padding: 0 8px 8px 8px;
      }

      .posts-grid .text-muted {
      margin-bottom: 8px;
      }

      .posts-grid .thumbnail img {
      display: block;
      width: 100%;
      height: auto;
      }

      .posts-grid p {
      text-align: justify;
      }

      .posts-grid .post {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      }

      .posts-grid .read-more {
      margin-top: auto;
      }

      .pagination>li>a,
      .pagination>li>a:hover,
      .pagination>li>span {
      color: #585858;
      line-height: 1;
      padding: 6px 12px;
      text-decoration: none;
      }

      .pagination>.active>a,
      .pagination>.active>span,
      .pagination>.active>a:hover,
      .pagination>.active>span:hover,
      .pagination>.active>a:focus,
      .pagination>.active>span:focus {
      background-color: #007bff;
      border-color: #2b7c2b;
      color: #fff;
      }

      @media (max-width: 767px) {
      .container {
      max-width: 100%;
      }
      }

      @media (max-width: 575px) {
      .container {
      max-width: 100%;
      padding-left: 0;
      padding-right: 0;
      }
      .posts-grid>[class*='col-'] {
      padding-left: 5px;
      padding-right: 5px;
      }
      }

      <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
      <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>

      <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
      <!-- Brand -->
      <a class="navbar-brand" href="#">My Blog</a>
      <!-- Toggler/collapsibe Button -->
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
      <span class="navbar-toggler-icon"></span>
      </button>

      <!-- Navbar links -->
      <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="navbar-nav ml-auto">
      <li class="nav-item">
      <a class="nav-link active" href="#">Contacts</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">About us</a>
      </li>
      <li class="nav-item">
      <a class="nav-link btn btn-outline-primary" href="#">Login</a>
      </li>
      </ul>
      </div>
      </nav>

      <div data-ng-app="postsApp">
      <div class="container" data-ng-controller="postsCtrl">
      <div class="row">
      <div class="col-sm-9 mx-auto">
      <div class="form-group search-box mt-3 px-3">
      <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
      </div>
      </div>
      </div>
      <div class="posts-grid" ng-if="postList.length > 0">
      <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
      <div class="post">
      <div class="thumbnail">
      <img src="//lorempixel.com/450/300" />
      </div>
      <div class="text">
      <h3 class="card-title">{{post.title}}</h3>
      <p class="text-muted">{{post.body}}</p>
      </div>
      <div class="read-more">
      <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
      </div>
      </div>
      </div>
      </div>
      <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
      <div ng-if="pageMax > 1">
      <ul class="pagination pagination-sm justify-content-center">
      <li class="page-item"><a href="#" ng-click="prevPage()"><i class="fa fa-chevron-left"></i></a></li>
      <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
      <a href="#" ng-click="currentPage($index)">{{$index+1}}</a>
      </li>
      <li><a href="#" ng-click="nextPage()"><i class="fa fa-chevron-right"></i></a></li>
      </ul>
      </div>
      </div>
      </div>

      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
      <script>
      $(document).ready(function($) {
      $('.pagination > li > a').click(function() {
      $("html, body").animate({
      scrollTop: 0
      }, 500);
      return false;
      });
      });
      </script>






      javascript performance angular.js ajax pagination






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited yesterday









      Sᴀᴍ Onᴇᴌᴀ

      8,07461751




      8,07461751










      asked Sep 4 at 10:08









      Razvan Zamfir

      859




      859






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          0
          down vote















          1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?




          Try it with 10 times as many posts. I know that just duplicates each post 10 times without modifying the unique values like id but it should demonstrate the performance. I can see the page links but unless I make the window full screen and zoom out (on my 24" monitor) I can't see the all the page links - e.g. the link for Page 1 is cut off, as well as anything beyond page 42.





          1. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?




          Unless the API endpoint accepts a parameter for the page size, you would likely need to utilize a server-side scripting language like Python, Ruby on Rails, PHP, C#, VB.NET, ASP, etc. which could return the specified pagesize.





          1. How would you optimize this application, on the front end?




          I don't see much that stands out as an obvious place to optimize, though I do notice that the promise callback to $http.get(url) has two calls to filterList() separated by three assignment lines:



          $http.get(url)
          .then(function(data) {
          // posts arary
          $scope.posts = data.data;
          $scope.filterList();

          // Paginate
          $scope.pageNum = 1;
          $scope.perPage = 24;
          $scope.startAt = 0;
          $scope.filterList();


          Is there any way to reduce the two calls to a single call there? Perhaps the first call is not needed?



          Then looking at that promise callback, I see the three methods set on $scope there: currentPage, prevPage and nextPage. I would recommend moving those methods out of the callback handler, so that all it does is set the post data on $scope and related paging variables.



          Also, I see a flaw with the jQuery code used for scrolling to the top (which doesn't appear to be working because there are no elements matching the selector .pagination > li > a when the DOM is loaded). I tried finding an equivalent in AngularJS but found little... There is a solution I found from this post that uses vanillaJS. I had to alter the href attributes on the anchors to use it.



          $scope.scrollToTop = function() {
          var scrollDuration = 500;
          var scrollStep = -window.scrollY / (scrollDuration / 10);

          var scrollInterval = setInterval(function() {
          if (window.scrollY != 0) {
          window.scrollBy(0, scrollStep);
          } else {
          clearInterval(scrollInterval);
          }
          }, 15);
          };


          Then that can be used in the click handler methods:



            $scope.currentPage = function(index) {
          $scope.pageNum = index + 1;
          $scope.startAt = index * $scope.perPage;
          $scope.scrollToTop();
          };


          Though in order to stop the anchor navigation, the default event handling will need to be prevented:



          <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>


          Updated code



          See modified code below.






          var root = 'https://jsonplaceholder.typicode.com';

          // Create an Angular module named "postsApp"
          var app = angular.module("postsApp", );

          // Create controller for the "postsApp" module
          app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
          var url = root + "/posts";
          $scope.postList = ;
          $scope.search = "";
          $scope.filterList = function() {
          var oldList = $scope.postList || ;
          $scope.postList = $filter('filter')($scope.posts, $scope.search);
          if (oldList.length != $scope.postList.length) {
          $scope.pageNum = 1;
          $scope.startAt = 0;
          };
          $scope.itemsCount = $scope.postList.length;
          $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
          };


          $scope.currentPage = function(index) {
          $scope.pageNum = index + 1;
          $scope.startAt = index * $scope.perPage;
          $scope.scrollToTop();
          };

          $scope.prevPage = function() {
          if ($scope.pageNum > 1) {
          $scope.pageNum = $scope.pageNum - 1;
          $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
          $scope.scrollToTop();
          }
          };

          $scope.nextPage = function() {
          if ($scope.pageNum < $scope.pageMax) {
          $scope.pageNum = $scope.pageNum + 1;
          $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
          $scope.scrollToTop();
          }
          };
          $scope.scrollToTop = function() {
          var scrollDuration = 500;
          var scrollStep = -window.scrollY / (scrollDuration / 10);

          var scrollInterval = setInterval(function() {
          if (window.scrollY != 0) {
          window.scrollBy(0, scrollStep);
          } else {
          clearInterval(scrollInterval);
          }
          }, 15);
          };
          $http.get(url)
          .then(function(data) {
          // posts arary
          $scope.posts = data.data;
          $scope.filterList();

          // Paginate
          $scope.pageNum = 1;
          $scope.perPage = 24;
          $scope.startAt = 0;
          $scope.filterList();
          });
          }]);

          .posts-grid {
          margin-top: 25px;
          display: flex;
          flex-wrap: wrap;
          }

          .posts-grid>[class*='col-'] {
          display: flex;
          flex-direction: column;
          margin-bottom: 25px;
          }

          .posts-grid .post {
          background: #fff;
          border-top: 1px solid #d5d5d5;
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
          }

          .posts-grid .text {
          padding: 8px;
          }

          .posts-grid .card-title {
          font-size: 1.25rem;
          margin-bottom: 8px;
          text-transform: capitalize;
          }

          .posts-grid .read-more {
          padding: 0 8px 8px 8px;
          }

          .posts-grid .text-muted {
          margin-bottom: 8px;
          }

          .posts-grid .thumbnail img {
          display: block;
          width: 100%;
          height: auto;
          }

          .posts-grid p {
          text-align: justify;
          }

          .posts-grid .post {
          flex-grow: 1;
          display: flex;
          flex-direction: column;
          }

          .posts-grid .read-more {
          margin-top: auto;
          }

          .pagination>li>a,
          .pagination>li>a:hover,
          .pagination>li>span {
          color: #585858;
          line-height: 1;
          padding: 6px 12px;
          text-decoration: none;
          }

          .pagination>.active>a,
          .pagination>.active>span,
          .pagination>.active>a:hover,
          .pagination>.active>span:hover,
          .pagination>.active>a:focus,
          .pagination>.active>span:focus {
          background-color: #007bff;
          border-color: #2b7c2b;
          color: #fff;
          }

          @media (max-width: 767px) {
          .container {
          max-width: 100%;
          }
          }

          @media (max-width: 575px) {
          .container {
          max-width: 100%;
          padding-left: 0;
          padding-right: 0;
          }
          .posts-grid>[class*='col-'] {
          padding-left: 5px;
          padding-right: 5px;
          }
          }

          <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
          <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
          <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
          <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


          <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
          <!-- Brand -->
          <a class="navbar-brand" href="#">My Blog</a>
          <!-- Toggler/collapsibe Button -->
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
          <span class="navbar-toggler-icon"></span>
          </button>

          <!-- Navbar links -->
          <div class="collapse navbar-collapse" id="collapsibleNavbar">
          <ul class="navbar-nav ml-auto">
          <li class="nav-item">
          <a class="nav-link active" href="#">Contacts</a>
          </li>
          <li class="nav-item">
          <a class="nav-link" href="#">About us</a>
          </li>
          <li class="nav-item">
          <a class="nav-link btn btn-outline-primary" href="#">Login</a>
          </li>
          </ul>
          </div>
          </nav>

          <div data-ng-app="postsApp">
          <div class="container" data-ng-controller="postsCtrl">
          <div class="row">
          <div class="col-sm-9 mx-auto">
          <div class="form-group search-box mt-3 px-3">
          <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
          </div>
          </div>
          </div>
          <div class="posts-grid" ng-if="postList.length > 0">
          <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
          <div class="post">
          <div class="thumbnail">
          <img src="//lorempixel.com/450/300" />
          </div>
          <div class="text">
          <h3 class="card-title">{{post.title}}</h3>
          <p class="text-muted">{{post.body}}</p>
          </div>
          <div class="read-more">
          <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
          </div>
          </div>
          </div>
          </div>
          <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
          <div ng-if="pageMax > 1">
          <ul class="pagination pagination-sm justify-content-center">
          <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
          <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
          <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
          </li>
          <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
          </ul>
          </div>
          </div>
          </div>








          share|improve this answer





















            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203089%2fpaginated-angularjs-posts-application%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            0
            down vote















            1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?




            Try it with 10 times as many posts. I know that just duplicates each post 10 times without modifying the unique values like id but it should demonstrate the performance. I can see the page links but unless I make the window full screen and zoom out (on my 24" monitor) I can't see the all the page links - e.g. the link for Page 1 is cut off, as well as anything beyond page 42.





            1. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?




            Unless the API endpoint accepts a parameter for the page size, you would likely need to utilize a server-side scripting language like Python, Ruby on Rails, PHP, C#, VB.NET, ASP, etc. which could return the specified pagesize.





            1. How would you optimize this application, on the front end?




            I don't see much that stands out as an obvious place to optimize, though I do notice that the promise callback to $http.get(url) has two calls to filterList() separated by three assignment lines:



            $http.get(url)
            .then(function(data) {
            // posts arary
            $scope.posts = data.data;
            $scope.filterList();

            // Paginate
            $scope.pageNum = 1;
            $scope.perPage = 24;
            $scope.startAt = 0;
            $scope.filterList();


            Is there any way to reduce the two calls to a single call there? Perhaps the first call is not needed?



            Then looking at that promise callback, I see the three methods set on $scope there: currentPage, prevPage and nextPage. I would recommend moving those methods out of the callback handler, so that all it does is set the post data on $scope and related paging variables.



            Also, I see a flaw with the jQuery code used for scrolling to the top (which doesn't appear to be working because there are no elements matching the selector .pagination > li > a when the DOM is loaded). I tried finding an equivalent in AngularJS but found little... There is a solution I found from this post that uses vanillaJS. I had to alter the href attributes on the anchors to use it.



            $scope.scrollToTop = function() {
            var scrollDuration = 500;
            var scrollStep = -window.scrollY / (scrollDuration / 10);

            var scrollInterval = setInterval(function() {
            if (window.scrollY != 0) {
            window.scrollBy(0, scrollStep);
            } else {
            clearInterval(scrollInterval);
            }
            }, 15);
            };


            Then that can be used in the click handler methods:



              $scope.currentPage = function(index) {
            $scope.pageNum = index + 1;
            $scope.startAt = index * $scope.perPage;
            $scope.scrollToTop();
            };


            Though in order to stop the anchor navigation, the default event handling will need to be prevented:



            <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>


            Updated code



            See modified code below.






            var root = 'https://jsonplaceholder.typicode.com';

            // Create an Angular module named "postsApp"
            var app = angular.module("postsApp", );

            // Create controller for the "postsApp" module
            app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
            var url = root + "/posts";
            $scope.postList = ;
            $scope.search = "";
            $scope.filterList = function() {
            var oldList = $scope.postList || ;
            $scope.postList = $filter('filter')($scope.posts, $scope.search);
            if (oldList.length != $scope.postList.length) {
            $scope.pageNum = 1;
            $scope.startAt = 0;
            };
            $scope.itemsCount = $scope.postList.length;
            $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
            };


            $scope.currentPage = function(index) {
            $scope.pageNum = index + 1;
            $scope.startAt = index * $scope.perPage;
            $scope.scrollToTop();
            };

            $scope.prevPage = function() {
            if ($scope.pageNum > 1) {
            $scope.pageNum = $scope.pageNum - 1;
            $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
            $scope.scrollToTop();
            }
            };

            $scope.nextPage = function() {
            if ($scope.pageNum < $scope.pageMax) {
            $scope.pageNum = $scope.pageNum + 1;
            $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
            $scope.scrollToTop();
            }
            };
            $scope.scrollToTop = function() {
            var scrollDuration = 500;
            var scrollStep = -window.scrollY / (scrollDuration / 10);

            var scrollInterval = setInterval(function() {
            if (window.scrollY != 0) {
            window.scrollBy(0, scrollStep);
            } else {
            clearInterval(scrollInterval);
            }
            }, 15);
            };
            $http.get(url)
            .then(function(data) {
            // posts arary
            $scope.posts = data.data;
            $scope.filterList();

            // Paginate
            $scope.pageNum = 1;
            $scope.perPage = 24;
            $scope.startAt = 0;
            $scope.filterList();
            });
            }]);

            .posts-grid {
            margin-top: 25px;
            display: flex;
            flex-wrap: wrap;
            }

            .posts-grid>[class*='col-'] {
            display: flex;
            flex-direction: column;
            margin-bottom: 25px;
            }

            .posts-grid .post {
            background: #fff;
            border-top: 1px solid #d5d5d5;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
            }

            .posts-grid .text {
            padding: 8px;
            }

            .posts-grid .card-title {
            font-size: 1.25rem;
            margin-bottom: 8px;
            text-transform: capitalize;
            }

            .posts-grid .read-more {
            padding: 0 8px 8px 8px;
            }

            .posts-grid .text-muted {
            margin-bottom: 8px;
            }

            .posts-grid .thumbnail img {
            display: block;
            width: 100%;
            height: auto;
            }

            .posts-grid p {
            text-align: justify;
            }

            .posts-grid .post {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            }

            .posts-grid .read-more {
            margin-top: auto;
            }

            .pagination>li>a,
            .pagination>li>a:hover,
            .pagination>li>span {
            color: #585858;
            line-height: 1;
            padding: 6px 12px;
            text-decoration: none;
            }

            .pagination>.active>a,
            .pagination>.active>span,
            .pagination>.active>a:hover,
            .pagination>.active>span:hover,
            .pagination>.active>a:focus,
            .pagination>.active>span:focus {
            background-color: #007bff;
            border-color: #2b7c2b;
            color: #fff;
            }

            @media (max-width: 767px) {
            .container {
            max-width: 100%;
            }
            }

            @media (max-width: 575px) {
            .container {
            max-width: 100%;
            padding-left: 0;
            padding-right: 0;
            }
            .posts-grid>[class*='col-'] {
            padding-left: 5px;
            padding-right: 5px;
            }
            }

            <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
            <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
            <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


            <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
            <!-- Brand -->
            <a class="navbar-brand" href="#">My Blog</a>
            <!-- Toggler/collapsibe Button -->
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
            <span class="navbar-toggler-icon"></span>
            </button>

            <!-- Navbar links -->
            <div class="collapse navbar-collapse" id="collapsibleNavbar">
            <ul class="navbar-nav ml-auto">
            <li class="nav-item">
            <a class="nav-link active" href="#">Contacts</a>
            </li>
            <li class="nav-item">
            <a class="nav-link" href="#">About us</a>
            </li>
            <li class="nav-item">
            <a class="nav-link btn btn-outline-primary" href="#">Login</a>
            </li>
            </ul>
            </div>
            </nav>

            <div data-ng-app="postsApp">
            <div class="container" data-ng-controller="postsCtrl">
            <div class="row">
            <div class="col-sm-9 mx-auto">
            <div class="form-group search-box mt-3 px-3">
            <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
            </div>
            </div>
            </div>
            <div class="posts-grid" ng-if="postList.length > 0">
            <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
            <div class="post">
            <div class="thumbnail">
            <img src="//lorempixel.com/450/300" />
            </div>
            <div class="text">
            <h3 class="card-title">{{post.title}}</h3>
            <p class="text-muted">{{post.body}}</p>
            </div>
            <div class="read-more">
            <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
            </div>
            </div>
            </div>
            </div>
            <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
            <div ng-if="pageMax > 1">
            <ul class="pagination pagination-sm justify-content-center">
            <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
            <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
            <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
            </li>
            <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
            </ul>
            </div>
            </div>
            </div>








            share|improve this answer

























              up vote
              0
              down vote















              1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?




              Try it with 10 times as many posts. I know that just duplicates each post 10 times without modifying the unique values like id but it should demonstrate the performance. I can see the page links but unless I make the window full screen and zoom out (on my 24" monitor) I can't see the all the page links - e.g. the link for Page 1 is cut off, as well as anything beyond page 42.





              1. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?




              Unless the API endpoint accepts a parameter for the page size, you would likely need to utilize a server-side scripting language like Python, Ruby on Rails, PHP, C#, VB.NET, ASP, etc. which could return the specified pagesize.





              1. How would you optimize this application, on the front end?




              I don't see much that stands out as an obvious place to optimize, though I do notice that the promise callback to $http.get(url) has two calls to filterList() separated by three assignment lines:



              $http.get(url)
              .then(function(data) {
              // posts arary
              $scope.posts = data.data;
              $scope.filterList();

              // Paginate
              $scope.pageNum = 1;
              $scope.perPage = 24;
              $scope.startAt = 0;
              $scope.filterList();


              Is there any way to reduce the two calls to a single call there? Perhaps the first call is not needed?



              Then looking at that promise callback, I see the three methods set on $scope there: currentPage, prevPage and nextPage. I would recommend moving those methods out of the callback handler, so that all it does is set the post data on $scope and related paging variables.



              Also, I see a flaw with the jQuery code used for scrolling to the top (which doesn't appear to be working because there are no elements matching the selector .pagination > li > a when the DOM is loaded). I tried finding an equivalent in AngularJS but found little... There is a solution I found from this post that uses vanillaJS. I had to alter the href attributes on the anchors to use it.



              $scope.scrollToTop = function() {
              var scrollDuration = 500;
              var scrollStep = -window.scrollY / (scrollDuration / 10);

              var scrollInterval = setInterval(function() {
              if (window.scrollY != 0) {
              window.scrollBy(0, scrollStep);
              } else {
              clearInterval(scrollInterval);
              }
              }, 15);
              };


              Then that can be used in the click handler methods:



                $scope.currentPage = function(index) {
              $scope.pageNum = index + 1;
              $scope.startAt = index * $scope.perPage;
              $scope.scrollToTop();
              };


              Though in order to stop the anchor navigation, the default event handling will need to be prevented:



              <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>


              Updated code



              See modified code below.






              var root = 'https://jsonplaceholder.typicode.com';

              // Create an Angular module named "postsApp"
              var app = angular.module("postsApp", );

              // Create controller for the "postsApp" module
              app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
              var url = root + "/posts";
              $scope.postList = ;
              $scope.search = "";
              $scope.filterList = function() {
              var oldList = $scope.postList || ;
              $scope.postList = $filter('filter')($scope.posts, $scope.search);
              if (oldList.length != $scope.postList.length) {
              $scope.pageNum = 1;
              $scope.startAt = 0;
              };
              $scope.itemsCount = $scope.postList.length;
              $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
              };


              $scope.currentPage = function(index) {
              $scope.pageNum = index + 1;
              $scope.startAt = index * $scope.perPage;
              $scope.scrollToTop();
              };

              $scope.prevPage = function() {
              if ($scope.pageNum > 1) {
              $scope.pageNum = $scope.pageNum - 1;
              $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
              $scope.scrollToTop();
              }
              };

              $scope.nextPage = function() {
              if ($scope.pageNum < $scope.pageMax) {
              $scope.pageNum = $scope.pageNum + 1;
              $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
              $scope.scrollToTop();
              }
              };
              $scope.scrollToTop = function() {
              var scrollDuration = 500;
              var scrollStep = -window.scrollY / (scrollDuration / 10);

              var scrollInterval = setInterval(function() {
              if (window.scrollY != 0) {
              window.scrollBy(0, scrollStep);
              } else {
              clearInterval(scrollInterval);
              }
              }, 15);
              };
              $http.get(url)
              .then(function(data) {
              // posts arary
              $scope.posts = data.data;
              $scope.filterList();

              // Paginate
              $scope.pageNum = 1;
              $scope.perPage = 24;
              $scope.startAt = 0;
              $scope.filterList();
              });
              }]);

              .posts-grid {
              margin-top: 25px;
              display: flex;
              flex-wrap: wrap;
              }

              .posts-grid>[class*='col-'] {
              display: flex;
              flex-direction: column;
              margin-bottom: 25px;
              }

              .posts-grid .post {
              background: #fff;
              border-top: 1px solid #d5d5d5;
              box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
              }

              .posts-grid .text {
              padding: 8px;
              }

              .posts-grid .card-title {
              font-size: 1.25rem;
              margin-bottom: 8px;
              text-transform: capitalize;
              }

              .posts-grid .read-more {
              padding: 0 8px 8px 8px;
              }

              .posts-grid .text-muted {
              margin-bottom: 8px;
              }

              .posts-grid .thumbnail img {
              display: block;
              width: 100%;
              height: auto;
              }

              .posts-grid p {
              text-align: justify;
              }

              .posts-grid .post {
              flex-grow: 1;
              display: flex;
              flex-direction: column;
              }

              .posts-grid .read-more {
              margin-top: auto;
              }

              .pagination>li>a,
              .pagination>li>a:hover,
              .pagination>li>span {
              color: #585858;
              line-height: 1;
              padding: 6px 12px;
              text-decoration: none;
              }

              .pagination>.active>a,
              .pagination>.active>span,
              .pagination>.active>a:hover,
              .pagination>.active>span:hover,
              .pagination>.active>a:focus,
              .pagination>.active>span:focus {
              background-color: #007bff;
              border-color: #2b7c2b;
              color: #fff;
              }

              @media (max-width: 767px) {
              .container {
              max-width: 100%;
              }
              }

              @media (max-width: 575px) {
              .container {
              max-width: 100%;
              padding-left: 0;
              padding-right: 0;
              }
              .posts-grid>[class*='col-'] {
              padding-left: 5px;
              padding-right: 5px;
              }
              }

              <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
              <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
              <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
              <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
              <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


              <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
              <!-- Brand -->
              <a class="navbar-brand" href="#">My Blog</a>
              <!-- Toggler/collapsibe Button -->
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
              <span class="navbar-toggler-icon"></span>
              </button>

              <!-- Navbar links -->
              <div class="collapse navbar-collapse" id="collapsibleNavbar">
              <ul class="navbar-nav ml-auto">
              <li class="nav-item">
              <a class="nav-link active" href="#">Contacts</a>
              </li>
              <li class="nav-item">
              <a class="nav-link" href="#">About us</a>
              </li>
              <li class="nav-item">
              <a class="nav-link btn btn-outline-primary" href="#">Login</a>
              </li>
              </ul>
              </div>
              </nav>

              <div data-ng-app="postsApp">
              <div class="container" data-ng-controller="postsCtrl">
              <div class="row">
              <div class="col-sm-9 mx-auto">
              <div class="form-group search-box mt-3 px-3">
              <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
              </div>
              </div>
              </div>
              <div class="posts-grid" ng-if="postList.length > 0">
              <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
              <div class="post">
              <div class="thumbnail">
              <img src="//lorempixel.com/450/300" />
              </div>
              <div class="text">
              <h3 class="card-title">{{post.title}}</h3>
              <p class="text-muted">{{post.body}}</p>
              </div>
              <div class="read-more">
              <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
              </div>
              </div>
              </div>
              </div>
              <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
              <div ng-if="pageMax > 1">
              <ul class="pagination pagination-sm justify-content-center">
              <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
              <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
              <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
              </li>
              <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
              </ul>
              </div>
              </div>
              </div>








              share|improve this answer























                up vote
                0
                down vote










                up vote
                0
                down vote











                1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?




                Try it with 10 times as many posts. I know that just duplicates each post 10 times without modifying the unique values like id but it should demonstrate the performance. I can see the page links but unless I make the window full screen and zoom out (on my 24" monitor) I can't see the all the page links - e.g. the link for Page 1 is cut off, as well as anything beyond page 42.





                1. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?




                Unless the API endpoint accepts a parameter for the page size, you would likely need to utilize a server-side scripting language like Python, Ruby on Rails, PHP, C#, VB.NET, ASP, etc. which could return the specified pagesize.





                1. How would you optimize this application, on the front end?




                I don't see much that stands out as an obvious place to optimize, though I do notice that the promise callback to $http.get(url) has two calls to filterList() separated by three assignment lines:



                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();


                Is there any way to reduce the two calls to a single call there? Perhaps the first call is not needed?



                Then looking at that promise callback, I see the three methods set on $scope there: currentPage, prevPage and nextPage. I would recommend moving those methods out of the callback handler, so that all it does is set the post data on $scope and related paging variables.



                Also, I see a flaw with the jQuery code used for scrolling to the top (which doesn't appear to be working because there are no elements matching the selector .pagination > li > a when the DOM is loaded). I tried finding an equivalent in AngularJS but found little... There is a solution I found from this post that uses vanillaJS. I had to alter the href attributes on the anchors to use it.



                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };


                Then that can be used in the click handler methods:



                  $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };


                Though in order to stop the anchor navigation, the default event handling will need to be prevented:



                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>


                Updated code



                See modified code below.






                var root = 'https://jsonplaceholder.typicode.com';

                // Create an Angular module named "postsApp"
                var app = angular.module("postsApp", );

                // Create controller for the "postsApp" module
                app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
                var url = root + "/posts";
                $scope.postList = ;
                $scope.search = "";
                $scope.filterList = function() {
                var oldList = $scope.postList || ;
                $scope.postList = $filter('filter')($scope.posts, $scope.search);
                if (oldList.length != $scope.postList.length) {
                $scope.pageNum = 1;
                $scope.startAt = 0;
                };
                $scope.itemsCount = $scope.postList.length;
                $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
                };


                $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };

                $scope.prevPage = function() {
                if ($scope.pageNum > 1) {
                $scope.pageNum = $scope.pageNum - 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };

                $scope.nextPage = function() {
                if ($scope.pageNum < $scope.pageMax) {
                $scope.pageNum = $scope.pageNum + 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };
                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };
                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();
                });
                }]);

                .posts-grid {
                margin-top: 25px;
                display: flex;
                flex-wrap: wrap;
                }

                .posts-grid>[class*='col-'] {
                display: flex;
                flex-direction: column;
                margin-bottom: 25px;
                }

                .posts-grid .post {
                background: #fff;
                border-top: 1px solid #d5d5d5;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
                }

                .posts-grid .text {
                padding: 8px;
                }

                .posts-grid .card-title {
                font-size: 1.25rem;
                margin-bottom: 8px;
                text-transform: capitalize;
                }

                .posts-grid .read-more {
                padding: 0 8px 8px 8px;
                }

                .posts-grid .text-muted {
                margin-bottom: 8px;
                }

                .posts-grid .thumbnail img {
                display: block;
                width: 100%;
                height: auto;
                }

                .posts-grid p {
                text-align: justify;
                }

                .posts-grid .post {
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                }

                .posts-grid .read-more {
                margin-top: auto;
                }

                .pagination>li>a,
                .pagination>li>a:hover,
                .pagination>li>span {
                color: #585858;
                line-height: 1;
                padding: 6px 12px;
                text-decoration: none;
                }

                .pagination>.active>a,
                .pagination>.active>span,
                .pagination>.active>a:hover,
                .pagination>.active>span:hover,
                .pagination>.active>a:focus,
                .pagination>.active>span:focus {
                background-color: #007bff;
                border-color: #2b7c2b;
                color: #fff;
                }

                @media (max-width: 767px) {
                .container {
                max-width: 100%;
                }
                }

                @media (max-width: 575px) {
                .container {
                max-width: 100%;
                padding-left: 0;
                padding-right: 0;
                }
                .posts-grid>[class*='col-'] {
                padding-left: 5px;
                padding-right: 5px;
                }
                }

                <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
                <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
                <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


                <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
                <!-- Brand -->
                <a class="navbar-brand" href="#">My Blog</a>
                <!-- Toggler/collapsibe Button -->
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
                <span class="navbar-toggler-icon"></span>
                </button>

                <!-- Navbar links -->
                <div class="collapse navbar-collapse" id="collapsibleNavbar">
                <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                <a class="nav-link active" href="#">Contacts</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="#">About us</a>
                </li>
                <li class="nav-item">
                <a class="nav-link btn btn-outline-primary" href="#">Login</a>
                </li>
                </ul>
                </div>
                </nav>

                <div data-ng-app="postsApp">
                <div class="container" data-ng-controller="postsCtrl">
                <div class="row">
                <div class="col-sm-9 mx-auto">
                <div class="form-group search-box mt-3 px-3">
                <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
                </div>
                </div>
                </div>
                <div class="posts-grid" ng-if="postList.length > 0">
                <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
                <div class="post">
                <div class="thumbnail">
                <img src="//lorempixel.com/450/300" />
                </div>
                <div class="text">
                <h3 class="card-title">{{post.title}}</h3>
                <p class="text-muted">{{post.body}}</p>
                </div>
                <div class="read-more">
                <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
                </div>
                </div>
                </div>
                </div>
                <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
                <div ng-if="pageMax > 1">
                <ul class="pagination pagination-sm justify-content-center">
                <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
                <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
                </li>
                <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
                </ul>
                </div>
                </div>
                </div>








                share|improve this answer














                1. What if there ware 10 or 100 times as many posts, would the application have a performance (page load) problem?




                Try it with 10 times as many posts. I know that just duplicates each post 10 times without modifying the unique values like id but it should demonstrate the performance. I can see the page links but unless I make the window full screen and zoom out (on my 24" monitor) I can't see the all the page links - e.g. the link for Page 1 is cut off, as well as anything beyond page 42.





                1. Is there a better way to paginate the application; one that would load as many items from posts.json as there are displayed on one page (24 items), instead of the entire JSON file?




                Unless the API endpoint accepts a parameter for the page size, you would likely need to utilize a server-side scripting language like Python, Ruby on Rails, PHP, C#, VB.NET, ASP, etc. which could return the specified pagesize.





                1. How would you optimize this application, on the front end?




                I don't see much that stands out as an obvious place to optimize, though I do notice that the promise callback to $http.get(url) has two calls to filterList() separated by three assignment lines:



                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();


                Is there any way to reduce the two calls to a single call there? Perhaps the first call is not needed?



                Then looking at that promise callback, I see the three methods set on $scope there: currentPage, prevPage and nextPage. I would recommend moving those methods out of the callback handler, so that all it does is set the post data on $scope and related paging variables.



                Also, I see a flaw with the jQuery code used for scrolling to the top (which doesn't appear to be working because there are no elements matching the selector .pagination > li > a when the DOM is loaded). I tried finding an equivalent in AngularJS but found little... There is a solution I found from this post that uses vanillaJS. I had to alter the href attributes on the anchors to use it.



                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };


                Then that can be used in the click handler methods:



                  $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };


                Though in order to stop the anchor navigation, the default event handling will need to be prevented:



                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>


                Updated code



                See modified code below.






                var root = 'https://jsonplaceholder.typicode.com';

                // Create an Angular module named "postsApp"
                var app = angular.module("postsApp", );

                // Create controller for the "postsApp" module
                app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
                var url = root + "/posts";
                $scope.postList = ;
                $scope.search = "";
                $scope.filterList = function() {
                var oldList = $scope.postList || ;
                $scope.postList = $filter('filter')($scope.posts, $scope.search);
                if (oldList.length != $scope.postList.length) {
                $scope.pageNum = 1;
                $scope.startAt = 0;
                };
                $scope.itemsCount = $scope.postList.length;
                $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
                };


                $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };

                $scope.prevPage = function() {
                if ($scope.pageNum > 1) {
                $scope.pageNum = $scope.pageNum - 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };

                $scope.nextPage = function() {
                if ($scope.pageNum < $scope.pageMax) {
                $scope.pageNum = $scope.pageNum + 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };
                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };
                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();
                });
                }]);

                .posts-grid {
                margin-top: 25px;
                display: flex;
                flex-wrap: wrap;
                }

                .posts-grid>[class*='col-'] {
                display: flex;
                flex-direction: column;
                margin-bottom: 25px;
                }

                .posts-grid .post {
                background: #fff;
                border-top: 1px solid #d5d5d5;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
                }

                .posts-grid .text {
                padding: 8px;
                }

                .posts-grid .card-title {
                font-size: 1.25rem;
                margin-bottom: 8px;
                text-transform: capitalize;
                }

                .posts-grid .read-more {
                padding: 0 8px 8px 8px;
                }

                .posts-grid .text-muted {
                margin-bottom: 8px;
                }

                .posts-grid .thumbnail img {
                display: block;
                width: 100%;
                height: auto;
                }

                .posts-grid p {
                text-align: justify;
                }

                .posts-grid .post {
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                }

                .posts-grid .read-more {
                margin-top: auto;
                }

                .pagination>li>a,
                .pagination>li>a:hover,
                .pagination>li>span {
                color: #585858;
                line-height: 1;
                padding: 6px 12px;
                text-decoration: none;
                }

                .pagination>.active>a,
                .pagination>.active>span,
                .pagination>.active>a:hover,
                .pagination>.active>span:hover,
                .pagination>.active>a:focus,
                .pagination>.active>span:focus {
                background-color: #007bff;
                border-color: #2b7c2b;
                color: #fff;
                }

                @media (max-width: 767px) {
                .container {
                max-width: 100%;
                }
                }

                @media (max-width: 575px) {
                .container {
                max-width: 100%;
                padding-left: 0;
                padding-right: 0;
                }
                .posts-grid>[class*='col-'] {
                padding-left: 5px;
                padding-right: 5px;
                }
                }

                <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
                <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
                <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


                <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
                <!-- Brand -->
                <a class="navbar-brand" href="#">My Blog</a>
                <!-- Toggler/collapsibe Button -->
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
                <span class="navbar-toggler-icon"></span>
                </button>

                <!-- Navbar links -->
                <div class="collapse navbar-collapse" id="collapsibleNavbar">
                <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                <a class="nav-link active" href="#">Contacts</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="#">About us</a>
                </li>
                <li class="nav-item">
                <a class="nav-link btn btn-outline-primary" href="#">Login</a>
                </li>
                </ul>
                </div>
                </nav>

                <div data-ng-app="postsApp">
                <div class="container" data-ng-controller="postsCtrl">
                <div class="row">
                <div class="col-sm-9 mx-auto">
                <div class="form-group search-box mt-3 px-3">
                <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
                </div>
                </div>
                </div>
                <div class="posts-grid" ng-if="postList.length > 0">
                <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
                <div class="post">
                <div class="thumbnail">
                <img src="//lorempixel.com/450/300" />
                </div>
                <div class="text">
                <h3 class="card-title">{{post.title}}</h3>
                <p class="text-muted">{{post.body}}</p>
                </div>
                <div class="read-more">
                <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
                </div>
                </div>
                </div>
                </div>
                <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
                <div ng-if="pageMax > 1">
                <ul class="pagination pagination-sm justify-content-center">
                <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
                <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
                </li>
                <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
                </ul>
                </div>
                </div>
                </div>








                var root = 'https://jsonplaceholder.typicode.com';

                // Create an Angular module named "postsApp"
                var app = angular.module("postsApp", );

                // Create controller for the "postsApp" module
                app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
                var url = root + "/posts";
                $scope.postList = ;
                $scope.search = "";
                $scope.filterList = function() {
                var oldList = $scope.postList || ;
                $scope.postList = $filter('filter')($scope.posts, $scope.search);
                if (oldList.length != $scope.postList.length) {
                $scope.pageNum = 1;
                $scope.startAt = 0;
                };
                $scope.itemsCount = $scope.postList.length;
                $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
                };


                $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };

                $scope.prevPage = function() {
                if ($scope.pageNum > 1) {
                $scope.pageNum = $scope.pageNum - 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };

                $scope.nextPage = function() {
                if ($scope.pageNum < $scope.pageMax) {
                $scope.pageNum = $scope.pageNum + 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };
                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };
                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();
                });
                }]);

                .posts-grid {
                margin-top: 25px;
                display: flex;
                flex-wrap: wrap;
                }

                .posts-grid>[class*='col-'] {
                display: flex;
                flex-direction: column;
                margin-bottom: 25px;
                }

                .posts-grid .post {
                background: #fff;
                border-top: 1px solid #d5d5d5;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
                }

                .posts-grid .text {
                padding: 8px;
                }

                .posts-grid .card-title {
                font-size: 1.25rem;
                margin-bottom: 8px;
                text-transform: capitalize;
                }

                .posts-grid .read-more {
                padding: 0 8px 8px 8px;
                }

                .posts-grid .text-muted {
                margin-bottom: 8px;
                }

                .posts-grid .thumbnail img {
                display: block;
                width: 100%;
                height: auto;
                }

                .posts-grid p {
                text-align: justify;
                }

                .posts-grid .post {
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                }

                .posts-grid .read-more {
                margin-top: auto;
                }

                .pagination>li>a,
                .pagination>li>a:hover,
                .pagination>li>span {
                color: #585858;
                line-height: 1;
                padding: 6px 12px;
                text-decoration: none;
                }

                .pagination>.active>a,
                .pagination>.active>span,
                .pagination>.active>a:hover,
                .pagination>.active>span:hover,
                .pagination>.active>a:focus,
                .pagination>.active>span:focus {
                background-color: #007bff;
                border-color: #2b7c2b;
                color: #fff;
                }

                @media (max-width: 767px) {
                .container {
                max-width: 100%;
                }
                }

                @media (max-width: 575px) {
                .container {
                max-width: 100%;
                padding-left: 0;
                padding-right: 0;
                }
                .posts-grid>[class*='col-'] {
                padding-left: 5px;
                padding-right: 5px;
                }
                }

                <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
                <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
                <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


                <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
                <!-- Brand -->
                <a class="navbar-brand" href="#">My Blog</a>
                <!-- Toggler/collapsibe Button -->
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
                <span class="navbar-toggler-icon"></span>
                </button>

                <!-- Navbar links -->
                <div class="collapse navbar-collapse" id="collapsibleNavbar">
                <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                <a class="nav-link active" href="#">Contacts</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="#">About us</a>
                </li>
                <li class="nav-item">
                <a class="nav-link btn btn-outline-primary" href="#">Login</a>
                </li>
                </ul>
                </div>
                </nav>

                <div data-ng-app="postsApp">
                <div class="container" data-ng-controller="postsCtrl">
                <div class="row">
                <div class="col-sm-9 mx-auto">
                <div class="form-group search-box mt-3 px-3">
                <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
                </div>
                </div>
                </div>
                <div class="posts-grid" ng-if="postList.length > 0">
                <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
                <div class="post">
                <div class="thumbnail">
                <img src="//lorempixel.com/450/300" />
                </div>
                <div class="text">
                <h3 class="card-title">{{post.title}}</h3>
                <p class="text-muted">{{post.body}}</p>
                </div>
                <div class="read-more">
                <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
                </div>
                </div>
                </div>
                </div>
                <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
                <div ng-if="pageMax > 1">
                <ul class="pagination pagination-sm justify-content-center">
                <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
                <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
                </li>
                <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
                </ul>
                </div>
                </div>
                </div>





                var root = 'https://jsonplaceholder.typicode.com';

                // Create an Angular module named "postsApp"
                var app = angular.module("postsApp", );

                // Create controller for the "postsApp" module
                app.controller("postsCtrl", ["$scope", "$http", "$filter", function($scope, $http, $filter) {
                var url = root + "/posts";
                $scope.postList = ;
                $scope.search = "";
                $scope.filterList = function() {
                var oldList = $scope.postList || ;
                $scope.postList = $filter('filter')($scope.posts, $scope.search);
                if (oldList.length != $scope.postList.length) {
                $scope.pageNum = 1;
                $scope.startAt = 0;
                };
                $scope.itemsCount = $scope.postList.length;
                $scope.pageMax = Math.ceil($scope.itemsCount / $scope.perPage);
                };


                $scope.currentPage = function(index) {
                $scope.pageNum = index + 1;
                $scope.startAt = index * $scope.perPage;
                $scope.scrollToTop();
                };

                $scope.prevPage = function() {
                if ($scope.pageNum > 1) {
                $scope.pageNum = $scope.pageNum - 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };

                $scope.nextPage = function() {
                if ($scope.pageNum < $scope.pageMax) {
                $scope.pageNum = $scope.pageNum + 1;
                $scope.startAt = ($scope.pageNum - 1) * $scope.perPage;
                $scope.scrollToTop();
                }
                };
                $scope.scrollToTop = function() {
                var scrollDuration = 500;
                var scrollStep = -window.scrollY / (scrollDuration / 10);

                var scrollInterval = setInterval(function() {
                if (window.scrollY != 0) {
                window.scrollBy(0, scrollStep);
                } else {
                clearInterval(scrollInterval);
                }
                }, 15);
                };
                $http.get(url)
                .then(function(data) {
                // posts arary
                $scope.posts = data.data;
                $scope.filterList();

                // Paginate
                $scope.pageNum = 1;
                $scope.perPage = 24;
                $scope.startAt = 0;
                $scope.filterList();
                });
                }]);

                .posts-grid {
                margin-top: 25px;
                display: flex;
                flex-wrap: wrap;
                }

                .posts-grid>[class*='col-'] {
                display: flex;
                flex-direction: column;
                margin-bottom: 25px;
                }

                .posts-grid .post {
                background: #fff;
                border-top: 1px solid #d5d5d5;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
                }

                .posts-grid .text {
                padding: 8px;
                }

                .posts-grid .card-title {
                font-size: 1.25rem;
                margin-bottom: 8px;
                text-transform: capitalize;
                }

                .posts-grid .read-more {
                padding: 0 8px 8px 8px;
                }

                .posts-grid .text-muted {
                margin-bottom: 8px;
                }

                .posts-grid .thumbnail img {
                display: block;
                width: 100%;
                height: auto;
                }

                .posts-grid p {
                text-align: justify;
                }

                .posts-grid .post {
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                }

                .posts-grid .read-more {
                margin-top: auto;
                }

                .pagination>li>a,
                .pagination>li>a:hover,
                .pagination>li>span {
                color: #585858;
                line-height: 1;
                padding: 6px 12px;
                text-decoration: none;
                }

                .pagination>.active>a,
                .pagination>.active>span,
                .pagination>.active>a:hover,
                .pagination>.active>span:hover,
                .pagination>.active>a:focus,
                .pagination>.active>span:focus {
                background-color: #007bff;
                border-color: #2b7c2b;
                color: #fff;
                }

                @media (max-width: 767px) {
                .container {
                max-width: 100%;
                }
                }

                @media (max-width: 575px) {
                .container {
                max-width: 100%;
                padding-left: 0;
                padding-right: 0;
                }
                .posts-grid>[class*='col-'] {
                padding-left: 5px;
                padding-right: 5px;
                }
                }

                <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
                <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
                <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.js"></script>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>


                <nav class="navbar navbar-expand-md bg-dark navbar-dark sticky-top">
                <!-- Brand -->
                <a class="navbar-brand" href="#">My Blog</a>
                <!-- Toggler/collapsibe Button -->
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
                <span class="navbar-toggler-icon"></span>
                </button>

                <!-- Navbar links -->
                <div class="collapse navbar-collapse" id="collapsibleNavbar">
                <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                <a class="nav-link active" href="#">Contacts</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="#">About us</a>
                </li>
                <li class="nav-item">
                <a class="nav-link btn btn-outline-primary" href="#">Login</a>
                </li>
                </ul>
                </div>
                </nav>

                <div data-ng-app="postsApp">
                <div class="container" data-ng-controller="postsCtrl">
                <div class="row">
                <div class="col-sm-9 mx-auto">
                <div class="form-group search-box mt-3 px-3">
                <input type="text" class="form-control" id="search" placeholder="Search post" data-ng-model="search" ng-change="filterList()">
                </div>
                </div>
                </div>
                <div class="posts-grid" ng-if="postList.length > 0">
                <div class="col-xs-12 col-sm-6 col-lg-4 col-xl-3" data-ng-repeat="post in postList | limitTo : perPage : startAt">
                <div class="post">
                <div class="thumbnail">
                <img src="//lorempixel.com/450/300" />
                </div>
                <div class="text">
                <h3 class="card-title">{{post.title}}</h3>
                <p class="text-muted">{{post.body}}</p>
                </div>
                <div class="read-more">
                <a class="btn btn-block btn-sm btn-primary" href="#">Read more</a>
                </div>
                </div>
                </div>
                </div>
                <p ng-if="postList.length <= 0" class="text-center">There are no posts</p>
                <div ng-if="pageMax > 1">
                <ul class="pagination pagination-sm justify-content-center">
                <li class="page-item"><a href="#" ng-click="prevPage(); $event.preventDefault();"><i class="fa fa-chevron-left"></i></a></li>
                <li ng-repeat="n in .constructor(pageMax) track by $index" ng-class="{true: 'active'}[$index == pageNum - 1]">
                <a href="#" ng-click="currentPage($index); $event.preventDefault();">{{$index+1}}</a>
                </li>
                <li><a href="#" ng-click="nextPage(); $event.preventDefault();"><i class="fa fa-chevron-right"></i></a></li>
                </ul>
                </div>
                </div>
                </div>






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered yesterday









                Sᴀᴍ Onᴇᴌᴀ

                8,07461751




                8,07461751






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203089%2fpaginated-angularjs-posts-application%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Mont Emei

                    Province de Neuquén

                    Journaliste