How to Create a Basic Single Page Application Using Angular Router?

This tutorial is adapted from Web Age course Introduction to Angular 8 Programming.

In this tutorial, we will develop a very simple single page application (SPA) using the Angular Component Router module. The main goal will be to understand how routing works. We will keep the business logic very simple.

Part 1 – Get Started

1. Open a command prompt window.

2. Create  C:\LabWork folder.

3. Run this command to create a new project called route-test.

ng new route-test --routing --style css

Part 2 – The Business Logic

We will develop an SPA that has three pages (views):

  1. The home page. Shown by default.

  2. The news page. Mapped to the “/news” URL.

  3. The about page. Mapped to the “/about” URL.

After we develop this application we will be able to navigate between the pages without reloading the browser.

Part 3 – Create the Components

Each view in SPA is implemented by a separate component. These components can employ child components if needed. We will now create the components for our applications.

1. Change to the root project.

cd C:\LabWork\route-test

2. Run these commands to create the components.

ng g c home
ng g c about
ng g c news

The shortcut g stands for generate and c for component.

3. Open src/app/home/home.component.html.

4. Change the template like this:

  <h2>Home</h2>
  <p>This is the Home page</p>

5. Save the file.

6. Open src/app/about/about.component.html.

7. Change the template like this:

  <h2>About</h2>
  <p>This is the About page</p>

8. Save the file.

9. Open src/app/news/news.component.html.

10. Change the template like this:

  <h2>News</h2>
  <p>This is the News page</p>

11. Save the file.

The selectors for these components do not really play any role. We never manually add these components to any template. The router system inserts these components for us based on the URL.

Part 4 – Define the Route Table

1. Open route-test/src/app/app-routing.module.ts

2. Add these import statements for the component classes.

import { HomeComponent } from './home/home.component';
import { NewsComponent } from './news/news.component';
import { AboutComponent } from './about/about.component';

3. Set up the route table as shown in bold face below.

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'news', component: NewsComponent },
  { path: 'about', component: AboutComponent }
];

4. Save changes.

Part 5 – Setup the Component Host

1. Edit \src\app\app.component.html and replace all of its contents with the code below:

  <h1>Simple SPA!</h1>
  <a [routerLink]="['/']">Home</a>&nbsp;
  <a [routerLink]="['/news']">News</a>&nbsp;
  <a [routerLink]="['/about']">About</a>

  <div id=main>
    <router-outlet></router-outlet>
  </div>

The App component will act as the root of the application. It will render HTML content that is shared by all pages in the app.

Note these key aspects of the code:

  • We use the routerLink attribute directive to define navigational links. Here we are using links like “/news” and “/about”. We will later map these to the corresponding components in a route table.

  • Use <router-outlet> tag to define where the component for the pages should be inserted.

2. Save changes.

3. Edit \src\styles.css

4. Add the following content and save the file:

h2 {
    margin:0px;
}
#main{
    border: 1px solid grey;
    width: 250px;
    background-color: lightgray;
    margin-top: 5px;
    padding: 5px;
}

5. Save changes.

Part 6 – Test

1. Open a command prompt and go to the root folder of the project:

C:\LabWork\route-test

2. Run the following command to compile TypeScript and start the server:

npm start

3. Open a browser to http://localhost:4200

4. Verify that the home page is shown by default.

5. Click the News and About links. Make sure that the corresponding pages are shown. Verify that the URL in the browser’s address bar changes as you navigate.

6. Verify that the back button navigation works as expected.

7. Navigate to the About page.

8. Now refresh the browser. You should still see the About page.

Note: This behavior is primarily because the project uses the embedded server and all requests get routed to Angular. If you were using a web server that might first look for a local HTML file, refreshing the URL from the Router may not work and you may get a 404 error.

9. In the command prompt, hit ‘<CTRL>-C‘ to terminate the embedded server.

10. Close all open files.

Part 7 – Review

In this tutorial, we created a single page application with routing.

