Angular js fetch data from multiple table at the same time - angularjs

This is not a question about something where I am getting stuck. It's a question about good practice and performance. My code is working properly. But I want to know the correct way of doing this task. Maybe I am already right. Just look at the scenario and the code and please suggest me the right way.
Requirement
I have near about 20 tables in the database, when a user logs in and go to the dashboard page I need to show the data from all the tables. It is not required to show all the data so I am fetching 25 rows from each table using Angular $http and displaying in the dashboard page.
Code
My Angular js code is:
$scope.$on('$viewContentLoaded', function (event) {
$scope.loadDashboardQue(event, '/route1/getDashboardData?user_id=123', 'table1');
$scope.loadDashboardQue(event, '/route2/getDashboardData?user_id=123', 'table2');
$scope.loadDashboardQue(event, '/route3/getDashboardData?user_id=123', 'table3');
$scope.loadDashboardQue(event, '/route4/getDashboardData?user_id=123', 'table4');
$scope.loadDashboardQue(event, '/route5/getDashboardData?user_id=123', 'table5');
$scope.loadDashboardQue(event, '/routeN/getDashboardData?user_id=123', 'tableN');
});
Now loadDashboardQue function defination
$scope.loadDashboardQue = function (event, url, tableName) {
$http.get(url)
.success(function (response) {
if (response.status === 'success') {
//Assigning data to HTML table
if (tableName === 'table1') {
$scope.table1Data = response.data;
}
if (tableName === 'table2') {
$scope.table2Data = response.data;
}
if (tableName === 'table3') {
$scope.table3Data = response.data;
}
if (tableName === 'table4') {
$scope.table4Data = response.data;
}
if (tableName === 'table5') {
$scope.table5Data = response.data;
}
if (tableName === 'tableN') {
$scope.tableNData = response.data;
}
} else {
console.log("Something wrong while fetching data from table ", tableName);
}
})
.error(function (error) {
console.log("The error is :", err);
});
});
HTML table
<table style="width:100%">
<tr>
<th>Nme</th>
<th>Id</th>
<th>Contact No</th>
</tr>
<!--Table1 Data-->
<tr ng-repeat="data in table1Data">
<td>{{ data.user_name }}</td>
<td>{{ data.user_id }}</td>
<td>{{ data.mobile }}</td>
</tr>
<!--Table2 Data-->
<tr ng-repeat="data in table2Data">
<td>{{ data.customerName }}</td>
<td>{{ data.customerId }}</td>
<td>{{ data.phone }}</td>
</tr>
<!--Table3 Data-->
<tr ng-repeat="data in table3Data">
<td>{{ data.student_name }}</td>
<td>{{ data.roll_no }}</td>
<td>{{ data.mobile }}</td>
.
.
.
<!--TableN Data-->
<tr ng-repeat="data in tableNData">
<td>{{ data.developer_name }}</td>
<td>{{ data.id }}</td>
<td>{{ data.mobile }}</td>
</tr>
</table>
You can see in each table the column name is different so I am not able to show all the data in a single ng-repeat. So I have written separate ng-repeat for each and every table.
This code is working fine but when the page starts loading it takes more than 7 seconds so this is my worry(performance wise). So please suggest me if any better way is available to achieve this.
Thanks for your valued time.

Merge Requests
Create a new end-point on your API which will get data from all tables and return it in one request. This will definitely save you some time especially if your request are cross-domain.
The following will not speed your application up, but they you asked about best practices so here goes.
Abstract API Calls to a Service
Try to avoid using $http in your controller. The controller should not be concerned with how to get the data, only that it needs to get it and then what to do with it.
Map Results
If you want to use single ng-repeat in your template, map every result (or part of result if you merged all the requests) so the object structure is the same for every table. The following is a simplified example for the table1.
$scope.tableData = [];
result.data.forEach(row => {
$scope.tableData.push({
id: row.user_id,
mobile: row.mobile,
name: user_id
});
});
This way you can loop through all table data using one ng-repeat.
Use $q.all()
This applies only if you cannot merge you requests on the API level. In you example you are calling the function 25-times manually. It would make sense to make a for loop from 1 to 25 and pu each request into an array:
const promises = [];
for (let i = 1; i <= 25; i++) {
promises.push(loadDashboardQue(YOUR_ARGS_HERE));
}
After that you can wait for all of them to resolve and access the results in the response which will by an array in the same order in which you pushed your requests into the promises variable.
$q.all(promises).then(response => {
// response[0] is table1data etc...
});
I hope this help you somehow. Let me know if you have some questions.

