Angular Material table datasource is not syncing with my HTTP calls and not re rendering the dom - javascript

So I'll get straight to the point, im building an angular application and I am using Angular Material Tables. Bit of a disclaimer I'm pretty new to Angular and development
So basically I'm creating a dataSource variable to where I'm going to constantly update my tables data with each call and this is the code where that happens
const siteFilter: SiteFilter = {}; this.siteControllerService.getAllSitesFilteredAndSorted(siteFilter, this.page).subscribe((data) => { this.data = data; // eslint-disable-next-line #typescript-eslint/no-non-null-assertion this.data.sort((a, b) => (a.siteId! > b.siteId! ? 1 : -1)); this.dataSource = new MatTableDataSource(this.data); this.dataSource.sort = this.sort; });
(Originally the last 3 lines were also in ngoninit and I put them into a 1 second timeout which did solve my issue but it is extremely not good at all, I'm also aware that its bad practice to put this into my constructor but its there because I am in the belief that if it runs before my oninit and I get the data and then trigger a detechchanges on my oninit it should detect that my data is not up to date with the current dom and re render it.)
my #Component looks like this:
#Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'app-table', templateUrl: './table.component.html', styleUrls: ['./table.component.scss'], })
And inside the ngOnInit I am calling the this.ref.detectChanges(); which is in the constructor as this:
private ref: ChangeDetectorRef
Now the reason this is all here is that right now the above HTTP call returns me an array of objects I put that object into the dataSource and my table should re render with the newly added data as far as I'm aware, but it came to my attention that sometimes I make a call to get 20 entries of the data I want, and only 14 or 16 or however much is visible on my table and so I got confused logged the response and even though my table had 16 entries the object I got was precisely 20 as much as I asked for.
Not sure how this stackoverflow works but I'll continue on inside the what did I try section so I tried to use a changeDetector since I realsed that the data is there inside my variable but its not updatin the DOM I implemented the above example and I'm still on the same issue, as if the http response time is a big longer than usual then the data I see in my table is not what the dataSource is at that moment.
Which means basically that the dom is not representing the actual data inside my dataSource.
So my question is basically is there a way for me to detect whenever my http call a 100% ended and then I do the this.dataSource = new MatTable... etc. I've tried going into the success arrow function inside the call but that didin't do much either and I've also looked up how async await works but I got really nowhere there since im really newbie at this still and have no idea how to implement an async await functionality.
Not sure if this information is relevant at all but I'll share this since If it is then thank god we got this out of the way but none of the http calls are created by me or anyone else at that matter, this generated through the dto-s the BE has and whatnot which is is somehow compiled automatically into a service and downloaded as an NPM package from an artifactory and we use the Service which this package has and we cannot edit the http calls at all we just use the respective method and give the asked data to them. Not sure if this is relevant or a good to know for the question but here it is.
If I could get any direction to go to and help that would be great.
(I asked chat GPT also if someone suggest me that, offered me the above example which didn't work.)

Okay so after 2 weeks of struggling to understand why this would hapen I have finally been able to figure out a solution if anyone is ever interested or has a similar issue this is what solved it for me.
I've moved the logic to get the data from the API to its own method as shown here:
GetAllSites() {
const siteFilter: SiteFilter = {};
this.siteControllerService
.getAllSitesFilteredAndSorted(siteFilter, this.page)
.pipe(first())
.subscribe((data) => {
this.data = data;
this.data.sort((a, b) => (a.siteId! > b.siteId! ? 1 : -1));
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.sort = this.sort;
this.ref.detectChanges();
});
And now this is the first thing what is called inside my ngOnInit hook.
The only difference is that im using a .pipe(first()) before my subscription, I have played around with this solution with different sizes of data and simulation of backend delay and this seems to be working, and whenever the call is finished the dataSource is updated correctly and the dom is refreshing with the newly updated data.
What a ride.

Related

Angular Material Table won't populate with external data on initial load

I'm working on an Angular 6 project where I'm loading data in from an AWS DynamoDB table via Observable into a Material Table component. I used the angular-cli to generate the initial structure of the table, and then added my own service to fetch external data, since the example used hard coded data in an array.
Everything seems to be working (I can see the correct data returned via console.log) except for the fact that on my initial load, the data that I'm returning from the observable isn't getting populated into the table. In fact if I inspect the "this.data" variable it seems like it's immediately getting set back to "undefined." If I select and change the number of results per page on the pagination component, the data returned by the observable is inserted.
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
observableOf(this.data),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map(() => {
return this.getPagedData(this.getSortedData([...this.data]));
}));
}
I've put the project on stackblitz if someone wouldn't mind taking a look.
To reproduce the issue:
Go to: https://stackblitz.com/edit/mat-table-dynamo
Notice there is no data in the table.
Select the "Items per page" pulldown and change to a different value.
The table populates with the data returned from the Observable.
Thanks for your help!
The rule of thumb of writing any service in angular is that if you have a .subscribe() inside your service, you are probably(99%) do it wrong. Always return an Observable, and let your component do the .subscribe().
The reason why your code doesn't work is because you subscribe your data first inside your service, and then re-wrap it using Observable.of(). That won't work, because your http call is asynchronous. By the time your subscription inside your constructor has received emission, your connect has long established and this.data is first undefined before it can be assigned any values.
To solve your problem, simply change the of(this.data) to its original Observable source, and everything is working:
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
this.dynamoService.getData(this.params),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map((received) => {
return this.getPagedData(this.getSortedData([...received]));
}));
}
Here is the working working StackBlitz

Private variables in Ember-data DS.Model

I want to store a private variable on each DS.Model. Its purpose is to store a pending callback (in case I want to cancel it).
I have tried this (and it works):
DS.Model.reopen({
init() {
let _pending; // my private var
this._getPending = () => _pending; // get private var
this._setPending = callback => _pending = callback; // set private var
this._super(...arguments);
}
});
I have placed this in an initializer, and it works as I expect it to.
My questions are: Is this a good practise? is it likely to mess anything up? ...and, is there a better way?
Personally, I'm happy with the way it works.. but I'm not sure if its the "Ember" way. This is going to go into an Ember-cli addon, so I would like it to be the most "best practise" as possible. (the _getPending/_setPending method are only to be used internally within the addon.)
Here are my 2 cents on this. I would say no it is not a good practice, but it should be okay since they are just Ember Objects. The question here is what is Ember data model used for? From doc it says:
"Models are objects that represent the underlying data that your application presents to the user."
By definition this is not what they are designed for, so just because you are able to it does not mean that you should use them like this.
Pending callback so it can be canceled? Ember model API has defined state objects that can be used for this purpose. http://emberjs.com/api/data/classes/DS.Model.html Flags like isDeleted, isValid, isNew...gives all possible state.
I would place them in router actions where they are easy tested with integration tests.
You can check this screencast that explains them:
https://www.emberscreencasts.com/posts/102-ember-data-20-model-states-and-flags
Hope it helps.

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

Loading and waiting for "global" meteor subscriptions

Here's my scenario:
I have a layout template that needs to check to see if a User belongs to at least one Team. If not, then display a div across the entire site. A user can only see the teams they belong to, so I created a simple publication that works: (code samples are CoffeeScript)
Meteor.publish 'teams', ->
return null if !#userId
Teams.find {'members._id': #userId}
This works great and Teams.find().fetch() in console gives expected results.
However, if I put this code in, say, the Template.layout.rendered, it doesn't work.
Template.layout.rendered = ->
teams = Teams.find().fetch()
hasTeams = teams.length > 0
if !hasTeams
...do stuff..
Obviously this doesn't work because the Teams find is async and not loaded when it needs to make the decision. With a normal template / page I would just use the IronRouter waitOn() but what do I do on the layout?
I could do a waitOn in my router, but since the data is "global" and going to be used everywhere, and because a user could deep link into the site all over the place, I don't want to add that waitOn to EVERY single route.
So what is the proper pattern? How do I get the meteor client to load global data and wait for the data before running the route?
More thinking and searching found the answer right here on SO: struggling to wait on subscriptions and multiple subscriptions
I changed my Router.configure to this:
Router.configure
layoutTemplate: 'layout'
waitOn: ->
return [
Meteor.subscribe('teams')
]
Multiple subscriptions can be added to the return array, and I believe it will wait on all of them.
I had a similar issue with iron router subscribing to a chat in two different publications with waitOn.
Meteor.subscribe('chats')
Only when I switched positions of the following publish blocks, would it let me see any data on the route. The following is the correct order for the two.
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("chats", function(id) {
return Chats.find({eventRoom: id});
});

Ember.js model- and setupController-hook with nested routes

I'm currently evaluating Ember.js and therefore I am building a small sample app. Currently everything went quite smooth so far, but now I don't seem to be able to fix my last little problem.
When I access the app normaly via the route films, everything works as expected. The list of films is displayed. Now when I click onto a film, the details of the film are loaded via setupController hook just below the list of films. That's all just fine.
Here comes my problem: I would like to be able to access the film details directly via url, but somehow in this case another request is fired to grab the film details, with the value of undefinded. As far as I understand that is the model hook.
I can only guess, but I think it is the model hook which is beeing executed.
Can someone point me to the probably obvious mistake I'm making? And on the other hand, is the code i wrote so far "correct"? Or is there a better way of doing this?
(I am aware of the bad way I use to render the film details. I will remove the {{#each}} tag, and change the way I asign the response to the film variable.
Here the link to the sample app: http://jsbin.com/ewiN/1#/films
UPDATE
Ok, now I am getting really confused. I almost have it working, hopefully someone can point it out to me, because it's such a simple task, but it nearly seems impossible to do without knowing ember really well...
When accessing the app via url, it only works when I remove the setupController hook. But I need that hook, to load the FilmDetails on clicking onto the links to properly load the FilmDetails.
http://jsbin.com/ewiN/16#/films/tt0100669
Many thanks for the feedback!
Regards
Reto
Instead of use jQuery.getJSON, use Ember.RSVP.Promise, because internally ember use this promise api instead of jquery. I think that using both, can make inconsistencies.
return new Ember.RSVP.Promise(function(resolve, reject) {
var films = [];
jQuery.getJSON("http://www.omdbapi.com/?s=" + searchTerm, function (response) {
$.each(response.Search, function (index, value) {
films.pushObject(Kitag.Films.create({
title: value.Title,
id: value.imdbID
}));
});
}).fail(reject);
resolve(films);
});
Because we are returning a promise instead of an object, we need to use the model hook, because it is wait until the promise is resolve to render templates.
Kitag.FilmsRoute = Ember.Route.extend({
model: function() {
return Kitag.Films.getMovies('spiderman');
}
});
I have removed the Kitag.FilmRoute, because the expected:
Kitag.FilmRoute = Ember.Route.extend({
model: function(params) {
return Kitag.Film.find(params.id)
},
serialize: function (model) {
return { film_id: model.get("id") };
}
});
is the default.
This is the final result http://jsbin.com/ewiN/15/edit

Categories

Resources