How to cope a data to a scope as a backup field in Angularjs to roll back when request failed? - angularjs

I'm having a block of data, then when i edit one of them, i want to copy the current date of the item to a scope like $scope.backup_field to be able to roll back when the update failed. As my code below, the $scope.backup_field can get the item's data which i'm editing, but when the updated failed, i console log out the $scope.backup_field is also modify following the newest data that i modified.
My code:
$scope.block_data = [
[
{id: 1, name: 'Data 1'},
{id: 2, name: 'Data 2'}
],
[
{id: 3, name: 'Data 3'}
]
];
$scope.backup_field = [[],[],[],[],[],[]];
$scope.editItem = function(_no, index){// _no has value from 0 to 6
$scope.backup_field[_no][index] = $scope.block_data[_no][index];
}
$scope.updateItem = function(_no, index){
$http.post(.....).then(function (response){
var res = response.data;
if (res.status === 200){
//Do something if update successfully
}else {
alert('Failed');
$scope.block_data[_no][index] = $scope.backup_field[_no][index]; //The problem is here, the $scope.backup_field[_no][index] value also change following the data of item that user modified in UI.
}
})
}

you have to use angular.copy for that -
Creates a deep copy of source, which should be an object or an array.
This functions is used internally, mostly in the change-detection
code. It is not intended as an all-purpose copy function, and has
several limitations (see below).
$scope.block_data[_no][index] = angular.copy($scope.backup_field[_no][index]);

Related

More than one getItem localStorage in a state

Is it possible to have more than one localStorage.getItem in state?
Right now I have this:
const [list, useList] = useState(
JSON.parse(localStorage.getItem("dictionary")) || [] //tasks in my to-do
);
and I should also keep in this state my subtasks, contained in a task, with this structure:
- task {
- id
- body
- subtasks
[{
- id
- body
}]
}
Can I save also the subtasks in local storage and access them with getItem?
These are what I want to use to get my subtasks:
JSON.parse(localStorage.getItem("domain")) || []
JSON.parse(localStorage.getItem("range")) || []
Yes, you can have more than one array of values in local storage. You need to set the item before you can access it though, you should also serialize the object or array to a string when saving it.
localStorage.setItem("dictionary", JSON.stringify([]));
localStorage.setItem("domain", JSON.stringify([]));
localStorage.setItem("range", JSON.stringify([]));
alert(JSON.parse(localStorage.getItem("dictionary")));
alert(JSON.parse(localStorage.getItem("domain")));
alert(JSON.parse(localStorage.getItem("range")));
Lucky me, I saw your other question which contains a running code snippet, you should add it here too!
From what I saw you're trying to create a tree of tasks, dictionary is a task and it can have subtasks such as domain and range, right? Then you should have a data structure like this:
singleTask = {
id: 0,
body: "task",
domain: [
{
id: 00,
body: "subtask domain 1"
},
{
id: 01,
body: "subtask domain 2"
}
],
range: [
{
id: 10,
body: "subtask range 1"
},
{
id: 11,
body: "subtask range 2"
}
]
}
When you're rendering a task as TaskListItem, you render the task.body. Then pass task.domain to a SubtaskDomain component, task.range to a SubtaskRange component.
When you submit a subtask, update the main list in App, after you do that, update local storage, you already do that, but you actually only need one set item, and it's
localStorage.setItem("dictionary", JSON.stringify(listState));
because you have everything in it!

Angularjs : How to retrieve data from ng-options in angularjs using single string or only one loop

good day, i would like to ask question about retrieving selected data from ng options using string as comparator.
in my controller i have this data
$scope.myList = [
{ id: 0, name: "foo" },
{ id: 1, name: "foo1" },
{ id: 2, name: "foo2" }
];
for my index.html
<select
ng-options="list.name for listin myList"
ng-model="selectedList"
/select>
so the scenario is this, i have my own way of retrieving data but it includes nested looping and my question is, is there any way's to do this just using one loop or a one liner code? because basically i already have the value from database, is there any way so that i don't need to iterate the whole list just to show the data in HTML select input or if not possible at least i wont use nested loops, thanks
this is what i tried so far
// assume the value retrieved from database is "foo1"
var retrievedData = "foo1";
for(var i=0; i<myList.length; i++) {
if(myList[i]['name'] == retrievedData ) {
$scope.selected = myList[i];
}
}
You can do this in one line like bellow
var retrievedData = "foo1";
$scope.selected = myList.find((ele) => ele['name'] == retrievedData);
find() will return the first match.
You can do this in one line like bellow
myList.filter(function(listitem,listitemid){return listitem.name==retrievedData});

