I have an application which displays a table (xeditable) by retrieving data from a mySQL database.
The database table which displays the content constantly changes in the background and I am trying to figure out how I can reflect those changes in the UI without refreshing the page or the entire table each time a change was made in the database.
Basically, I would like to "push" the changes to the view and a change has been detected in the database.
I understand that is not available with PHP/MySQL so I guess there must be a way around it by using either a 3rd party library or even a mid tier nodeJS...
I read on google's firebase (with ReactJS) how this can be achieved OTB and thought it may be possible to do without rewriting my entire app.
Any thoughts?
snippets of my current codebase:
app.js
var app = angular.module('myApp', ['ui.bootstrap','countTo','xeditable']);
app.filter('startFrom', function() {
return function(input, start) {
if(input) {
start = +start; //parse to int
return input.slice(start);
}
return [];
}
});
app.filter('abs', function () {
return function(val) {
return Math.abs(val);
}
});
app.run(function(editableOptions) {
editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'default'
});
app.config(['$compileProvider', function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|http?|file|data):/);
}]);
app.controller('customersCrtl', function ($scope, $http, $timeout, $interval, $window) {
var init = function loadItems(){
$scope.$emit('LOAD');
$scope.progressBar = { progress : 0 };
$http.get('ajax/getItems.php').success(function(data){
$scope.list = data;
$scope.updateID = data.updateID;
$scope.currentPage = 1; //current page
$scope.entryLimit = 50; //max no of items to display in a page
$scope.filteredItems = $scope.list.length; //Initially for no filter
$scope.totalItems = $scope.list.length;
$scope.priceInfo = {
min: 0,
max: 100000
}
$scope.$emit('UNLOAD');
var array = $scope.list;
var flags = [], output = [], l = array.length, i;
for(i=0; i<l; i++) {
if( flags[array[i].category]) continue;
flags[array[i].category] = true;
output.push(array[i].category);
}
var array_manufact = $scope.list;
var flags = [], output_manufact = [], l = array_manufact.length, i;
for( i=0; i<l; i++) {
if( flags[array_manufact[i].manufacturer]) continue;
flags[array_manufact[i].manufacturer] = true;
output_manufact.push(array_manufact[i].manufacturer);
}
$scope.cats = output;
$scope.manufact = output_manufact;
//console.log($scope.cats);
});
(function progress(){
if($scope.progressBar.progress < 100){
$timeout(function(){
$scope.progressBar.progress += 1;
progress();
},5);
}
})();
index.html:
<html>
<tbody>
<tr ng-repeat="data in filtered = (list | filter:search | filter:{category:by_cat} | filter:{notes:by_notes} | filter:{Locked:by_search_locked} | filter:{external_link:by_external_link} | filter:{name:by_name} | filter:{cat2:by_cat2} | filter:{errorStatus:search`enter code here`_status} | filter:{error_status:search_status_opt} | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit" ng-class="{'success' : data.error_status=='--example1--', 'warning' : data.place_1_name!='my_stock', 'danger' : data.error_status == 'not loaded'}">
<td ng-class='{red : data.Locked==1}'><center>{{data.ID}}</center></td>
<td ng-class='{red : data.Locked==1}'><center>{{data.name}}</center></td>
<td ng-class='{red : data.Locked==1}'>
<center>
cat: {{data.category}}
<br/>
cat2: {{data.category2}}
</center></td>
<td ng-class='{red : data.Locked==1}'><center>{{data.price}}</center></td>
<td ng-class='{red : data.Locked==1}'><center>
<div ng-if="data.notes.length > 0">
<div ng-repeat="i in data.notes">
<b><font color="red">{{i}}</font></b>
</div>
</div>
<div ng-if="data.notes.length==0">
---
</div>
</center>
</td>
<td ng-class='{red : data.Locked==1}'>
<center>
<div ng-if="data.minPrice !=''">
<a href="#" editable-text="data.minPrice" onbeforesave="updateMinPrice(data.productID,$data)">
<font class='green'>{{data.minPrice}}</font>
</a>
<span ng-if="data.minPrice_last_changed!='---'">
<br/>
<br/>
LastChangedON: <font dir="ltr">{{data.minPrice_last_changed}}</font>
</span>
</div>
<div ng-if="data.minPrice == '' ">
<div editable-text="data.minPrice" onbeforesave="updateMinPrice(data.productID,$data)">
<font class='orange'>click to add</font>
</div>
</div>
</center></td>
</html>
Use the $interval1 service to poll your endpoint
app.controller('MainCtrl', function($scope, $http, $interval) {
$interval(function(){
$http.get('/api/metrics').then(function(res){
$scope.data = res.data;
});
},5000);
});
Here is a plunker simulating polling an endpoint with $interval
https://docs.angularjs.org/api/ng/service/$interval
Related
Unable to bind the table with data i.e. collected from service.
Controller code is as:
app.controller('outletTypeController', ['$scope', '$location', '$timeout', 'outletTypesService', '$uibModal', 'NgTableParams', '$q', '$log',
function ($scope, $location, $timeout, outletTypesService, $uibModal, NgTableParams, $q, $log) {
$scope.message = "";
$scope.animationsEnabled = true;
$scope.outletTypes = [];
//To Load Outlet Types in same controller
$scope.LoadOutletTypes = function () {
outletTypesService.getOutletTypes().then(function (results) {
$scope.outletTypes = results.data.Result;
}, function (error) {
var errors = [];
if (response.data != null) {
for (var key in response.data.modelState) {
for (var i = 0; i < response.data.modelState[key].length; i++) {
errors.push(response.data.modelState[key][i]);
}
}
}
$scope.message = "Failed to load data due to:" + errors.join(' ');
});
};
//To Bind OutletType With Options in same controller
$scope.outletTypeWithOptions = function () {
//to set outletTypes
$scope.LoadOutletTypes();
var initialParams = {
count: 2 // initial page size
};
var initialSettings = {
// page size buttons (right set of buttons in demo)
counts: [10, 50, 100],
// determines the pager buttons (left set of buttons in demo)
paginationMaxBlocks: 5,
paginationMinBlocks: 1,
dataset: $scope.outletTypes
};
return new NgTableParams(initialParams, initialSettings);
};
and HTML View Code is as:
<div id="body" ng-controller="outletTypeController">
<table ng-table="outletTypeWithOptions" class="table table-striped" show-filter="true" cellspacing="0" width="100%">
<tr data-ng-repeat="type in $data">
<td title="'Logo'" class="text-center">
<div class="logoImg">
<img src="images/halalpalc.png" width="30" />
</div>
</td>
<td title="'Outlet Type'" filter="{ OTypeName: 'text'}" sortable="'OTypeName'">
{{ type.OTypeName }}
</td>
<td title="'Icon Rated'">I R</td>
<td title="'Icon Unrated'">I U</td>
<td title="'Status'" class="text-center status">
{{ type.IsActive}}
</td>
<td title="'Action'" class="text-center">
<!--<a href="#" class="editAction" data-toggle="modal" data-target="#myModal" title="Edit">
<img src="images/SVG_icons/edit_action.svg" />
</a>-->
<button data-ng-click="open()" class="btn btn-warning">Edit</button>
<!--<img src="images/SVG_icons/close_action.svg" />-->
</td>
</tr>
</table>
</div>
But unable to bind the data, However I try to bind it with Static Data it's properly working.
Here $scope.LoadOutletTypes is an async method call, which will asynchronously populate $scope.outletTypes with results. Rest of the code after LoadOutletTypes method call won't wait for result instead proceeds for execution, which will populate dataset in initialSettings object with null value(If it was unable to load the outlet types in time. But this will work for static data).
Try the below code..
$scope.LoadOutletTypes = function () {
outletTypesService.getOutletTypes().then(function (results) {
$scope.outletTypes = results.data.Result;
$scope.setOutletTypeWithOptions();
}, function (error) {
var errors = [];
if (response.data != null) {
for (var key in response.data.modelState) {
for (var i = 0; i < response.data.modelState[key].length; i++) {
errors.push(response.data.modelState[key][i]);
}
}
}
$scope.message = "Failed to load data due to:" + errors.join(' ');
});
};
$scope.setOutletTypeWithOptions = function () {
var initialParams = {
count: 2 // initial page size
};
var initialSettings = {
// page size buttons (right set of buttons in demo)
counts: [10, 50, 100],
// determines the pager buttons (left set of buttons in demo)
paginationMaxBlocks: 5,
paginationMinBlocks: 1,
dataset: $scope.outletTypes
};
$scope.NgTableParams = new NgTableParams(initialParams, initialSettings);
};
<div id="body" ng-controller="outletTypeController">
<table ng-table="NgTableParams" ng-init="LoadOutletTypes()" class="table table-striped" show-filter="true" cellspacing="0" width="100%">
<tr data-ng-repeat="type in $data">....
I 'm a beginner in AngularJS. I have created a page that retrieves records from database along with total records using an Ajax call. After populating the grid with the records, I wanted to refresh the pager control based on values received from the Ajax call such as the total records and the current page number as 1. I'm seeing the pager but is not refreshed. Can you please verify and let me know what I'm missing here?
var angularPersonModule = angular.module("angularPersonModule", ['ui.bootstrap']);
angularPersonModule.controller("AngularPersonController", AngularPersonController);
angularPersonModule.controller("PaginationController", PaginationController);
angularPersonModule.factory("PaginationService", PaginationService);
PaginationController.$inject = ['$scope', '$timeout', '$rootScope', 'PaginationService'];
function PaginationController($scope, $timeout, $rootScope, PaginationService) {
$scope.setPage = function(pageNo) {
if ($scope.currentPage === pageNo) {
return;
}
$scope.currentPage = pageNo;
$rootScope.$broadcast("personPageChanged", $scope.currentPage);
alert("call from setPage");
};
$scope.pageChanged = function() {
var i = 0;
};
$scope.$on('personRefreshPagerControls', function(event, args) {
//the solution:
setTimeout(function() {
//setTimeout is a deffered call (after 1000 miliseconds)
$timeout(function() {
$scope.totalItems = PaginationService.getTotalRecords();
$scope.currentPage = PaginationService.getPageNumber();
$scope.itemsPerPage = PaginationService.getPageSize();
$scope.numPages = PaginationService.getPageCount();
alert("totalItems = " + $scope.totalItems);
alert("currentPage = " + $scope.currentPage);
alert("itemsPerPage = " + $scope.itemsPerPage);
alert("pageCount = " + $scope.numPages);
});
}, 900);
});
}
AngularPersonController.$inject = ['$scope', '$rootScope', 'AngularPersonService', 'PaginationService'];
function AngularPersonController($scope, $rootScope, AngularPersonService, PaginationService) {
$scope.getAll = function() {
$scope.persons = AngularPersonService.search();
PaginationService.setTotalRecords(3);
PaginationService.setPageNumber(1);
PaginationService.setPageSize(2);
PaginationService.setPageCount(2);
$rootScope.$broadcast("personRefreshPagerControls", 3);
}
$scope.$on('personPageChanged', function(event, args) {
alert("pageChanged Event fired");
alert("Received args = " + args);
$scope.getAll();
});
$scope.getAll();
}
function PaginationService() {
var currentPage = 1;
var pageCount = 2;
var pageSize = 2;
var totalRecords = 3;
return {
getPageNumber: function() {
return currentPage;
},
setPageNumber: function(pageNumber) {
currentPage = pageNumber;
},
getPageSize: function() {
return pageSize;
},
setPageSize: function(newPageSize) {
pageSize = newPageSize;
},
getTotalRecords: function() {
return totalRecords;
},
setTotalRecords: function(newTotalRecords) {
totalRecords = newTotalRecords;
},
getPageCount: function() {
return pageCount;
},
setPageCount: function(newPageCount) {
pageCount = newPageCount;
}
}
}
angularPersonModule.factory("AngularPersonService", function($http) {
return {
search: function() {
return [{
"Name": "Hemant"
}, {
"Name": "Ashok"
}, {
"Name": "Murugan"
}];
}
}
});
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-resource.min.js"></script>
<script src="https://raw.github.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-0.14.3.js"></script>
<script src="https://raw.githubusercontent.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-0.14.3.js"></script>
<div ng-app="angularPersonModule">
<div class="panel panel-default">
<div class="panel-body" ng-controller="AngularPersonController">
<table class="table table-striped table-hover">
<thead>
<tr>
<th class="col-md-3">
Name
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="person in persons">
<td>
<span>{{person.Name}}</span>
</td>
</tbody>
</table>
</div>
</div>
<div ng-controller="PaginationController">
<uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
</div>
</div>
Update
Now I have updated the snippet so that you can execute and see the result manually.
I would like to detect the amount of results returned in an ng-repeat loop and then if it is more than a certain number, execute code eg. hide an HTML tag. So if p in pics is more than X then hide something. Not sure how to go about it:
Here is a snippet of my code:
HTML
<li ng-repeat="p in pics">
<img ng-src="{{p.images.thumbnail.url}}" />
<p>{{p.comments.data|getFirstCommentFrom:'alx_lloyd'}}</p>
</li>
JS
(function(){
//Place your own Instagram client_id below. Go to https://instagram.com/developer/clients/manage/ and register your app to get a client ID
var client_id = ''; //redacted
//To get your user ID go to http://jelled.com/instagram/lookup-user-id and enter your Instagram user name to get your user ID
var user_id = ''; //redacted
var app = angular.module('instafeed', ['ngAnimate']);
app.filter('getFirstCommentFrom',function() {
return function(arr, user) {
for(var i=0;i<arr.length;i++) {
if(arr[i].from.username==user)
return arr[i].text;
}
return '';
}
})
app.factory("InstagramAPI", ['$http', function($http) {
return {
fetchPhotos: function(callback){
var endpoint = "https://api.instagram.com/v1/users/self/media/liked/";
endpoint += "?access_token=foobar";
endpoint += "&callback=JSON_CALLBACK";
/* var endpoint = "https://api.instagram.com/v1/users/" + user_id + "/media/recent/?";
endpoint += "?count=99";
endpoint += "&client_id=" + client_id;
endpoint += "&callback=JSON_CALLBACK";
*/
$http.jsonp(endpoint).success(function(response){
callback(response.data);
});
}
}
}]);
app.controller('ShowImages', function($scope, InstagramAPI){
$scope.layout = 'grid';
$scope.data = {};
$scope.pics = [];
InstagramAPI.fetchPhotos(function(data){
$scope.pics = data;
console.log(data)
});
});
})();
You can use ng-hide since your pics are just in an array and check the length of the array, e.g:
<h2 ng-hide="pics.length > 5">HIDE ME</h2>
(function() {
//Place your own Instagram client_id below. Go to https://instagram.com/developer/clients/manage/ and register your app to get a client ID
var client_id = '83aaab0bddea42adb694b689ad169fb1';
//To get your user ID go to http://jelled.com/instagram/lookup-user-id and enter your Instagram user name to get your user ID
var user_id = '179735937';
var app = angular.module('instafeed', ['ngAnimate']);
app.filter('getFirstCommentFrom', function() {
return function(arr, user) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].from.username == user)
return arr[i].text;
}
return '';
}
})
app.factory("InstagramAPI", ['$http',
function($http) {
return {
fetchPhotos: function(callback) {
var endpoint = "https://api.instagram.com/v1/users/self/media/liked/";
endpoint += "?access_token=179735937.83aaab0.e44fe9abccb5415290bfc0765edd45ad";
endpoint += "&callback=JSON_CALLBACK";
$http.jsonp(endpoint).success(function(response) {
callback(response.data);
});
}
}
}
]);
app.controller('ShowImages', function($scope, InstagramAPI) {
$scope.layout = 'grid';
$scope.data = {};
$scope.pics = [];
InstagramAPI.fetchPhotos(function(data) {
$scope.pics = data;
console.log(data)
});
});
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.js"></script>
<div ng-app='instafeed' ng-controller='ShowImages'>
<li ng-repeat="p in pics">
<a href="{{p.link}}" target="_blank">
<img ng-src="{{p.images.thumbnail.url}}" />
</a>
<p>{{p.comments.data|getFirstCommentFrom:'alx_lloyd'}}</p>
</li>
<h2 ng-hide="pics.length > 5">HIDE ME</h2>
</div>
If you want mean is something like "show only the first four results" then you can do this by using $index from the ng-repeat.
For instance the following will show items with 0 <= $index <= 3.
<li ng-repeat="p in pics" ng-if="$index < 4">
You can reference $index anywhere inside the repeat - not just on the repeat itself:
<li ng-repeat="p in pics">
<img ng-src="{{p.images.thumbnail.url}}" />
<p ng-if="$index<4">{{p.comments.data|getFirstCommentFrom:'alx_lloyd'}}</p>
</li>
Alternatively if you want to hide the whole lot then you should be able to take the length from the array:
<div id="container" ng-if="pics.length <= 4">
<li ng-repeat="p in pics">
...
</li>
</div>
For any of these you can choose between ng-if and ng-hide. I would tend to prefer ng-if, as it causes the render to be ignored completely. ng-hide will render all the markup, and then just set to display:none;, which is more likely to be useful where the condition can change due to the user's input to the page (for example).
As #DTing points out, you can also use a filter on the repeat itself if you want to apply the filter at that level:
<li ng-repeat="p in pics | limitTo: 4">
I'd like to implement pagination with AngularJS but I have some problems.
I don't know how to update the $scope.agreements object when clicking on a new page...
var app = angular.module("GestiawebApp", []);
app.controller("AgreementsController", function($scope, $http) {
var nbPages, limit;
$http.get('/api/agreement').success(function(data, status, headers, config) {
limit = data.limit;
nbPages = Math.ceil(data.agreements.length/data.limit);
$scope.agreements = data.agreements.slice(0, limit);
});
$scope.getPages = function() {
var pages = [];
for (var i = 1; i <= nbPages; i++) {
pages.push(i);
}
return pages;
};
$scope.goToPage = function(page){
// simple test to change the agreements, but it seem's not working
$scope.agreements = $scope.agreements.slice(page*limit, (page*limit)+limit);
};
});
<!-- Loop -->
<tr ng-repeat="agreement in agreements | filter:search">
<td>{{agreement.number}}</td>
<td>{{agreement.reference}}</td>
<td>
{{agreement.billbook.capital}} €
</td>
</tr>
<!-- End loop -->
<!-- Paginaiton -->
<ul>
<li ng-repeat="i in getPages() track by $index" ng-click="goToPage($index+1)">{{$index+1}</li>
</ul>
<!-- End pagination -->
You should store all agreements data in a variable to use for paginating
$scope.allAgreements = [];
$http.get('/api/agreement').success(function(data, status, headers, config) {
limit = data.limit;
nbPages = Math.ceil(data.agreements.length/data.limit);
$scope.allAgreements = data.agreements; //new change
$scope.agreements = $scope.allAgreements.slice(0, limit);
});
$scope.goToPage = function(page){
// simple test to change the agreements, but it seem's not working
$scope.agreements = $scope.allAgreements.slice(page*limit, (page*limit)+limit);
};
I have a simple ng-repeat that uses a scroll directive. On load, the first 10 results are displayed, on scoll, more results are show.
The issue i am seeing is that when you type a value in the input, the data that is filtered seems to be the data that is displayed at the current time.
So in the example in my plunker: http://plnkr.co/edit/1qR6CucQdsGqYnVvk70A?p=preview
If you type in Last in the textbox after loading the page (do not scroll), that data is never shown. However, if you scroll to the bottom, then type Last, that result is found.
HTML:
<div id="fixed" directive-when-scrolled="loadMore()">
<ul>
<li ng-repeat="i in items | limitTo: limit | filter: search">{{ i.Title }}</li>
</ul>
</div>
<br>
<input ng-model="searchText">
<button ng-click="performSearch(searchText)">Submit</button>
JS:
app.controller('MainCtrl', function($scope, $filter) {
$scope.name = 'World';
$scope.limit = 5;
var counter = 0;
$scope.loadMore = function() {
$scope.limit += 5;
};
$scope.loadMore();
$scope.performSearch = function(searchText) {
$scope.filtered = $filter('filter')($scope.items, $scope.search);
}
$scope.search = function (item){
if (!$scope.searchText)
return true;
if (item.Title.indexOf($scope.searchText)!=-1 || item.Title.indexOf($scope.searchText)!=-1) {
return true;
}
return false;
};
});
app.directive("directiveWhenScrolled", function() {
return function(scope, elm, attr) {
var raw = elm[0];
elm.bind('scroll', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.directiveWhenScrolled);
}
});
};
});
You need to apply the filter before you limit the results:
<li ng-repeat="i in items | filter: search | limitTo: limit">{{ i.Title }}</li>