for a while I am trying to find how to make a load more(elements from an array) button,using Angular.
I have 9 elements in array, I use ng-repeat to loop them, and limitTo:3 to output first 3.
Questions:
1: is possible to make a load more button using only angular?(load more button is at bottom in example)
2: if not, how to make this work using jQuery?
http://plnkr.co/edit/1gHB9zr0lbEBwlCYJ3jQ
Thanks!
You don't need to think of jQuery, as you could solve this problem easily by using AngularJS itself.
You could maintain a variable inside your controller, name it as limit, then increment the limit variable inside loadMore() function.
Markup
<div ng-repeat="elem in travel.cruise | limitTo:travel.limit" class="cruises">
....COntent here...
</div>
Controller
app.controller('TravelController', function($scope) {
var vm = this;
vm.cruise = cruises;
vm.limit = 3;
$scope.loadMore = function() {
var increamented = vm.limit + 3;
vm.limit = incremented > vm.cruise.length ? vm.cruise.length : increamented;
};
});
Demo Plunkr
You can combine #Pankaj Parkar response with infiniteScroll so you dont need even the button.
No need of cracking any deep coding. Its simple to add just one line of code
<div ng-repeat="elem in travel.cruise | limitTo:travel.limit" class="cruises">
..NG Repeat..
</div>
Controller
$scope.loadMore = function() {
vm.limit = vm.limit + 3;
};
Assigning variable to increase by the 3 or more number will make your code work easily.
Simple and easy trick
Related
I'm calculating containers heights as per viewport by ng-style through my custom method.
Everything works well, but it keeps calling the method even if the element is styled. I have a big amount of DOM elements that are need to be styled. That's why, I can't allow continuous execution for all elements. Please note, I can't use ng-class because each element contains different content. And can't use controller scope variable due to unlimited numbers of elements.
HTML:
<div class="myElement" ng-style="styleElement('myElement')">
...
...
</div>
Function:
$scope.styleElement = function (elementCls) {
var elementToSet = $('.'+elementCls+':visible');
if(elementToSet.length){
var winHeight = $( window ).height();
var eTop = elementToSet.offset().top;
if(eTop == 0){
var elemChilds = elementToSet;
var elemChildsLen = elemChilds.length;
for(var i=0;i<elemChildsLen;i++){
var elem = elemChilds[i];
var r = elem.getBoundingClientRect();
if(r.top != 0){
eTop = r.top;
i= elemChildsLen;
}
}
}
var nScrollHeight = winHeight - eTop - 20;
return {
'height': nScrollHeight + 'px',
'overflow-x': 'hidden',
'overflow-y': 'auto'
};
}
};
I've tried using a custom directive but binding DOM or writing a watcher isn't a preferable solution for me due to performance. Thanks in advance!
Use one time binding, which will only call styleElement once.
<div class="myElement" ng-style="::styleElement('myElement')">
...
...
</div>
I am working on a cart using Angular, during process of that I faced a issue about calculation.
In my scenario I have code something like
<label>No. of item</label>
<div>{{totalItems}}</div>
<div ng-repeat="cartElement in currentCartDetails.cartElements">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
What I want, add all something like
totalItems += cartElement.quantity
I know there are so many option to display the value
eg. using calculation from server side, iteration in calculation to controller
But what I am looking for, when I am iterate object on view page is there any way to calculate there and get benefit of tow way binding.
Have you tried to do it with function?
Define a function inside your controller like calculateTotal then call it for every iteration.
$scope.totalItems = 0;
...
$scope.calculateTotal = function(cart){
$scope.totalItems += cart.quantity;
}
...
then in your template
<div ng-repeat="cartElement in currentCartDetails.cartElements" ng-init="calculateTotal(cartElement)">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
You can also achieve this by ,
Method 1:
HTML :
<div>{{gettotalItems(currentCartDetails.cartElements)}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
return num;
}
Method 2:
HTML:
<div ng-init="gettotalItems(currentCartDetails.cartElements)>{{totalItems}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
$scope.totalItems = num;
}
Advantage is You can get the total cart value directly in one call by passing entire List of Objects. instead of, counting each time during iteration.
i am about to bang my head to walls. i thought i had an understanding of how angular works (filters too). but i just cant find the problem about my filter. it causes infdig. and i even dont change source array in filter.
(function () {
angular.module('project.filters').filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var resultArray = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
resultArray.push(data.slice(i, i + chunk));
}
return resultArray;
};
});
})();
i have lists where i need to split data to x columns. it is complicated to solve with limitTo.
(limitTo: $index*x | limitTo: $last ? -z : -x)
it causes a dirty template file. so i decided to create a filter which splits an array to groups.
[1,2,3,4,5,6,7,8] -> [[1,2,3],[4,5,6],[7,8]]
so i can easily use it in my template.
Can u help me about what causes infdig in this filter?
Edit: the error message itself looks strange with some numbers in that don't appear anywhere in the code, which can be seen at http://plnkr.co/edit/pV1gkp0o5KeimwPlEMlF
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: regularInterceptedExpression","newVal":23,"oldVal":20}],[{"msg":"fn: regularInterceptedExpression","newVal":26,"oldVal":23}],[{"msg":"fn: regularInterceptedExpression","newVal":29,"oldVal":26}],[{"msg":"fn: regularInterceptedExpression","newVal":32,"oldVal":29}],[{"msg":"fn: regularInterceptedExpression","newVal":35,"oldVal":32}]]
HTML Template
<div class="row" ng-repeat="chunk in docProfile.SysMedicalInterests | splitListFilter: 3">
<div class="col-md-4" ng-repeat="medInterest in chunk">
<label style="font-weight:normal;">
<input type="checkbox" value="{{medInterest.ID}}" ng-click="docProfile.saveInterest(medInterest.ID)" ng-checked="docProfile.isMedChecked(medInterest.ID)"> {{medInterest.Name}}
</label>
</div>
</div>
Controller Code
var me = this;
me['SysMedicalInterests'] = null;
var loadMedicalInterests = function(){
var postData = { 'Data': me['data']['subData'] };
return docService.loadMedicalInterests(postData).then(function(resp) {
me['SysMedicalInterests'] = resp['data'];
}, function(){});
};
loadMedicalInterests();
so array starts with a null reference and loads data from server. which changes array causes a second filter run. but it doesnt stop after that
Edit: here is plunkr http://plnkr.co/edit/OmHQ62VgiCXeVzKa5qjz?p=preview
Edit: related answer on so https://stackoverflow.com/a/21653981/1666060 but this still doesn't explain angular built in filters.
here is angularjs limitTo filter source code
https://github.com/angular/angular.js/blob/master/src/ng/filter/limitTo.js#L3
About what exactly causes it, I suspect is something to do with the fact that every time you run the filter a new array reference is created and returned. However, Angular's built-in filter filter does the same thing, so I'm not sure what is going wrong. It could be something to do with the fact that it's an array of arrays that is being returned.
The best I have come up with is a workaround/hack, to cache the array reference manually as an added property, which I've called $$splitListFilter on the array, and only change it if it fails a test on angular.equals with the correct results calculated in the filter:
app.filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var results = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
results.push(data.slice(i, i + chunk));
}
if (!data.$$splitListFilter || !angular.equals(data.$$splitListFilter, results)) {
data.$$splitListFilter = results;
}
return data.$$splitListFilter;
};
});
You can see this working at http://plnkr.co/edit/vvVJcyDxsp8uoFOinX3V
The answer uses Angular 1.3.15
The JS fiddle works fine: http://jsfiddle.net/3tzapfhh/1/
Maybe you use the filter wrongly.
<body ng-app='app'>
<div ng-controller='ctrl'>
{{arr | splitListFilter:3}}
</div>
</body>
Within Firebase, I have a list of 'ideas.' If a user presses a button associated with the idea, I'd like a value to be appended to that idea under an attribute called 'newValue.'
For example, the below html, uses ng-repeat to show the array of ideas and creates an associated button called 'Append Value.' I want a new value to be appended to the idea's attribute called 'newValue' every time a user presses 'Append Value.'
<body ng-controller="ctrl">
<table>
<tr class="item" ng-repeat="(id,item) in ideas">
<td>{{item.idea}}</td>
<td><input ng-model="newValue"></td>
<td><button ng-click="ValueAppend(id,newValue)">Append Value</button></td>
</tr>
</table>
</body>
Below is my attempt to create this function.
var app = angular.module("app", ["firebase"]);
app.factory("Ideas", ["$firebase", function($firebase) {
var Ref = new Firebase('https://crowdfluttr.firebaseio.com/');
var childRef = Ref.child('ideas');
return $firebase(childRef).$asArray();
}]);
app.controller("ctrl", ["$scope","Ideas", function($scope,Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.ValueAppend = function (id,newValue) {
var URL = "https://crowdfluttr.firebaseio.com/ideas/" + id + "newValue";
var IdeaRef = new Firebase(URL);
var IdeaData = $firebase(IdeaRef);
$scope.IdeaAttributes = IdeaData.$asArray();
$scope.IdeaAttributes.$add({
newValue: newValue,
timestamp: Date.now()
});
};
}]);
See my codepen for my working example: http://codepen.io/chriscruz/pen/PwZWKG
More Notes:
I understnad that AngularFire provides $add() and $save() to modify this array, but how could I use these methods so that I can add a new 'string' under an item in an array.
I'm not sure if these are your problems, but they are two typoes of mistakes in the code above and the codepen: typos and conceptual.
Typos
You forgot to inject $firebase into the controller, which leads to:
"ReferenceError: $firebase is not defined"
Solution is simply of course:
app.controller("ctrl", ["$scope","Ideas", "$firebase", function($scope,Ideas,$firebase) {
In addition you seem to be missing a slash before newValue, which means that you're trying to create a new idea instead of adding the value to an existing one. Solution is simple again, add a slash before newIdea as in:
var URL = "https://crowdfluttr.firebaseio.com/ideas/" + id + "/newValue";
If you find yourself making this mistake more often, you might be better server by the child function. Although it typically is a bit more code, it lends itself less to this typo of typo. Creating the ref to the newValue node becomes:
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(id).child("newValue");
Conceptual
With those trivial typos out of the way, we can focus on the real problem: which is easiest to see if you console.log the URL that you generate:
https://crowdfluttr.firebaseio.com/ideas/0/newValue
Yet if you look up the same data in the Firebase forge (by going to https://crowdfluttr.firebaseio.com/ideas/ in your browser), you'll see that the correct URL is:
https://crowdfluttr.firebaseio.com/ideas/-JbSSmv_rJufUKukdZ5c/newValue
That '0' that you're using comes from the id and it is the index of the idea in the AngularJS array. But it is not the key that Firebase uses for this idea. When AngularFire loads your data with $asArray it maps the Firebase keys to Angular indexes. We need to perform the reverse operation to write the new value to the idea: we need to map the array index (in id) back to the Firebase key. For that you can call [$keyAt(id)][1]. Since you keep the array of ideas in Ideas, it is simply:
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(Ideas.$keyAt(id)).child("newValue");
So the controller now becomes:
app.controller("ctrl", ["$scope","Ideas", function($scope,Ideas) {
$scope.ideas = Ideas;
$scope.idea = "";
$scope.ValueAppend = function (id,newValue) {
var URL = "https://crowdfluttr.firebaseio.com/ideas/";
var IdeaRef = new Firebase(URL).child(Ideas.$keyAt(id)).child("newValue");
var IdeaData = $firebase(IdeaRef);
$scope.IdeaAttributes = IdeaData.$asArray();
$scope.IdeaAttributes.$add({
newValue: newValue,
timestamp: Date.now()
});
};
}]);
I quickly gave it a spin in your codepen and this seems to work.
Say I have
<div ng-repeat="item in scope.allItems">
<div ng-click="doSomething(...)"></div>
</div>
If I want to do something with item upon click, which one is a better option?
Option 1
Use ng-click="doSomething($index) and have:
$scope.doSomething = function($index) {
var myItem = $scope.allItems[$index];
}
Option 2
Use ng-click="doSomething(item) and have:
$scope.doSomething = function(myItem) {
// Do whatever
}
If you are just doing something to the item, pass that in. This way the function doesn't need to know which array the item belongs to:
$scope.addColor = function(car) {
car.color = 'red';
};
If, on the other hand, you need to modify the array I prefer to pass in $index and save having to loop through the array looking for a match:
$scope.deleteCar = function(index, cars) {
cars.splice(index, 1);
};
If you want use filter for scope.allItems, use Option 2 - because using filters change element $index.
If you don't use filter,you can use Option 1.
IMHO Option 2 more easy and useful than Option 1, so i already use Option 2.