Angularjs watch array and get changed object

In the context of inserting or deleting from an array in angular, is it possible to watch the array and then get the object that was added or deleted from the array? I don't care about the objects properties in the array, only the objects themselves being added or deleted. So I believe $watchCollection is a good fit here so it's not a deep watch.
For example, I have this array as a model for a dual list box:
$scope.employees = [
{
name: "Bob",
id: "0"
},
{
name: "Carl",
id: "1"
},
{
name: "Bill",
id: "2"
}
];
The listbox will automatically update $scope.employees when i move one off of it or onto it (insert/delete). If I do:
$scope.$watchCollection('employees', function(){
//somehow get changed object
var changedObject = ...;
$scope.changedItems.push(changedObject);
});
I want to be able to get the added/deleted item so I can use it or save it somewhere.
The $watchCollection handler function receives both new and old value:
$scope.$watchCollection('employees', function(newValue, oldValue){
console.log(newValue);
console.log(oldValue);
var addedArray = newValue.filter(x => !oldValue.find(x));
var removedArray = oldValue.filter(x => !newValue.find(x));
var changedObject = {added: addedArray, removed: removedArray};
$scope.changedItems.push(changedObject);
});
For more information, see AngularJS $watchCollection API Reference

Handling Subsidiary Views in Backbone.js

I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).

Best practice to save form state and progress in angularjs application?

I'm writing a multi step wizard in angularjs. On each step, I wish to save to the server, and progress to the next step in the the wizard.
Using simple links to progress to the next step, and saving to the backend server, is simple and it updates my location, maintaining browser history, but this approach violates the intent of GET being safe and idempotent.
I'm taking this approach instead;
$scope.progressToSetp2 = function() {
$http.put('quote/' + $routeParams.quoteId, $scope.quote)....;
$scope.OnAfterSubmit = function() {
$location.path('/steptwo').replace();
$scope.$apply();
};
$scope.includeFormPartial = 'partials/steptwo.html';
};
Is this a good approach? Are there better approaches?
Thanks.
of course there are! why are you saving to the server in each step? it is not the angular way of thinking.
the best practice is to save the state in the scope and do local validation, and after finishing you save to the back end the whole steps in on send. (local validation does not cancel the need to back end validation)
If you want even better practice, use a service to hold the data. Look at this
angular.module('myApp', [])
.service('notesService', function () {
var data = [
{id:1, title:'Note 1'},
{id:2, title:'Note 2'},
{id:3, title:'Note 3'},
{id:4, title:'Note 4'},
{id:5, title:'Note 5'},
{id:6, title:'Note 6'},
{id:7, title:'Note 7'},
{id:8, title:'Note 8'}
];
return {
notes:function () {
return data;
},
addNote:function (noteTitle) {
var currentIndex = data.length + 1;
data.push({
id:currentIndex, title:noteTitle
});
},
deleteNote:function (id) {
var oldNotes = data;
data = [];
angular.forEach(oldNotes, function (note) {
if (note.id !== id) data.push(note);
});
}
};
})
If you care about the user input in each step and want to keep it for him/her for multiple sessions, you may save the input data in the client side temporarily until finishing all the steps.
also read all this blog
A better approach is to break down your process in to tasks and serialize the information and persist that to the database upon each task completion.
When your complete the final step you don't have to resubmit everything again either. You can just call the web api that processes the persisted steps in the workflow.
Like you've mentioned this gives the user the ability to continue the workflow later (intentionally or in the case of a system failure).
Breaking down the workflow / process in to tasks will also help you understand the process a bit more too. Which is a nice side effect of this approach.

Resources