Overview
Updated 2021-10-11 for Angular 12.x (original post on 2016-06-01, then updated yearly)
We will create a simple angular application. It will be a simple "hello world"-style application so you can focus on the infrastructure and the absolute basics of the angular framework.
We will be using the result from the blog article on webpack. That result is also available from github
Starting point
If you have not completed the webpack then go ahead and clone the solution of that tutorial:
$ git clone https://github.com/edc4it/webpack-intro-tutorial hellow
$ cd hellow
Next let's install the necessary libraries using npm.
$ npm install
This puts you right at the end of the aforementioned blog article.
Then remove all the contents** of the src folder as we will be building a new application from scratch.
$ rm src/*
Configure the project
Install dependencies
Let's first install the required angular libraries:
$ npm install @angular/core@^12 \
@angular/common@^12 \
@angular/compiler@^12 \
@angular/platform-browser@^12 \
@angular/platform-browser-dynamic@^12 \
@angular/router@ \
@angular/forms@^12 \
rxjs@6
Angular also requires a couple of shims in order to support older browsers, install those and we make sure and we create a separate chunk for them afterwards.
$ npm install zone.js@0.11.4
Polyfills
Create a new file named src/polyfills.ts and add dependencies to our shims:
import 'zone.js';
Let's then add an webpack entry (polyfills) for our webpack configuration (above the main entry that is already there.):
entry: { // add as an object
polyfills: "./src/polyfills.ts", // add this
main: "./src/app.ts" //change this
},
Notice we also changed the name of the main entry to src/app.ts.
Our HtmlWebpackPlugin configuration from the previous post will include this on our page (as a
first entry)
TypeScript configuration
With angular we use decorators (they provide component metadata). By default this is not enabled. We therefore need to adjust the tsconfig.json together with some other configuration:
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"module": "es2020",
"importHelpers": true,
"target": "es2017",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"lib": [
"es2018",
"dom"
]
}
}
Webpack dev configuration
While we are here let's add two more pieces of configuration (you can add this to the bottom of the webpack configuration)
devtool: "source-map",
devServer: {
historyApiFallback: true
}
The first option instructs webpack to generate source-maps, the second option is required when using the webpack-dev-server together with angular routing.
We will need to add some more configuration later, but we'll do that in context.
Prepare the html
We need to place a component element to our html file so that angular can replace our component on the page (something like a placeholder). We will use a custom element (we could use any CSS3 selector to target the placement in the DOM).
Create a new file named src/index.html with the following contents:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Angular Hellow</title>
</head>
<body>
<hellow-app>loading...</hellow-app>
</body>
</html>
The angular component we'll write later on is going to replace the contents of the hellow-app element.
Notice we have placed some text inside it ("loading..."), this will be shown during application initialisation.
We don't need to include any javascript files for angular, polyfills or our application code, this is handled by webpack and in particular the HtmlWebpackPlugin. This means we are all done with this step!
The Component
Create a new file named src/app.ts. In it declare an HelloComponent class within:
class HelloComponent {
}
Next lets add the metadata using the @Component decorator from "@angular/core" (don't forget the
import if your IDE does not do it for you). Add a selector (pointing to the hellow-app element
in our index.html, and the template text for this component:
import {Component} from "@angular/core";
@Component({
selector: "hellow-app",
template: "<h1>Hello Friend</h1>",
})
class HelloComponent {
}
Angular applications are structured using modules (not to be confused with webpack modules). A module consists of various related angular
resources (components, services etc). Angular modules can dependent on each other. In our case we need to "import" the built-in BrowserModule. We will also need to register our component
and indicate it is used to bootstrap our application. We then finally need to register the module to be used as the bootstrap module
(using platformBrowserDynamic)
You'll need at least one such module. Lets go ahead and declare an "application"-module inside the same app.ts file"
import {Component, NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
…
// Your HelloComponent
…
@NgModule({
imports: [BrowserModule ], // import Angular's BrowserModule
bootstrap: [HelloComponent], // indicate the bootstrap component
declarations: [HelloComponent] // register our component with the module
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule); // bootstrap with our module
Start the webpack-dev-server:
$ npx webpack-dev-server --mode development
Open http://localhost:8080 and Bang! it shows "Hello friend"!
Restructure
Great that we got it all working so fast in a single file, but we really should reorganise the files and resources better:
- Place components inside their own files (and directories)
- Place template html (and later styles) inside their own files
- Place the module in its own file
Eventually the structure needs to resemble:
src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ └── app.module.ts
├── index.html
├── app.ts
├── polyfills.ts
Separate Module from Component
Create the src/app/app.component.ts and cut/paste the component from app.ts. Make sure you export as this needs to now
be accessed by another module:
import {Component} from "@angular/core";
@Component({
selector: "hellow-app",
template: "<h1>Hello Friend</h1>",
})
export class HelloComponent {
}
And let's move the AppModule declaration to a file named src/app/app.module.ts (again. don't forget the imports and export):
import {NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {HelloComponent} from "./app.component";
@NgModule({
imports: [BrowserModule ], // import Angular's BrowserModule
bootstrap: [HelloComponent], // indicate the bootstrap component
declarations: [HelloComponent], // register our component with the module
})
export class AppModule {}
Fix the reference inside the app.ts by using an import:
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app/app.module";
platformBrowserDynamic().bootstrapModule(AppModule); // bootstrap with our module
Your browser should again be showing "Hello Friend"
Move the html to a file
Let's now remove the html template from the component decorator and place it in its own file src/app/app.component.html:
<h1>Hello Friend</h1>
Update the metadata and use node's require to obtain the html text:
@Component({
selector: "hellow-app",
template: require("./app.component.html"),
})
note: Without webpack you would have used the templateUrl metadata property and point to the
html file using a string url: app/app.component.html. But as we are using webpack we are really just
loading the html as a string using node's require function.
But hold on: we are declaring a dependency to a "html-module". Recall from the previous article, that webpack only knows how to handle javascript modules. We therefore already added some module rules. We now need to add one for html modules
First install the loader:
$ npm install add html-loader@2 --save-dev
Then configure the rule:
{
test: /\.html$/,
loader: "html-loader",
options: {
esModule: false,
},
},
We need to specify esModule: false so that the loader does not generate a ES module (it needs to be the raw html)
Add styling
Add a scss file named src/app/app.component.scss with the following contents
$color: #0086b3;
h1 {
font-size: 1.2em;
color: $color;
}
note: The $color variable should ideally come from a global .scss file which you would then
import using @import "../main";. We are using it here just to use a specific non-css scss feature.
Update the metadata to include a styles property and use again node's require:
@Component({
selector: "hellow-app",
styles: [ require("./app.component.scss") ],
template: require("./app.component.html"),
})
But wait a minute, hold on again! How will webpack process this? We have an existing rule for scss. But in this case we don't want
to have the style on the page. We just want it to be made available to the styles metadata property above. Change
the existing rule and only use the sass-loader followed by a raw-loader (which we need to install):
{
test: /\.scss$/,
use: [
{
loader: "raw-loader",
options: {
esModule: false,
}
},
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
}
Don't forget to install the loader:
$ npm install add raw-loader@4 --save-dev
Restart your dev server and your browser should now be showing a styled "Hello World" ;)
One-way binding
In this step you will use interpolation using an expression which will be initialised to a static value. (Later we will use two-way binding).
Update our component's template (app/app.component.html)
<h1>{{message}}</h1>
Notice your browser, you will notice the page no longer display any text. You are already seeing
angular in action. The reason you don't see any text is because the property message value has no value (if
you wanted you could use '{{!message}}and the paye would displaytrue`).
Let's add a property and initialise it with a value inside our component:
export class HelloComponent {
message = "Hoo Ah!";
}
You should now see your message in the browser (and shout it out like Lt. Col. Frank Slade when you see it in your browser)
Two-way binding
You will now bind the message model using two-way data binding.
Add an input element and use the ngModel applying the two-way binding style: [(...)].
<h1>{{message}}</h1>
<input type="text" [(ngModel)]="message"/>
You will need to import the FormsModule into your module:
…
import {FormsModule} from "@angular/forms";
@NgModule({
imports: [BrowserModule, FormsModule ],
…
})
export class AppModule {}
Try it out in the browser! You should notice the text input contains "Hoo Ah!", but now when you change the value, it instantly updates the header value.
This completes this introduction on Angular. The solution can be be found on github: github