I have an ngRepeat that iterates through a list of objects and displays them. These objects do not contain any images, but the ID can be used to fetch the image. In the contents of the ngRepeat, I want to show the image that corresponds to the object, and so I call a function within an ngSource to grab the image corresponding with the current object. However, the ngSource fails to load on the actual page.
HTML:
<div class="list-group">
<a ng-click="courseSelected(course.course_id)" class="list-group-item" ng-repeat="course in searchResultData.courseList" id="searchResults">
<div>
<img ng-src="{{getLogoByCourse(course)}}"/>
<h4 class="list-group-item-heading">{{course.name}}, {{course.city}}, {{course.state}}</h4>
<p class="list-group-item-text miscData" ng-show="course.price"><span>Price: {{course.price}}</span></p>
<p class="list-group-item-text miscData">
<span>Phone number: {{course.phone}}</span><br/>
<span>Address: {{course.address}}</span><br/>
<span>Website: {{course.website}}</span>
</p>
</div>
</a>
</div>
Directive Snippet:
$scope.getLogoByCourse = function(course) {
console.log("Getting logo");
courseFactory.getLogoByID(course.course_id)
.then(function(result){
console.log("logo: ", result);
result = "app/images/directory/" + result.data[0];
return result;
}
);
}
$scope.getImageByCourse = function(course) {
courseFactory.getImagesByID(course.course_id)
.then(function(result){
console.log("Image: ", result);
result = "app/images/directory/" + result.data[0];
return result;
}
);
}
Contents of image tag on loaded page:
<img class="logo-img">
The problem is that $scope.getLogoByCourse doesn't actually return anything. It looks like you're trying return a value from a promise, but since that returns asynchronously, you'll always just get undefined as the return value from $scope.getLogoByCourse.
You may want to consider getting all of the URLs at once and loading them into an array. Then you can attach each URL in the ng-repeat using the $index variable.
$scope.logos = [];
for (var i = 0; i < searchResultData.courseList.length; i++) {
(function (j) {
var course = searchResultData.courseList[j];
courseFactory.getImagesByID(course.course_id)
.then(function (result) {
console.log("Image: ", result);
result = "app/images/directory/" + result.data[0];
$scope.logos[j] = result;
$scope.$apply();
}, function (error) {
$scope.logos[j] = null;
});
)(i);
}
Then in your template:
<img ng-src="{{ logos[$index] }}" />
Related
It is not like it is slow on rendering many entries. The problem is that whenever the $scope.data got updated, it adds the new item first at the end of the element, then reduce it as it match the new $scope.data.
For example:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
This script is updating the $scope.data:
$scope.load = function() {
$scope.data = getDataFromDB();
}
Lets say I have 5 entries inside $scope.data. The entries are:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
When the $scope.data already has those entries then got reloaded ($scope.data = getDataFromDB(); being called), the DOM element for about 0.1s - 0.2s has 10 elements (duplicate elements), then after 0.1s - 0.2s it is reduced to 5.
So the problem is that there is delay about 0.1s - 0.2s when updating the ng-repeat DOM. This looks really bad when I implement live search. Whenever it updates from the database, the ng-repeat DOM element got added up every time for a brief millisecond.
How can I make the rendering instant?
EDITED
I will paste all my code here:
The controller:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
The view:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
Calling it from:
$scope.loadViewModels($scope.orderBy, 'notes');
The sqlite query:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
It is apache cordova framework, so it uses webview in Android emulator.
My Code Structure
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
So there is controller inside controller. The parent is pageController and the child is noteController. Is a structure like this slowing the ng-repeat directives?
Btw using track by is not helping. There is still delay when rendering it. Also I can modify the entries as well, so when an entry was updated, it should be updated in the list as well.
NOTE
After thorough investigation there is something weird. Usually ng-repeat item has hash key in it. In my case ng-repeat items do not have it. Is it the cause of the problem?
One approach to improve performance is to use the track by clause in the ng-repeat expression:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
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.
For more information, see
AngularJS ngRepeat API Reference -- Tracking and Duplicates
In your html, try this:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
After thorough research, I found my problem. Every time I reset / reload my $scope.viewModels I always assign it to null / empty array first. This what causes the render delay.
Example:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
So instead of assigning it to null / empty array, I just replace it with the new loaded data, and the flickering is gone.
I have offers table and users table on parse server. I did a query for he offers table and it worked great (both console log and html - I had issues with async and the Q.promise helped). Now I'm trying to add two elements that are in the users table. I get it on the console, but not on the page. Here is what I have on the offers.service:
this.getAllOffers = function () {
var Q = $q.defer();
console.log('getAllOffers called');
//all offers filter is selected
this.allOffersFilter = false;
var offers = Parse.Object.extend("Offer");
var exchanges = Parse.Object.extend("Exchanges");
var users = Parse.Object.extend("User");
var query = new Parse.Query(offers);
var userQuery = new Parse.Query(users);
var results = [];
query.descending("createdAt");
query.limit(4);
userQuery.find().then(function(users) {
for (i = 0; i < users.length; i++) {
foundUsers = users[i];
query.find().then( function(offers){
for(i = 0; i < offers.length; i++){
found = offers[i];
var result = {};
result.date = found.get("createdAt");
result.price = found.get("price");
result.status = found.get("accepted");
result.lastName = foundUsers.get("lastName");
result.companyName = foundUsers.get("companyName");
console.log(result.companyName);
console.log(result.price);
}
});
results.push(result);
}
Q.resolve(results);
});
return Q.promise;
};
Then my HTML:
<!--List of offers-->
<div class="col-md-3">
<h4>List of offers</h4>
<div ng-if="offersList">
<div ng-repeat="offer in offersList">
<div class="offer card">
<div>{{offer.username}}</div>
<div>{{offer.companyName}}</div>
<div>{{offer.date}}</div>
<div>{{offer.price}}</div>
<div>{{offer.status}}</div>
</div>
</div>
</div>
<div ng-if="!(offersList)">There are no offers</div>
</div>
Then my component:
angular.module('offersPage')
.component('offersPage', {
templateUrl: 'pages/offers-page/offers-page.template.html',
controller: function(AuthService, PageService, OffersService,
$scope) {
// Functions for offers-page
// Check if user is logged in and verified on page load
AuthService.userLoggedin(function(loggedIn, verified) {
if(!verified) {
PageService.redirect('login');
}
});
this.$onInit = function() {
OffersService.getAllOffers().then(function(offersList) {
$scope.offersList = offersList;
});
}
}
});
THANKS IN ADVANCE !
You are resolving $q before results is populated, so, you list is empty.
I don't know about Parse server, but if userQuery.find().then is async, then need to move Q.resolve(results); inside it, or probably inside query.find().then.
When you do an ng-if in angularjs it literally takes out the element and when it puts it in it is as a child scope. To fix this you need to make sure and put $parent on any child element inside an ng-if. See below. Make sure to use track by $index to when you are doing repeats its good practice. Also notice you dont need to $parent anything in the repeat since it is referencing offerwhich is defined.
Code:
<div ng-if="offersList">
<div ng-repeat="offer in $parent.offersList track by $index">
<div class="offer card">
<div>{{offer.username}}</div>
<div>{{offer.companyName}}</div>
<div>{{offer.date}}</div>
<div>{{offer.price}}</div>
<div>{{offer.status}}</div>
</div>
</div>
</div>
It's like there is a loading div that I want to show if two API calls are yet not ready with the results.
SO that the results all of sudden do not jump into the view causing it to flicker.
My view looks something like this :
<div ng-show="vm.loading" class="table-overlay table-overlay-loading">
<div class="table-overlay-content">
<div class="table-overlay-message">Loading…</div>
</div>
</div>
<div ng-show="vm.loadError" class="table-overlay table-overlay-error">
<div class="table-overlay-content">
<div class="table-overlay-message"><i class="icon-error-indicator"></i>
Error encountered. Please try again.
</div>
</div>
</div>
<div class="inner" ng-show="!vm.loading && !vm.loadError">
<div class="info-panel">
<h3>Current Pricing</h3>
<p>
Billing Period:
<br>
<em>{{vm.invoiceCoverageStartDate}} to {{vm.invoiceCoverageEndDate}}</em>
<br>
<big><b>${{vm.invoiceTotal}}</b>/month</big>
<br>
<small>(See Details)</small>
</p>
</div>
And the methods to populate the interpolated values look like :
vm.getCurrentPricingDetails = function(){
HttpWrapper.send(url1, {"operation":'GET'})
.then(function(result){
console.log("Current Pricing Response: ", result);
vm.invoiceCoverageStartDate = $filter('date')(result.invoice.coverageStartDate, "dd/MM/yyyy");
vm.invoiceCoverageEndDate = $filter('date')(result.invoice.coverageEndDate, "dd/MM/yyyy");
vm.invoiceTotal = result.invoice.invoiceTotal;
}, function(error) {
console.log("Error: Could not fetch current pricing details", error);
});
}
vm.getProjectedPricing = function(){
$timeout(function(){
var selectedPricingMappingObj = dataStoreService.getItems('server');
selectedPricingMappingObj.forEach(function(item){
vm.totalProjectedPricingSum += parseFloat(item.selectedMapping.cost);
});
vm.totalProjectedPricingSum = vm.totalProjectedPricingSum.toFixed(2);
},1000);
}
But in my components $onInit method I tried to resolve the same using promise.
vm.$onInit = function() {
vm.loading = true;
vm.loadError = false;
var currentPricingDetails = vm.getCurrentPricingDetails();
var projectedPricingDetails = vm.getProjectedPricing();
$q.all([currentPricingDetails,projectedPricingDetails]).then(function(results) {
debugger;
vm.loading = false;
}, function(error){
debugger;
vm.loading = false;
vm.loadError = true;
});
But still the screen flickers and the loading div does not show .
I want the loading div to show until and unless the two method calls are not done with the results.`
How to achieve that ?
What am I doing wrong ?
vm.getCurrentPricingDetails() and vm.getProjectedPricing() does not return anything. So your line
$q.all([currentPricingDetails,projectedPricingDetails])
is really just
$q.all([undefined, undefined])
$q.all expects an array of promises. $timeout returns a promise when it's done. And it seems like your HttpWrapper also returns a promise. So I think that all you need to do is to add returns to your code:
return HttpWrapper.send(url1, {"operation":'GET'})
and
return $timeout(function(){
vm.getCurrentPricingDetails and vm.getProjectedPricing are not returning promises and hence the $q.all has no chance knowing when they are finished
m.getCurrentPricingDetails = function(){
var defer = $q.defer();
HttpWrapper.send(url1, {"operation":'GET'})
.then(function(result){
console.log("Current Pricing Response: ", result);
vm.invoiceCoverageStartDate = $filter('date')(result.invoice.coverageStartDate, "dd/MM/yyyy");
vm.invoiceCoverageEndDate = $filter('date')(result.invoice.coverageEndDate, "dd/MM/yyyy");
vm.invoiceTotal = result.invoice.invoiceTotal;
defer.resolve();
}, function(error) {
console.log("Error: Could not fetch current pricing details", error);
defer.reject();
});
return defer.promise;
}
vm.getProjectedPricing = function(){
var defer = $q.defer();
$timeout(function(){
var selectedPricingMappingObj = dataStoreService.getItems('server');
selectedPricingMappingObj.forEach(function(item){
vm.totalProjectedPricingSum += parseFloat(item.selectedMapping.cost);
});
vm.totalProjectedPricingSum = vm.totalProjectedPricingSum.toFixed(2);
defer.resolve();
},1000);
return defer.promise;
}
I'm new to AngularJS, so sometimes when I do some mistake that is obvious, I still can't figure out what is going wrong with my code. So saying, here is my doubt:
HTML code:
<body ng-controller = "Ctrl">
<script id="Page6.html" type="text/ng-template">
<div class="list card" style="background-color: beige">
<div class="item item-icon-left">
<i class="icon ion-home"></i>
<input type="text" placeholder = "Enter display name" ng-model="user.nam">
</div>
<a ng-click = "saveedit(user)"<button class="button button-clear">SAVE DETAILS</button></a>
</div>
</script>
</body>
CONTROLLER.JS
.controller('Ctrl',function($scope,$rootScope,ContactService){
$rootScope.saveedit=function(user) {
ContactService.save({names: user.nam, image:"images.jpg"},ContactService.getid("Donkey"));
}
});
THIS IS THE SERVICE:
.service('ContactService', function () {
var items = [
{ id: 1, names: 'Dolphin', image: 'dolphin.jpg',}, { id: 2, names: 'Donkey', image: 'donkey.jpg'}, { id: 3, empid: 'FG2043', image: 'penguin.jpg'}];
var im = [{image: ''}];
var ctr=0;
var uid=3;
this.save = function (contact,id) {
ctr=0;
for (i=0;i<items.length;i++) {
if(items[i].id == id)
{
im[0].image= items[i].image;
ctr=100;
break;
}
}
uid = (uid+1);
contact.id = uid;
items.push(contact);
if (ctr==100 ) {
alert("in save putting the image");
items[contact.id].image = im[0].image; //doubt
alert("finished putting image");
}
}
//simply search items list for given id
//and returns the object if found
this.getid = function (name) {
for (i=0;i<items.length;i++) {
if (items[i].names == name) {
return (i+1);
}
}
}
//simply returns the items list
this.list = function () {
return items;
}
});
The problem I am facing is this: Everything works, except one thing. In ContactService, push() function, the line I have commented as //doubt is not getting executed.
The alert before it "in save putting the image" runs, but the alert "finished putting image" doesn't. What is the mistake there??
The problem here is that you're using the id's, which start at 1, to navigate in an array whose indexes start at 0.
To access the most recently pushed element, you should rather do :
items[contact.id - 1].image = im[0].image;
But you actually don't need to access the array : items[contact.id - 1] will return the object that you just pushed, and which is already referenced by variable contact, so you could just do :
contact.image = im[0].image;
I followed information on this answer
But it doesn't work in my situation.
Chrome Inspector console says "ReferenceError: dataResponse is not defined"
maybe that is the problem?
I am trying to GET this JSON from url:
[{"app_id":1,"app_name":"oh yeeah","app_description":"desc","app_price":111,"is_activated":false,"video":"videolink"},{"app_id":2,"app_name":"oh yeaaaeah","app_description":"ewaewq","app_price":222,"is_activated":false,"video":"fuck off"},{"app_id":3,"app_name":"oh yeaaaeah","app_description":"ewaewq","app_price":333,"is_activated":false,"video":"fuck off"}]
This is my javascript code
var appstore = angular.module('appstore', []);
appstore.service('dataService', function($http) {
delete $http.defaults.headers.common['X-Requested-With'];
this.getData = function(callbackFunc) {
$http({
method: 'GET',
url: '/administrator/components/com_apps/loadAppsJson.php'
}).success(function(data){
callbackFunc(data);
}).error(function(){
alert("error");
});
}
});
appstore.controller('app_Ctrl', function($scope, dataService) {
$scope.apps = [
{app_id:1, app_name:'oh yeah', app_description:'$app_description', app_price:111, is_activated:false, video:'$videolink'},
{app_id:2, app_name:'oh yeah', app_description:'$app_description', app_price:111, is_activated:false, video:'$videolink'},
{app_id:3, app_name:'oh yeah', app_description:'$app_description', app_price:111, is_activated:false, video:'$videolink'},
];
//$scope.apps = null;
dataService.getData(function(dataResponse) {
$scope.apps = dataResponse;
alert(dataResponse);
});
console.log(dataResponse);
console.log($scope.apps);
//get images thumbs
for(app = 0; app <= $scope.apps.length-1; app++) {
$scope.apps[app].thumb = ("000" + $scope.apps[app].app_id).slice(-3);
}
//separate apps to columns
var columns = [];
for (var i = 0; i < $scope.apps.length; i++ ) {
if (i % 3 == 0) columns.push([]);
columns[columns.length-1].push($scope.apps[i]);
}
$scope.columns = columns;
});
My HTML view
<div ng-controller="app_Ctrl">
<div class="row"></div>
<div class="row">
<div class="row" ng-repeat="apps in columns">
<div id="app_id_{{ app.app_id }}" class="col-lg-4" ng-repeat="app in apps | filter:search">
<div class="thumbnail" ng-class="app.is_activated ? 'activated' : ''">
<!-- -->
<img ng-src="/images/apps/app_images/{{ app.thumb }}_thumb.jpg" alt="{{ app.app_name }}" title="{{ app.app_name }}">
<div class="caption">
<h3>{{ app.app_name }}</h3>
<p class="app_price">{{ app.app_price }} €</p>
<div style="clear:both;"></div>
<p class="app_card_description">{{ app.app_description | limitTo:100 }}...</p>
Info
Video <span class="glyphicon glyphicon-facetime-video"></span>
{{ app.is_activated ? 'Aktivované' : 'Aktivovať' }}
</div>
</div>
</div>
</div>
To elaborate on what #Mritunjay said in the comments; review this code with comments:
dataService.getData(
// this is your callback function which has an argument for dataResponse
// the dataResponse variable will only be defined within the Call back function
function(dataResponse) {
$scope.apps = dataResponse;
alert(dataResponse);
// The Curly Brackets that follow mark the end of the callback handler method
});
// This log statement is not in the callback handler and there is no defined dataResponse variable which is probably why you got an error in the console
console.log(dataResponse);
You can fix this by moving the dataResponse log into the callback method, like this:
dataService.getData(function(dataResponse) {
$scope.apps = dataResponse;
alert(dataResponse);
console.log(dataResponse);
});
There appear to be other problems with your code, in that you are trying to access $scope.apps before the data is returned; which will hinder your processing. Easiest approach would be to move that processing into the result handler:
// define $scope.columns outside of the result handler
$scope.columns = [];
// call to method in service
dataService.getData(function(dataResponse) {
$scope.apps = dataResponse;
alert(dataResponse);
console.log(dataResponse);
// inside the result handler; you run this code after $scope.apps is defined:
for(app = 0; app <= $scope.apps.length-1; app++) {
$scope.apps[app].thumb = ("000" + $scope.apps[app].app_id).slice(-3);
}
//separate apps to columns
var columns = [];
for (var i = 0; i < $scope.apps.length; i++ ) {
if (i % 3 == 0) columns.push([]);
columns[columns.length-1].push($scope.apps[i]);
}
$scope.columns = columns;
});
That's what promises and asynchronous calls are all about.
console.log(dataResponse);
console.log($scope.apps);
The first one won't work because dataResource is a private variable and not part of the same scope you're trying to print.
The second one won't work either because that get's populated at future time (after X seconds), after the $http request is finished so it will only be availableat that point.
One way to do something after the object is populated is to use
$scope.$watch("apps", function (){
// do stuff
});