Angular: where to calculate values - angularjs

Given an array of “visits” [{ date:DATE, summary:TEXT }, { date:DATE, summary:TEXT }, …]
, if I need to show the last visit, where would I do the calculation:
In the controller and add the calculated value to the $scope - <div>{{lastVisit}}</div>
Using a $scope method - <div>{{getLastVisit()}}</div>
In the view (this definitely doesn’t feel right) - <div>{{visits[visits.length-1]}}</div>
I am avoiding for now the question whether the model should be manipulated inside the controller or in its own service.

With option 1, you'd have to add a watch to update the lastVisit in model any time the visits array changes. Option 2 is better but requires writing an additional one-liner function in your model.
The third option is legit and require zero javascript so if you only need to simply show the last element of the array this is the way to go.
It's also the most efficient as it doesn't require any additional objects in memory, and doesn't call any other function (than angular parse internally)
If you don't want the logic in your view, Option 2 is your best choice. But I would create a more generic method that returns the last element of the array like that:
<div>{{getLastItem(visits)}}</div>
$scope.getLastItem = function(arr){
return arr[arr.length - 1];
};
See fiddle: http://jsfiddle.net/hZM23/1/

My personal preference on the above approaches:
Yes, the best approach, since you don't have your business logic in your view.
This will have the same effect as the 1st option, but accessing Model from view comes under best practices.
No, since your business logic might change in future.

Option 1: Doing calculations in the controller and storing the value to $scope. When that value is updated, angular will automatically update your view. This is likely what you want.
To clarify: In your controller, when the visits array is updated ( element added, deleted, etc... ), calculate the last visit value and store it to $scope.lastVisit.
var function newVisit( visit ){
visits.append( visit );
$scope.lastVisit = visit; // this will update your view
}
Why I don't think option 2 is right: Angular will be binding the function and not the value itself. Binding to the value itself is likely what you mean.
You are correct about 3, keep logic out of the view if possible.

Related

ngAnimate to detect changes from $http-call with interval

I have an array with a few items in it. Every x seconds, I receive a new array with the latest data. I check if the data has changed, and if it has, I replace the old one with the new one:
if (currentList != responseFromHttpCall) {
currentList = responseFromHttpCall;
}
This messes up the classes provided by ng-animate, as it acts like I replaced all of the items -- well, I do actually, but I don't know how to not.
These changes can occur in the list:
There's one (or more) new item(s) in the list - not necessaryly at the end of the list though.
One (or more) items in the list might be gone (deleted).
One (or more) items might be changed.
Two (or more) items might have been swapped.
Can anyone help me in getting ng-animate to understand what classes to show? I made a small "illustation" of my problem, found here: http://plnkr.co/edit/TS401ra58dgJS18ydsG1?p=preview
Thanks a lot!
To achieve what you want, you will need to modify existing list on controller (vm.list) on every action. I have one solution that may work for your particular example.
you would need to compare 2 lists (loop through first) similar to:
vm.list.forEach((val, index)=>{
// some code to check against array that's coming from ajax call
});
in case of adding you would need to loop against other list (in your case newList):
newList.forEach((val, index)=>{
// some code to check array on controller
});
I'm not saying this is the best solution but it works and will work in your case. Keep in mind - to properly test you will need to click reset after each action since you are looking at same global original list which will persist same data throughout the app cycle since we don't change it - if you want to change it just add before end of each function:
original = angular.copy(vm.list);
You could also make this more generic and put everything on one function, but for example, here's plnkr:
http://plnkr.co/edit/sr5CHji6DbiiknlgFdNm?p=preview
Hope it helps.

How do I modify/enrich an Eloquent collection?

I'm using eloquent relationships.
When I call $carcollection = $owner->cars()->get(); I have a collection to work with. So let's say that I have, for this particular owner, retrieved three cars. The collection is a collection of three arrays. Each array describes the car.
This is all working fine.
Now I want to add more attributes to the array, without breaking the collection. The additional attributes will come from a different source, in fact another model (e.g. servicehistory)
Either I retrieve the other model and then try merge() them, or I try manipulate the arrays within the collection without breaking the collection.
All this activity is taking place in my controller.
Is one way better than another, or is there a totally different approach I could use.... perhaps this logic belongs in the model themselves? Looking for some pointers :).
Just to be specific, if you do $owner->cars()->get(); you have a collection of Car Models, not array.
That have been said, you can totally load another relation on you Car model, using
$carcollection = $owner->cars()->with('servicehistory')->get();
$carcollection->first()->servicehistory;
You can try to use the transform method of the collection.
$cars = $owner->cars()->get();
$allServiceHistory = $this->getAllService();
$cars->transform(function($car) use($allServiceHistory) {
// you can do whatever you want here
$car->someAttribute = $allServiceHistory->find(...):
// or
$car->otherAttribute = ServiceHistoryModel::whereCarId($car->getKey())->get();
});
And this way, the $cars collection will be mutated to whatever you want.
Of course, it would be wiser to lazy load the data instead of falling into an n+1 queries situation.

angular one way binding