Related

Clicking first tr of table with Protractor

I have a table of notes that I want to test.
<table class="table" ng-if="!NotesCtrl.loadingNotes && (NotesCtrl.notes | filter: { client : ClientCtrl.client.id }).length">
<tr class="noteTableItem" ng-repeat="note in NotesCtrl.notes | filter: { client : ClientCtrl.client.id } track by note.id" ng-click="NotesCtrl.showNote(note.id)">
<td>{{ note.title }}</td>
<td>{{ note.created | date: 'dd-MM-yyyy - HH:mm' }}</td>
</tr>
</table>
When I write a test to see if there is only 1 row, it passes:
it('should see one note-item', function(){
var notes = element.all(by.css('.noteTableItem'));
expect(notes.count()).toEqual(1);
});
When I then want to click on the first note, to trigger the ng-click, it fails with the message:
it('should click the first item', function(){
var notes = element.all(by.css('.noteTableItem'));
expect(notes.count()).toEqual(1);
notes.first().click();
expect(noteDialog.isDisplayed());
});
Failed: Element <tr class="noteTableItem ng-scope"> could not be scrolled into view
How can I click the first item with Protractor ? (When I work with a list <ul></ul>, it does work...)
Try with xpath:
element(by.xpath("//table/tr/td[0]")).click();
or
$$("table tr td").get(0).click();
Use executeScript method(native javaScript) like below to scroll the object in the view:
var elem= element(locatorCss('Locator definition'));
browser.executeScript("arguments[0].scrollIntoView();", elem.getWebElement()).then(function(){
elem.click().then(function () {
console.log("Object Clicked!!!");
});
});

loop through firebase database object and ng-repeat the result

I am ng-repeating the objects stored in my $firebaseArrayobject.
mycontroller:
var projectquery = DatabaseRef.ref("projects").orderByKey().equalTo("-KUTPrs6ARZXjbjtNr7O");
$scope.projectslist = $firebaseArray(projectquery);
html view with ng-repeat:
<tr ng-repeat="obj in projectslist">
<td>{{ obj.field1}}</td>
<td>{{ obj.field2 }}</td>
<td>{{ obj.field3 }}</td>
<td>{{ obj.field4 }}</td>
</tr>
this is displaying only one project because I am passing in the project key manually to equalTo and thus filtering to show one.
From the other hand, I am getting a set of project keys from the below function by doing a forEach on another database where I get my project keys:
var keyquery = DatabaseRef.ref('/users/' + userId).orderByKey().once("value")
.then(function onSuccess(snapshot) {
snapshot.child("savedprojects").forEach(function(childSnapshot) {
var userprojects = childSnapshot.val();
console.log("my list is :", userprojects );
});
});
the output of the console is like:
my list is : -KUTLAZENGlxtzCtEfaZ
my list is : -KUTLM8r_kmVSTaxzLW5
my list is : -KUTPrs6ARZXjbjtNr7O
Question:
how is it possible to pass on the above list of project keys obtained from forEach into the first function which shows the projects and filters them by key?
Could somebody please give a relevant answer and help me solve this, without referring to other answers which are not directly related to my question?
thanks in advance!
edit:
my DatabaseRef refers to this factory, which is initializing the firebase database:
app.factory("DatabaseRef", function() {
return firebase.database();
});
update:
After implementing Gourav's answer, my console shows undefined in place of the project IDs:
as you can see, the user has six projects under his profile, but all of them are empty and undefined.
update 2:
here is console output after Gourav's edit:
and the error for ng-repeat:
I don't know much about DatabaseRef but this might help.
$scope.projectslist = [];
DatabaseRef.ref('/users/' + userId).orderByKey().once("value")
.then(function onSuccess(snapshot) {
snapshot.child("savedprojects").forEach(function (childSnapshot) {
var userprojects = childSnapshot.val();
console.log("my list is :", userprojects);
var projectquery = DatabaseRef.ref("projects").orderByKey().equalTo(userprojects);
$scope.projectslist.push(($firebaseArray(projectquery))[0]);
});
});

