Material Table not reflecting changes on datasource - javascript

This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.

You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.

Related

Observable fires only once

I decided to pick up this RxJS tutorial over the weekend to learn about reactive programming. The goal is to set up a simple page that generates an interactive list of users from the Github users API using Observables.
The list displayed is a subset of the total number of users retrieved (in my case 3 out of 30). The list needs to be refreshable (show a new set of users), and you need to be able to remove entries from it by clicking the 'remove' button on each respective entry.
I've set up a chain of Observables to define the behavior of the page. Some act as events to trigger processing, and some publish processed results for use in the app. This chain should dynamically cause my list to be updated. Currently, the default flow is like this:
Startup!
Suggestions refresh triggered! (this is where the data is retrieved)
30 new suggestions available!
List update triggered! (this is where I choose 3 users to display)
List updated! (at this point, the list is showing on the page)
A list update is triggered on startup, by refreshing the list and by removing something from the list. However, when I refresh the list, this happens:
Refresh button clicked!
Suggestions refresh triggered!
30 new suggestions available!
As you can see, the trigger to update the list of users is not set off. As I understand it, by emitting some value at the start of a stream, the rest of the stream should be executed consequently. However, this only seems to be happening the first time I run through the chain. What am I missing?
You can find a running version of my project here.
I think the issue is in the way userStream$ Observable is created.
Filtering users not closed and then taking the first 3 is something that can be done directly on the UserModel[] array passed into the pipe chain by displayEvents$ via filter and slice methods of Array.
If you do so, you remove the need of using the from function to create an Observable<UserModel> on which you then have to apply flatMap (which is currently better known as mergeMap) to apply finally toArray to transform it back into an Array of UserModel.
In other words you can simplify the code as in the following example, which as side effect solves the refresh problem.
this.userStream$ = this.displayEvent$.pipe(
map(users => users
.filter((user: UserModel) => !this.closedUsers.has(user))
.slice(0, this.numberOfUsers))
// flatMap((users: UserModel[]) => from(users))
// // Don't include users we've previously closed.
// , filter((user: UserModel) => !this.closedUsers.has(user))
// , take(this.numberOfUsers)
// , toArray()
, tap(() => console.log('List updated!'))
// Unless we explicitly want to recalculate the list of users, re-use the current result.
, shareReplay(1));
To be honest though I have not fully grasped why your original solution, which is a sort of long detour, does not work.

Angular.copy keeps giving me the same object

I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}

SharePoint listitem properties get lost after update via JS & CSOM

On my SharePoint there is a website where a single list-item is loaded based on a user's selection with JavaScript and CSOM. This list item has a total of ~60 properties defined in it's list definition.
In HTML input fields the user can modify most of the properties after jQuery filled in the loaded properties to their corresponding input fields. When the "save" button is pressed, the properties are collected from the inputs via jQuery and put into a simple JS-object (itemProps):
var itemprops = {
'foo': $('#foo-input').val(),
'bar': $('#bar-input').val()
}
Then, the following function gets called:
function updateListItem(itemProps, onItemAdded, onItemError) {
var list = web.get_lists().getByTitle('ListTitle');
var listItem = list.getItemById(id);
for (var propName in itemProps) {
if (itemProps.hasOwnProperty(propName)) {
listItem.set_item(propName, itemProps[propName]);
}
}
listItem.update();
context.executeQueryAsync(
function() {
onItemAdded(listItem);
},
onItemError
);
}
Debugging shows me, that the data in itemProps are valid. But sometimes (I can't reproduce that effect deterministically) some properties get lost and when I look at the list item in the list on the SharePoint some of the properties are empty, as if itemProps had null or "" associated to that property. When I first tried to debug this I simply created an item and saved it (correctly, with all properties) and then loaded and saved it again without modification but some properties got lost.
Other properties get updated correctly and sometimes this doesn't happen at all.
Is there any way to make sure this effect does't occur or at least to detect it and retry updating the data, before the user's inputs get lost?
I notice you're not invoking context.load(listItem) before calling context.executeQueryAsync() which might cause issues with the listItem object's values being stale or dehydrated.
The code in your question looks like it should still be setting the specified values correctly on the list item, although there may be some code later on (such as in the onItemAdded function) that's running into false assumptions or subtle data differences from the inadequately loaded list item.

Optimal way of accessing parent view property (Ionic 2, Angular 2)

I'm testing Ionic 2 and Angular 2, and I've got a doubt about accessing to parent view's properties.
Per example, I've got a test app in which my view is a list of items, and when I click one item, I enter to their details. Pretty straightforward, huh? Well, that details view has got functions that edit the element, and then apply the changes.
For this, I use three different ways:
One is to pass the object reference and just edit it, which edits it back in the list (I guess this is pretty optimal)
Before the typical navCtrl.pop(), pass a parameter via navParam to the function "ionViewDidEnter()", which executes just when you come back to a view, and filter it there, so you can perform the task you desire. Problem: it doesn't work (probably it's a bug).
Here comes the krakken: when removing the element, this won't work, since I have to remove it from the list, per example, with the typical list.splice(index, 1);
I found two different methods of performing this: you can either pass the new view a reference of the list, or you can access it from the NavController, just as I do here:
remove(){
let list = this.navCtrl._views[0].instance.list;
for(var i=0;i<list.length;i++){
if(list[i].id === this.contact.id){
list.splice(i,1);
}
}
this.navCtrl.pop();
}
Here I have another example of this weird technique, reusing the edit view for creating a new element:
editContact(obj){
if(this.onEdit){
this.onEdit = false;
this.editBtnTxt = "Edit contact";
if(this.onCreate){
this.navCtrl._views[0].instance.list.push(this.contact);
this.navCtrl.pop();
}
}else{
this.editBtnTxt = 'Apply changes';
this.onEdit = true;
}
}
Although this works pretty nicely and isn't throwing any errors, I guess I'm just being somewhat lucky, because: how do you know the index of the view you want to access, if you're not in a simple test project like this with two views, per example? I guess there can be a lot of errors with this way of doing things.
But as it works, and it seems to be more optimal than passing tons of parameters, or using localStorage as a "global" variable, I'm sticking with this by the moment.
What I would like to know, is... which way is the most optimal of accessing parent view properties?
You should try to avoid accessing the parent view.
Use #Output()s in the child and (someEvent) bindings in the parent and notify the parent about the actions it should take on the model.
If they are not direct parent child (like when the child is added by the router) use shared services with observables instead.