I am using angular 1.3.15. I want to bind data, such a way that, first time variable($scope.twotap_builtin_cart.sites[sikey].shipping ) assigned data from $scope.shipping_address. Later on even if , variable named $scope.twotap_builtin_cart.sites[sikey].shipping data modified, it should not haveve any impact on other $scope.shipping_address. I am talking about one time binding or one way binding
you should use angular.copy() for deep coping
$scope.shipping_address = angular.copy($scope.twotap_builtin_cart.sites[sikey].shipping)
this way even if $scope.twotap_builtin_cart.sites[sikey].shipping modify its not gonna bind to the $scope.shipping_address
I think that you are not looking for binding but simply assigning a value of a variable to another. When working with JSON objects (and $scope is one such object), making a = b is NOT copying the contents of b to a, but making both a and b reference the same object. The best way is to perform the assignment as:
b = JSON.parse(JSON.stringify(a)) ;
In your case:
$scope.twotap_builtin_cart.sites[sikey].shipping = JSON.parse(JSON.stringify($scope.shipping_address)) ;
One you do this, both variables hold the same information but they can be changed without affecting the other.
A similar requirement was asked in this question:
Edit with bootstrap modal and angulajs
Similarly, you can use the AngularJS copy function to replicate your data without any lingering bindings.
$scope.twotap_builtin_cart.sites[sikey].shipping = angular.copy($scope.shipping_address);
Here we are copying the value from $scope.shipping_address into the other variable. Now even if you make a change to $scope.twotap_builtin_cart.sites[sikey].shipping, this will not be reflected in $scope.shipping_address - which is what you want.

Are there any objective reasons why it is better to not call functions in Angularjs Views to display data?

To make a long story short, in our application at work, we have a function that creates multiple objects that inherit methods from a prototype.
As such:
function MileCounter(totalMilesRan, numOfDaysToRunThem) {
this.totalMilesRan = totalMilesRan;
this.numOfDaysToRunThem = numOfDaysToRunThem;
};
MileCounter.prototype.avgMilesPerDay = function() {
return (this.totalMilesRan/this.numOfDaysToRunThem);
}
And then in the view, this is called like this:
<div> {{mileObj.avgMilesPerDay()}} </div>
The disagreement comes from their belief that the average should be provided to the mileObj in the controller so the average can be called in the view just as they would to get moneyObj.totalMilesRun as:
<div> {{mileObj.avgMilesPerDay}} </div>
Something to keep in mind is that the actual objects in question have many more properties than just two and the number of objects being created is usually in the dozens but could eventually sky rocket into the hundreds or even thousands.
My Coworkers believe that the view should not be concerned at all with calculating data and should only be concerned with displaying it.
My Question: Is there an objective reason why it would be better to add the avgMilesPerDay value directly to each object, rather than just calling a prototype method to handle it? It is my understanding that adding a bunch of properties to objects could eventually be a drag on memory when there are enough objects being created, with enough properties on each one, and that having simple prototype methods could help ease that burden.
Advantages of calling a function:
less memory usage
more encapsulation: changing the value of totalMilesRan or numOfDaysToRunThem automatically changes the value of the average
Advantages of adding a field for the average:
slightly more efficient: the average doesn't need to be computed again and again
I would keep using the function from the view unless you have a performance problem, and have proven that it comes from the function call, and it can't be solved in another way (like one-time binding for example).

Converting angular-dragdrop to work with firebase $asArray

Pretty new to AngularJS and Firebase here, I am trying to convert angular-dragdrop.js as per the following link below to work with angularFire 0.8.0 $asArray feature:
https://github.com/codef0rmer/angular-dragdrop/blob/master/src/angular-dragdrop.js
I was just wondering if the following changes would be sufficient:
1) Include firebase within function declaration
(function (window, angular, firebase, undefined) {
2) Include $firebase within jqyoui callback function
var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$firebase', '$timeout', '$parse', function($timeout, $parse) {
3) Change all the "push" and "splice" update on the dropModelValue and dropModelValue to $add and $remove instead.
dropModelValue.$add(dragItem);
4) Add $save after dropModelValue and dropModelValue assignments
dragModelValue[dragSettings.index] = dropItem;
dragModelValue[dragSettings.index].$save(dragSettings.index);
Your help is much appreciated. Much thanks in advance.
You can utilize $extendFactory to override the push/splice behaviors instead of hacking on the drag drop lib. Ideally, you would just update the priority on the records and let the server move them.
Keep in mind that Firebase data is a JSON object (not an array and therefore not ordered in JavaScript), so moving items in the array has no effect on their position on the server. You must use priorities if you want to enforce an order on the data, other than lexicographical sorting by keys.
Also, you aren't using $save correctly--you call array.$save(item), not item.$save(itemIndex). Judging by these misconceptions, there are likely to be lots of other issues. A trip through the Angular Guide and the Firebase JS Guide be a great primer here.
One technique I have used to reorder a Firebase array using drap & drop, is to rebind the keys to the values so that their lexicographical order match the new order set by the user. Since Firebase will enforce lexicographical order of keys, swapping the keys of 2 values will swap the values. Every time the user drops an item, rebind the keys:
_(myFirebaseArray)
.map('$id')
.sortBy()
// at this stage we have the array of keys sorted lexicographically
// we pair each key with the values, which are sorted by the user
.zipObject(myFirebaseArray)
// for each pair, bind the key to the value and save
.each(function (value, newKey) {
value.$id = newKey
myFirebaseArray.$save(value)
})
This is probably sub-optimal. I was not aware of priorities. This technique can probably be adapted fairly easily to use priorities. The code above should be compatible with lodash from 2 to 4.
Example of this technique in an application here. The ranking array is bound to the drap & dropping through Angular UI.Sortable.

Resources