Angular-CLI Meets Webpack

the single greatest improvement since ‘ng g’

A few months ago I tried my hand at the angular-cli. It was nice but it had enough deterrents to keep me away until the team worked some things out. Foremost was the incredibly slow app refresh during development due to SystemJS loading every file dynamically —definitely a deal breaker.

All the gains made with scaffolding efficiency were quickly offset by the painfully slow app refreshes and the 5 step process to add 3rd party npm modules. So I backburnered it in favor of Angular 2 Webpack seeds.

Then in skunkworks like fashion, a branch mysteriously appeared in the angular-cli repo covertly named, “webpack”. There was hope!

Oh what a delight to see the team moved the build system from SystemJS to Webpack!!

Why did the CLI team move to Webpack?

  • Fast
  • Improved Livereload reliability
  • Community Adoption
  • Vast Loader & Plugin Ecosystem

New Improvements

Here are the highlights that we’ll review.

  • Fast iteration reloads
  • Simplified 3rd party module configuration
  • Tree Shaking — compile bundles with all unused code removed
  • Inject External Custom Styles & Scripts
  • Webpack 2.1.0-beta.21 pre-configured out of the box

BOOM!!

Kudos to the cli team for making a bold improvement! This was a pretty large undertaking and there remains a lot of testing before the team is comfortable labeling it “beta”. Regardless, I’ll bask a while in the single greatest improvement to angular-cli since ‘ng g’.

Same Great Features

The angular-cli still has the same great scaffolding features. So we’re not here to re-explore generator commands. Instead we’ll review the main improvements Webpack brings to angular-cli — speed, convenience and ease.

Since the Angular Material 2 library has made some great gains in recent weeks, I will review the new CLI improvements by busting out a quick Material 2 demo app.

I’ll also take this opportunity to help with the impedance mismatches in the Material 2 Getting Started guide (as of this writing), brought about by the Webpack switch. Plus there are few md-[component] workarounds to address that naturally occur when mixing two alpha efforts.

We’ll build a quick Material 2 demo with a few md-components using these libraries:

  • angular-cli beta-11 (circa: “1.0.0-beta.11-webpack.2”)
  • Material 2 (circa: “2.0.0-alpha.7–4”)

You can access the code samples and demo project here:


Setup Angular-CLI

Install angular-cli
If you previously installed angular-cli, then you should remove the old version and clean npm cache.

npm uninstall -g angular-cli
npm cache clean
npm install -g angular-cli

Generate a New App

ng new PROJECT_NAME
cd PROJECT_NAME
ng serve

Now That’s a Reload

Open a browser at http://localhost:4200, make a change to the title property in app.component.ts and save. Notice the page refresh improvements! It’s about twice as fast as the SystemJS version. When refreshing an app over 100 times a day, this is a much welcomed improvement!

Webpack has another feature that supercharges iteration speeds; Hot Module Reload (aka HMR). HMR hot-reloads only the parts of the application that have changed without refreshing the browser. Although HMR is not available in this version of angular-cli, there is talk of adding HMR to a future release. In the meantime, Patrick at AngularClass has created a library that integrates HMR with Angular 2 if curiosity strikes you.

To Configure, or Configure Not?

Looking at the resulting tree structure you may have noticed the absence of a webpack.config.js. According to the cli team, this is by plan. One of their primary reasons for moving from SystemJS to Webpack was help those new to Angular, build an initial Angular app quickly without knowledge of the cli ecosystem. Well, they nailed that — no webpack.config.js == no webpack configuration.

However, this benefit to greenhorns becomes the achilles heel to those who desire to take advantage of Webpack’s highly configurable nature. Until the MVP features are locked down, Webpack vets will have to wait for the “add-on” system the cli team has planned.

UPDATE 9/5/2016: There is movement on this issue. See issue #2954.


Setup Material 2

Next let’s install Material 2 and a few components: Button, Card, Checkbox, Radio, Slider and Tooltip

npm install --save @angular2-material/core @angular2-material/button @angular2-material/card @angular2-material/radio @angular2-material/checkbox @angular2-material/slider @angular2-material/tooltip

TypeScript 2.0

The new CLI requires TypeScript 2.0. If you haven’t upgraded yet, you’ll see the following UNMET PEER DEPENDENCY typescript@2.0.0 Error when adding npm packages.

The app appeared to work fine with the current release of TypeScript 1.8.10. Regardless, I chose to install TypeScript 2.0. It’s been in beta for a while and by all accounts is stable. Obviously, it’s your call here.

Add Material Modules to the App

This version of angular-cli is current with Angular2 RC5 and therefore, includes ngModule. So adding 3rd party modules is eezy peezy. Just import, then ref them in the imports:[] array inside the @NgModule decorator. Done! No more rocket surgery.

UPDATE 9/5/2016: If you are using Material 2.0.0-alpha.8-1 released after this post, you must append .forRoot() to your NgModule imports to indicate that the module is for use app-wide. Read more on the Material2 repo and Angular docs explains .forRoot() here.

And the HTML Component

Let’s add some markup to put our Material components to use.

HammerJS Errors

Running the app you may see the following compilation error:

ERROR in [default] /material-app/node_modules/@angular2-material/core/gestures/MdGestureConfig.d.ts:4:39
Cannot find name ‘HammerManager’.

And the runtime error:

ERROR: hammer.js is not loaded, can not bind slide event