Knockout component updating observable not being subscribed to by parent view model?

I've written a component called Upload which allows users to upload files and then report back with a JSON object with these files. In this particular instance, the Upload component has a parameter which comes from a parent view model:
<upload params="dropzoneId: 'uploadFilesDropzone', postLocation: '/create/upload', uploadedFiles: uploadedFiles"></upload>
The one of importance is called uploadedFiles. The parameter binding here means I can reference params.uploadedFiles on my component and .push() new objects onto it as they get uploaded. The data being passed, also called uploadedFiles, is an observableArray on my parent view model:
var UploadViewModel = function () {
// Files ready to be submitted to the queue.
self.uploadedFiles = ko.observableArray([]);
};
I can indeed confirm that on my component, params.uploadedFiles is an observableArray, as it has a push method. After altering this value on the component, I can console.log() it to see that it has actually changed:
params.uploadedFiles.push(object);
console.log(params.uploadedFiles().length); // was 0, now returns 1
The problem is that this change does not seem to be reflected on my parent viewmodel. self.uploadedFiles() does not change and still reports a length of 0.
No matter if I add a self.uploadedFiles.subscribe(function(newValue) {}); subscription in my parent viewmodel.
No matter if I also add a params.uploadedFiles.valueHasMutated() method onto my component after the change.
How can I get the changes from my array on my component to be reflected in the array on my parent view model?
Why do you create a new observable array when the source already is one? You can't expect a new object to have the same reference as another one: simply pass it to your component viewModel as this.uploads = params.uploads. In the below trimmed-down version of your example, you'll see upon clicking the Add button that both arrays (well the same array referenced in different contexts) stay in sync.
ko.components.register('upload', {
viewModel: function(params) {
this.uploads = params.uploads;
this.addUpload = function() { this.uploads.push('item'); }.bind(this);
},
template: [
'<div><button type="button" data-bind="click: addUpload">Add upload</button>',
'<span data-bind="text: uploads().length + \' - \' + $root.uploads().length"></span></div>'].join('')
});
var app = {
uploads: ko.observableArray([])
};
ko.applyBindings(app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="component: {name: 'upload', params: {uploads: uploads}}"></div>
It is only in case your source array is not observable that things get a little more complicated and you need to have a manual subscription to update the source, eg. you would insert the following in the viewModel:
this.uploads.subscribe(function(newValue) { params.uploads = newValue; });
Additionally the output in the text binding would not be updated for the source because it is not observable. If for some reason that I cannot conceive of you would want to have 2 different observableArrays (1 source & 1 component), you should still be able to do with the line above, but replace the function code with params.uploads(newValue)
The problem may be related to this bug (to be confirmed): https://github.com/knockout/knockout/issues/1863
Edit 1: So this was not a bug. You have to unwrap the raw param to access the original observable. In your case, it would be:
params.$raw.uploadedFiles() //this would give you access to the original observableArray and from there, you can "push", "remove", etc.
The problem is that when you pass a param to a component, it gets wrapped in a computed observable and when you unwrap it, you don't have the original observableArray.
Reference: http://knockoutjs.com/documentation/component-custom-elements.html#advanced-accessing-raw-parameters
While Binding Property that involves Parent --> Child Relation
Use Binding in this way
If You want to bind data to Child Property
data-bind='BindingName : ParentViewmodel.ChildViewModel.ObservableProperty'
Here it seems you want to subscibe to a function when any data is pushed in Array for that you can write subscribe on Length of Observable array which can help you capture event that you want.
This should solve your problem.

Categories

Resources