Tour of Heroes in Production with HashLocationStrategy

I decided it was time to learn Angular 2 so I figured I would start with the Tour of Heroes turotial. While Angular 2 is a drastic change from Angular 1 it is still familiar enough as not to be completely foreign. I recommend skipping the quickstart package and starting with Angular CLI. If you are not familiar with Webpack then Angular CLI is the easiest way to get your app production ready.

When building doing the tutorial I installed with Angular CLI and created a project.

ng create angular-tour-of-heroes cd angular-tour-of-heroes ng serve

Then open a browser and navigate to localhost:4200 and you will see the default application. Find the app directory you'll want to get rid of the default files and start following the tutorial at this point. I followed the complete tutorial including the setup. Since I completed the tutorial I used Angular CLI to build a project and I simply replaced the app directory of the new project with the one from the tutorial. You can use either of these methods. The rest of this tutorial assumes the app directory is located in the project created by Angular CLI.

Once I completed the tutorial I wanted to package it to run in production. The first thing you will want to do switch from the mock web service to real data. I did this be dropping a simple PHP file on a local LAMP server.

<?php

header("Access-Control-Allow-Origin: *"); header('Content-Type: application/json');

$heroes = [ ['id'=> 11, 'name'=>'Mr. Nice'], ['id'=> 12, 'name'=>'Narco'], ['id'=> 13, 'name'=>'Bombasto'], ['id'=> 14, 'name'=>'Celeritas'], ['id'=> 15, 'name'=>'Magneta'], ['id'=> 16, 'name'=>'RubberMan'], ['id'=> 17, 'name'=>'Dynama'], ['id'=> 18, 'name'=>'Dr IQ'], ['id'=> 19, 'name'=>'Magma'], ['id'=> 20, 'name'=>'Tornado'] ];

//Select a single hero by id if(!empty($_GET['id'])){ $row = array_search($_GET['id'], array_column($heroes, 'id')); $heroes = $heroes[$row]; }

//Search for heros by name if(!empty($_GET['name'])){ $h=array(); foreach ($heroes as $key => $value) {

    if (stripos($value\['name'\], $\_GET\['name'\])!==false) {
    $h\[\] = $heroes\[$key\];
    }

}
$heroes = $h;

}

echo json_encode($heroes);

In hero.service.ts change private heroesUrl = 'api/heroes'; to private heroesUrl = 'http://localhost/api/heroes/index.php';. Then change .then(response => response.json().data as Hero) to .then(response => response.json() as Hero). .json().data is called in several places throughout the project. I don't know why but this casues a faliure when not using the mock http connection. Remove .data from any code that does not work.

In hero-search.service.ts change .get(`app/heroes/?name=${term}`) to .get(`http://localhost/api/heroes/index.php?name=${term}`).

Now you will want to remove the following lines from app.module.ts.

import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryDataService } from './in-memory-data.service';

InMemoryWebApiModule.forRoot(InMemoryDataService),

Once I completed the tutorial I wanted to package it to run in production. The Angular CLI makes this sure easy with ng build.

ng build --prod

Since I'm normally running on a LAMP stack I have an Apache server up and running locally, this is where I decided to test my production package. For this I opted to build /var/www

ng build --prod --output-path=/var/www/prod

The package will now build on the path /var/www/prod this means I can open a browser to localhost/prod and see the application running. While this served an index page it only showed a loading message. The first thing to do is to fix the base tag. Open index.htm in an editor and <base href="/"> to <base href=".">. Now if you rerfresh the page everything shouldwork as it did on the dev server.

In the development environment all of the URLs worked as expected if I started navigating from the root directory. Deep links, however, did not work. This is because, by default, your web server will try to resolve to the specified directory. You could configure your web server to understand your base path but that means you'll need to have that level of server access. If you do not, then you can use hashtags (this is the default in Angular 1). By adding the following lines I was able to get the hash tags up and running.

import {LocationStrategy, HashLocationStrategy} from '@angular/common';

providers: [ HeroService, {provide: LocationStrategy, useClass: HashLocationStrategy}],

My final app.module.ts file looks like this.

import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard.component'; import { HeroesComponent } from './heroes.component'; import { HeroSearchComponent } from './hero-search.component'; import { HeroDetailComponent } from './hero-detail.component'; import { HeroService } from './hero.service'; import {LocationStrategy, HashLocationStrategy} from '@angular/common';

@NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, AppRoutingModule ], declarations: [ AppComponent, DashboardComponent, HeroDetailComponent, HeroesComponent, HeroSearchComponent ], providers: [ HeroService, {provide: LocationStrategy, useClass: HashLocationStrategy}], bootstrap: [ AppComponent ] }) export class AppModule {}

Now if you rebuild the project deep links will work.