Security in Angular Applications

This tutorial is adapted from Web Age course Advanced Angular 8 Programming.

1.1 What is Authentication and Authorization?

Authentication is the process of reliably identifying a user. Authorization is the process of giving a user access to only the resources she is allowed to access. Some common programming tasks that need authenication and authorization are: Provide a login page where user enters her credentials, upon a successful login the server issues a token identifying the user-the browser side saves that token in memory or local storage, Limit access to application functions based on user privilege-this is usually done using role based security, With every HTTP call to the backend include the user identity token;depending on the technology used this is usually done as a cookie or HTTP header, If the user accesses a protected resource (such as an Angular route) and the identity token does not exist or has expired then redirect to the login page, If a HTTP call returns 403 or similar then redirect to the login page.

1.2 What is Identity Token?

Two most common ways to create an identity token are SAML2 and JWT. In the end they both achieve the same goals. An identity token issued by the server contains user’s unique ID, name, e-mail address, various security roles and any other kind of business specific payload. The token is signed by the server. It is very difficult to generate a fake token by a third party. When a client sends a token with an HTTP request the server can reliably ensure that the token was issued by it and has not been tampered with. A token is however vulnerable to stealing via man in the middle attack. The server can not discern if a token was stolen. To prevent against that always transfer tokens over HTTPS and set a token to expire every few hours. SAML2 is widely used in the enterprise. But JWT is a more modern standard and much easier to use. In this tutorial we will use JWT in the examples but the basic approach will be same for SAML2.

1.3 How to exchange the Identity Token?

Upon a successful login the server will issue the identity token. The client needs to send this back in future with every HTTP request. Exactly how the server and client do this depends on various things. Here are two common scenarios:

  • Cookie based – The server expects the token to be in a cookie. In which case the server sets the cookie and the browser automatically sends it back with every request.

  • Header based – In JWT the client usually sends the token back in the “Authorization” request header. In that case the server issues the token in the response body of the login request. The client needs to write code to save that token and set the Authorization header for all future requests.

//Example response from server containing a JWT token
{
  token: "abc-123"
}

//Example Authorization request header
Authorization: Bearer abc-123

1.4 Example Login Code in Angular Service

Saves identity token in session storage

@Injectable(...)
export class AuthenticationService {
  constructor(private http:HttpClient) { }

  login(credentials:Object) : Observable<Object> {
    return this.http.post<Object>("/login", credentials)
      .pipe(tap(response => 
          window.sessionStorage.setItem("auth-token", 
          response["token"])))
  }
  getAuthToken() : string {
    return window.sessionStorage.getItem("auth-token")
  }
}

Notes

The example above assumes that the identity token is available from the response of the “/login” call as follows:

{ token: “eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.0gyt4I1ZFGzzDZBrK9jwOIH4NSWvs0k4snp-ZcwKLVU” }

1.5 What are the Options for Saving the Identity Token?

You can save the token in a service class member variable. Because Angular apps are single page the service instance is preserved as the user navigates between views. In session storage: This is per server domain and per tab. The storage is cleared when the tab is closed. In local storage. This is per domain and survives a browser restart.

1.6 How to send the Token to Server?

You can develop an interceptor to set the Authorization header for every backend call.

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authSvc: AuthenticationService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.authSvc.getAuthToken() === null) {
      return next.handle(req)
    }

    req = req.clone({
      setHeaders: { "Authorization": `Bearer ${this.authSvc.getAuthToken()}` }
    })

    return next.handle(req)
  }
}

1.7 How to Obtain User Information?

JWT allows the server to encode user information such as full name, profile photo URL and user roles in the identity token. This information is called payload of the token. You can decode the token to obtain the payload. Use the jwt-decode package to do this.

npm install jwt-decode --save

npm install @types/jwt-decode –save-dev
  • Example code.

import * as decode from 'jwt-decode'

export class UserProfile {
  photoURL:string
  name:string
  roles:string[]
}

