AngularJS - Issues with references when using services and ng-repeat - angularjs

Im finding myself fighting with 'pointers' in javascript. It's like I'm in C++ again. I would like to know your approach to the following issue Im having.
My case: I have a ng-repeat that goes over a collection. When clicking one of the collection elements, I do a copy of the element, do changes over it and POST/PUT data to server.
If the server replies 200 then I will apply those changes to the collection. The collection contains objects, so it means that Im working with references NOT values.
The module definition with a service:
angular.module('dataModule', [])
.service('DataService', function () {
this.collection = [{id:0, who: 'I'},
{id:1, who: 'YOU'},
{id:2, who: 'HE'},
{id:3, who: 'SHE'},
{id:4, who: 'IT'}];
})
And here the controller:
.controller('listCtrl', function ($scope, $timeout, DataService) {
// Data used in view
$scope.collection = DataService.collection;
// Action trigger from the view
$scope.change = function(data, index){
// Get a copy
var copy = angular.copy(data);
// Apply changes over the copy
copy.id = data.id*100*index;
// Simulate POSTing/Updating data to server
console.log('Sending data to server...');
$timeout(function(){
// Response is 200
var response = 200;
// Assigning copy -> data
data = copy;
// The prev. assignment is not updating the collection
// Of course I could do $scope.collection[index] = copy;
// because this case is simple enough.
// Im finding myself having the service with methods find, edit, ...
// What's a better approach?
}, 2000)
}
});
As I said in the comments, Im finding myself implementing functions like find, edit or get in the service. Is that the approach to go???
Here is the jsfiddle http://jsfiddle.net/kitimenpolku/M66LV/5/ in case I was not able to explain it correctly.

From my perspective, there are two ways you can go with updating the view after you write to the server:
You ask the server for the entire collection again, and then set it
locally. This requires the use of promises and replacing the
current collection with a new one. This replacement will
automatically trigger an update in the ng-repeat directive you are
using in your template.
You perform the same transformation locally, as you mentioned yourself. This entails finding the correct index of the array and swapping out the old object for the edited one. This is how I like to setup my own applications and it's really not so hard to do. Here's how:
I can see that you really are thinking of this javascript replacement as if it were in c++. Here, you are correct in that data = copy; in your server callback is merely updating a reference, not any data itself.
Instead, you can alter the collection itself directly: $scope.collection[index] = copy
Here's a working fiddle: http://jsfiddle.net/wilsonjonash/GZ2eR/

data is a reference to the original item but using data = copy is just invalidating data so the original never gets updated.
So to update the original item you need to perform.
data.id = copy.id;
data.who = copy.who;
or
$scopy.collection[index] = copy
fiddle with updated code
http://jsfiddle.net/M66LV/7/

Related

AngularJs forEach logic, not so academic but working

I've got this code working, but i don't think it is the right way to do. I think i need to use $q, but it is difficult to understand for me .
I receive correctly this nice array of json (data.data) from my angularJs factory, from my php server-mysql backend . It is some rendez-vous with a start date:
[{"id":"1","title":"Loi Travail","infos":null,
"adresse":"12 avenue des lis 78013 paris",
"criticite":"4","fichiers":null,
"start":"2017-06-11T22:37:59.012Z"},
{"id":"17","title":"jjtyjyjt","infos":"jytjjyjyj",
"adresse":"tjtyjjyj","criticite":"7","fichiers":"",
"start":"2017-06-11T22:37:59.012Z"}]
The problem is that angular-material-datetimepicker doesn't recognise the start date, because it is a string, so i need to do a loop to add new Date(), for converting each of my "start" elements.
So, i've done this short code, that is working :
rdvFactory.get_rdvs().then(function(data){
$scope.events = data.data;
angular.forEach($scope.events,function(value,index){
value.start = new Date(value.start);
})
})
It is working, meaning that i can see all of the rendez vous appearing inside angular-material-datetimepicker, but i don't think it is the right way to do, because I'm changing several time the scope(2 way binding) each iterations of angular.forEach, i suppose it is very consuming.
I think i should do a foreach on data.data for converting the dates, and after it is finished, i should set up $scope.events only after that .
I don't know if you see what i mean, but it is difficult to work with $q, i would appreciate if somebody could explain me $q a little more because even with the examples given on stackoverflow, i t is really difficult to understand the syntax.
Question : Do you think it is better to add a second .then like this ?
rdvFactory.get_rdvs().then(function(data){
$scope.events = data.data;
}).then(function(data){
angular.forEach($scope.events,function(value,index){
value.start = new Date(value.start);
})
});
In this last example, do you think the second.then really wait for $scope.events to get populated ? I'm not sure ... It is working but it is the right way to do to avoid performances problems ?
Have a nice day
Nick - you are right, changing a $scope variable is costly and would generate unnecessary digest cycles. I would suggest you do this instead -
rdvFactory.get_rdvs().then(function(data){
var tempData = data.data; // A temp local variable
angular.forEach(tempData ,function(value,index){
value.start = new Date(value.start);
}
$scope.events = tempData; // assign the final JSON to scope variable.
})
With regards to $q, its used mainly to combine two or more AJAX responses and then perform the operation. For example -
If there is an AJAX call to fetch User Details (based on ID) & another call to fetch User access rights (based on ID), then we may want to fire them together as both needs to be success for a user to login.
Incorrect flow -
Fetch User details.on success then(
//fetch user access right // on success of user details call fire user access rights.
)
Instead, you queue both the calls in an array and wait for both of them to pass or fail together.
$q = [call1, call2]
$q.then (
//on success, we know that user is correct and has right user access.
)

AngularJS + Hoodie: How to do a correct data flow?

I would like to combine AngularJS and Hoodie, but I am not sure how to do it the correct way.
Let's say I have a basic AngularJS controller
app.controller("c", function($scope) {
$scope.table = [];
$scope.onAdd = function(newEntry) {
$scope.table.push(newEntry);
};
});
After clicking on e.g.
<button ng-click="onAdd('test');">add</button>
Everything works as expected. But now I want to use Hoodie. Currently I am trying this:
app.controller("c", function($scope) {
$scope.table = [];
$scope.onAdd = function(newEntry) {
$scope.table.push(newEntry);
};
hoodie.store.on('entries:add', $scope.onAdd);
});
and anywhere: hoodie.store.add("entries", tmpObject);
The onAdd() gets called and is inserted into the table array (verified in the developer console), but my html table (via ng-repeat="entry in table") is not updated.
First question: Do you know why? What do I think wrong here?
Second question: What would be a good way to combine Hoodie and AngularJS? Would it be better to $watch on the table and insert items into it and store it via Hoodie after getting a change or the other way around and add a new item to the hoodie.store and then on("entries:add") do some Angular stuff?
Thanks in advance!
Heyho =)
First: If you update data-structure via an 'external' event(in this case hoodie), you need to trigger the Digest-Cycle e.g. $rootScope.$apply();
Second: There is a hoodie-angular-plugin that is written by Elmar and me. That solved the most of your basic tasks. That would be the easiest way in this case.

