angularjs ng-grid keep selection on update - angularjs

I'm trying to use the ng-grid setup and I have the following problem.
The data I am displaying changes very frequently, every 5 seconds or so. But not a lot of new data gets added to the list.
When i set data to the ng-grid the user can start looking at the data. but when I update the data after about 5 seconds the selections the user has made and the grouping is lost.
http://plnkr.co/edit/eK1aeRI67qMROqDUtPnb
Is there anyway to keep the selection and/or the grouping?

You're going to have to go through and merge the data in a for loop. If you replace the entire array, you're replacing the object references, and therefor you will lose any changes you've made.
The other option would be to keep your selections in a different array or dictionary, then remap your properties after you replace your array. Notice here you're going to need to use a reference type so changes persist to your selections array.
So like [psuedo-code]:
// a dictionary of reference types (IMPORTANT that they are objects!)
// to hold selection data.
var selections = {
'Name1' : { value: 'selection' },
'Name2': { value: 'selection2' }
}
$scope.getMyData = function () {
// do whatever to get your data here.
$scope.myData = [
{ name: 'Name1' },
{ name: 'Name2' }
];
// update your objects in your array.
for(var i = 0; i < $scope.myData.length; i++) {
var data = $scope.myData[i];
var selection = selections[data.name];
if(selection) {
data.selection = selection;
}
}
};
// initial load
$scope.getMyData();
// your test interval
setInterval(function () {
$scope.$apply(function (){
$scope.getMyData();
});
}, 5000);

We are going to be adding a primaryKey option in the next version that will allow the grid to key off that instead of references.

Related

Vuejs : How to make a computed property which observe a list of items?

I want to synchronize data inside two arrays in my application. I am using vue.js.
My first array looks like that :
var testLayout = [
{"x":0,"y":0,"w":2,"h":2,"i":"0"},
{"x":2,"y":0,"w":2,"h":4,"i":"1"}
];
And the second one which contains item object is like that :
var items = [
item {
//other properties
Position = {
x,
y,
Width,
Height,
MinWidth,
MaxWidth,
MinHeight,
MaxHeight
}
},
....
]
I want the properties inside the first array react to change from the second one and vice versa.
I need to synchronize my data like that : testLayout[...].myAnonymousObject.x <=> items[...].item.Position.x
How to create a computed properties or something I can use to achive this ? I can't change the structure of those two arrays/objects, but I need to keep them updated of change in each way.
I tried to do this inside my vue instance:
computed: {
layout: {
get: function () {
let allPositions = [];
for (var item of items) {
allPositions.push(
{
x: item .Position.x,
y: item .Position.y,
h: item .Position.Height,
w: item .Position.Width,
i: item .Id
//do not set here min/maxW and min/maxH
}
);
}
return allPositions;
},
set: function () {
???
}
}
},
But this will not work, I don't know how to really bind those properties together. Do you know how to do that ? I am right to use computed property to do that ?
I passed by something similar to this on my actual project.
I've searched about it and there is an option to watch deep, is the best option for array of arrays, array of objects, object of arrays, etc...
Should be something like this:
watch: {
<variableName>: { // should be the name of the variable you want to watch
// if you want, this handler() allows 2 params like (newValue, oldValue) to compare something if you want.
handler() {
// do your logic
// call a function with the logic
},
deep: true
}
}

Is it possible to make ng-repeat delay a bit redrawing of array content