Angular JS, bind scope property to function

I'm new to Angular JS. I am creating an app that stores data in the web storage. WHen an enquiry form is submitted, it gets the current arrays from the web storage, strigifies the new form data, adds it to an array, and posts it back to web storage. Works great and all gets saved correctly.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
}
function getStoredData(storeName) {
// get or create enquiry store
var storedData = (typeof localStorage.getItem(storeName) !== 'undefined' && localStorage.getItem(storeName)) || "[]";
// parse store into object
storedData = JSON.parse(storedData);
return storedData;
}
function addStoredData(data, storeName) {
var storedData = getStoredData(storeName);
var count = storedData.length;
// form data into next submission slot
storedData[count] = data;
// turn back into JSON
storedData = JSON.stringify(storedData);
// slap it back in web storage
localStorage.setItem(storeName, storedData);
}
On my HTML page I have this;
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date</th>
</tr>
<tr ng-repeat="e in enquiries">
<td>{{ e.firstName }}</td>
<td>{{ e.lastName }}</td>
<td>{{ e.date }}</td>
</tr>
</table>
So I want to be able to do this...
// list of stored enquiries
$scope.enquiries = function () {
return getStoredData(ENQUIRY_STORE);
}
But it doesn't bind and there are no rows. If I do this...
$scope.enquiries = getStoredData(ENQUIRY_STORE);
it works, but then I have to keep setting it when a new enquiry is submitted. Maybe Anulgar doesn't let you have use functions to return data for binding, but I thought it did.
Any help much appreciated. Thanks.
In the first way ($scope.enquiries = function() ...) it doesn't bind because you are making an ngRepeat over a function (it doesn't throw an error because a function has actually a length).
However, even if you did <tr ng-repeat="e in enquiries()"> you would have a infinite digest error because enquiries() is returning a different object each time and angular is waiting to have the same result twice to stop the digests cycle.
The best way would be:
$scope.submit = function() {
...
setEnquiries();
}
...
function setEnquiries() {
$scope.enquiries = getStoredData(ENQUIRY_STORE);
}
And keep looping through enquiries:
<tr ng-repeat="e in enquiries">
$scope.selectedENQUIRY_STORE = null;
$scope.getStoredData= function (e) {
$scope.selectedENQUIRY_STORE = e;
};
it binds when you clicked on that enquiry form on name
You simply have to retrieve the data again, upon submitting, so move your $scope.enquiries() function into your $scope.submit() function.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
// FIRE UP THE FUNCTION
$scope.enquiries();
}
Note that you should put getStoredData() and other data related function into an angular service.
Luckily, there's already a good module for dealing with LocalStorage operations :
https://github.com/tymondesigns/angular-locker

Angular JS - Display only the field with the highest value from JSON data