Update Angular View after resource post

Here is my problem! I use $resource API to post data from the client side to the server side. After the server side successfully updates the database, I kinda hope my data in view will be updated.
I chose to achieve this with:
$scope.magazines = Magazines.query();
$scope.addMagazine = function() {
var magazine = new Magazines({...payload data});
Magazines.save(magazine, function() {
$scope.magazines = Magazines.query();
});
};
Magazines is a defined ngResrouce factory.
This code works fine. My only problem is, whenever I activate this function (through mouseclick), the view flashes (refreshes) once. It is a really really bad user experience.
I wonder if there is any other way to add the new "magazine" to the scope without refreshing the entire $scope.magazines ?
By the way, the api at the backend will add a few properties (like created time, or id to it). Even if I write code to get the last added item (a different factory method I suppose), I'll still have to add it to the $scope.magazines and I don't know how to do that.
Any thought?
Just push the new Magazine onto the existing $scope.magazines (assuming it's an Array)
var magazine = new Magazines({...payload data});
$scope.magazines.push(magazine);

How to clear a model when my AngularFire app loads initially?

I am new to AngularFire and am trying to understand a simple concept. If you go through the AngularFire tutorial located at the following url: http://angularfire.com/tutorial/index.html#gettingstarted there is a rudimentary example of using AngularFire to build a primitive "chat" application.
The tutorial is very clear and concise but I do not understand one primary point with it:
function MyCtrl($scope, angularFire){
$scope.messages = [];
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/messages");
angularFire(ref, $scope, 'messages');
$scope.messages = []; //shouldn't this clear the data locally and remotely?
}
The issue is that a model is first created, and then the binding magic with AngularFire is setup such that there is now a 3-way binding to the model. If there is an array of data already stored in Firebase, that data is fetched and synced and your model will now have this data locally.
What I simply do not understand is, when the controller code runs, suppose I set the model to an empty array AFTER the angularFire binding is wired up, why doesn't the Firebase data get cleared out? Never mind the fact, that refreshing the page would basically keep wiping out the data (the behavior I want).
Now, I can get this behavior to work, if I wire up an ng-click event to a button, that calls a method named clear defined on my $scope object. If within, that method, I simply call: $scope.messages = [];, then my model is cleared locally, and remotely.
But why doesn't this work on initialization?
Help is always appreciated.
I think I may have found an answer to my own problem. It looks like, you must wait until the promise returns to actually start modifying the model like so. Now whenever I refresh my page, when my .then() runs, it will clear out my data.
I suppose this is how it should be done. Can anyone confirm?
$scope.messages = [];
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/items");
var prom = angularFire(ref, $scope, 'messages');
prom.then(function(){
console.log("data loaded");
$scope.messages = [];
});
I'm guessing the remote data hasn't returned yet and is populated after your second call to $scope.messages =[]
edit:
Why not just explicitly remove your data from FB before binding it to a local list.
var ref = new Firebase("https://<xxxxxx>.firebaseio.com/messages");
ref.remove();

How to refresh local data fetched using $resource service in AngularJS

I have AngularJS application that use $resource service to retrieve data using query() method and create new data using model.$save() method. This works fine exactly as the docs say it should.
My question is how to update my local data fetched using MyService.query() in the first place after I've changed it?
I took the most simple approach for now and I simply call the query() method again. I know this is the worst way efficiency-wise but it's the simplest one.
In my server-side I return the whole state-representation of the new model. How can I add the newly created model to the array of the local data?
UPDATE
I've end up simply pushing the model return from the server but I'll still be happy to know if that's the way to go. From what I can understand from the source code the return array is plan-old-javascript-array that I can manipulate myself.
This is the code I used
$scope.save = function () {
var newComment = new CommentsDataSource();
newComment.Content = $scope.todoText;
newComment.$save({ id: "1" }, function (savedComment) {
$scope.comments.push(savedComment);
});
}
I would simply get the whole list again, to be able to see the modifications brought to the list by other users.
But if the solution you're using suits you, then use it. It's corrrect. Angular uses bare-bones JavaScript objects. Adding a new instance to a list in the scope will refresh the list displayed on the page.

Resources