Server side rendering, angular 5 and subscriptions. - javascript

I'm trying to implement server side rendering on an angular 5 app. When the app is build and served everything works fine, however the source of the page isn't being rendered properly. I've discovered that if I use subscriptions and async pipes this solves the problem - what I see on the screen is also in the source. However, if I try to pipe the subscription and perform any actions with the return value the html is rendered correctly, however the source isn't.
This is how its implemented in the typescript when working correctly :
ngOnInit() {
this.httpResponse$ = this._dataService
.send(new BrochureRequest(this._stateService.params.propertyId));
}
This is the html
<div *ngIf="httpResponse$ | async as httpResponse; else loading">
{{content goes here}}
</div>
In this case the info is displayed on the screen, ie the html waits for the async response - and crucially - all the data on the screen is reflected in the source.
However, in many cases I will want to perform some actions on the data returned from the service - SEO, ad targeting, etc... in which case I would like to do something like this
ngOnInit() {
this.httpResponse$ = this._dataService
.send(new BrochureRequest(this._stateService.params.propertyId)).pipe(
tap((httpResponse) => {
this.activeLink =
this._headerService.getActiveLink(this.httpResponse.Id);
this._seoService.setSeoDetails(this.httpResponse.SeoDetails);
// do more stuff
return httpResponse;
}));
}
In this case, the info is displayed on screen as expected and all the other functions are called, however nothing in this component is appearing in the source. Its like the html was initially served without any of the data and not updated when the data was populated.
Any ideas why I'm not getting all my data in the source of the page? Is there a way I can delay any html rendering until the httpResponse is returned?

maybe you can use an event like ngAfterViewChecked? Read more about angular lifecycle hooks
here https://angular.io/guide/lifecycle-hooks

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

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.

At which point does one fetch a Backbone Model on which a view depends?