let jwtToken = "ABC1728...2xcv"
let userProfile = decode<UserProfile>(jwtToken)

1.8 What is Cross Site Scripting (XSS) Attack?

This attack is mounted by a user by somehow embedding a <script> tag in a page. This can be done for example by submitting a comment or writing a user profile that contains the <script> tag. When another user visits the infected page, the script can then collect private information about that user from the DOM such as cookie, names and emails. This information then can be passed to a third party site using a mechanism that bypasses the same origin policy. Such as setting the src attribute of an image or adding a <script> tag with source from another site. XSS is one of most common forms of security breaches on the web. To prevent XSS reject any HTML content from untrusted sources. For example, you can severely limit or completely eliminate any HTML in user comments. But allow a company staff to allow entering HTML in product catalog content.

1.9 Angular and XSS

By default Angular considers values stored in any component variable as untrusted. But any HTML directly entered in the template is trusted. In this example, the <div> and <b> tags in the template are trusted. But the <h1> tag in the comment variable is not trusted. Angular will sanitize variables (escape the HTML) before setting DOM element properties.

@Component({...
  template: `<div><b>Comment:</b> {{comment}}</div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
}

//Generates this DOM element
<div><b>Comment:</b> &lt;h1&gt;Hello World&lt;/h1&gt;</div>

1.10 How to Sanitize Content?

Angular uses different strategies to sanitize the value in a variable depending on where in the template the variable is used:

  • HTML – When the variable is used to set the innerHTML of an element. We see this in the example in the previous slide.

  • Style – When you bind the variable to the “style” property of an element.

  • URL – When you bind the variable to the “href” property.

  • Resource URL – When you bind the variable to the “src” attribute of an image or script.

A different strategy is used in these cases because an XSS attack is mounted differently in each of these situations.

1.11 How to Trust HTML Content?

To bypass Angular’s sanitization for trusted content take these steps:

  • Convert a string to SafeHtml by calling the bypassSecurityTrustHtml() method of DomSanitizer service.

  • Bind the SafeHtml object to the innerHTML property of an element.

  • The same example as before converted to bypass sanitization.

import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({...,
  template: `<div><b>Comment:</b> <span  [innerHTML]="getSafeComment()"></span></div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
  constructor(private sanitizer: DomSanitizer){}
  getSafeComment() : SafeHtml {
    return this
      .sanitizer
      .bypassSecurityTrustHtml(this.comment)
  }
}

1.12 What is Cross-Site Request Forgery (XSRF)?

In this attack a page from a malicious site (evil.com) somehow sends a request to a legitimate site (good.com). Such a request needs to bypass the same origin policy which is quite easy. For example, request an image from good.com or submit a form using JavaScript. If the user is currently logged into the legitimate site then such a request can cause all kinds of unintended consequence. The damage level increases with the level of privilege the user has with the legitimate site. XSRF attack is technically very easy to mount. The only challenge is in how to lure users into the malicious site. This is usually done through phishing attack. Details of XSRF and strategies to mitigate it are beyond the scope of this class. Read about it in www.owasp.org.

1.13 Preventing XSRF Using Angular

One of the strategies used to prevent XSRF is to set a custom request header from the client and verify its existence in the server side. Currently there is no way for a malicious web page to set custom headers prior to requesting an image or submitting a form to the legitimate web site.

Angular’s HttpClient service has some support for this. To use this, take these steps:

  • The server must first issue a unique token using the XSRF-TOKEN cookie. set-cookie: XSRF-TOKEN=example-xsrf-token;Path=/

  • For all future POST, PUT, DELETE requests HttpClient will set the X-XSRF-TOKEN custom header. X-XSRF-TOKEN: example-xsrf-token

  • From the server side verify that X-XSRF-TOKEN custom header is set.

  • In addition, take all other mitigation steps outlined in OWASP web site. Like verify the Referer request header to make sure request is coming from a page served by the legitimate web site.

1.14 Summary

In this tutorial we covered:

  • Upon a successful authentication the server should issue an identity token. Angular app should save the token in memory, local or session storage depending on the business requirement.

  • To make any secure backend call the application needs to send that token back. Server validates the token to make sure that it was not made up by a third party or has not expired.

  • You can obtain user information such as name and email by decoding the token.

  • Angular provides ways to mitigate Cross Site Scripting and Cross Site Request Forgery attacks.

 

Debugging Angular Applications

In this tutorial you will get hands-on experience with:

  • Template parse errors

  • Typescript code errors

  • Accessing components at runtime with ng.probe()

  • Breakpointing in Typescript code

Before you start working on this tutorial, install the required software as mentioned in the Set Up Guide here.

Part 1 – Get Started

1.Create C:\LabWork folder in your machine.

2. Download rest-client and rest-server zip files and extract each file  to C:\LabWork folder on your machine

3. From C:\LabWork\rest-client start the development server.

npm start

4. From C:\LabFiles\rest-server folder start the backend web service server.

npm start

5. Open a browser and enter the URL: http://localhost:4200 and make sure you can use the app normally.

6. Open the browser developer tool (F12) and the Console pane. We will use the console a lot.

Part 2 – Simulate Typescript Compile Errors

1. Open C:/LabWork/rest-client/src/app/edit-book/edit-book.component.ts.

2. In the EditBookComponent class add a variable with obvious compilation error.

badVar:number = "hello";

You can add this var before the “constructor()” line.

This statement tries to assign a string to a number type variable. It should produce a TypeScript compile error.

3. Save changes.

4. Compile problems are reported in the development server console. You should see this message:

 

 

5. Also verify that the browser was not updated. The development server does not push code if build fails.

6. Remove the newly added variable and save changes and make sure that there are no more compile problems.

Part 3 – Simulate Template Errors

Syntax errors in the template are not reported by the dev server. They are discovered at runtime and reported in the browser console.

1. Open book-list/book-list.component.html.

2. Change the first *ngIf to *if (line 6).

3. Save.

4. Notice that the dev server compiles the code just fine.

5. Back in the browser you will see a blank page. The console should show the actual error.

Important

During development always watch the dev server output as well as the browser console. Problems can be reported in either of these places.

6. Fix the problem (change if back to ngIf). Make sure that the app is working properly.

Part 4 – Putting a Break Point

Learning how to put a break point in Typescript code is an essential skill. This will help you investigate difficult problems using the debugger tool.

1. Open book-list/book-list.component.ts in an editor. We will put a break point in the deleteBook() method.

2. In the browser’s developer tool, click the Sources tab.

 

3. To search for a file hit Control+P (Cmd+P in Mac). Start typing book-list.

4. Pick book-list.component.ts.

5. Put a break point in the first line of deleteBook() method. (line numbers in your code may differ from those in the screenshot below)

 

6. In the application, click the DELETE button for a book. You should now halt at the break point.

7. In the variables pane under local Scope you should be able to inspect the book variable. The this variable points to the instance of BookListComponent.

 

8. Hit the resume   button to continue. Click OK or Cancel.

Part 5 – Use ng.probe

This is an easy way to inspect the internal state of a component without using a debugger. If you have many components on a page you can precisely select the one you wish to inspect.

1. In the application, click the EDIT button for a book. You will now be routed to the EditBookComponent. We will inspect its internal state.

2. In the browser’s developer tool click the Elements tab.

3. Click the Select button.

4. Then click on one of the form input boxes. You can click anywhere within the boundary of the component.

5. Switch back the Console tab in the developer tool.

6. Type this and hit enter:

ng.probe($0).componentInstance

7. You should now be able to expand the instance of EditBookComponent and look at its internal state (such as the book variable).

8. In both command prompt windows, hit Control+C .

9. Close all.

Part 6 – Review

In the tutorial, we learned a few common techniques to investigate errors in Angular applications.