AngularJS : data binding not working as expected using a service - angularjs

I'm having trouble with services in AngularJS.
Being a newbie it's probably something crucial I'm missing here.
The title {{p01g.visiteTitel}} isn't magically refreshing but keeps displaying "sometitle".
The ng-repeat is working as expected.
(dataFactory is a service that connects to a remote server using $resource)
My service :
myApp.service('p00Service', ['dataFactory', function(dataFactory) {
var service = this;
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum) {
dataFactory.get({verb: "search", q: datum}, function (data) {
angular.copy(data.visites, service.visites);
service.visiteAantal = service.visites.length;
if (service.visiteAantal === 0) {
service.visiteTitel = "geen visites op " + datum
} else if (service.visiteAantal === 1) {
service.visiteTitel = "1 visite op " + datum
} else {
service.visiteTitel = service.visiteAantal + " visites op " + datum
}
});
};
}]);
My controller :
myApp.controller('p01gCtrl', ['p00Service', function (p00Service) {
var vm = this;
var datum = moment(); //I'm using moment.js -> moment() is date of today
p00Service.findVisites(datum);
vm.visites = p00Service.visites;
vm.visiteTitel = p00Service.visiteTitel;
}]);
My HTML :
<div class="p01g" ng-controller="p01gCtrl as p01g">
<div class="well_grey" style="min-height:40px;max-height:40px;max-width:330px">
<p style="font-size:20px;text-align:center;cursor:pointer;">
{{p01g.visiteTitel}}
</p>
</div>
<div class="well" style="min-height:190px;max-height:190px;max-width:330px">
<table style="width:100%;line-height:40px">
<tbody ng-repeat="visite in p01g.visites">
<tr>
<td style="width:20%;line-height:40px;padding-left:7px"><span style="font-size:16px">{{visite.t133datum | date:"dd/MM/yy"}}</span></td>
<td style="width:60%;line-height:40px;text-align:center"><span style="font-size:16px">{{visite.t133achternaam}}</span></td>
<td style="width:20%;line-height:40px;padding-left:30px"><span style="font-size:16px">{{visite.t133classificatie}}</span></td>
</tr>
</tbody>
</table>
</div>
</div>

the dataFactory looks like this :
myApp.factory("dataFactory", ['$resource', function ($resource) {
return $resource("/vf/rest/visites/:verb", {}, {
get: {method: "GET", isArray: false, cache: false}
});
}]);

I copied your code exactly and created a plunkr and added my own dataFactory to return sample data and there are no errors and the data is getting bound fine, so I believe the issue is in your implementation of dataFactory, or the way you are calling it (make sure it's expecting a callback function, since that is what you are passing it).
Edit: Here is a new plunkr with updated code that I believe reproduces your issue. So the reason why the title is not getting updated is because
vm.visiteTitel = p00Service.visiteTitel;
is setting vm.visiteTitel to the value of the string in p00Service.visiteTitel, but it is NOT getting a reference to p00Service.visiteTitel, so if you update visiteTitel in your p00Service after this assignment (which is happening in this case because the $resource callback is async), then it has no effect on vm.visiteTitel.
One way to make this work would be to pass a callback to p00Service to update your controller values like so:
p00Service.findVisites(datum, function(visites, visiteTitel) {
vm.visites = visites;
vm.visiteTitel = visiteTitel;
});
and then update your service to call this callback after the data is loaded:
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum, callback) {
dataFactory.get({}, function (data) {
...
if(callback) {
callback(service.visites, service.visiteTitel);
}
});
};
This code can be cleaned up a bit, but here is a workable plunkr demonstrating vm.visites and vm.visiteTitel getting updated correctly.

Related

Unable to bind Json Data with Table Header using AngularJS

