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 an interface for 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:
- What if there ware 10 or 100 times as many posts, would the application have a performance problem (e.g. with page load)?
- 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?
- How would you optimize this application, on the front end?