Scaffolding an Angular + Bootstrap App

March 16th, 2017
snohomish river

Snohomish River, WA, cyanotype print

Given the complexity of modern JavaScript web applications, it makes sense to use a scaffolding tool to set up. Fountainjs is a good way to go if you want to have your app built and served with Gulp, Sass and Webpack. I went to Fountain’s Download page, and selected:

  • framework: Angular 1 (version 1.6 actually)
  • module and build: Webpack and NPM
  • JavaScript preprocessing: none
  • CSS preprocessing: Sass
  • template: Hello World

So, that’s a good start. Do npm install, and gulp serve, and you’ll be at Hello World.

Now suppose you want to use Bootstrap with your project? This is not so simple. First off if you just use the Get Bootstrap stuff, the scripts will be dependent on jQuery. I’m not the sort of dev who hates jQuery, and while its possible to use it on top of Angular, its not Best Practice. Angular has its own jqlite for DOM manipulation. If you use jQuery and Bootstrap, believe me, you’ll get errors. BTW, in this post I’m using Bootstrap 3. I have not used Bootstrap 4 yet.

The best way to add Bootstrap is to use Angular UI Bootstrap, which makes a nice set of Angular directives for Bootstrap’s javascript components. UI Bootstrap has no jQuery dependency. You do need to load angular-animate and angular-touch. And of course you need to load the Bootstrap CSS.

Here is how I set up my app’s index.js:

var angular = require('angular');
require('angular-ui-router');
require('angular-sanitize');
require('angular-animate');
require('angular-touch');

// this one gives you Angular UI Bootstrap directives
require('angular-ui-bootstrap'); 

// this loads Boostrap's Sass into webpack.. yes I know its odd to load it here
require('bootstrap-loader'); 

var routesConfig = require('./routes.config');
var siteHeader = require('./app/components/header/header');
var navbar = require('./app/components/navbar/navbar');
var siteFooter = require('./app/components/footer/footer');
var collectionPosts = require('./app/components/collection/collection');
var singlePost = require('./app/components/single/single');
var pageComponent = require('./app/components/page/page');

// inject ui.bootstrap, and its dependencies
angular.module('app', ['ui.router', 'ngSanitize', 'ngAnimate', 'ngTouch', 'ui.bootstrap'])
  .config(routesConfig)
  .component('siteHeader', siteHeader)
  .component('navbar', navbar)
  .component('siteFooter', siteFooter)
  .component('home', collectionPosts)
  .component('collectionPosts', collectionPosts)
  .component('singlePost', singlePost);

You can see that ui.bootstrap is added as a Module level dependency. All the directives that are nicely documented on angular-ui.github.io should be available in your app. All you need is the CSS and you’re good, right? Well that’s no so simple actually. I guess you could put a link to Bootstrap’s CSS in the document head, and you’d be fine, as long as you like the default Bootstrap look and feel. I prefer more control over things, and I don’t want to write elaborate CSS overrides.

What I need is to bring Boostrap’s Sass into the webpack build. And it turns out that there is another node module to do just that: bootstrap-loader. Go ahead and install that also, as a Dev Dependency. And require it in your index.js, as above.

The nice thing about bootstrap-loader is that you can configure it however you want (the bad part is that you have to). Do this by putting a .bootstraprc file (like this one) in your project root. This is written in yaml.

Now comes the hopefully fun part—configuration. Note that if you try to build with the default config, it won’t work. The first thing to do is turn off all javascript in bootstrap-loader. We only want the JS that comes with Angular UI Bootstrap, not Bootstrap’s own jQuery-dependent JS. So at the bottom of the bootstraprc, you’ll see scripts section. Just put in scripts: false, and then delete all the script component lines.

You will probably still be getting build errors. And check both dev and production builds. This is how I set my style loaders in bootstraprc:

# Webpack loaders, order matters
styleLoaders:
  - style-loader
  - css-loader
  - postcss-loader
  - sass-loader

disableSassSourceMap: true
env:
  development:
    extractStyles: false
  production:
    extractStyles: true

I had to disable the Sass sourcemap to prevent webpack from adding a half-meg blob to my production CSS file.

You will probably be getting other errors if you do a production build (gulp serve:dist). It will break on the fonts because Fountain’s scaffolding does not account for font urls in Sass. The solution is to add the correct loaders to the webpack config.

First install url-loader: npm install url-loader --save-dev

Then add these two loaders to the loaders object in the /conf/webpack-dist.js file:

{
  test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
  loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{
  test: /\.(ttf|otf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?|(jpg|gif)$/,
  loader: 'file-loader'
}

Hopefully your app is now building correctly and you have access to the Bootstrap CSS and component library. Webpack config is something of a black art, and it might take some time to tweak out errors.

You might be wonder when do you get to the fun part? How about this:
preBootstrapCustomizations: './src/scss/bootstrap-config/_pre-customizations.scss'

You link this to a file changing any of Bootstrap’s Sass variables. Want to give your site a default text size of 17px and default width of 1200px? Easy:

$font-size-base: 17px;
$grid-gutter-width: 30px;
$container-large-desktop: (1200px + $grid-gutter-width);

And you can change default colors, etc. Sweet. Bootstrap has LOTS of Sass variables. That’s what I call fun.

Then you can put in a link to your application Sass, which will be appended after the Bootstrap Sass. Enjoy!

p.s. This is not a very lightweight application. My build ends up with 400k of javascript, and 124k of css. I don’t know if its possible to slim it down much.

Leave a Reply

Your email address will not be published. Required fields are marked *