I have a JSON retrieve from database
[{"id":1,"firstname":"Alan Cayetano","numbers":2},{"id":2,"firstname":"Bong Marcos","numbers":0},{"id":3,"firstname":"I Dont Care","numbers":3},{"id":4,"firstname":"toto tata","numbers":0},{"id":5,"firstname":"titi terter","numbers":0},{"id":6,"firstname":"Ian Go","numbers":0}]
this is the result when displayed in table result
firstname lastname numbers
Alan Cayetano 10
Bong Marcos 4
Ian Go 3
What Ever 0
I only want the data with the highest number value
In this case
firstname lastname numbers
Alan Cayetano 10
This data is dynamically fetch from database
My angular.js
<script src="//code.angularjs.org/1.4.8/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('//');
$interpolateProvider.endSymbol('//');
});
app.controller('customersCtrl',['$scope','$http',function($scope, $http) {
//$http.get("http://localhost:8093/voters/voters_angular")
$http.get("{{ path('vp_president') }}")
.success(function (response) {
$scope.names= JSON.parse(response);
});
}]);
//console.log(names);
</script>
Table
<div ng-app="myApp" ng-controller="customersCtrl">
<table class="table">
//names//
<thead>
<tr>
<th>Firsname</th>
<th>Lastname</th>
<th>NUm</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="x in names">
<td>//x.firstname//</td>
<td>//x.lastname//</td>
<td>//x.numbers//</td>
</tr>
</tbody>
</table>
</div>
How to achieve this? I am still learning Angular Js
I wonder if Angular's $last filter will work on this
This should work as describe
Link
<tr ng-repeat="x in names| orderBy:'-numbers' | limitTo:1">
<td>//x.id//</td>
<td>//x.firstname//</td>
<td>//x.numbers//</td>
</tr>
It's better to find your max in the controller, not in the view.
function findMax(names) {
var result = null;
for (var i = 0; i < names.length; i++) {
var name = names[i];
if (result == null || name.numbers > result.numbers) {
result = name;
}
}
return result;
}
$scope.name = findMax($scope.names);
And in html
<tr>
<td>{{name.firstname}}</td>
<td>{{name.lastname}}</td>
<td>{{name.numbers}}</td>
</tr>
In AngularJS it's better to pre-sort your data before showing in the view, the ng-repeat creates a watcher for each object it repeat's, in AngularJS the number of watchers is associated with performance if you have many watchers your performance it's worst.
If you don't need the other values to appear, there is no need to create watchers for that values, so it's better to pre-sort the array.
you can do this :
<tr ng-repeat="x in names | orderBy:numbers:reverse | limitTo:1">
You can use:
angular.forEach($scope.names, function (value, key) { //code });
With angular foreach you can read row by row in your $scope.names, and use your logic for get the msx value.
When:
key -> is the position (use an alert(key) to see the info)
value -> is a the row of the json. For get data use value.id, value.firstname, etc.

angular js ng repeat asynchronously

I want to show a list of elements on view asynchronously.
For example i have a method which returns promise and result will look like this.
var result = {
books: [
{name: 'The Kite Runner', author: 'Khaled Hosseini'},
{name: 'The Book Thief', author: 'Markus Zusak'},
{name: 'A Thousand Splendid Suns', author: 'Khaled Hosseini'},
]
}
Here is a method:
function getBooks(userId) {
return BookProduct.getBooksByUser.query({ id: userId }).$promise;
};
And then i have a method which invokes getBooks.
$scope.showBooks = function(userId) {
//some users id
var users_id = [1,2,3,4,5];
$scope.tables = [];
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
}
Thereafter i want to show it to user on a view using angular ng-repeat.
<div data-ng-repeat="table in tables">
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="book in table.books">
<td>{{ name }}</td>
<td>{{ author }}</td>
</tr>
</tbody>
</table>
</div>
It works good but ng-repeat will work when function $scope.showBooks is executed and $scope.tables is already full. I am looking for more nice solution. I want to load books for user piecemeal on each iteration when method getBooks() invokes.
So in this part for example when the first iteration is processing and i get result of 2 elements, then i pass them to the view and user can see the first part. Next iteration i get for example other 3 elements, so i add them to previous result and pass to the view, so user can see 5 elements now, and so on. I know it will be almost insensibly for user but if my server responds slowly it will help me to be more user friendly.
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
I am not asking you HOW to implement it. I just want to know is it possible or not and where should i dig to know more, cause i tried some googling but without success.
Angular has a cycle in which it performs all scope functionality and after each cycle of "watches" it renders changes to the view. By default, Angular performs this automatically - for example simple change of variable in scope also rerenders view. But in some cases you may want to intercept because your operation didn't trigger the rerender.
For these cases, $apply and $digest are useful - more in documentation https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Resources