How to use Showdown in Angular and NodeJS


Codever Logo

(P) Codever is an open source bookmarks and snippets manager for developers & co. See our How To guides to help you get started. Public bookmarks repos on Github ⭐🙏


When managing my dev bookmarks via www.codever.dev, I often have the need to place in the bookmark’s description either a code snippet (might be a command), add a list or add links and emphasize words - this seems like a perfect match for Markdown1. In this post I will demonstrate how I enabled Markdown support with the help of ShodownJS2, both in front end (developed with Angular3) and in backend, developed with NodeJS4.

Octocat Source code for Codever.dev is available on Github - Star

What is Showdown?

So what it Showdown? Well, according to the its authors “showdown is a Javascript Markdown to HTML converter, based on the original works by John Gruber. Showdown can be used client side (in the browser) or server side (with NodeJs).”1

How to use Showdown in Angular?

I use showdown in front end everytime I create a new bookmark or I update one. The notes written in Markdown are transformed in html and persisted in the database. I thought it would be more performant than just to persist the markdown code and generate html on the fly.

Showdown dependencies

First thing is to add the proper dependencies @types/showdown and showdown in package.json:

  "dependencies": {
    "@angular/common": "2.4.2",
    "@angular/compiler": "2.4.2",
    "@angular/core": "2.4.2",
    "@angular/forms": "2.4.2",
    "@angular/http": "2.4.2",
    "@angular/platform-browser": "2.4.2",
    "@angular/platform-browser-dynamic": "2.4.2",
    "@angular/router": "3.4.2",
    "@types/showdown": "^1.4.31",
    "bootstrap": "^4.0.0-alpha.5",
    "core-js": "^2.4.1",
    "immutable": "^3.7.6",
    "keycloak-js": "2.3.0",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.1",
    "showdown": "^1.6.4",
    "zone.js": "^0.7.2"
  },

Then and npm install and you’re ready to go.

Code setup

I am building the application with Webpack 2.x5, so I need to import showdown/dist/showdown.js in the vendor.ts file:

// Angular 2
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';

import 'rxjs';
import '@angularclass/hmr';

// Other vendors for example jQuery, Lodash or Bootstrap
// You can import js, ts, css, sass, ...
import 'bootstrap/dist/css/bootstrap.css';
import "keycloak-js/dist/keycloak.js";
import "showdown/dist/showdown.js";

Now the library is ready to be used in code. First require the showdown module, then create a showdown converter

import {Component, OnInit} from "@angular/core";
import {Bookmark} from "../../model/bookmark";
import {FormGroup, FormBuilder, Validators} from "@angular/forms";
import {KeycloakService} from "../../keycloak/keycloak.service";
import {UserBookmarkStore} from "../../personal/store/UserBookmarkStore";
import {Router} from "@angular/router";
import {BookmarkService} from "../../bookmark/bookmark.service";

const showdown = require('showdown');
const converter = new showdown.Converter();

and finally call the makeHthml method on the converter, before the bookmark is created and persisted:

  saveBookmark(model: Bookmark) {
    model.tags = model.tagsLine.split(",");
    var newBookmark = new Bookmark(model.name, model.location, model.category,model.tagsLine.split(","), model.description, null);

    newBookmark.userId = this.userId;
    newBookmark.shared = model.shared;

    newBookmark.descriptionHtml = converter.makeHtml(newBookmark.description);

    let obs = this.userBookmarkStore.addBookmark(this.userId, newBookmark);

    obs.subscribe(
      res => {
        console.log(res);
        this.router.navigate(['/personal']);
      });
  }

I use the same approach when I update a bookmark. Just generate new html from the description and persist it.

How to use Showdown in NodeJS?

At this point, there isn’t much of a need to use showdown in the back-end, but I kept it for two reasons:

  1. there might be cases when the descriptionHtml not present is, like API calls
  2. I get to write about how to do it in this post, maybe I’ll still need it later

Similar to front-end, add the showdown dependency in package.json and then npm install it:

{
  "name": "bookmarks-api.codepedia.org",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.15.1",
    "cheerio": "latest",
    "cookie-parser": "~1.4.3",
    "debug": "~2.2.0",
    "express": "~4.13.4",
    "jade": "~1.11.0",
    "keycloak-connect": "^2.5.0",
    "mongoose": "~4.3.7",
    "mongoose-unique-validator": "1.0.2",
    "morgan": "~1.7.0",
    "request": "latest",
    "serve-favicon": "~2.3.0",
    "showdown": "^1.6.4"
  },
  "devDependencies": {
    "nodemon": "^1.10.2"
  }
}

Then require the showdown module and create a showdown converter:

var showdown  = require('showdown'),
  converter = new showdown.Converter();

and finally use the makeHtml method of the converter in the post and put routes:

router.post('/:id/bookmarks', keycloak.protect(), function(req, res, next){
  var descriptionHtml = req.body.descriptionHtml ? req.body.descriptionHtml: converter.makeHtml(req.body.description);

  .............

});

...............

router.put('/:userId/bookmarks/:bookmarkId', keycloak.protect(), function(req, res, next) {
  if(!req.body.descriptionHtml){
    req.body.descriptionHtml = converter.makeHtml(req.body.description);
  }

  Bookmark.findOneAndUpdate({_id: req.params.bookmarkId, userId: req.params.userId}, req.body, {new: true}, function(err, bookmark){
  .............
  });

});

Works like a charm.

If you liked this, please show some love and give us a star Star

References

Subscribe to our newsletter for more code resources and news

Adrian Matei (aka adixchen)

Adrian Matei (aka adixchen)
Life force expressing itself as a coding capable human being

routerLink with query params in Angular html template

routerLink with query params in Angular html template code snippet Continue reading