I'm using ng-repeat to (guess) put array content in table.
Content is drawn dynamically, and it works well, when I'm modifying single elements of an array. But when I reload a whole array, there is this moment, when array is reassigned with new value, and ng-repeat draws blank table (which is actually logically correct). Is there a way to delay redrawing of content that way, the ng-repeat ignores the moment when the array is empty? Like the content is switched to new content without the 'clear' time.
I'm assigning new elements to array this way:
items = newItems;
where items is the array ng-repeat uses and newItems is an array of items freshly downloaded from database. The newItems is complete, when the assignment occurres. I'm not doing items = []; before the assignemt.
I'm usign angular 1.3
EDIT:
the ng-repeat:
<tr ng-repeat="order in submittedOrders">
stuff
<\tr>
js:
`$scope.reloadView = function() {
$scope.submittedOrders = OrdersService.getOrdersByStatus(ORDER_STATUS.submitted);
};`
Can it be the that the table is cleared in the first place, before call to database(service takes data from database) and during the wait, the table is cleared?
You may have to make use of Observables and async pipe of Angular.
Here are few steps you can take:
Convert your newItems to a rxjs Subject.
newItems$ = new Subject();
Whenever you get new values for your array, emit them via subject.
this.newItems$.next(newItems);
Make the items an observable of newItems$, and filter out empty arrays.
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
In your template, use async pipe to iterate over array.
*ngFor="item of items | async"
Below is relevant parts of code that can get you started.
import { Observable, of, from, Subject } from 'rxjs';
import { filter, mapTo } from 'rxjs/operators';
...
newItems$ = new Subject();
items = this.newItems$.pipe(
filter((a:any[]) => {
return a.length != 0;
})
);
...
// A test method - link it to (click) handler of any div/button in your template
// This method will emit a non-empty array first, then, after 1 second emit an empty
// array, and then, after 2 seconds it will emit a non-empty array again with updated
// values.
testMethod() {
this.newItems$.next([3,4,5]);
setTimeout((v) => {
console.log("Emptying the array - should not be displayed browser");
this.newItems$.next([]);
}, 1000);
setTimeout((v) => {
console.log("Updating the array - should be displayed in browser");
this.newItems$.next([3,4,4,5]);
}, 2000);
}

Array as Model for Service Breaks when Empty

I have two routes: one has a custom component that repeats the data in an array and allows the user to add and remove items, the other route only displays the model. The model is stored in a service. The model JSON data looks like this:
[
{name: "one"},
{name: "two"},
{name: "three"}
]
The components are all using ng-model and assigning this to a variable vm. Following all the best practices from John Papa style guide.
If I empty the array either by using slice(), pop(), or setting the array length to 0, it breaks. You can still add data to it, but if you navigate to the other route, the model will show as an empty array. And if you navigate back again, the array is still empty.
If I make my model an object with a key and the array as the value, everything works as expected. So my question is, is this just a limitation or am I doing something wrong?
{
myarray: [
{name: "one"},
{name: "two"},
{name: "three"}
]
}
Here is the working example using the object containing the array.
And here is the non working example just using the array.
You'll see on the one that does not work, you'll empty the array and then add to it, it will not persist data across the routes.
you'll empty the array and then add to it, it will not persist data across the routes
1st Problem: in getAsync() method.
When your model is empty you call callAtInterval() every 100 milliseconds and you never resolve your promise (infinite loop).
function getAsync() {
function callAtInterval() {
if (!_.isEmpty(genericHttpModel.model)){
$interval.cancel(promise);
deferred.resolve(get());
}
}
var deferred = $q.defer();
var promise = $interval(callAtInterval, 100);
return deferred.promise;
}
Therefore when user goes to home (root) route:
genericHttpService.getAsync().then(function(model){
vm.model = model; // <-- never called
});
So remove if (!_.isEmpty(genericHttpModel.model)) statement
function callAtInterval() {
$interval.cancel(promise);
deferred.resolve(get());
}
}
2nd problem: in add method:
function add() {
if (modelEmpty()) {
initModelAndAddOne();
} else {
vm.model.push({});
}
}
In initModelAndAddOne you reset original instance of vm.model with:
vm.model = [];
Your model is already empty, why to redefine it with =[], make it simple:
function add() {
vm.model.push({});
}
Working Example Plunker
working example using the object containing the array.
So why it works:
1st off _.isEmpty(genericHttpModel.model) will always return false because object contains field names a.e: genericHttpModel.model = {names:[]}
2nd - vm.model = [] resets names field only and not service object

How to transform a 'flat' observableArray into a nested observableArray with Knockout

