This is a very basic introduction to Angular 4 and the use of its navigation functionality.
Introduction
When I started learning Angular 4. I was surprised how different it was from Angular 1.x. The most difficult aspect about Angular 4 is that the whole application architecture has changed. Even though I thought I was pretty good with Angular 1.x, it was still a little difficult for me to get started. The focus of this tutorial is about how to setup a basic Angular 4 application for page navigation. I would also like to discuss some of the gotchas I have noticed.
Running Hello World Sample Project
Here are the key steps I took to get this simple client side app to work. Here is the summary of steps:
- Install the Latest Node JS.
- Get the Hello World sample app.
- Install All the Necessary NPM Packages.
- Run the Sample App.
- Change the Sample App with a new Design
- Run the new App for Testing.
Install Node.JS
When I first started learning Angular 2/Angular 4 application development, I had a very old version of Node.JS installed. It was probably nine months old. When I used it on Angular 2 or Angular 4 application development, I was seeing weird errors to which I can't find answers on search engines. After some struggle, I just gave up and installed the newest version and everything just worked. So if you see weird errors and couldn't find out what went wrong, think about the version of Node.JS you are using. Upgrade if you must and see if it solves your problem.
The download page on nodejs.org is this: https://nodejs.org/en/download/.
Getting the Hello World Sample App
The official Angular site has a basic walkthrough tutorial: The Hero Editor. I think it was a wonderful tutorial. The best basic tutorial on Angular 2/Angular 4 you can find on the web. Once you completed all the tutorials, you will no longer be a novice and can sufficiently work with Angular 4.
Anyways, I got sidetracked. For me, the easiest way to get started with Angular 4 is to get a working Hello World sample app. Do some customization of the application architecture, and I get an Angular application of my own.
Official Angular 4 tutorial provided a Hello World sample project, which is the one I used. The link to the sample app can be found: https://angular.io/guide/setup. Please follow this setup guide to configure your development environment.
Once you download it, unzip it and put the unzipped source code in a meaningful folder, preferably a folder with a short name like C:\sample\Hello. When you start running your code, the node_modules downloaded will have extremely long file names. Sometimes, this can be problematic if the base directory has a long file name.
Install Latest NPM Packages
Once you get the sample Hello World from angular.io, you can run npm command to install all the npm packages needed for the project. First, you need to unzip the sample application. Go to the unzipped file directory, in there, should be a src directory. Go to this src directory, and run the following command:
npm install
This command will download and install all the necessary packages used by the sample project. It would take some time get all the packages. I would wait until all packages are downloaded and installed, then go to the next step.
In case you are wondering where the packages are defined, go to the directory above src, and you will find a file called package.json. You can open this in a text editor, and in it, you will find two sections:
dependencies
, and devDependencies
These are the places where dependencies
can be modified.
I used bootstrap for my sample application. So I added two new dependencies bootstrap
(version 3.3.7 or above) and jquery
(versiion 3.2.1 or above) to the end of the dependencies
section:
"dependencies": {
"@angular/animations": "~4.2.0",
"@angular/common": "~4.2.0",
"@angular/compiler": "~4.2.0",
"@angular/compiler-cli": "~4.2.0",
"@angular/core": "~4.2.0",
"@angular/forms": "~4.2.0",
"@angular/http": "~4.2.0",
"@angular/platform-browser": "~4.2.0",
"@angular/platform-browser-dynamic": "~4.2.0",
"@angular/platform-server": "~4.2.0",
"@angular/router": "~4.2.0",
"@angular/tsc-wrapped": "~4.2.0",
"@angular/upgrade": "~4.2.0",
"angular-in-memory-web-api": "~0.3.2",
"core-js": "^2.4.1",
"rxjs": "5.0.1",
"systemjs": "0.19.39",
"zone.js": "^0.8.4",
"jquery": "^3.2.1",
"bootstrap": "^3.3.7"
}
After I modified the package.json file, I ran the same npm command again. And it will just add the newly specified packages.
Note that by default, the node packages (called mode_modules
) are installed in the base directory (parent directory of src). I believe this has something to do with the relative path ../node_modules/@types/ in tsconfig.json. That is all you need to worry about relative paths, which will be described in the next section.
Running the Sample Project
To run the sample project, here is all you need to do:
npm start
After entering the command, the console will spit out a lot of output, and the default browser would load and display the sample output page of Hello World. Even though I made the change to the package.json file, it should not affect the original sample project.
If you can see Hello World on the browser, then you are ready for the second part of this tutorial, modified to have navigation, and some basic Angular 4 functionality for user interaction.
Angular 4 Navigation and Basic UI Interaction
In this section, I am going to walk through the changes I have made to the Hello World project in order to get page navigation to work. To make this sample app a little bit more meaningful, I also included a little UI interaction on one of the pages. I will explain how that works as well.
Why Navigation?
When I looked into this, I found page navigation in Angular 4 as an interesting challenge. And it is very important to learn. So I decided to use this as an opportunity to learn the basic of it. Angular 4 is used to create single page application. But it does not mean the application is composed with just one page. This would make the application very clogged. A better way is to split the functionality of an application into multiple functionality, and each functionality is served by its own pages. This feature is supported since Angular 1.x. I thought it would be simple to grasp. It turned out to be quite complicated.
I started out by reading the tutorial on angular.io. Then I read the source code of a project from angular.io. It turned out to be complicated. With that source code and some other resources, I have forgotten where I found them (sorry), I was able to get it to work. In the next section, I will begin with the entry of the sample application. Subsequent sections will discuss the design further.
The Core
The core of an Angular 2/Angular 4 application is a file called main.ts. This file will be compiled into main.js and main.js will be included by the index.html file. The content of the main.ts looks like this:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
All this file does is bootstrap a class called AppModule
, which is found in a file called app.module.ts. You can see the reference in the above code snippet -- ./app/app.module. It tells where the file is, under the sub directory app, there is the file app.module.ts.
app.module.ts is the file that aggregates all the other necessary modules together. The content of this file is like this:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AboutComponent } from './components/about.component';
import { HomeComponent } from './components/home.component';
@NgModule({
imports: [ BrowserModule, AppRoutingModule ],
declarations: [ AppComponent, AboutComponent, HomeComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
I have to import AppRoutingModule
, AppComponent
, AboutComponent
and HomeComponent
. These are all the modules and components I have made. There is just one class in this file, called AppModule
. The class has a decoration @NgModule
, something similar to attribute in .NET Framework and annotation in Java. The decoration does three different things:
- It marks the class
AppComponent
as a bootstrapping class -- the start up class. - It marks three classes as controllers.
- And it imports two modules. One of the modules is the routing module.
The Start Up Class
The class AppComponent
is marked as the start up class. It is defined in a file called app.component.ts. The content of this file looks like this:
import { Component } from "@angular/core";
import { Routes } from '@angular/router';
import { HomeComponent } from './components/home.component';
import { AboutComponent } from './components/about.component';
@Component({
selector: 'my-app',
templateUrl: './templates/app.tpl.html'
})
export class AppComponent { }
The top part of this file imports four different components. The first two are from the Angular library. They are needed because:
- the
AppComponent
class uses the @Component
decorator; - the template which
AppComponent
is using, references the routerLink
and router-outlet
which is from the Routes
class.
The second half of the file is the definition of the AppComponent
. This class is like the two classes from previous sections, it has no properties or methods. And it has one decorator called @Component
. For this class, the decorator @Component
has two properties, one is called the selector
. The other is called the templateUrl
. The selector
property defines on the index page what element will be replaced with a new UI template. The templateUrl
defines what page template will be used. Essentially, AppComponent
is the controller, and the templateUrl
references the view which the controller can render.
The selector is assigned with value my-app
. So where is this my-app
selector used? You can find it in the index.html. When index.html loads successfully, it will first start up main.js (which is transformed from main.ts), and it will load the app.module.js (which is transformed from app.module.ts). And if you remembered from previous sections, one of the modules loaded is the app.components.js (transformed from app.components.ts). That is how AppComponent
is getting bootstrapped as a start up class.
Now, take a look at the template file for AppComponent
:
<div class="container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<!--
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Brand</a>
</div>
<!--
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">Navigation
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a [routerLink]="['/']">Home</a></li>
<li><a [routerLink]="['/about']">About</a></li>
</ul>
</li>
</ul>
</div><!--
</div><!--
</nav>
<router-outlet></router-outlet>
</div>
This page template can be viewed as two parts. The top part is a navigation menu. There is a lot of html code used to create this navigation menu. The real good part about it is the two anchors:
<li><a [routerLink]="['/']">Home</a></li>
<li><a [routerLink]="['/about']">About</a></li>
These two anchors define different pages that can be navigated within my sample project. One is the Home page (navigation by /), and the other is the About page (navigation by /about). The mark up in the template html file does not make the navigation work. What makes it work is my AppRoutingModule
. Which will be discussed next.
The Routing Module
The routing module is defined in the file app-routing.module.ts. The class is called AppRoutingModule
. Here is something I learned when I finally got this class integrated with the rest of the project. The file name matters. I believe Angular 2/Angular 4 searches the file system locally based on naming conventions. This is one thing I struggled with when I got to this point. I tried many different names for the routing module file, and included it in the app.module.ts. None worked, until I realized the file name should be app-routing.module.ts, which Angular probably interprets the class as AppRoutingModule
in the file. Here is a hint, if you find your project broken and everything else seems to be correct, check your file name. The conversion rules are:
- The file name should all be lower case.
- The file name should be roughly the same as the class name. For example, when I export a class called
AppRoutingModule
, the file name would have app
, routing
, module
in it. - Based on what the export file is, a module or a component. The file name will contain
.module
or .component
, followed by file extension .ts. - The class name contains multiple words, and these words will be lower case and separated by dash. For example,
AppRoutingModule
, the word AppRouting
would translate into file name as app-routing.
There is also the problem of relative path. Sometimes, you can try finding the file from the current directory via relative path, and sometimes, you can just use absolute path. I will show examples for both.
The content of the app-routing.module.ts is as follows:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './components/about.component';
import { HomeComponent } from './components/home.component';
export const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes
)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
The file has three different parts:
- The first part imports multiple modules or components into this file.
- The second part will export an array called
appRoutes
. Inside this array, there two elements (objects) that define different routings. - The third part creates the
AppRoutingModule
module class. The class is decorated by @NgModule
. Inside the decorator, the appRoutes
is used to bind with the RouterModule
, which is associated with the exported class AppRoutingModule
.
This class shows a very basic routing module class. There are more complicated ways to define a routing module, you can see it in the sample project from angular.io. The most important part of the file is the declaration of the appRoutes
array. In this declaration, each url path is associated with a component. A component, if you remember from before, is a controller that is associated with a view. So in this case, the router module uses this appRoutes
array to route the url path to a specific controller, which in turn manages the associated view.
Next, we are going to see what these controllers look like.
The Controllers and The Views
I have created two pages for this navigation example. The controllers for these two pages are:
HomeComponent
AboutComponent
In Angular 4/Angular 2, there is no concept of controller anymore. Instead, a controller is more of a component. A view can be associated with a component through the @Component
decorator, using the property of template
or templateUrl
of the @Component
decorator. You can see this in previous section "The Start Up Class". In this section, I listed out the code of the index component. The two components for the pages of navigation look very similar. Here is the source code for the HomeComponent
:
import { Component } from "@angular/core";
@Component({
templateUrl: 'app/templates/home.tpl.html'
})
export class HomeComponent {
public sunshine: string;
constructor() {
this.sunshine = "Cowboy Jackie";
}
}
The way HomeComponent
works is that the decorator @Component
binds a view to this HomeComponent
. The templateUrl
defines where the view is. I personally prefer putting the view in a separated file, which makes the component class file a little cleaner. There are a lot of examples out there that put the view file in the same component class file. This is not wrong, but I personally prefer separating the view into another file.
The class HomeComponent
has a public
property and a constructor. In this constructor, the public
property is initialized to a string
value "Cpwboy Jackie
". The reason I did this is to test whether I can display the value of the property, sunshine
, on the view. I want to point out that declaring the property as public
is a bad practice. I should have used property accessors instead.
Here is the view for HomeComponent
:
<div class="row">
<div class="col-xs-12 col-sm-offset-1 col-sm-10">
<div class="row">
<div class="col-xs-12">
<h3>Home</h3>
<hr>
<p>This is the home. Hello <span style="color: darkgreen">
{{sunshine}}</span>!</p>
</div>
</div>
</div>
</div>
In the above html code, the way property sunshine
was used is {{sunshine}}
. The double curly braces enclosure was Angular's way of binding a model variable from the controller (a.k.a. component class) to the view. Note that such binding is two way. If the value on the view changed via user interaction, the value is also reflected in the controller (the component class) as well. To illustrate this and to show user interaction, I created the other controller called AboutComponent
.
What I was trying to do with this About page is that on this page, there is a button. And there is a counter that was initialized to one. When user clicks on the button, the counter would increment by one. The change in value would be shown on the page.
Here is the code for AboutComponent
:
import { Component } from "@angular/core";
@Component({
templateUrl: 'app/templates/about.tpl.html'
})
export class AboutComponent {
public viewCount: number;
constructor() {
this.viewCount = 1;
}
public incrementViewCount() {
this.viewCount = this.viewCount + 1;
}
}
The source code for AboutComponent
is very similar to the source code for HomeComponent
, in terms of the class definition. This AboutComponent
is just slightly more complex. First, I defined a public
property called viewCount
. Its type is number. Again, I must stress the fact that declaring this property as public
is bad practice, one should use property accessor.
In the constructor, the property viewCount
is set to one. There is also a public
method called incrementViewCount
. This method would increment the viewCount
by one. This method is used by the view, specifically, it is the callback method for the button's click event. Whenever the button is clicked, this method is called and increments the property viewCount
by one.
Here is the source code of the About view which is used by AboutComponent
:
<div class="row">
<div class="col-xs-12 col-sm-offset-1 col-sm-10">
<div class="row">
<div class="col-xs-12">
<h3>About This Site</h3>
<hr>
<p>This is an about page. This page has been viewed {{viewCount}} times.</p>
</div>
<div class="col-xs-12">
<p>
Click <button class="btn btn-success"
(click)="incrementViewCount()">Increment</button> to increment view counts;
</p>
</div>
</div>
</div>
</div>
This is a simple view. It has two parts, the top one has the sentence of "This is an about page. This page has been viewed X times.
" The value X
was displayed by binding with property viewCount
. Model binding is done by: {{viewCount}}
.
The button (named Increment
) has a click event, and is bound with the method incrementViewCount()
from the controller class, AboutComponent
. It is done with this: (click)="incrementViewCount()"
.
Test the Sample Application
Before you can compile and run the sample application, you must go to the src directory of this sample project. And locate the following two files:
- systemjs.config.jstxt
- systemjs-angular-loader.jstxt
Rename these two files to:
- systemjs.config.js
- systemjs-angular-loader.js
The reason I had to rename the two files and rename them back is that the js files are not permitted files going through the email server.
Once files are renamed, you can run the following command to install all the node.js packages:
npm install
Then, to run the sample application, use the following command:
npm start
Summary
That is it. In this tutorial, I have walked through all the important aspects of this simple Angular 4 sample application. My sample is a little more complicated than a simple Hello World sample application. This application has a two-page navigation. On one page, there is a simple binding of a data model in the component to the view. On the other page, there is a simple two way binding of a data model. This sample application provides me a baseline so that I can work out a more complicated application.
There are many parts of this sample project for which I didn't provide explanation. I don't understand some of these parts. And it didn't really matter because I didn't need to modify these parts, not yet anyways. And I believe if I take a better look at the manual on these, I should be able to understand what they do. I believe you can do the same.
There is one thing that really bothered me. I was not able to run this sample application outside tnpm lite server. I believe there is a way. I need to configure webpack and use it as part of the build process. I should be able to package all the necessary JavaScript code into a small package and can be added into a ASP.NET application or a Java WAR file. I don't know how to accomplish this. It is something I will work on later. I think it is a solvable.
Anyways, I hope you've enjoyed this tutorial. Good luck to you learning Angular 4.
History
- 11th September, 2017 - First draft
- 19th January, 2023 - Fixed broken links