Angular 2 Conventions

Automated Angular 2 Conventions with Webpack

Angular 2 is still new, yet the community has banded together to create common conventions for folder structure and file naming. Let’s dive right in to check them out.

One example of a convention within Angular 2 is creating a folder for our components with all of the files needed for that part of our ui.

home/
  ├──home.ts
  ├──home.css
  └──home.html

Here’s a component with a template and css. We normally provide these file paths as convention within the metadata for the Component decorator.

@Component({
  selector: 'home',
  templateUrl: 'home.html',
  styleUrls: ['home.css']
})
export class Home {
}

This already seems like a lot of boilerplate for a basic component and we have to do this for every component we make. Can we do better? We can with the help of Webpack. Webpack is a module bundler that takes an entry file and outputs a bundled file. There are two keywords here entry file and bundled file we have an entry file which creates a graph of dependencies for that one file. If our entry file has three import statements then Webpack will analyze the statements and load each of these files. Webpack will do this to determine all of the dependencies for our entry file then bundled them all into one file. For example,


;(function bundledFile(entryFileName) {
  var dependencies = {
    'foo.js': function fooFile() {
      console.log('foo.js');
    },
    'bar.js': function barFile() {
      console.log('bar.js');
    },
    'app.js': function appFile(exports, require) {
      require('foo.js');
      require('bar.js');
      console.log('hi')
    }
  };

  function fakeRequire(fileName) {
    return dependencies[fileName]({});
  }

  return dependencies[entryFileName]({}, fakeRequire);
}());

Webpack is mocking out the filesystem and concatting our files into one after wrapping them with javascript that’s creating the fake filesystem. That’s great but we can also overload some of the features within webpack such as loading other files to either wrap or transforms the file before returning it. In webpack, we call them webpack loaders and we can achieve this by providing a configuration for Webpack’s module config to a loader that we installed. In this example let’s the finished version of our loader that we can refer to as conventions-loader
npm install --save-dev @angularclass/conventions-loader

module.exports = {
  // entry
  // output
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: '@angularclass/conventions-loader'
      }
    ]// end loaders
  }// end module
}// end webpack.config

This configuration is telling webpack that we want to overload the module part of the webpack bundling process and include extra loaders. Again the loaders, within webpack, simply transforms the file before returning it. We can also provide even more loaders for different file types. For example let’s transform use TypeScript.

module.exports = {
  // entry
  // output
  module: {
    loaders: [
      {
        test: /\.ts$/,
        loaders: [
          'ts-loader',
          '@angularclass/conventions-loader'
        ]
      }
    ]// end loaders
  }// end module
}// end webpack.config

Notice how we have to include ts-loader and our conventions-loader. This is because we are transforming the file with conventions-loader before passing it to our TypeScript loader ts-loader. Loaders are triggered by the RegExp, regular expression, test is ran on each import file value. For example if we had something like

import 'my-file.ts'; import {Something} from 'my-file.ts'

The value 'my-file.ts' will be ran against our RegExp

var testLoader = /\.ts$/;
var fileImport = 'my-file.ts'
testLoader.test(fileImport); // true`

Because our fileImport value is tested true then our loader is going to run on this file which is a large string

var fileString = `
@Component({
  selector: 'home',
  templateUrl: 'home.html',
  styleUrls: ['home.css']
})
export class Home {
}
`
var conventionsLoader = require('@angularclass/conventions-loader');
// pseudocode
return conventionsLoader(fileString);

We can see that our conventions-loader is transforming the string value. We can take advantage of loaders in webpack to provide power default values so let’s look at how @angularclass/conventions-loader works.

If we start with this code example.

@Component({})
export class Home {
}

Our loader will do a few things to provide defaults for our @Component decorator. First we must grab the selector and if there isn’t one provided we must then grab the class name in this case it’s Home. Next we convert that value to dash-case so ours would be "home" easy enough. Now let’s inject the selector code inside our metadata.

@Component({
  selector: "home"
})
export class Home {
}

We can then use the selector or filename to determine if there are corresponding files for the two file types .css and .html. If they exist then we will require them and inject it within our @Component metadata.

@Component({
  selector: "home",
  template: require('./home.html'),
  styles: [
    require('./home.css')
  ]
})
export class Home {
}

We are using require to require the files inline but if we wanted to and if we had TypeScript 2.0 support then we can use ES2015 modules, previously named ES6 modules.

import template_home from './home.html';
import styles_home from './home.css';
@Component({
  selector: "home",
  template: template_home,
  styles: [
    styles_home
  ]
})
export class Home {
}

Woah, so webpack is powerful we can rewrite our code with code and even analysis our code to recode where needed. This is great for patching a legacy 3rd party package (that we don’t have control over), optimizing filesize, providing a better debugging experience, replacing run-time code with build-time generated code, or providing defaults for conventions as we’re doing here. Now we can start writing our Angular applications with these defaults and provide configuration when necessary. For example, if we wanted to change our selector we can do so simply

@Component({
  selector: 'my-home'
})
export class Home {
}

This is great because our build-tool will now check for ./my-home.css and ./my-home.html. If we want more control over our code we would just provide more configuration. This allows us to reap the benefits of “conventions over configuration” when we want it most, during the start of our app or new feature, while still maintaining control over our code with configuration. We can progressively add more code only when it’s needed while being able to focus more on our application code which is the most important part.

If you want learn more about Webpack or other advanced topics make sure to follow our blog and twitter account @AngularClass. We also provide a Free course on Angular 2 Fundamentals that you should also check out.

Finished Github Repo of our conventions-loader:
@angularclass/conventions-loader

Related Posts

Angular 2 for AngularJs Developers

Now that Angular 2 is in Release Candidate someone moved our cheese! Let’s put on our running shoes. First off, let me start off by saying I have been writing Angular 2 for a year and a half now (actually, since this tweet) and writing AngularJS…

Angular 2 for React Developers

Angular is here, the time has come for us to drop everything and learn something new, again. The good news is, like React and Angular 1.x, Angular 2 is here to stay for a while so it’s a good investment to become productive with this new framework…
twitter tweet