Im getting data in this format from api, but when i try binding it to table using angularjs it is creating empty space instead of values. Im also getting more then one table from some Api's please explain who to bind different datatables in different tables too. thanks
{"Table":
[{
"SchoolId":1,
"schoolname":"Microsoft",
"SCHOOLCODE":"29911583",
"WEBSITE":"JLR",
"USEREMAIL":"faucibus#aliquamiaculislacus.org",
"PHONE":"841-9331",
"ADDRESS1":"682-5760 Felis Street",
"ISACTIVE":0,
"PLANTYPE":3
}]
}
Angular Controller
SMSApp.factory('GetStudentService', function ($http) {
studobj = {};
studobj.getAll = function () {
var stud=[];
stud = $http({ method: 'Get', url: 'http://localhost:58545/api/Student?Studentid=1' }).
then(function (response) {
return response.data;
});
return stud;
};
return studobj;
});
SMSApp.controller('studentController', function ($scope, GetStudentService) {
$scope.msg = "Welcome from Controller";
GetStudentService.getAll().then(function (result) {
$scope.school = result;
console.log(result);
});
});
HTML Code
<tbody ng-controller="studentController">
<tr ng-repeat="schools in school track by $index">
<td>{{schools.SchoolId}}</td>
<td>{{schools.schoolname}}</td>
<td>{{schools.SCHOOLCODE}}</td>
<td>{{schools.WEBSITE}}</td>
<td>{{schools.USEREMAIL}}</td>
</tr>
</tbody>
WHAT I GET
Change in your Angular Controller :
SMSApp.controller('studentController', function ($scope, GetStudentService) {
$scope.msg = "Welcome from Controller";
GetStudentService.getAll().then(function (result) {
/********************* Changed Here ********************/
$scope.school = JSON.parse(result._body); // Or only JSON.parse(result)
$scope.school = $scope.school.table;
});
});
And your HTML Code :
<tbody ng-controller="studentController">
<tr ng-repeat="schools in school track by $index">
<td>{{schools.SchoolId}}</td>
<td>{{schools.schoolname}}</td>
<td>{{schools.SCHOOLCODE}}</td>
<td>{{schools.WEBSITE}}</td>
<td>{{schools.USEREMAIL}}</td>
</tr>
</tbody>
Naming the data school is confusing. Do instead:
GetStudentService.getAll().then(function (data) {
$scope.tableObj = data;
console.log(data);
});
<tbody ng-controller="studentController">
<tr ng-repeat="school in tableObj.Table track by school.SchoolId">
<td>{{school.SchoolId}}</td>
<td>{{school.schoolname}}</td>
<td>{{school.SCHOOLCODE}}</td>
<td>{{school.WEBSITE}}</td>
<td>{{school.USEREMAIL}}</td>
</tr>
</tbody>
From the Docs:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g. item in items track by item.id. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
— AngularJS ng-repeat API Reference
Just replace your result with result.Table in your controller because if you properly see your response it is inside "Table" named array. Try this you should be able to see your records
SMSApp.controller('studentController', function ($scope, GetStudentService) {
$scope.msg = "Welcome from Controller";
GetStudentService.getAll().then(function (result) {
$scope.school = result.Table;
console.log(result);
});
});
Note: I have replaced your API call in the jsfiddle link with the response mentioned in the question.
JSFiddle Link : http://jsfiddle.net/zu8q7go6/9/

WebSQL query causing $digest reached 10 iterations error

I'm using Angular and WebSQL in a Cordova project, and I've started a new list controller that will list the results of a table from the WebSQL database.
I have a sqlSvc that queries the database like so:
service.upc = function(newUpc) {
var deferred = $q.defer();
var resolveResults = function (tx, results) {
deferred.resolve(results.rows);
}
var selectUpcs = function() {
var queryString = "SELECT * FROM UPC";
service.db.transaction(function (tx) {
tx.executeSql(queryString, [], resolveResults, rejectWithError);
});
}
deferUntilInit(function () {
if (newUpc) {
insertOrReplaceAndSelect(newUpc); //omitted
} else {
selectUpcs();
}
});
return deferred.promise;
}
All the controller does is this:
var listCtrl = function($scope, sqlSvc) {
sqlSvc.upc().then(function(result) {
$scope.list = result;
});
}
angular.module("RDb").controller("listCtrl", ["$scope", "sqlSvc", listCtrl]);
And it's binding to a simple UI view template:
<div id="scanList">
<ul class="list-unstyled">
<li ng-repeat="scan in list">
<div>{{scan.upc}} ({{scan.datetime}})</div>
</li>
</ul>
</div>
This is giving me the 10 $digest iterations reached error, and it seems to be caused by the way WebSQL is return its results. The error goes away if I deep copy the data, changing resolveResults to:
var data = JSON.parse(JSON.stringify(results.rows));
deferred.resolve(results.rows);
I would like to be able to get this service to work without having to deep copy every results set that it gets. Can anyone help me understand why this is happening?

Angular-DataTables custom filter

