3

I'm developing an app using MEAN.js, and I'm trying to fix the Articles (blog) so it's more friendly for social sharing. The problem I'm having is populating the open graph meta data so that it populates the og:url value with the actual url being shared. Here are what the files look like:

express.js

// Setting application local variables, to be rendered in the template's variables in layout.server.view.html below.
app.locals.siteName = config.app.siteName;
app.locals.title = config.app.title;
app.locals.description = config.app.description;
app.locals.keywords = config.app.keywords;
app.locals.imageUrl = config.app.imageUrl;
app.locals.facebookAppId = config.facebook.clientID;
app.locals.jsFiles = config.getJavaScriptAssets();
app.locals.cssFiles = config.getCSSAssets();

// Passing the request url to environment locals
app.use(function(req, res, next) {

    // NOTE: This is the line that's setting the url on the layout template.
    res.locals.url = req.protocol + '://' + req.headers.host + req.url;
    next();
});

layout.server.view.html

// This is the template being rendered, using Swig as the template engine.
// The variables (e.g. siteName, title, etc.) are being set using the app.locals and req.locals.url variables in the express.js file above.

<!-- Facebook META -->
<meta id="fb-app-id" property="fb:app_id" content="{{facebookAppId}}">
<meta id="fb-site-name" property="og:site_name" content="{{siteName}}">
<meta id="fb-title" property="og:title" content="{{title}}">
<meta id="fb-description" property="og:description" content="{{description}}">
<meta id="fb-url" property="og:url" content="{{url}}">
<meta id="fb-image" property="og:image" content="{{imageUrl}}">
<meta id="fb-type" property="og:type" content="website">

<!-- Twitter META -->
<meta id="twitter-title" name="twitter:title" content="{{title}}">
<meta id="twitter-description" name="twitter:description" content="{{description}}">
<meta id="twitter-url" name="twitter:url" content="{{url}}">
<meta id="twitter-image" name="twitter:image" content="{{imageUrl}}">

articles.server.controller.js

// Accessed only when a user visits one of the 'Articles' (blog) pages.
// When a user visits a url for an Article, this file grabs the article
// from the database and updates the local variables with article-specific
// values. This works for everything EXCEPT the url variable.

exports.articleByUrl = function(req, res, next, id) {
    var url = req.params.articleUrl;
    Article.findOne({urlTitle: url}).populate('user', 'displayName').exec(function(err, article) {
        if (err) return next(err);
        if (!article) return next(new Error('Failed to load article ' + id + ' with friendly URL "' + url + '".'));
        req.app.locals.title = article.title;
        req.app.locals.description = article.summary;

        // This is the line that I'm HOPING will update the url variable in
        // the template file above, but it's not working. I've also tried
        // using req.app.locals.url, but that didn't work either.
        res.locals.url = req.protocol + '://' + req.get('host') + '/#!' + req.originalUrl;

        req.app.locals.imageUrl = article.img;
        req.article = article;
        next();
    });
};

Currently, all of the values populate correctly EXCEPT the url value - it always shows the root url (i.e. http://localhost:3000 on my dev machine). Any help, or smarter ways to do what I want would be REALLY appreciated. I've spent 2 days on this, and my stubbornness is finally giving out.

aikorei
  • 570
  • 1
  • 7
  • 23
  • Can you inspect your code and see if `res.locals.url` is assigned the intended value correctly? – GPX May 30 '15 at 07:15
  • How are you using `articleByUrl()`? If it's a proper middleware, the order in which it is `use()`'d matters (some `console.log` statements will show which is called last: `articleByUrl` or your "generic" middleware). Also, you're overwriting `req.app.locals` with request-dependent information. That will cause undefined behaviour with multiple concurrent requests (although that's most likely not the cause of the problem you're describing). – robertklep May 30 '15 at 08:20
  • @GPX that's a good question - technically `res.locals.url` is correct because that's the route Angular is `GET`-ing to find `articleByUrl()`. But that's not the end url the user sees because Angular does the actual routing. – aikorei May 30 '15 at 08:44
  • @robertklep Yes, it's proper middleware, as far as I can tell. I've inserted log statements for both of these functions, and `articleByUrl()` is printed after the more general `res.locals.url` in my express.js file. This is part of what's confusing me so much - I'm _expecting_ it to overwrite the general one, but it doesn't seem to be. As for using `req.app.locals`, I'm doing this because `req.locals.title` throws an error. If it's just a `req` instance of app, is that still a cause for concern with concurrent requests? (And do you mean from multiple users, or concurrent requests from 1 user?) – aikorei May 30 '15 at 08:49
  • 1
    @aikorei `req.locals` should be `res.locals`, so that would explain the error that's being thrown; `req.app` points to the "global" `app` object, so any changes to `req.app.local` are reflected throughout the app, and across all requests. So request B may be overwriting the values set by request A when they come in at almost the same time (regardless whether they are initiated by the same user or not). – robertklep May 30 '15 at 12:47
  • @robertklep re: `req.app` - thanks, I didn't realize that. As for using `res.locals` instead of `req.locals`, you're right...no error is thrown...but now the values on my page aren't updating. I don't really care how it happens, but that fb/twitter meta data on my server layout page needs to change when someone is visiting an article. Is there a smarter way to do it? – aikorei May 30 '15 at 13:48
  • @aikorei I wonder if perhaps Swig doesn't use `res.locals` at all. When you call `res.render()`, try passing it explicitly (`res.render("template", { locals : req.locals })` I think). – robertklep May 30 '15 at 14:47
  • @robertklep unfortunately, that didn't work either. I just realized something, though - `articleByUrl()` isn't actually standard middleware, like I thought (oops!). It's a the results of an `app.router` call. Basically, the page gets rendered, then Angular makes a call to grab the article details from the DB, then binds the data when the data is passed back to the client. No wonder this is giving me a headache. FWIW, I'm fairly confident that Swig is somehow using locals because a couple of those layout variables weren't rendered until I added them to my express.js file with the others. – aikorei May 30 '15 at 15:21
  • @aikorei Were you able to get this working properly? (I added a +50 bounty because it's a fantastic question! And I'd love to see a best-practice code sample based on your question.) – tonejac Sep 26 '15 at 18:59
  • Is there any way you could link me to your code (maybe on GitHub or GitLab)? I've reconstructed a model of your situation but everything works on my end. I'd love to see a bigger picture. – Grant Hames-Morgan Sep 27 '15 at 19:00
  • @tonejac unfortunately I never found a great/clean/simple way to do it. This question didn't receive a lot of traffic, so I posed it differently (along with my own workable solution) here: http://stackoverflow.com/questions/30605210/mean-js-social-sharing. It's perhaps not the best solution, but it works so long as the right URL is shared. Angular just borks things in this situation. – aikorei Sep 27 '15 at 20:50
  • @grant-hames-morgan if you go to meanjs.org and install the basic MEAN app (with the articles module) you'll be able to see the code in the file names I mention above. – aikorei Sep 27 '15 at 20:50

1 Answers1

0

When you use:

req.app.locals

It changes all the values for the app for everyone (until server restart). The proper way is to use:

res.app.locals

BUT when you do, it only does it for the initial page request and doesn't persist when the hash change is applied on the client side.

The recommended approach would be to create a new server layout file with the URL in question so there is no hash change, then perhaps your navigation for the new server template file can link back to the URLs in you one-page app (with the original server template).

Best of luck.

tonejac
  • 1,083
  • 3
  • 18
  • 32