We get these errors because, well… hammerjs is not installed! Even though Material 2 has a dependency on “hammerjs”: “2.0.8” we need to do the install manually.

npm install --save hammerjs
npm install --save @types/hammerjs

Then add the hammerjs import:

// app.module.ts 
import 'hammerjs'

When the app reloads, you should see everything in order. Well, not everything.


Tooltip and overlay.css

When hovering over the RAISED button you should see a tooltip under the button, but instead you haz something like…

The prescribed solution in the Material 2 Getting Started guide instructs us to add a style link to overlay.css:

<link href="vendor/@angular2-material/core/overlay/overlay.css" rel="stylesheet">

However, this will not work with the webpack based app produced by angular-cli. The solution is to use a new feature in angular-cli.json.

External Styles and Scripts to the Rescue

Within angular-cli.json, there are new “styles” and “scripts” config settings. This is where you can inject external css and js files into your application and the build process will include them in your bundle. There are two options available to us to solve the overlay.css issue.

External Styles Option 1

By default, the cli places a styles.css file in the src/ directory and a pointer to it in the styles:[] array. This is a great place to include global styles for the entire app without any encapsulation workarounds. To inject overlay.css into our app, just add the path to the overlay.css file to the styles:[] array.

//-- angular-cli.json --//
{
//...
“apps”: [
{
//...
styles”: [
“styles.css”,
../node_modules/@angular2-material/core/overlay/overlay.css
],
scripts”: [],
“environments”: {
// ...
}
}
],

Since angular-cli.json is a config file, you’ll need to restart the server with ng serve. That’s it! Now the build process will include your external css file. When the app fires back up you should see the tooltip working properly.

External Styles Option 2

Alternatively, you could simply @import overlay.css in the styles.css. Add the following code to the src/style.css file, remove the overlay.css reference in angular-cli.json config, and restart the server.

/* style.css */
@import “../node_modules/@angular2-material/core/overlay/overlay.css”;

External Scripts

Similar to injecting external styles, you can inject external scripts by using the scripts:[] array in angular-cli.json. Any scripts added here will get loaded to the “window” object. This is particularly handy with plugins that require jQuery to be accessed from the “window” object. More on this here.

What About LESS and SASS?

Maybe you’re like me and you prefer to have a little more control over your css with your favorite css pre-processor. Well that couldn’t be easier. Just rename the .css file extensions to one of the following: .sass / .scss / .less / .styl. Angular-cli will take care of the rest. You should also change the “styleExt”: in angular-cli.json so any new generated files have the proper extension. Here are some other tips on this topic.


Material Icons

To use md-icons add the following to the index.html page:

  1. Add the link to the CDN in index.html:
<!-- index.html --> 
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

2. Install the package:

npm install --save @angular2-material/icon

3. Add the module to app.module.ts:

// app.module.ts
//...
import { MdIconModule } from '@angular2-material/icon';
@NgModule({
//...
imports: [
. . .
MdCoreModule, MdCardModule, MdButtonModule, MdCheckboxModule, MdRadioModule, MdSliderModule, MdTooltipModule, MdIconModule
],
//...
})

Using md-icons has been simplified greatly from previous releases. Add this to app.component.html:

<!-- app.component.html -->
...
<md-icon>fingerprint</md-icon>
<button md-icon-button>
<md-icon class="md-24">favorite</md-icon>
</button>
<button md-fab>
<md-icon class="md-24">fingerprint</md-icon>
</button>
<button md-mini-fab>
<md-icon class="md-24">add</md-icon>
</button>

You can browse the Material Icon Library here:

Font Awesome Icons

If you want some additional variety, you can add the popular Font Awesome library with a few steps.

1. Add the link to the CDN:

<!-- index.html --> 
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1">

2. Register the font with MdIconRegistry:

// app.module.ts //
. . .
import { MdIconModule, MdIconRegistry } from '@angular2-material/icon';
. . .
export class AppModule {
constructor(mdIconRegistry: MdIconRegistry) {
mdIconRegistry.registerFontClassAlias(‘fontawesome’, ‘fa’);
}

}

3. Then use them like so:

<!-- app.component.html -->
...
<md-icon class="fa-2x" fontSet="fa" fontIcon="fa-birthday-cake"></md-icon>
<button md-raised-button>
<md-icon class="md-24 fa-lg" fontSet="fa" fontIcon="fa-camera-retro"></md-icon>
Say Cheez!
</button>

You can browse the Font Awesome library here:


Tree Shaking

The last major feature the Webpack angular-cli brings is Tree-Shaking. This is a process that scours your entire project and prevents any unreferenced code from being included in the bundle. This significantly reduces the bundle size. In the case of our demo app, the bundle size is cut by nearly 1/3!!

Before

ng serve

After

ng build --prod

Summary

angular-cli@webpack is a huge improvement over the previous SystemJS based version. There are many great improvements that will make your development life easier and faster. As the CLI team has stated, there remains some work to get to beta, so give it a spin and report any issues.

The two obvious Webpack related features that remain are Hot Module Reload and custom configuration of Webpack builds. If you are a Webpack’er and need access to the “blackbox”, you’ll need to wait a bit. Hopefully these are coming soon.

UPDATE 9/5/2016: There is movement on this issue. See issue #2954.

If you need to gen-up an Angular 2 app with speedy iteration reloads, quick module integrations, and all the generator conveniences, then angular-cli@webpack is your ticket.