25.10.2016 01:41:00
In this lab, you will start with an AngularJS 1.x project that has been partly refactored to use components. Most of it has also been ported to TypeScript and webpack has been introduced as a bundling solution.
Now, your task is refactoring the rest of this solution.
Before you start
This is a part of a tutorial Ive organized for ngEurope 2016 in Paris. Make sure that you have complete the other parts before you start this part. Please find the content overview here.
Getting started
- To prevent annoying caching problems, install the Chrome plugin
Clear Cache .
- If you dont manage to install
Clear Cache make sure to disable caching in the network tab within the dev tools (F12) in Chrome.
- Open the root folder of the project in Visual Studio Code. The root folder is the folder that contains the file
tsconfig.json .
-
Compile the project with CTRL+SHIFT+b (b like build) to make sure that there are no TypeScript errors.
- When Visual Studio asks you to create a build task, choose the option TypeScript.
-
If and only if, Visual Studio Code creates a task.json after that update it with the following snippet:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "npm",
"isShellCommand": true,
"args": ["run", "tsc"],
"showOutput": "silent",
"problemMatcher": "$tsc"
}
-
Switch to the console (CTRL+SHIFT+c) and start the project:
npm start
After the web server started, navigate to http://localhost:8080 and use the application to search for flights from Hamburg (in Germany) to Graz (in Austria). Select one of the found flights.
Inspecting the project
- Open the project in Visual Studio Code and have a look at the file
tsconfig.json . Assure yourself that it configures the TypeScript compiler to compile TypeScript code down to EcmaScript 5 and CommonJS modules.
- Have a look to the configured entry points within the file
webpack.config.js .
- Open the file
app.module.ts and find out, that this file contains the main module of the app.
- Open the files
flight-search.js and flight-search.html in the folder app/flight-search . Assure yourself that these files contain a classic AngularJS 1.x controller as well as a corresponding template.
- Have a look at the file
app.routes.ts and examine that it defines the routes for the application. Also contemplate the route for the FlightSearchController to find out that it uses controllerAs .
- The application has to define the
FlightSearchController before it is used within the routing configuration. In all other cases, webpack assures this by following the references between the TypeScript files. Those references are defined by ES6 import statements. Since the FlightSearchController is written in a traditional way the application has to assure this manually. For this purpose the app.ts , which is the entry point of the app bundle, references the files app.module.ts , flight-search.ts and app.routes.ts in the right order. Assure yourself of this fact.
Refactoring flight-search.js to TypeScript
- Rename the file
flight-search.js to flight-search.ts
- Remove the IIFE expression at the begin and at the end of the file. This expression isnt needed anymore because ES6 / TypeScript creates a module with an own namespace for each file.
- Add an
export statement in front of the declaration of the function to make the function visible in other modules.
- Remove the registration of the controller. Later, you will reintroduce it within the file
app.module.ts .
-
Make sure that your file looks like in the following listing:
// Remove IIFE expression:
// (function() {
// Add export statement:
export function FlightSearchController(flightService, bookingEventService) {
[...]
}
// Remove the registration for the controller:
// angular.module(flight-app)
// .controller(FlightSearchController, FlightSearchController);
// Remove IIFE expression:
// })();
-
Open the file app.module.ts and import the FlightSearchController . Register this controller with the module:
import { FlightSearchController } from ./flight-search/flight-search;
[...]
var app = angular.module(flight-app, [ngMessages, ui.router, tabs]);
[...]
app.controller(FlightSearchController, FlightSearchController);
-
Please note that the import statement introduced in the app.module.ts also makes sure that webpack bundles up the files in the right order. Therefore, it is not necessary to reference it within the file app.ts :
require("./app.module");
// Remove reference to flight-search.ts
// require("./flight-search/flight-search");
require("./app.routes");
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Refactoring FlightSearchController to a class
-
Refactor the constructor function FlightSearchController to a class. Try to do this without the following snippet and just use it when you get stuck.
import {FlightService} from "../services/flight.service";
import {BookingEventService} from "../services/booking-event.service";
import {Flight} from "../shared/flight";
export class FlightSearchController {
public from: string = Hamburg;
public to: string = Graz;
public selectedFlight: Flight = null;
private flightService: FlightService;
private bookingEventService: BookingEventService;
constructor(flightService: FlightService, bookingEventService: BookingEventService) {
this.flightService = flightService;
this.bookingEventService = bookingEventService;
}
getFlights() {
return this.flightService.flights;
}
search() {
return this
.flightService
.find(this.from, this.to)
.catch(function (resp) {
console.debug(resp);
});
}
select(f: Flight) {
this.selectedFlight = f;
this.bookingEventService.publish(f);
}
}
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Refactoring FlightSearchController to a component
-
Add an exported constant FlightSearchComponent at the end of the file flight-search.ts which provides the metadata for a component using the FlightSearchController as well as its template. In addition to that, remove the export statement from the declaration of the FlightSearchController as this controller isnt needed outside of the current file anymore:
import {FlightService} from "../services/flight.service";
import {BookingEventService} from "../services/booking-event.service";
import {Flight} from "../shared/flight";
// Remove the export statement
// export class FlightSearchController {
class FlightSearchController {
[...]
}
export const FlightSearchComponent: angular.IComponentOptions = {
controller: FlightSearchController,
templateUrl: ./flight-search.html
}
-
Switch to the file app.module.ts and import the FlightSearchComponent instead of the FlightSearchController . Also replace the registration for the FlightSearchController with a registration for the FlightSearchComponent . Make sure to use the name flightSearch for this component and note that the corresponding tag name is flight-search :
// Remove import for FlightSearchController
// import { FlightSearchController } from ./flight-search/flight-search;
// Add import for FlightSearchComponent
import { FlightSearchComponent } from ./flight-search/flight-search;
[...]
var app = angular.module(flight-app, [ngMessages, ui.router, tabs]);
[...]
// Remove registration for FlightSearchController
// app.controller(FlightSearchController, FlightSearchController);
// Add registration for FlightSearchComponent
app.component(flightSearch, FlightSearchComponent);
-
Open the file app.routes.ts and update the route configuration for the FlightSearchComponent :
[...]
.state(flightBooking.flightSearch, {
url: /flight,
template: <flight-search></flight-search>
})
[...]
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Rename files to align with Angular 2 conventions
- Rename the file
flight-search.ts to flight-search.component.ts to align with typical conventions in Angular 2.
- For the same reason, rename the file
flight-search.html to flight-search.component.html . Dont forget to update the reference to this file within the component metadata in the file flight-search.component.ts .
- Update references to these files within the files
flight-search.component.ts and app.module.ts .
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Refactor the FlightCardDirective to a component
- Open the file
flight-card.directive.ts in the folder app/flight-search and assure yourself that it defines the factory function createFlightCardDirective that creates a directive description object (DDO) for the FlightCardDirective.
- Try to find out how the directive in question works.
- Also examine that the directive uses both,
controllerAs as well as bindToController .
- Then have a look at the corresponding template in the file
flight-card.directive.html .
-
Replace the function createFlightCardDirective in the file flight-card.directive.ts with an exported metadata object for a FlightCardComponent :
export const FlightCardComponent: angular.IComponentOptions = {
controller: FlightCardController,
templateUrl: ./flight-card.directive.html,
transclude: true,
bindings: {
item: =,
selectedItem: =
}
}
-
Open the file app.module.ts and replace the registration of the FlightCardDirective with a registration of FlightCardComponent :
[...]
// Remove registration for the directive flightCard
// app.directive(flightCard, createFlightCardDirective);
// Add registration for the FlightCardComponent
app.component(flightCard, FlightCardComponent);
[...]
var app = angular.module(flight-app, [ngMessages, ui.router, tabs]);
[...]
// Remove registration for the directive flightCard
// app.directive(flightCard, createFlightCardDirective);
// Add registration for the FlightCardComponent
app.component(flightCard, FlightCardComponent);
[...]
Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Rename files to align with Angular 2 conventions
- Rename the file
flight-card.directive.ts to flight-card.component.ts to align with typical conventions in Angular 2.
- For the same reason, rename the file
flight-card.directive.html to flight-card.component.html . Dont forget to update the reference to this file within the component metadata in the file flight-card.component.ts .
- Update references to these files within the files
flight-card.component.ts and app.module.ts .
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Switch to unidirectional data flow
-
Update the FlightCardController in the file flight-card.component.ts with a property selectedItemChange that can point to an event handler. Call this event handler at the end of select :
class FlightCardController {
item: Flight;
selectedItem: Flight;
selectedItemChange: Function;
select() {
this.selectedItem = this.item;
if (this.selectedItemChange) {
this.selectedItemChange(this.selectedItem);
}
}
}
-
Update the bindings within the FlightCardComponent to use unidirectional property bindings and an event handler:
export const FlightCardComponent: angular.IComponentOptions = {
controller: FlightCardController,
templateUrl: ./flight-card.component.html,
transclude: true,
bindings: {
item: <,
selectedItem: <,
selectedItemChange: &
}
}
- Switch to the file
flight-search.component.html and update the usage of flight-card by setting up an event-handler.
<flight-card
item="f"
selected-item="$ctrl.selectedFlight"
selected-item-change="$ctrl.selectedFlight = $locals">
- Compile the project to make sure that there are no TypeScript errors.
- Refresh the project in your browser and ensure that the refactored project still works.
Life Cycle Hooks
- Look up the presented slides regarding life cycle hooks.
- Find out how to create a life cycle hook for
$onInit and $onChanges .
- Create those live cycle hooks within the
FlightCardComponent and ensure yourself that AngularJS 1.x calls them.
|