I seem to often end up in a situation where I am rendering a view, but the Model on which that view depends is not yet loaded. Most often, I have just the model's ID taken from the URL, e.g. for a hypothetical market application, a user lands on the app with that URL:
http://example.org/#/products/product0
In my ProductView, I create a ProductModel and set its id, product0 and then I fetch(). I render once with placeholders, and when the fetch completes, I re-render. But I'm hoping there's a better way.
Waiting for the model to load before rendering anything feels unresponsive. Re-rendering causes flickering, and adding "loading... please wait" or spinners everywhere makes the view templates very complicated (esp. if the model fetch fails because the model doesn't exist, or the user isn't authorized to view the page).
So, what is the proper way to render a view when you don't yet have the model?
Do I need to step away
from hashtag-views and use pushState? Can the server give me a push? I'm all ears.
Loading from an already-loaded page:
I feel there's more you can do when there's already a page loaded as opposed to landing straight on the Product page.
If the app renders a link to a Product page, say by rendering a ProductOrder collection, is there something more that can be done?
<ul id="product-order-list">
<li>Ordered 5 days ago. Product 0 (see details)</li>
<li>Ordered 1 month ago. Product 1 (see details)</li>
</ul>
My natural way to handle this link-to-details-page pattern is to define a route which does something along these lines:
routes: {
'products/:productid': 'showProduct'
...
}
showProduct: function (productid) {
var model = new Product({_id: productid});
var view = new ProductView({model: model});
//just jam it in there -- for brevity
$("#main").html(view.render().el);
}
I tend to then call fetch() inside the view's initialize function, and call this.render() from an this.listenTo('change', ...) event listener. This leads to complicated render() cases, and objects appearing and disappearing from view. For instance, my view template for a Product might reserve some screen real-estate for user comments, but if and only if comments are present/enabled on the product -- and that is generally not known before the model is completely fetched from the server.
Now, where/when is it best to do the fetch?
If I load the model before the page transition, it leads to straightforward view code, but introduces delays perceptible to the user. The user would click on an item in the list, and would have to wait (without the page changing) for the model to be returned. Response times are important, and I haven't done a usability study on this, but I think users are used to see pages change immediately as soon as they click a link.
If I load the model inside the ProductView's initialize, with this.model.fetch() and listen for model events, I am forced to render twice, -- once before with empty placeholders (because otherwise you have to stare at a white page), and once after. If an error occurs during loading, then I have to wipe the view (which appears flickery/glitchy) and show some error.
Is there another option I am not seeing, perhaps involving a transitional loading page that can be reused between views? Or is good practice to always make the first call to render() display some spinners/loading indicators?
Edit: Loading via collection.fetch()
One may suggest that because the items are already part of the collection listed (the collection used to render the list of links), they could be fetched before the link is clicked, with collection.fetch(). If the collection was indeed a collection of Product, then it would be easy to render the product view.
The Collection used to generate the list may not be a ProductCollection however. It may be a ProductOrderCollection or something else that simply has a reference to a product id (or some sufficient amount of product information to render a link to it).
Fetching all Product via a collection.fetch() may also be prohibitive if the Product model is big, esp. in the off-chance that one of the product links gets clicked.
The chicken or the egg? The collection.fetch() approach also doesn't really solve the problem for users that navigate directly to a product page... in this case we still need to render a ProductView page that requires a Product model to be fetched from just an id (or whatever's in the product page URL).
Alright, so in my opinion there's a lot of ways that you can fix this. I'll list all that I've thought of and hopefully one will work with you or at the very minimum it will inspire you to find your optimal solution.
I'm not entirely opposed to T J's answer. If you just go ahead and do a collection.fetch() on all the products when the website is loading (users generally expect there to be some load time involved) then you have all of your data and you can just pass that data round like he mentioned. The only difference between what he's suggesting and what I normally do is that I usually have a reference to app in all my views. So, for example in my initialize function in app.js I'll do something like this.
initialize: function() {
var options = {app: this}
var views = {
productsView: new ProductsView(options)
};
this.collections = {
products: new Products()
}
// This session model is just a sandbox object that I use
// to store information about the user's session. I would
// typically store things like currentlySelectedProductId
// or lastViewedProduct or things like that. Then, I just
// hang it off the app for ease of access.
this.models = {
session: new Session()
}
}
Then in my productsView.js initialize function I would do this:
initialize: function(options) {
this.app = options.app;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
The subviews that I create in the initialize in productsView.js are arbitrary. I was mostly just trying to demonstrate that I continue to pass that options object to subviews of views as well.
What this does is allows every view, whether it be a top level view or deeply nested subview, every view knows about every other view, and every single view has reference to the application data.
These two code samples also introduce the concept of scoping your functionality as precise as you possibly can. Don't try to have a view that does everything. Pass functionality off to other views so that each view has one specific purpose. This will promote reuse of views as well. Especially complex modals.
Now to get back to the actual topic at hand. If you were going to go ahead and load all of the products up front where should you fetch them? Because like you said you don't want a blank page just sitting there in front of your user. So, my advice would be to trick them. Load as much of your page as you possibly can and only block the part that needs the data from loading. That way to the user the page looks like it's loading while you're actually doing work behind the scenes. If you can trick the user into thinking the page is steadily loading then they are much less likely to get impatient with the page load.
So, referencing the initialize from productsView.js, you could go ahead and let the headerView and footerView render. Then, you could do your fetch in the render of the productsListView.
Now, I know what you're thinking. Have I lost my mind. If you do a fetch in the render function then there's no way that the call will have time to return before we hit the line that actually renders the productsViewList template. Well, luckily there's a couple of ways around that. One way would be to use Promises. However, the way I typically do it is to just use the render function as its own callback. Let me show you.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
}
Now, by structuring our render this way the actual template won't load until the data has fully loaded.
While we have a render function here I want to introduce another concept that I use every where. I call it postRender. This is a function where I execute any code that depends on DOM elements being in place once the template has finished loading. If you were just coding a plain .html page then this is code that traditionally goes in the $(document).ready(function() {});. It may be worth noting that I don't use .html files for my templates. I use embedded javascript files (.ejs). Continuing on, the postRender function is a function that I have basically added to my boiler plate code. So, any time I call render for a view in the code base, I immediately chain postRender onto it. I also use postRender as a call back for itself like I did with the render. So, essentially the previous code example would look something like this in my code base.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the products list after
// it's loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
By chaining these functions like this we guarantee that they'll run sequentially.
=========================================================================
So, that's one way to tackle the problem. However, you mentioned that you don't want to necessarily load all of the products up front for fear that the request could take too long.
Side Note: You should really consider taking out any information related to the products call that could cause the call to take a considerable amount of time, and make the larger pieces of information a separate request. I have a feeling that users will be more forgiving about data taking a while to load if you can get them the core information really fast and if the thumbnails related to each product takes a little longer to load it shouldn't be then end of the world. That's just my opinion.
The other way to solve this problem is if you just want to go to a specific product page then just implement the render/postRender pattern that I outlined above on the individual productView. However note that your productView.js will probably have to look something like this:
initialize: function(options) {
this.app = options.app;
this.productId = options.productId;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.get(this.productId).fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the product after it's
// loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
The only difference here is that the productId was passed along in the options object to the initialize and then that's pulled out and used in the .fetch in the render function.
=========================================================================
In conclusion, I hope this helps. I'm not sure I've answered all of your questions, but I think I made a pretty good pass at them. For the sake of this getting too long I'm going to stop here for now and let you digest this and ask any questions that you have. I imagine I'll probably have to do at least 1 update to this post to further flush it out.
You started saying:
I have a listing of items in one Collection view
So what does a collection have..? Models..!
When you do collection.fetch() you retrieve all the models.
When the user selects an item, just pass the corresponding model to the item view, something like:
this.currentView = new ItemView({
model: this.collection.find(id); // where this points to collection view
// and id is the id of clicked model
});
This way, there there won't be any delay/ improper rendering.
What if your collections end point returns huge volume of data..?
Then implement common practices like pagination, lazy loading etc.
I construct a Product model with the given ID
To me that sounds wrong. If you have a collection of products, you shouldn't be constructing such models manually.
Have the collection fetch your models before rendering the list view. This way all the problem you mentioned can be avoided.

MVC Kendo Dropdownlist rendered with EditorFor has massive javascript related issues

This is fairly complex, so please bear with me.
Let me first say that I am very new to MVC. My experience in even the basics are minimal, but my overall development experience with .NET/C# is extensive. Point being, the solution may be simple and I just do not know it lol.
Now.. We have a massive app that uses "EditorTemplates" (Partial views) via #Html.EditorFor() rendering in order to render customized UI elements based on database content as needed.
Now... I was tasked with creating one of these elements, and due to the complexity of our system and how long it takes to load it just to test, I decided to first develop my UI/Logic in its own standalone view/controller.
The view consisted of 3 cascading Kendo dropdownlists which pull data from a method in the controller (which returns a list of class objects).
I got this working beautifully in a standalone view/controller.
Now... I ported the HTML and logic from the controller in to the partial view files in our "EditorTemplates" folder, and ran our app for the first time.
Immediately I get a javascript error
"filterRabu2" is not defined.
Here is the associated code:
<label for="rabu2">Rabu2:</label>
#(Html.Kendo().DropDownList()
.Name("rabu2")
.HtmlAttributes(new { style = "width:300px" })
.OptionLabel("Select ...")
.DataTextField("Name")
.DataValueField("ID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetRabu2", "Datasets")
.Data("filterRabu2");
})
.ServerFiltering(true);
})
//.Enable(false)
.AutoBind(false)
.CascadeFrom("rabu1")
)
<script>
function filterRabu2() {
return {
rabu1: $("#rabu1").val()
};
}
</script>
Now... I got rid of that error by moving that function to a .js file that was already being included in the page.
But... when the dropdownlists render, the first one (which should be enabled and with data present), it appears to be disabled and is not clickable. I confirmed that our data access routine is being hit and returning a valid and populated list of data, just as it did (working) in the standalone view.
That's where I'm at... I can't get the DDLs to function, and the existence of that JS error leads me to theorize that JS is not being rendered or allowed to execute properly? I have no idea.
This is being rendered via the #Html.EditorFor() method from another partial view inside a loop which renders templates for data based on that data... it's quite complex... but I could really use a hand at figuring out whats going on.
It could be that the dom is rendering before the the javascript has executed. Try wapping the calling method up in a
$(document).ready(
function(){
//initial method call here
});
This will ensure that the javascript executes after the dom has loaded.

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});
});

Categories

Resources