Hooking up Angular

In this article we're going to look at Hooking AngularJS into Webpack.

We're going to start where we left off in the last article. Where we installed and configured Typescript. If you'd like to catch up.

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout hookup-angular
npm install

Angular also recommends using the Angular CLI for most things. And for most things it's really great. As to setting up a new project so that the developer can understand it, is not one of them. The Angular CLI gives you a finished webpack boilerplate, running and compiling with Webpack, but it hides the configuration away and obscures changing it. The opposite of what I'm trying to do in this course. I invite you to explore the Angular CLI as well as it has some other great features.

We're going to include the Angular Framework like it's just another package, so we can focus on how Webpack works with Angular.

In our terminal, let's install a bunch of packages that will be the base of functionality for the framework.

npm i @angular/core \
      @angular/common \
      @angular/compiler \
      @angular/platform-browser \
      @angular/platform-browser-dynamic \
      @angular/http \
      @angular/router \
      @angular/forms \
      rxjs \
      core-js \
      reflect-metadata \
      zone.js \

Let's also install some types, so Typescript can interact with node and with core-js. Without these you'll get some pretty strange errors.

npm install @types/node \
            @types/core-js --save-dev

Now let's create 2 new files that will server as webpack entry points for our Angular app.

touch src/angular.ts src/angular-polyfills.ts

angular.ts will be our base file and angular-polyfills.ts will keep angular working properly, in older browsers.

So next we need to include them as entry points.

In webpack.dev.js add the follow to your entry and devServer config objects. resolve is the only new object.

entry: {
    angular: "./src/angular.ts",
    polyfills: "./src/angular-polyfills.ts"
},
resolve: {
    extensions: [".js", ".ts"]
},
devServer: {
  historyApiFallback: true
}

In the plugins section, we need to add a plugin that rewrite's webpack's context when it sees a System.import. We'll get into Dynamic imports later, but suffice to say, Angular uses System.import internally and doesn't want webpack to mess with them first.

new webpack.ContextReplacementPlugin(
  /angular(\\|\/)core/,
  path.join(__dirname, "./src"),
  {}
),

Now, if we run npm run dev in our terminal, things should compile successfully, but of course those files are empty. So that's not too big a win. it's still helpful to check.

First Component

Now it's time to add our first angular component. Let's create the files in our terminal:

mkdir src/components src/components/app
cd src/components/app
touch app.module.ts \
      app.component.ts \
      app.component.css \
      app.component.html

So that's the basic setup for a component. Place it in it's own directory, in our case, app. And we want a app.module.ts to be the root of our component and to pull in the rest of the pieces from there.

In our src/angular.js file let's add these files and get cracking.

import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"
import { AppModule } from "./components/app/app.module"

platformBrowserDynamic().bootstrapModule(AppModule)

With these 3 lines, we're telling Angular to load up the AppModule into a client side browser app.

In index.html:

<html>
  <head>
  </head>
  <body>
    <root-app>loading...</root-app>
  </body>
</html>

We have the basic layout for an angular app. Simple body with a <root-app></root-app> markup to indicate this is where the app will be inserted into the document.

In app.module.ts:

import { NgModule } from "@angular/core"
import { BrowserModule } from "@angular/platform-browser"
import { AppRoot } from "./app.component"

@NgModule({
  bootstrap: [AppRoot],
  declarations: [AppRoot],
  imports: [BrowserModule]
})
export class AppModule {}

We're importing everything we need in the way Angular needs it. It's definately the most "magical" of all the files. But it's meant as a declarative way to get started.

The real meat of the app exists in the next 3 files.

In app.component.html:

<div class="profile">
  <img src="../../images/link.jpg">
  <h1>{{message}}</h1>
</div>

Now this html by itself has some nice syntax. Just a handlebars type wrapping around a {{message}} variable. You'll notice we can refer to the image in src and it'll appear in the browser as we'd want it to.

Tying this together is the component's type script file.

In app.component.ts:

import { Component } from "@angular/core"

@Component({
  selector: "root-app",
  styles: [require("./app.component.css").toString()],
  template: require("./app.component.html")
})
export class AppRoot {
  message = "Hello Angular"
}

Here we define what's included in the component. The selector matches where we want it injected in the index.html ie. <root-app>. Styles and template are both required via webpack's usual loaders, so Angular once Angular gets it's hands on them, it sees them as strings in the final component.

Then inside an AppRoot class we have one variable, message. Any other variables or functions in the scope of this component should be declared here as well to be used in the app.component.html file.

Now if we restart our server and go back to our browser. We see we've got a new message. Angular is running successfully. Nice work. But that's not Angular's full power. What about 2-way Data binding? What about forms?

Form Input and Data Binding

We can do that. We want this heading to change whenever we update our input. Sort of the classic Angular starter project. So let's update our app.module.ts with a new import, that we pass along.

import { NgModule } from "@angular/core"
import { FormsModule } from "@angular/forms" // New
import { BrowserModule } from "@angular/platform-browser"
import { AppRoot } from "./app.component"

@NgModule({
  bootstrap: [AppRoot],
  declarations: [AppRoot],
  imports: [BrowserModule, FormsModule] // New
})
export class AppModule {}

We add the FormModule and import it into our AppRoot to be accessible by the children components of AppRoot.

You only have to add Form functionality once per project, but it shows that Angular has broken up there functionality into distinct modules and each has to be explicitly included. There are modules for Routing and AJAX. Angular has a solution for most everything.

We'll want to update the html to include an input under the heading.

In app.component.html:

<div class="profile">
  <img src="../../images/link.jpg" alt="">
  <h1>{{message}}</h1>
  <input class="input" type="text" [(ngModel)]="message"/>
</div>

Notice the [(ngModel)] syntax that adds an attribute to our input that magically binds it's value to the value of message.

We should give that input some style:

In app.component.css:

.input {
  padding: 20px;
  font-size: 2em;
  text-align: center;
  margin: 0 auto;
}

So back in our browser. If we reload, we get an input that updates our heading as we type. Not as we submit a button, but automatically as we type. That's pretty cool, if not slightly magical.

Lastly, so that Angular also works in older browsers. We need to add a few libraries to the polyfills file.

In src/angular-polyfill.js:

import "core-js/es6"
import "reflect-metadata"
import "zone.js/dist/zone"

In Sum

In this article we got up and running with Angular 5 and Webpack. We inlcuded our first component and got Two-way data binding working. We didn't look too deeply under the covers as to what angular is really doing. Angular is a bigger topic. Instead I wanted to show how Webpack handles Angular's Typescript and unique declarative API to easily cobble these kinds of components together.

If you need the final code:

git checkout hookup-angular-final

Up Next

If you notice however, Angular doesn't do the hot-reloading when changing a file. And to use it well, I'm sure we'd rather it did. Turns out there is a way to get Angular working with Hot Reloading. After that we'll look at VueJS in a similar way. Stick around, as we pair Webpack with other front-end frameworks.