100 days of Angular 2 - Day 7: Leaving a message for the next visitor

Now that I'm done with the seven part official Angular 2 tutorial it's time to venture out on my own. What will I create first?

How about a page where a visitor can see a "Message of the Moment" which can be then changed by that visitor. In other words, the server will hold onto one message, and every visitor can see it (or change it). A simple, silly, idea and excuse to learn some more Angular 2 stuff.

I'm going to eventually bring a database into this project, but I don't think I want to jump into that yet. Instead, I'll just store the message in memory (RAM), or possibly persist them to a file on the disk if I want the server to be able to handle server restarts a little more gracefully.

Reorganizing the project

Right now all of my client side (Angular 2) files are just in a single directory called client/. This is fine because I have four components that relate to one another, but I'll want to follow the Angular 2 style guide and keep related parts of the app in their respective folders.

I'll reorganize the project to contain two directories for the two disparate features. One for my 'Tour of Vegetables' and one for my 'Message of the Moment' feature that I'm going to build out.

I spent some time reading over and referencing the Angular 2 routing docs to figure out how to do this.

And by some time, I mean half a day just trying to reorganize the project into two submodules. Most of it was spent tracking down strange errors or imports that I forgot.

After considerable effort I now have a top level navigation bar, and a child navigation bar (in this case switching between the dashboard and the list of vegetables).

It's a lot more than putting a <router-outlet> in a <router-outlet>

Besides fixing all of the relative/absolute links, creating another router-outlet to put the existing router-outlet into, I had to create a new home page component to put all router-outlet into.

On top of that I created a new vegetables.module.ts which felt like a repeat of the original app.module.ts. One of my big mistakes was leaving out the

import { BrowserModule } from '@angular/platform-browser';

in the child module, which didn't seem necessary as I thought the parent module app.module.ts was the only one who needed that. As a result I got a lot of errors that looked like the following:

Can't bind to 'ngForOf' since it isn't a known property of 'div'. ("<h3>Very Best Vegetables</h3>
<div class="grid grid-pad">
  <div [ERROR ->]*ngFor="let vegetable of vegetables" (click)="gotoDetail(vegetable)" class="col-1-4">
<div class"): DashboardComponent@2:7
Property binding ngForOf not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section.

The first time seeing that had me confused for quite awhile. After all, it's not required in the child module (named heroes.module.ts) in the routing tutorial as seen below:

Once I added the BrowserModule to the child module, it went away, although it still puzzles me why I need to include it, so I'll need to revisit it at some point in the future. Regardless, things are working now.

The new project structure looks as follows:

Back to that feature...

Ok, so the idea is a user visits the Message of the Moment page and receives... the current message of the moment!

The message comes from the server, so anyone visiting the page will see that same message.

Unless someone changes the Message of the Moment! After some quick coding here's what the (admittedly awful) page looks like:

It's a pretty straight forward read / update operation. I've created a service on the Angular (client) side of things for this feature that has two functions:

getMessageOfTheMoment() which just performs a GET request to /api/get-message-of-the-moment

And postMessage(message) which just performs a POST request to /api/post-message

If you want more details, see the client side Angular 2 code in full on GitHub.

And on the server side, I've added two new APIs:

var express = require('express');
var router = express.Router();

let messageOfTheMoment = "If you ever fall off the Sears Tower...";

router.get('/get-message-of-the-moment', function(req, res, next) {
  res.json({'message': messageOfTheMoment});
});

router.post('/post-message', function(req, res, next) {
  if(req.body.message && req.body.message.length < 500) {
    messageOfTheMoment = req.sanitize(req.body.message);
    res.json({'message': req.sanitize(req.body.message)});
  } else {
    res.json({'error': 'message not set'});
  }
});

It's pretty bare bones at the moment, just like the UI. The server holds onto one string messageOfTheMoment and either serves that to clients or updates it and then serves back the update.

Here's what it looks like in action. In the following example I navigated to the page, received the current Message of the Moment, then submitted my own.

If you recall from day 5 I have two terminals open, one for running the front end and one for running the back end. The webpack-dev-server proxies any requests to /api/* to the back end.

I did add some basic sanitization using the express.js middleware called express-sanitizer. You should always sanitize user input, especially values that are then going to be served right back to other users.

If a user tried to enter <script>hello</script> world, the server would throw away anything in the script tag.

Day 7 summary and next steps

Most of the time (80-90%) spent today was just restructuring the project, trying to understand the Angular 2 router, and tracking down odd errors.

Therefore I implemented a suuuper basic, bare bones Message of the Day idea I had tossing around in my head. I'd like to polish it up tomorrow. Specifically

  • (client side) Switch to a <textarea> instead of <input type="text">
  • (client side) Make the UI look prettier. I've had an idea to incorporate Bootstrap into the project. Maybe now is the time?
  • (client side) Tell the user if the message is too long
  • (client side) Encourage people to share their Message of the Moment with their friends and family, because wouldn't Uncle Bob be pretty impressed?
  • (server side) Add some logging to the server side (will help identify abuse)
  • (server side) Rate limiting?
  • (server side) Keep track of IP addresses / user agent? (not foolproof, but can help identify some abuse)

As always, you can see my code on GitHub and you can see the current working version live here.

See day 8 here.