I am trying to add a custom filter to angular-DataTables with server side processing, which works perfectly with sorting and built in search of datatables.
I was following example Angular-DataTables, to build the server side processing and setup the DataTable, in searching around i have found some info but haven't been able to make it work.
What i am trying to get is to redraw the table with filtered data once the checkbox [Player] has been triggered.
Does anyone know a solution for this or has a working example for this?
have found this example Custom Table Filter, but it seems it doesn't work either.
HTML:
<div ng-app="showcase"><div ng-controller="ServerSideProcessingCtrl">
<label><input type="checkbox" id="customFilter" value="player"> Player</label>
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns" class="row-border hover"></table>
JS part:
'use strict';
angular.module('showcase', ['datatables'])
//.controller('ServerSideProcessingCtrl', ServerSideProcessingCtrl);
.controller('ServerSideProcessingCtrl',["$scope", "DTOptionsBuilder", "DTColumnBuilder", function($scope, DTOptionsBuilder, DTColumnBuilder) {
//function ServerSideProcessingCtrl(DTOptionsBuilder, DTColumnBuilder) {
console.log($scope);
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('ajax', {
// Either you specify the AjaxDataProp here
// dataSrc: 'data',
url: 'getTableData.php',
type: 'POST'
})
// or here
.withDataProp('data')
.withOption('serverSide', true)
.withPaginationType('full_numbers');
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('First name'),
DTColumnBuilder.newColumn('position').withTitle('Position'),
DTColumnBuilder.newColumn('type').withTitle('Type')
];
$scope.$on('event:dataTableLoaded', function(event, loadedDT) {
console.log(event);
console.log(loadedDT);
$('#customFilter').on('change', function() {
loadedDT.DataTable.draw();
} );
});
}]);
JSON on load:
{"draw":"1","recordsTotal":8,"recordsFiltered":8,"data":[{"id":"1","name":"Raul","position":"front","type":"player"},{"id":"2","name":"Crespo","position":"front","type":"player"},{"id":"3","name":"Nesta","position":"back","type":"player"},{"id":"4","name":"Costacurta","position":"back","type":"player"},{"id":"5","name":"Doc Brown","position":"staff","type":"medic"},{"id":"6","name":"Jose","position":"staff","type":"manager"},{"id":"7","name":"Ferguson","position":"staff","type":"manager"},{"id":"8","name":"Zinedine","position":"staff","type":"director"}]}
After searching and browsing, combined few examples and came up with this.
HTML :
<label><input type="checkbox" id="customFilter" value="player" ng-click="reload()" > Player</label>
JS:
'use strict';
angular.module('showcase', ['datatables'])
//.controller('ServerSideProcessingCtrl', ServerSideProcessingCtrl);
.controller('ServerSideProcessingCtrl',["$scope", "DTOptionsBuilder", "DTColumnBuilder","DTInstances", function ($scope, DTOptionsBuilder, DTColumnBuilder, DTInstances) {
//function ServerSideProcessingCtrl(DTOptionsBuilder, DTColumnBuilder) {
console.log($scope);
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('ajax', {
// Either you specify the AjaxDataProp here
// dataSrc: 'data',
url: 'getTableData.php',
type: 'POST',
// CUSTOM FILTERS
data: function (data) {
data.customFilter = $('#customFilter').is(':checked');
}
})
// or here
.withDataProp('data')
.withOption('serverSide', true)
.withPaginationType('full_numbers');
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('First name'),
DTColumnBuilder.newColumn('position').withTitle('Position'),
DTColumnBuilder.newColumn('type').withTitle('Type')
];
DTInstances.getLast().then(function (dtInstance) {
$scope.dtInstance = dtInstance;
});
$scope.reload = function(event, loadedDT) {
$scope.dtInstance.reloadData();
};
}]);
and on the backend just go through the $_POST and check for custom filter, hopefully this will help someone
You can use withFnServerData with fromSource functions instead of
withOption:
This API allows you to override the default function to retrieve the data (which is $.getJSON according to DataTables documentation) to something more suitable for you application.
It's mainly used for Datatables v1.9.4. See DataTable documentation.
$scope.dtOptions = DTOptionsBuilder.fromSource('data.json')
.withFnServerData(serverData);
function serverData (sSource, aoData, fnCallback, oSettings) {
oSettings.jqXHR = $.ajax({
'dataType': 'json',
'type': 'POST',
'url': sSource,
'data': aoData,
'success': fnCallback
});
:)
Ok sorry its not a full blown example. This only works with angular and datatables, if you do a filter on the ng-repeat eg | aFilter:this The this transfers the scope. The filtering applied can now be quite complex. Within the ng-controller <div> you can have an html partial containing drop downs or input texts, all having an ng-model value.
When these change they kick off the filter routineaFilter an angular.filter('aFilter'.... js routine. The records are piped through the afilter routine allowing the ones wanted to be pushed onto an array and this is what is returned with the return. It doesn't work with breeze, yet. Be aware it is unlikely to be server side. To deal with server side maybe an SQL call in the service....another day.
eg in the ng-table id="test" :
<tr ng-repeat="edRec in aSetOfJSonRecords | aFilter:this | orderBy:'summat'">
{{edRec.enCode}} etc
</tr>
in the aFilter, the fltEnCode represents the ng-model values, the test variable allows freedom from nulls causing issues upon comparison, good idea to test for undefined first:
app.filter('aFilter', [function () {
return function (items, $scope) {
var countItems = 0;
var filtered = [];
var isOK = 0;
angular.forEach(items, function (item) {
isOK = 1;
// some conditions
if ($scope.fltEnCode !== "") {
if (item.enCode === null) { test = ""; } else { test = item.enCode; }
if (test.indexOf($scope.fltEnCode) < 0) isOK = 0;
}
// end of conditions
if (isOK > 0) {
filtered.push(item);
countItems++;
}
});
// alert(countItems);
return filtered;
};
}]);
Hope its of some use. I've avoided boolean variables as they have given grief before. Odd occasions have needed an ng-change in the html items pointing to an angular function resetting the data by calling the getTheItemsForTest() in the controller. This redraws the list. Having
$scope.dtOptions = {
stateSave: false, .......
in your controller, keeps the sorting columns correct.
$(document).ready(function() {
var table = $('#test').DataTable();
table.draw();
};
might also be useful if its recalcitrant. I need to know how to make it work for breeze??? Enjoy..
here is what I really missed after I searched alot
bower install datatables-light-columnfilter

Angularjs ng-table - how to pass parameters into DreamFactory db api call

I am trying to make ng-table work by example 6 (ajax data loading) but instead of using mock backend I use actual DreamFactory backend connected to MongoDB. My relevant code looks like this now:
MainApp.factory('Servant', function ($resource) {
"use strict";
console.log('loading');
return $resource('https://dsp-mydspname.cloud.dreamfactory.com:443/rest/mongodb/tablename/:id/?app_name=appname&fields=*', {}, { update: { method: 'PUT' }, query: {
method: 'GET',
isArray: false
} });
});
var MainCtrl = function ($scope, $timeout, $resource, Servant, ngTableParams) {
"use strict";
$scope.action="Add";
var Api = Servant;
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
}, {
total: 0, // length of data
getData: function($defer, params) {
// ajax request to api
Api.get(params.url(), function(data) {
$timeout(function() {
// update table params
params.total(data.record.length);
// set new data
$defer.resolve(data.record);
}, 500);
});
}
});
}
The table is displying data but it displays all data on one page, I cant figure out how to pass "count" and "offset" parameters into my api call. Any help would be appreciated.
Sorry for the delayed response. I played a little with ng-table and found that I was spending a lot of time trying to make it work and couldn't get it to. So..I thought it would be more helpful to show you how to build your own table with pagination so you can adapt it for any situation that may arise using DreamFactory. Here's the code. You should be able to copy and paste. Just make sure to add your table fields to the table row for data. The table headers will populate automatically.
Here is the controller and the service with comments:
.controller('TableCtrl', ['$scope', 'Servant', function($scope, Servant) {
// function to get records for building the table
var _getRecords = function(fieldsStr, limitInt, offsetInt, schemaBool) {
Servant.get({fields: fieldsStr, limit: limitInt, offset: offsetInt, include_schema: schemaBool},
function(data) {
$scope.table = data;
}
)
};
// Get the total records on load
Servant.get({fields: 'id'}, function(data) {
// Get the total number of records
$scope.totalRecords = data.record.length;
});
// Options for rest call
$scope.fields = '*';
$scope.currentOffset = 0;
$scope.limit = 4;
// Used to do pagination
// store total records
$scope.totalRecords = 0;
// store page objects
$scope.pageObjs = [];
// Get initial data
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
// Pagination
$scope.next = function() {
//check if we are on the last page
if ($scope.currentOffset == $scope.pageObjs[$scope.pageObjs.length - 1].pageOffset) {
return false;
}
// we are not
// advance the page
else {
$scope.currentOffset = $scope.currentOffset + $scope.limit;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
}
};
// change page directly
$scope.changePage = function (offsetInt) {
$scope.currentOffset = offsetInt;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
};
$scope.back = function() {
// are we on the first page
if ($scope.currentOffset == 0) {
return false
}
// we are not
// go previous page
else {
$scope.currentOffset = $scope.currentOffset - $scope.limit;
_getRecords($scope.fields, $scope.limit, $scope.currentOffset, true);
}
};
// watch for total records to be populated. When we have this number
// we can generate our page objects that will help build our pagination
$scope.$watch('totalRecords', function(newValue, oldValue) {
var numPages = Math.ceil(newValue / $scope.limit);
for(var i = 0; i < numPages; i++) {
$scope.pageObjs.push({pageNumber: i, pageOffset: i*$scope.limit})
}
});
}])
.service('Servant', ['$resource', function($resource) {
// define and return our $resource
// replace /rest/db/TheTable with your mongodb/tablename
// you don't need the port either
return $resource('http://localhost:8081/rest/db/TheTable',
{
// set params to bind too
app_name: APP_NAME
fields: '#fields',
limit: '#limit',
offset: '#offset'
},
{
// set update method to 'PUT'
update: {
method: 'PUT'
}
}
)
}]);
Here is the template i used:
<table class="table">
<!-- this will build the table headers dynamically -->
<!-- they will populate in order of the table's schema -->
<tr>
<th data-ng-repeat="field in table.meta.schema.field">
{{field.name}}
</th>
</tr>
<!-- replace these fields with your field names -->
<!-- for example: {{row.YOUR_FIELD_NAME}} -->
<tr data-ng-repeat="row in table.record">
<td>
{{row.id}}
</td>
<td>
{{row.first_name}}
</td>
<td>
{{row.last_name}}
</td>
</tr>
</table>
<!-- this will build dynamically as well-->
<ul class="pagination">
<li data-ng-click="back()"><a>«</a></li>
<li data-ng-click="changePage(page.pageOffset)" data-ng-repeat="page in pageObjs"><a>{{page.pageNumber + 1}}</a>
</li>
<li data-ng-click="next()"><a>»</a></li>
</ul>

ui-bootsrap pagination: first page button is not disabled when page loads

I am displaying a list of elements inside a ng-include.
The list of elements comes from the server using $resource query service.
The list is paginated with the ui-bootstrap pagination directive. the server send the pagination informations inside the Json header (properties are named X-MyApp-…) and are intercepted by the query callback function.
here is the html :
<table ng-include src="'partials/tplList.html'" ng-init="listInit = {'type': collec.type, 'offset': 1}" ng-controller="ListCtrl" >
</table>
the tplList.html :
<tbody ng-init="loadList(listInit)"><tr ng-repeat="elm in list">
<td>{{elm.prop1}}</td><td>{{elm.prop2}}</td><td>{{elm.prop3}}</td>
</tr></tbody>
<tfoot><tr><td colspan="4">
<span ng-show="pageCount>1">
<pagination num-pages="pageCount" current-page="currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
</span>
</td></tr></tfoot>
and the controller:
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.currentPage = response("X-MyApp-currentPage");
$scope.pageCount = response("X-MyApp-pagesCount");
console.log($scope.currentPage); // returns 1 when the page loads.
});
}
}])
and the service :
factory('List', function($resource){
return $resource('url/to/the/json/:type', {type:'#type'});
})
everything is working fine except one thing : when the page loads, the first page button ("1") inside the pagination component is not disabled like it should (and the "previous" and "first" buttons are not either). It's not disabled until i click on another page number (which is disabled correctly when selected) and then click back on the first page button.
any idea ?
This happens because ng-include creates a new scope and the model is not modified in your $parent scope.
Try the following code or create a controller that communicates with the parent one.
<pagination num-pages="pageCount" current-page="$parent.currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
i found a way to make it work:
removed this line from the controller :
$scope.currentPage = response("X-MyApp-currentPage");
and added this one :
$scope.currentPage = 1;
which gives :
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.currentPage = 1;
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.pageCount = response("X-MyApp-pagesCount");
});
}
}])
apparently the pagination component doesn't need the X-MyApp-currentPage information from the server (and i'm not sure to understand why).

Resources