I have a 'flat' array with 3 items:
[{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"},
{"title":"test1","file":"test1.aspx","category":"housing"}]
The objective is to transform this into a nested observableArray with 2 items:
[{"category":"finance","content":[
{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"}]},
{"category":"housing","content":[
{"title":"test1","file":"test1.aspx","category":"housing"}]}]
http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html helped me to extract unique categories in two steps:
self.getcategories = ko.computed(function () {
var categories = ko.utils.arrayMap(self.pages(), function (item) {
return item.category();
});
return categories.sort();
});
self.uniqueCategories = ko.dependentObservable(function() {
return ko.utils.arrayGetDistinctValues(self.self.getcategories()).sort();
});
//uniqueCategories: ["finance","housing"]
However I can't figure out how to create the nested array. I got as far as this:
self.createCategories = ko.computed(function () {
ko.utils.arrayForEach(self.uniqueCategories(), function (item) {
var content = getCategoryContent(item);
var c = new category(item, content);
self.Categories.push(c);
});
return true;
});
function getCategoryContent(whichcategory) {
return ko.utils.arrayFilter(self.pages(), function (page) {
return page.category() === whichcategory;
});
}
It results however in 5 category items (finance 4x, housing 1x) where I expect just 2.
Your computed function createCategories is probably being invoked more than once. e.g. if you are adding items to your pages() array one at a time, this will trigger the createCategories function each time you add an item.
Normally you would make the categories array and return it from a computed function. Currently you are adding to the Categories observable array without clearing it each time.
An easy fix would be to clear out the Categories array at the top of the createCategories function. This would leave you in the odd situation of having to call createCategories at least once to set up the dependencies, but after that it would work automatically when the data changed.
Another option would be create an array and return it from the createCategories functions, then you could just rename the function to Categories and not have an observable array. This would be the way I would normally use computed functions.
On more option would be to just do the work as normal JavaScript functions (rather than computed) and just call createCategories manually when you change the original array (e.g. when you get a result back from your server).

How can I set nested array values in meteor publish function?

I have two collection "contents" and "units". In the content collection is a field "unitID" which refers to the unit-collection. In the meteor publish function I want to add the unit type name of all new created contents:
Meteor.publish("contents", function () {
var self = this;
var handle = Contents.find().observe({
changed: function(contentdoc, contentid) {
var UnitName = Units.findOne({_id: contentdoc.unittypeid }, {fields: {type: 1}});
self.set("contents", contentid, {'content.0.typename': UnitName});
self.flush();
}
});
}
This works but it creates a new attribut "content.0.UnitName" instead of inserting the attribute "UnitName" in the first element of the content array:
[
{
_id:"50bba3ca8f3d1db27f000021",
'content.0.UnitName':
{
_id:"509ff643f3a6690c9ca5ee59",
type:"Drawer small"
},
content:
[
{
unitID:"509ff643f3a6690c9ca5ee59",
name: 'Content1'
}
]
}
]
What I want is the following:
[
{
_id:"50bba3ca8f3d1db27f000021",
content:
[
{
unitID:"509ff643f3a6690c9ca5ee59",
name: 'Content1',
UnitName:
{
_id:"509ff643f3a6690c9ca5ee59",
type:"Drawer small"
}
}
]
}
]
What am I doing wrong?
this.set within Meteor.publish only works on the top-level properties of an object, meaning it doesn't support Mongo-style dotted attributes. You'll have to call set with the entire new value of the contents array.
Caveat: What I am about to say is going to change in a future release of Meteor. We're currently overhauling the custom publisher API to make it easier to use, but in a way that breaks back-compatibility.
That said...
It looks like what you're trying to do is build a server-side join into the published collection "contents". Here, for reference, is the current code (as of 0.5.2) that publishes a cursor (for when your publisher returns a cursor object):
Cursor.prototype._publishCursor = function (sub) {
var self = this;
var collection = self._cursorDescription.collectionName;
var observeHandle = self._observeUnordered({
added: function (obj) {
sub.set(collection, obj._id, obj);
sub.flush();
},
changed: function (obj, oldObj) {
var set = {};
_.each(obj, function (v, k) {
if (!_.isEqual(v, oldObj[k]))
set[k] = v;
});
sub.set(collection, obj._id, set);
var deadKeys = _.difference(_.keys(oldObj), _.keys(obj));
sub.unset(collection, obj._id, deadKeys);
sub.flush();
},
removed: function (oldObj) {
sub.unset(collection, oldObj._id, _.keys(oldObj));
sub.flush();
}
});
// _observeUnordered only returns after the initial added callbacks have run.
// mark subscription as completed.
sub.complete();
sub.flush();
// register stop callback (expects lambda w/ no args).
sub.onStop(function () {observeHandle.stop();});
};
To build a custom publisher that is joined with another table, modify the added callback to:
check if the added object has the key you want to join by
do a find in the other collection for that key
call set on your subscription with the new key and value you want to be published, before you call flush.
Note that the above is only sufficient if you know the key you want will always be in the other table, and that it never changes. If it might change, you'll have to set up an observe on the second table too, and re-set the key on the sub in the changed method there.

Resources