Skip to content

Instantly share code, notes, and snippets.

@JaimeStill
Last active July 19, 2018 11:42
Show Gist options
  • Save JaimeStill/4abc034749790c7e492f181db531e2d8 to your computer and use it in GitHub Desktop.
Save JaimeStill/4abc034749790c7e492f181db531e2d8 to your computer and use it in GitHub Desktop.
MVA Introduction to Angular

The primary concern with the demos in these notes is learning Angular. Though there is some styling in the examples, it is minimal and not really the point of the material being covered.

Contents

Links

Tools

References

Back to Top

Type Annotations

Variables

var num: number = 5;
var name: string = "Still";
var something: any = 123;
var list: Array<number> = [1,2,3];
var jumbledList: Array<any> = [1, "something", name];

JavaScript Equivalent

var num = 5;
var name = "Still";
var something = 123;
var list = [1,2,3];

Functions

function square(num: number): number {
    return num * num;
}

JavaScript Equivalent

function square(num) {
    return num * num;
}

Square receives a number in (num: number) and returns a number in : number {.

Classes

class Person {
    constructor(public name: string) {
        
    }
}

var person = new Person("Some Person");

JavaScript Equivalent

var Person = (function () {
    function Person(name) {
        this.name = name;
    }
    return Person;    
}());

Back to Top

Test out TypeScript in the browser at TypeScript Playground

TypeScript

class Demo {
    constructor(public id: number, public name: string, public scores: Array<number>) {
        this.Id = id;
        this.Name = name;
        this.Scores = scores;
    }

    public Id: number;
    public Name: string;
    public Scores: Array<number>;

    public WriteToDocument = function () {
        let pId = document.createElement('p');
        let pName = document.createElement('p');

        pId.innerText = "Id: " + this.Id.toString();
        pName.innerText = "Name: " + this.Name;

        document.body.appendChild(pId);
        document.body.appendChild(pName);

        let headerScore = document.createElement('h3');
        headerScore.innerText = "Scores";

        document.body.appendChild(headerScore);

        for (let score of this.Scores) {
            let pScore = document.createElement('p');
            pScore.innerText = score;
            document.body.appendChild(pScore);
        }

        document.body.appendChild(document.createElement('hr'));
    }
}

var d1: Demo = new Demo(1, "Jaime", [91, 94, 96, 87]);
d1.WriteToDocument();

JavaScript Equivalent

var Demo = (function () {
    function Demo(id, name, scores) {
        this.id = id;
        this.name = name;
        this.scores = scores;
        this.WriteToDocument = function () {
            var pId = document.createElement('p');
            var pName = document.createElement('p');
            pId.innerText = "Id: " + this.Id.toString();
            pName.innerText = "Name: " + this.Name;
            document.body.appendChild(pId);
            document.body.appendChild(pName);
            var headerScore = document.createElement('h3');
            headerScore.innerText = "Scores";
            document.body.appendChild(headerScore);
            for (var _i = 0, _a = this.Scores; _i < _a.length; _i++) {
                var score = _a[_i];
                var pScore = document.createElement('p');
                pScore.innerText = score;
                document.body.appendChild(pScore);
            }
            document.body.appendChild(document.createElement('hr'));
        };
        this.Id = id;
        this.Name = name;
        this.Scores = scores;
    }
    return Demo;
}());
var d1 = new Demo(1, "Jaime", [91, 94, 96, 87]);
d1.WriteToDocument();

Result
ts-demo

Back to Top

Installation is according to the Angular Setup Guide. Make sure that Git is installed and you are running the commands from the Git CMD window.

Scaffold an Angular app into the specified project directory, delete non-essential files, install dependent node modules, and open in Visual Studio Code

git clone https://github.com/angular/quickstart.git <project-dir>
cd <project-dir>
for /f %i in (non-essential-files.txt) do del %i /F /S /Q
npm install
code .

Back to Top

File Purpose
src/app/... Angular application files go here
e2e/... End-to-End tests for app, written in Jasmine and run by protractor e2e test runner
node_modules npm packages installed with the npm install command
.editorconfig Tooling configuration file. Ignore until you have a compelling reason to do otherwise
src/index.html The app host page. Loads a few essential scripts in a prescribed order. Then, boots the application, placing the root AppComponent in the custom <my-app> body tag
src/styles.css Global app styles. Initialized with an <h1> style for the QuickStart demo
src/systemjs.config.js Tells the SystemJS module loader where to find modules referenced in JavaScript import statements such as import { Component } from '@angular/core';. Do not modify unless fully versed in SystemJS configuration
src/systemjs.config.extras.js Optional extra SystemJS configuration. A way to add SystemJS mappings, such as for application barrels, without changing the original system.config.js
src/tsconfig.json Tells the TypeScript compiler how to transpile TypeScript source files into JavaScript files that run in all modern browsers
tslint.json The npm installed TypeScript linter inspects TypeScript code and complains when you violate one of its rules. The file defines linting rules favored by the Angular style guide and by the authors of the Angular documentation
bs-config.json Configuration file for lite-server. This is a lightweight, development only node server that serves a web app, opens it in the browser, refreshes when html or javascript change, injects CSS changes using sockets, and has a fallback page when a route is not found

Initial Project Configuration

Back to Top

Apart from the initial configuration files and dependencies listed above, the following setup is needed to get the project running.

The index.html file needs to be configured to load the appropriate scripts and provide an entry point for Angular via the component specified in the bootstrap property of the angular module (in this case, <my-app>)

src/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Angular QuickStart</title>
    <base href="/">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('main.js').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading AppComponent content here ...</my-app>
  </body>
</html>

A main.ts file is needed to bootstrap the main app module

See Bootstrapping in main.ts for a deep dive on module bootstrapping.

src/main.ts

import { platformBrowserDynamic } from 'angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

app.module.ts defines the root module for the application. It describes how the application parts fit together. Every application needs at least one Angular module, the root module that you bootstrap to launch the application.

Notice here the bootstrap property specifies AppComponent (defined next)

src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

app.component.ts (see bootstrap property for NgModule) is the main application view, called the root component, that hosts all other app views. Only the root module should set the bootstrap property.

src/app/app.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `<h1>Hello {{name}}</h1>`
})
export class AppComponent { name = 'Angular'; }

Clean Transpiled JavaScript

Back to Top

To de-clutter the transpiled .js and .js.map files from the project, install the gulp and gulp-clean modules as devDependencies from the terminal

npm install gulp --save-dev
npm install gulp-clean --save-dev

In the root of the project, create gulpfile.js

gulpfile.js

var
    gulp = require('gulp'),
    clean = require('gulp-clean');

gulp.task('clean', function () {
    return gulp.src(['src/app/*.js', 'src/app/*.js.map', 'src/main.js', 'src/main.js.map'])
    .pipe(clean({force: true}));
});

Optionally, you can setup the clean task as an npm script in package.json

package.json

"scripts": {
    "clean": "gulp clean"
}

Any time after running the application that you want to remove the transpiled files, you can use either of the below commands

npm run clean
gulp clean

Back to Top

Directives

  • Component - Templates (HTML), Styles (CSS), & Logic (TypeScript / JavaScript)
  • Attribute - Styling HTML
  • Structural - Manipulating HTML

Data Flow

  • Interpolation - Variable printing in Templates
  • Event Binding - Trigger Events
  • 2-Way Binding - Variables updated in real time

Providers

  • Services
    • Reusable Logic
    • Data Storing and Manipulation
  • Libraries

Back to Top

Component Directives are reusable building blocks for an application.

Component

  • HTML
  • CSS
  • TypeScript / JavaScript

Basic Example

@Component({
    selector: 'demo-block',
    template: '<h3>Demo Block Component</h3>',
    styles: ['h3 { color: gray; }']
})
export class DemoBlockComponent {
    console.log("Hello Angular");
}
<demo-block></demo-block>

Using External HTML & CSS Assets

@Component({
    selector: 'demo-block',
    templateUrl: 'demo-block.component.html',
    styleUrls: ['demo-block.component.css']
})
export class DemoBlockComponent {
    console.log("Hello Angular")
}
<demo-block></demo-block>

Back to Top

For non-root components, the moduleId property must be specified in the dependency injection metadata

src/app/tasks.component.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'tasks',
    templateUrl: 'tasks.component.html'
})
export class TasksComponent { }

src/app/tasks.component.html

<h4>This is the Tasks Component</h4>

After the task is created, it then needs to be registered with the module that hosts the component

app.module.ts

import { TasksComponent } from './tasks.component';
// additional imports

@NgModule({
    declarations: [ AppComponent, TasksComponent],
    // Additional injection metadata
})
export class AppModule { }

At this point, the component can then be used in other components that have access to the module it is defined in

app.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        <h1>Hello {{name}}</h1>
        <tasks></tasks>
    `
})
export class AppComponent { name = 'Angular'; }

Back to Top

Attribute Directives can change the appearance or behavior of an element

Modify tasks.component.ts to include a stylesheet reference and a toggle property in the class definition

tasks.component.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'tasks',
    templateUrl: 'tasks.component.html',
    styleUrls: [ 'tasks.component.css' ]
})
export class TasksComponent {
    toggle: boolean = true;
}

Modify tasks.component.html to use the following built-in attribute directives:

  • [class]
  • [ngClass]

tasks.component.html

<p [class.red]="toggle">[class.red]="toggle" syntax</p>
<p [ngClass]="{ red: !toggle, blue: toggle }">[ngClass]="&#123; red: !toggle, blue: toggle &#125;" syntax</p>

Add the css class that defines the styles for the component

tasks.component.css

p {
  font-family: Arial, Helvetica, sans-serif;
}

.red {
  color: red;
}

.blue {
  color: blue;
}

Run the app and see how the component renders with attribute directives on these two elements

ng-attr-directives

See Attribute Directives for details on building your own

Back to Top

Structural Directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.

Three Common Structural Directives

Directive Purpose
NgIf You can add or remove an element from the DOM by applying an NgIf directive to that element. Bind the directive to an expression that returns a boolean value. When the expression is truthy, the element will be added to the dom. When the expression is falsy, the element is removed from the DOM, destroying that component and all of its sub-components.
NgFor NgFor is a repeater directive - a way to present a list of items. You define a block of HTML that defines how a single item should be displayed. You tell Angular to use that block as a template for rendering each item in the list.
NgSwitch NgSwitch is like the JavaScript switch statement. It can display one element from among several possible elements, based on a switch condition. Angular puts only the selected element into the DOM. NgSwitch is actually a set of three, cooperating directives: NgSwitch, NgSwitchCase, and NgSwitchDefault.

Example

<div *ngIf="hero">{{hero.name}}</div>

<ul>
    <li *ngFor="let herof of heroes">{{hero.name}}</li>
</ul>

<div [ngSwitch="hero?.emotion">
    <happy-hero *ngSwitchCase="'happy'" [hero]="hero"></happy-hero>
    <sad-hero *ngSwitchCase="'sad'" [hero]="hero"></sad-hero>
    <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
    <unknown-hero *ngSwitchDefault [hero]="hero"></unknown-hero>
</div>

When you see a directive spelled in both PascalCase and camelCase, the PascalCase refers to the directive class, and the camelCase refers to the directive's attribute name.

In the next section, there are some setup steps and features added that go beyond the scope of this section. The focus here is on the structural directives, not things like adding module references or setting up click events.

Implementing Structural Directives

Back to Top

Import the Angular FormsModule in app.module.ts and add it to the imports injection metadata array

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TasksComponent } from './tasks.component';
import { FormsModule } from '@angular/forms';

@NgModule({
    imports: [ BrowserModule, FormsModule ],
    declarations: [ AppComponent, TasksComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Update tasks.component.css for elements used to add structure to the component layout

tasks.component.css

p, h3 {
  font-family: Arial, Helvetica, sans-serif;
}

.red {
  color: red;
}

.blue {
  color: blue;
}

hr.gradient {
  border: 0;
  height: 1px;
  background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
}

In tasks.components.ts, add an Array<string> property named tasks, an Array<string> property named categories, and a string property named category

tasks.components.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'tasks',
    templateUrl: 'tasks.component.html',
    styleUrls: [ 'tasks.component.css' ]
})
export class TasksComponent {
    toggle: boolean = true;
    tasks: Array<string> = [
        'Finish Angular notes',
        'Finish Bootstrap tutorial',
        'Write Web App for Azure'
    ];
    categories: Array<string> = [
        'business',
        'leisure',
        'research'
    ];
    category: string = '';

    setToggle() {
        this.toggle = !this.toggle;
    }
}

The following sections are added to tasks.component.html:

  • A toggle button that modifies the value of the toggle property in the component class. Above it is a label that indicates the current value.
  • A category select element that provides options for the values specified in the categories array. Binds to the category property, and provides a label above to indicate the current value of this property.
  • Add an <h3> for Attribute Directives and separate it from the Structural Directives section with a horizontal rule.
  • A Structural Directives section that demonstrates the following:
    • Toggling an element with NgIf
    • Displaying a list of tasks from an array using NgFor
    • Displaying the details of a category based on the selected value using NgSwitch

tasks.component.html

<p>Toggle: {{toggle}}</p>
<button (click)="setToggle()">Toggle</button>

<p>Category: {{category}}</p>
<select [(ngModel)]="category">
    <option *ngFor="let cat of categories" [value]="cat">{{cat}}</option>
</select>

<h3>Attribute Directives</h3>
<p [class.red]="toggle">[class.red]="toggle" syntax</p>
<p [ngClass]="{ red: !toggle, blue: toggle }">[ngClass]="&#123; red: !toggle, blue: toggle &#125;" syntax</p>

<hr class="gradient">

<h3>Structural Directives</h3>
<p><strong>NgIf</strong></p>
<p *ngIf="toggle">Shows when toggle is true</p>
<p><strong>NgFor Tasks</strong></p>
<ul>
  <li *ngFor="let task of tasks">
    <p>{{task}}</p>
  </li>
</ul>
<p><strong>NgSwitch</strong></p>
<div [ngSwitch]="category">
    <div *ngSwitchCase="'business'">
        <p>{{category}} - when actively working on a project</p>
    </div>
    <div *ngSwitchCase="'leisure'">
        <p>{{category}} - when doing something to mitigate burnout</p>
    </div>
    <div *ngSwitchCase="'research'">
        <p>{{category}} - when filling gaps in knowledge</p>
    </div>
    <div *ngSwitchDefault>
        <p>category not currently selected</p>
    </div>
</div>

Run the application, and see how the structural directives affect the layout of the component view

Click below image to see demonstration
struct-directives

Back to Top

Interpolation - A form of property data binding in which a template expression between double-curly braces renders as text. That text may be concatenated with neighboring text before it is assigned to an element property or displayed between element tags.

<label>My current hero is {{hero.name}}</label>

Also see Template Syntax - Interpolation

Property Binding - Write a template property binding to set a property of a view element. The binding sets the property to the value of a template expression. The most common property binding sets an element property to a component property value.

When setting an element property to a non-string data value, you must use property binding over interpolation

<!-- Binding the src property of an image lement to a component's heroImageUrl property -->
<img [src]="heroImageUrl" />

<!-- Disabling a button when the component says that it isUnchanged -->
<button [disabled]="isUnchanged">Cancel is disabled</button>

<!-- Setting a property of a directive -->
<div [ngClass]="classes">[ngClass] binding to the classes property</div>

<!-- Setting the model property of a custom component, enabling parent and child components to communicate -->
<hero-detail [hero]="currentHero"></hero-detail>

Event Binding - Event binding allows data to flow from an element to a component. The only way to know about a user action is to listen for certain events such as keystrokes, mouse movements, clicks, and touches. You declare your interest in user actions through Angular event binding. Event binding syntax consists of a target event within parenthesis on the left of an equal sign, and a quoted template statement on the right.

<button (click)="onSave()">Save</button>

<!-- canonical form of event binding -->
<button on-click="onSave()">Save</button>

A name between parenthesis - for instance, (click) above - identifies the target event.

In an event binding, Angular sets up an event handler for the target event. When the event is raised, the handler executes the template statement. The template statement typically involves a receiver, which performs an action in response to the event, such as storing a value from the HTML control into a model. The binding conveys information about the event, including data values, through an event object name $event. The shape of the event object is determined by the target event. If the target event is a native DOM element event, then $event is a DOM event object, with properties such as target and target.value.

<input [value]="currentHero.name" (input)="currentHero.name=$event.target.value" />

Two-way Data Binding - Allows you to both display a property and update that property when the user makes changes. ON the lement side that takes a combination of setting a specific element property and listening for an element change event. Angular offers a special two-way data binding syntax for this purpose, [(x)]. The [(x)] syntax combines the brackets of property binding, [x], with the parenthesis of event binding, (x).

The [(x)] syntax is easy to demonstrate when the element has a settable property called x and a corresponding event named xChange. Here's a SizerComponent that fits the pattern. It has a size value property and a companion sizeChange event.

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
    selector: 'my-sizer',
    template: `
    <div>
        <button (click)="dec()" title="smaller">-</button>
        <button (click)="inc()" title="bigger">+</button>
        <label [style.font-size.px]="size">Fontsize: {{size}}px</label>
    </div>`
})
export class SizerComponent {
    @Input() size: number | string;
    @Output() sizeChange = new EventEmitter<number>();
    
    dec() { this.resize(-1); }
    inc() { this.resize(+1); }
    
    resize(delta: number) {
        this.size = Math.min(40, Math.max(8, +this.size + delta));
        this.sizeChange.emit(this.size);
    }
}

The initial size is an input value from a property binding. Clicking the buttons increases or decreases the size, within min/max values constraints, and then raises (emits) the sizeChange event with the adjusted size.

Here's an example in which the AppComponent.fontSizePx is two-way bound ot the SizerComponent:

<my-sizer [(size)]="fontSizePx"></my-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

The AppComponent.fontSizePx establishes the initial SizerComponent.size value. Clicking the buttons updates the AppComponent.fontSizePx via the two-way binding. The revised AppComponent.fontSizePx value flows through to the style binding, making the displayed text bigger or smaller.

The two-way binding syntax is really just syntactic sugar for a property binding and an event binding. Angular desugars the SizerComponent binding into this:

<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>

Implementing Data Binding

Back to Top

In tasks.components.ts, add a number property named num set to the value 7, and a string property named color set to the value black. Also, a setNum() function is added for binding to a button click.

tasks.components.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'tasks',
    templateUrl: 'tasks.component.html',
    styleUrls: [ 'tasks.component.css' ]
})
export class TasksComponent {
    toggle: boolean = true;
    tasks: Array<string> = [
        'Finish Angular notes',
        'Finish Bootstrap tutorial',
        'Write Web App for Azure'
    ];
    categories: Array<string> = [
        'business',
        'leisure',
        'research'
    ];
    category: string = '';
    num: number = 7;
    color: string = 'black';

    setToggle() {
        this.toggle = !this.toggle;
    }

    setNum() {
        this.num++;

        if (this.num > 10) {
            this.num = 1;
        }
    }
}

In tasks.component.html, a Data Binding section is created that demonstrates the following features:

  • Interpolation - The num variable is interpolated in the paragraph element - Number: {{num}}
  • Event Binding - The num variable is modified by the setNum() function by binding to a button click event - (click)="setNum()"
  • Two-way Binding - The color variable is configured for two way binding on the input element - [(ngModel)]="color"
  • Property Binding - The style.color property is set to the value of the color variable using property binding interpolation - [style.color]="color"

tasks.component.html

<!-- previous demonstrations -->
<hr class="gradient">

<h3>Data Binding</h3>
<p><strong>Interpolation</strong></p>
<p>Number: {{num}}</p>
<p><strong>Event Binding</strong></p>
<button (click)="setNum()">Update Num</button>
<p><strong>Two-way Binding</strong></p>
<input [(ngModel)]="color">
<p [style.color]="color">The color of this text is determined by the value specified in the input!</p>

Run the app and check out the data binding interactions that were added

Click below image to see demonstration
data-binding

Back to Top

Service is a broad category encompassing any value, function, or feature that your application needs. Almost anything can be a service. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.

There is nothing specifically Angular about services. Angular has no definition of a service. There is no service base class, and no place to register a service. Yet services are fundamental to any Angular application. Components are big consumers of services.

Here's an example of a service class that logs to the browser console:

export class Logger {
    log(msg: any) { console.log(msg); }
    error(msg: any) { console.error(msg); }
    warn(msg: any) { console.warn(msg); }
}

Services are made available to components through dependency injection. Dependency injection is a way to supply a new instance of a class with the fully-formed dependencies it requires. Most dependencies are services. Angular uses dependency injection to provide new components with the services they need. Angular can tell which services a component needs by looking at the types of its constructor paramters. For instance:

constructor(private service: HeroService) { }

When Angular creates a component, it first asks an injector for the services that the component requires. An injector maintains a container of service instances that it has previosuly created. If a requested service instance is not in the container, the injector makes one and adds it to the container before returning the service to Angular. When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments. This is dependency injection.

Before you can inject services, a provider of a service must be registered with the injector. A provider is something that can create or return a service, typically the service class itself. Providers can be registered in modules or in components. In general, add providers to the root module so that the same instance of a service is availble everywhere.

providers: [
    BackendService,
    HeroService,
    Logger
]

See Dependency Injection and Lifecycle Hooks for an advanced look at dependency injection

Implementing Services

Back to Top

Create the src/app/tasks.service.ts file. It needs to be marked with the @Injectable attribute, which needs to be imported from @angular/core. The service will contain a task array property named tasks.

tasks.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class TasksService {
    tasks = [
        'First Task',
        'Second Task',
        'Third Task'
    ];
    constructor() { }
}

To make the service injectable across the entire module, register the TaskService with the providers array in the metadata region of app.module.ts. The TaskService class needs to be imported from the tasks.service file.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TasksComponent } from './tasks.component';
import { FormsModule } from '@angular/forms';
import { TasksService } from './tasks.service';

@NgModule({
    imports: [ BrowserModule, FormsModule ],
    declarations: [ AppComponent, TasksComponent ],
    bootstrap: [ AppComponent ],
    providers: [ TasksService ]
})
export class AppModule { }

In tasks.components.ts, import the TasksService class, then create a constructor in the TasksComponent class with the TasksService injected as the object property taskService.

import { TasksService } from './tasks.service';

// Metadata Configuration
export class TasksComponent {
    // Previous component logic
    
    constructor(public taskService : TasksService) { }
}

Add the Services section to tasks.component.html

<!-- previous demonstration -->
<hr class="gradient">

<h3>Services</h3>
<p><strong>Tasks</strong></p>
<!-- use an Angular pipe to interpolate the data as JSON instead of a string -->
<p>{{taskService.tasks | json}}</p>

Run the application to see how the service data is used by the component

service-example

Back to Top

The Angular Router enables navigation from one view to the next as users perform application tasks. The router can interpret a browser URL as an instruction to navigate to a client-generated view. It can pass optional parameters along to the supporting view component that help it decide what specific content to present. You can bind the router to links on a page and it will navigate to the appropriate application view when the user clicks a link. You can navigate imperatively when the user clicks a button, selects from a drop box, or in response to some other stimulus from any source. And the router logs activity in the browser's history journal so the back and forward buttons work as well.

<base href>
Most routing applications should add a <base> element to the index.html as the first child in the <head> tag to tell the router how to compose navigation URLs.

If the app folder is the application root, as it is in the current setup for these notes, set the href value exactly as below:

<base href="/">

Router Imports
The Angular Router is an optional service that presents a particular component view for a given URL. It is not part of the Angular core. It is in its own library package, @angular/router. Import what you need from it as you would from any other Angular package.

import { RouterModule, Routes } from '@angular/router/';

Configuration
A routed Angular application has one, singleton instance of the Router service. When the browser's URL changes, that router looks for a corresponding Route from which it can determine the component to display.

A router has no routes until you configure it. The following example creates four route definitions, configures the router via the RouterModule.forRoot method, and adds the result to the AppModule imports array.

const appRoutes: Routes = [
    { path: 'crisis-center', component: CrisisListComponent },
    { path: 'hero/:id',      component: HeroDetailComponent },
    {
        path: 'heroes',
        component: HeroListComponent,
        data: { title: 'Heroes List' }
    },
    {
        path: '',
        redirectTo: '/heroes',
        pathMatch: 'full'
    },
    { path: '**', component: PageNotFoundComponent }
];

@NgModule({
    imports: [
        RouterModule.forRoot(appRoutes)
        // other imports here
    ],
    // Other metadata
})
export class AppModule { }

The appRoutes array of routes describes how to navigate. Pass it to the RouterModule.forRoot method in the module imports to configure the router.

Each Route maps a URL path to a component. There are no leading slashes in the path. The router parses and builds the final URL for you, allowing you to use both relative and "absolute" paths when navigating between application views.

The :id in the second route is a token for a route parameter. In a URL such as /hero/42, "42" is the value of the id parameter. The corresponding HeroDetailComponent will use that value to find and present the hero whose id is 42.

The data property in the third route is a place to store arbitrary data associated with this specific route. The data property is accessible within each activated route. Use it to store items such as page titles, breadcrumb text, and other read-only, static data.
The empty path in the fourth route represents the default path for the application, the place to go when the path in the URL is empty, as it typically is at the start. This default route redirects to thte route for the /heroes URL and, therefore, will display the HeroesListComponent.

The ** path in the last route is a wildcard. The router will select this route if the requested URL doesn't match any paths for routes defined earlier in the configuration. This is useful for displaying a "404 - Not Found" page or redirecting to another route.

The order of routes in the configuration matters and this is by design. The router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. In the configuration above, routes with a static path are listed first, followed by an empty path route, that matches the default route. The wildcard comes last because it matches every URL and should be selected only if no other routes are matched first.

Router Outlet
Given this configuration, when the browser URL for this application becomes /heroes, the router matches that URL to the route path /heroes and displays the HeroListComponent after a RouterOutlet that you've placed in the host view's HTML.

<router-outlet></router-outlet>
<!-- Routed views go here -->

Router links
Now you have routes configured and a place to render them, but how do you navigate? The URL could arrive directly from the browser address bar. But most of the time you navigate as a result of some user action such as the click of an anchor tag.

<h1>Angular Router</h1>
<nav>
    <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
    <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

The RouterLink directives on the anchor tags give the router control over those elements. The navigation paths are fixed, so you can assign a string to the routerLink (a "one-time" binding).

Had the navigation path been more dynamic, you could ahve bound to a template expression that returned an array of route link parameters (the link parameters array). The router resolves that array into a complete URL.

The RouterLinkActive directive on each anchor tag helps visually distinguish the anchor for the currently selected "active" route. The router adds the active CSS class to the element when the associated RouterLink becomes active. You can add this directive to the anchor or to its parent element.

Router state
After the end of each successful navigation lifecycle, the router builds a tree of ActivatedRoute objects that make up the current state of the router. You can access the current RouterState from anywhere in the application using the Router service and the routerState property.

Each ActivatedRoute in the RouterState provides methods to traverse up and down the route tree to get information from the parent, child, and sibling routes.

Summary
The application has a configured router. The shell component has a RouterOutlet wher it can display views produced by the router. It has RouterLinks that users can click to navigate via the router.

Here are the key Router terms and their meanings:

Router Part Meaning
Router Displays the application component for the active URL. Manages navigation from one component to the next.
RouterModule A separate Angular module that provides the necessary service providers and directives for navigating through application views.
Routes Defines an array of Routes, each mapping a URL path to a component.
Route Defines how the router should navigate to a component based on a URL pattern. Most routes consist of a path and a component type.
RouterOutlet The directive (<router-outlet>) that marks where the router should display a view.
RouterLink The directive for binding a clickable HTML element to a route. Clicking an element with a routerLink directive that is bound to a string or a link parameters array triggers a navigation.
RouterLinkActive The directive for adding/removing classes from an HTML element when an associated routerLink contained on or inside the element becomes active/inactive.
ActivatedRoute A service that is provided to each route component that contains route specific information such as route parameters, static data, resolve data, global query params, and the global fragment.
RouterState The current state of the router including a tree of the currently activated routes together with convenience methods for traversing the route tree.
Link parameters array An array that the router interprets as a routing instructing. You can bind that array to a RouterLink or pass the array asn an argument to the Router.navigate method.
Routing component An Angular component with a RouterOutlet that displays views based on router navigations.

See Routing and Navigation for more information.

Implementing Routing

Back to Top

The demos in this overview are getting out of hand. It's time to separate each demo into its own component, then use routing to render each demo separately. To accomplish this, the following steps will be taken:

  1. Ensure that <base href="/"> is set in the <head> element of index.html
  2. Rename tasks.component.css and use this for each component. Slight modifications to CSS contents
  3. Build components for each demo segment specified in tasks.component.html and tasks.component.ts
  4. Delete tasks.component.ts and tasks.component.html
  5. Import the Angular Router components, as well as the newly created Components, and configure routing. Also, remove the TasksComponent import and declaration
  6. Update app.component.ts to use an app.component.html template and setup the master view / router outlet

src/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Angular QuickStart</title>
    <base href="/">
    <!-- remainder of header -->
  </head>
  <body>
    <my-app>Loading AppComponent content here...</my-app>
  </body>
</html>

src/app/app.component.css

p, h2, h3, h4, h5, h6, a, input, label, li, textarea {
  font-family: Arial, Helvetica, sans-serif;
}

.red {
  color: red;
}

.blue {
  color: blue;
}

hr.gradient {
  border: 0;
  height: 1px;
  background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
  background-image: linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
}

a {
  text-decoration: none;
  padding: 8px;
  background-color: #eee;
  color: dodgerblue;
}

a:hover {
  background-color: #ccc;
}

src/app/attribute.component.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'attribute-demo',
    templateUrl: 'attribute.component.html',
    styleUrls: [ 'app.component.css' ]
})
export class AttributeComponent {
    toggle: boolean = true;

    setToggle() {
        this.toggle = !this.toggle;
    }
}

src/app/attribute.component.html

<h3>Attribute Directives</h3>
<hr class="gradient">
<p>Toggle: {{toggle}}</p>
<button (click)="setToggle()">Toggle</button>
<p [class.red]="!toggle">[class.red]="!toggle" syntax</p>
<p [ngClass]="{ red: !toggle, blue: toggle }">[ngClass]="&#123; red: !toggle, blue: toggle &#125;" syntax</p>

src/app/structural.component.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'structural-demo',
    templateUrl: 'structural.component.html',
    styleUrls: [ 'app.component.css' ]
})
export class StructuralComponent {
    toggle: boolean = true;
    tasks: Array<string> = [
        'Finish Angular notes',
        'Finish Bootstrap tutorial',
        'Write Web App for Azure'
    ];
    categories: Array<string> = [
        'business',
        'leisure',
        'research'
    ];
    category: string = '';

    setToggle() {
        this.toggle = !this.toggle;
    }
}

src/app/structural.component.html

<h3>Structural Directives</h3>
<hr class="gradient">
<p>Toggle: {{toggle}}</p>
<button (click)="setToggle()">Toggle</button>
<p>Category: {{category}}</p>
<select [(ngModel)]="category">
    <option *ngFor="let cat of categories" [value]="cat">{{cat}}</option>
</select>
<hr class="gradient">
<h4>NgIf</h4>
<p *ngIf="toggle">Shows when toggle is true</p>
<p *ngIf="!toggle">Shows when toggle is false</p>
<h4>NgFor Tasks</h4>
<ul>
    <li *ngFor="let task of tasks">
        <p>{{task}}</p>
    </li>
</ul>
<h4>NgSwitch</h4>
<div [ngSwitch]="category">
    <div *ngSwitchCase="'business'">
        <p>{{category}} - when actively working on a project</p>
    </div>
    <div *ngSwitchCase="'leisure'">
        <p>{{category}} - when doing something to mitigate burnout</p>
    </div>
    <div *ngSwitchCase="'research'">
        <p>{{category}} - when filling gaps in knowledge</p>
    </div>
    <div *ngSwitchDefault>
        <p>category not currently selected</p>
    </div>
</div>

src/app/binding.component.ts

import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'binding-demo',
    templateUrl: 'binding.component.html',
    styleUrls: [ 'app.component.css' ]
})
export class BindingComponent {
    num: number = 7;
    color: string = 'black';

    setNum() {
        this.num++;

        if (this.num > 10) {
            this.num = 1;
        }
    }
}

src/app/binding.component.html

<h3>Data Binding</h3>
<hr class="gradient">
<h4>Interpolation</h4>
<p>Number: {{num}}</p>
<h4>Event Binding</h4>
<button (click)="setNum()">Update Num</button>
<h4>Two-way Binding</h4>
<input [(ngModel)]="color">
<p [style.color]="color">The color of this text is determined by the value specified in the input!</p>

src/app/services.component.ts

import { Component } from '@angular/core';
import { TasksService } from './tasks.service';

@Component({
    moduleId: module.id,
    selector: 'services-demo',
    templateUrl: 'services.component.html',
    styleUrls: [ 'app.component.css' ]
})
export class ServicesComponent {
    constructor(public taskService: TasksService) { }
}

src/app/services.component.html

<h3>Services</h3>
<hr class="gradient">
<h4>Tasks as JSON</h4>
<p>{{taskService.tasks | json}}</p>

With the new components created, tasks.component.ts and tasks.component.html can be deleted, and routing can be configured in app.module.ts. The new components are imported and declared in the declarations metadata array. Routes are stored in a const variable named appRoutes which is a Routes collection. The routes are configured in the RouterModule.forRoot() function in the imports metadata array.

src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { AttributeComponent } from './attribute.component';
import { StructuralComponent } from './structural.component';
import { BindingComponent } from './binding.component';
import { ServicesComponent } from './services.component';
import { TasksService } from './tasks.service';

const appRoutes: Routes = [
    {
        path: 'attribute-directives', component: AttributeComponent
    },
    {
        path: 'structural-directives', component: StructuralComponent
    },
    {
        path: 'binding', component: BindingComponent
    },
    {
        path: 'services', component: ServicesComponent
    },
    {
        path: '', redirectTo: '/attribute-directives', pathMatch: 'full'
    },
    {
        path: '**', redirectTo: '/attribute-directives'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        RouterModule.forRoot(appRoutes)
    ],
    declarations: [
        AppComponent,
        AttributeComponent,
        StructuralComponent,
        BindingComponent,
        ServicesComponent
    ],
    bootstrap: [ AppComponent ],
    providers: [ TasksService ]
})
export class AppModule { }

The root component, AppComponent, now needs to be configured by defining the necessary routerLinks, and providing the <router-outlet> for the view components to render to.

src/app/app.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html',
    styleUrls: [ 'app/app.component.css' ]
})
export class AppComponent { }

src/app/app.component.html

<h2>Angular Overview</h2>
<nav>
    <a routerLink="/attribute-directives" routerLinkActive="active">Attibute Directives</a>
    <a routerLink="/structural-directives">Structural Directives</a>
    <a routerLink="/binding">Data Binding</a>
    <a routerLink="/services">Services</a>
</nav>
<router-outlet></router-outlet>

Run the application to see the how each demo component is rendered using routing

Click below image to see demonstration
routing-demo

Back to Top

Use an Http Client to talk to a remote server. Http is the primary protocol for browser / server communication.

First, configure the application to use server communication facilities. The Angular Http client communicates with the server using a familiar HTTP request/response protocol. The Http client is one of a family of services in the Angular HTTP library.

When importing from the @angular/http module, SystemJS knows how to load services from the Angular HTTP library because the system.config.js file maps to that module name.

Before you can use the Http client, you need to register it as a service provider with the dependency injection system in the root module.

src/app/app.module.ts - Configuring HTTP Example

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

import { AppComponent } form './app.component';

@NgModule({
    import: [
        BrowserModule,
        FormsModule,
        HttpModule,
        JsonpModule
    ],
    declarations: [ AppComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

A sample component template to demonstrate making HTTP calls

hero-list.component.html - Example Template

<h1>Tour of Heroes {{mode}}</h1>
<h3>Heroes:</h3>
<ul>
    <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<label>New hero name: <input #newHeroName /></label>
<button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button>

<p class="error" *ngIf="errorMessage">{{errorMessage}}</p>

#newHeroName is a template reference variable, which is often a reference to a DOM element within a template. It can also be a reference to an Angular component or directive, or a web component. You can refer to a template reference variable anywhere in the template. In most cases, Angular sets the reference variable's value to the element on which it was declared. A directive can change that behavior and set the value to something else, such as itself. NgForm does that: #heroForm="ngForm". The reference variable accesses the value of the input box in the (click) event binding. When the user clicks the button, that value passes to the component's addHero method and then the event binding clears it to make it ready for a new hero name.

hero-list.component.ts - Example Component

export class HeroListcomponent implements OnInit {
    errorMessage: string;
    heroes: Hero[];
    mode = 'Observable';
    
    constructor (private heroService: HeroService) {}
    
    ngOnInit() { this.getHeroes(); }
    
    getHeroes() {
        this.heroService.getHeroes()
            .subscribe(
                heroes => this.heroes = heroes,
                error => this.errorMessage = <any>error);
    }
    
    addHero (name: string) {
        if (!name) { return; }
        this.heroService.addHero(name)
            .subscribe(hero => this.heroes.push(hero),
            error => this.errorMessage = <any>error);
    }
}

Angular injects a HeroService into the constructor and the component calls that service to fetch and save data. The component does not talk directly to the Angular Http client. The component doesn't know or care how it gets the data. It delegates to the HeroService.

This is a golden rule: always delegate data access to a supporting service class.

Although at runtime the component requests heroes immediately after creation, you don't call the service's get method in the component's constructor. Instead, call it inside the ngOnInit lifecycle hook and rely on Angular to call ngOnInit when it instantiates the component.

The service's getHeroes() and addHero() methods return an Observable of hero data that the Angular Http client fetched from the server. Think of an Observable as a stream of events published by some source. To listen for events in this stream, subscribe to the Observable. These subscriptions specify the actions to take when the web request produces a success event (with the hero data in the event payload) or a fail event (with the error in the payload).

hero.service.ts - Example Service that interfaces with HTTP

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

import { Hero } form './hero';

@Injectable()
export class HeroService {
    private heroesUrl = 'app/heroes';
    
    constructor (private http: Http) { }
    
    getHeroes(): Observable<Hero[]> {
        return this.http.get(this.heroesUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }
    
    private extractData(res: Response) {
        let body = res.json();
        return body.data || { };
    }
    
    private handleError (error: Response | any) {
        let errMsg: string;
        if (error instanceOf Response) {
            const body = error.json() || '';
            const err = body.error || JSON.stringify(body);
            errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
        } else {
            err.Msg = error.message ? error.message : error.toString();
        }
        
        console.error(errMessage);
        return Observable.throw(errMsg);
    }
}

If you are familiar with asynchronous methods in modern JavaScript, you might expect the get method to return a promise. You'd expect to chain a call to then() and extract the heroes. Instead, you're calling a map() method. Clearly this is not a promise.

In fact, the http.get method returns an Observable of HTTP Responses (Observable<Response>) from the RxJS library and map is one of the RxJS operators.

RxJS is a third party library, endorsed by Angular, that implements the asynchronous observable pattern. To make RxJS observables usable, you must import the RxJS operators individually. The RxJS library is large. Size matters when building a production application and deploying it to mobile devices. You should include only necessary features. Each code file should add the operators it needs by importing from an RxJS library. The getHeroes method needs the map and catch operators, so it imports them as follows:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

See Http Client for full details on working with the Http and Jsonp modules.

Implementing Observables and Http

Back to Top

For specific details on setting up the InMemoryWebApi see Http Client - In Memory Web API

Create a task.ts file in the app folder. This will represent the data objects that will be worked with in this segment

task.ts

export class Task {
    public title: string;
    public completed: boolean;
    public created_at: string;
    public updated_at: string;
}

Create a task-data.ts class in the app folder. This will represent the mock database interface that the Http module will call

src/app/task-data.ts

import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Task } from './task';

export class TaskData implements InMemoryDbService {
  createDb() {
    let tasks: Task[] = [
      {
        title: "First Task",
        completed: false,
        created_at: "",
        updated_at: ""
      },
      {
        title: "Second Task",
        completed: false,
        created_at: "",
        updated_at: ""
      },
      {
        title: "Third Task",
        completed: false,
        created_at: "",
        updated_at: ""
      },
      {
        title: "Fourth Task",
        completed: false,
        created_at: "",
        updated_at: ""
      }
    ];

    return {tasks};
  }
}

Create an observable-task.service.ts file in the app folder. This is the service that will execute the api calls using Observable and Http. Note that when adding the task object to the post call, it needs to be stringified. Otherwise, it will wrap the object in a task property, and will not be in the same format as the rest of the data when a subsequent getTasks() is called

observable-task.service.ts

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Task } from './task';

@Injectable()
export class ObservableTaskService {
    private tasksUrl = 'app/tasks';

    constructor(private http: Http) { }

    getTasks(): Observable<Task[]> {
        return this.http.get(this.tasksUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }

    addTask(task: Task): Observable<Task> {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this.tasksUrl, JSON.stringify(task), options)
            .map(this.extractData)
            .catch(this.handleError);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body.data || { };
    }

    private handleError(error: Response | any) {
        let errMsg: string;
        if (error instanceof Response) {
            const body = error.json() || '';
            const err = body.error || JSON.stringify(body);
            errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
        } else {
            errMsg = error.message ? error.message : error.toString();
        }
        console.error(errMsg);
        return Observable.throw(errMsg);
    }
}

Create an observable.component.ts file in the app folder. This component uses the ObservableTaskService to retrieve and add data without knowing the implementation details of the Http module

import { Component, OnInit } from '@angular/core';
import { ObservableTaskService } from './observable-task.service';
import { Task } from './task';

@Component({
    moduleId: module.id,
    selector: 'observable-demo',
    templateUrl: 'observable.component.html',
    styleUrls: ['app.component.css']
})
export class ObservableComponent implements OnInit {
    errorMessage: string;
    tasks: Task[];
    task: Task = new Task();

    constructor(private taskService: ObservableTaskService) { }

    ngOnInit() {
        this.getTasks(); 
    }

    getTasks() {
        this.taskService.getTasks()
            .subscribe(
            tasks => {
                this.tasks = tasks
            },
            error => this.errorMessage = <any>error
            );
    }

    addTask() {
        if (!this.task.title) { return; }
        this.taskService.addTask(this.task)
            .subscribe(
            task => {
                this.getTasks();
                this.task = new Task();
            },
            error => this.errorMessage = <any>error
            );
    }
}

Create an observable.component.html file in the app folder. This serves as the view template for the component

observable.component.html

<h3>Http and Observables</h3>
<hr class="gradient">
<h4>Tasks</h4>
<ul>
  <li *ngFor="let tsk of tasks">{{tsk.title}} - {{tsk.completed}}</li>
</ul>

<label>Task Title: <input [(ngModel)]="task.title" placeholder="title"/></label>
<label>Task Completed: <input type="checkbox" [(ngModel)]="task.completed" /></label>
<button (click)="addTask()">Add Task</button>

<p class="red" *ngIf="errorMessage">{{errorMessage}}</p>

Update the app.module.ts file to import the HttpModule, InMemoryWebApiModule, ObservableComponent, ObservableTaskService, and TaskData classes. Update the appRoutes collection to include an observable path for ObservableComponent. Configure the InMemoryWebApiModule in the imports metadata property. Add ObservableComponent to the declarations metadata array. Add ObservableTaskService to the providers metadata array

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { HttpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component';
import { AttributeComponent } from './attribute.component';
import { StructuralComponent } from './structural.component';
import { BindingComponent } from './binding.component';
import { ServicesComponent } from './services.component';
import { ObservableComponent } from './observable.component';
import { TasksService } from './tasks.service';
import { ObservableTaskService } from './observable-task.service';
import { TaskData } from './task-data';

const appRoutes: Routes = [
    {
        path: 'attribute-directives', component: AttributeComponent
    },
    {
        path: 'structural-directives', component: StructuralComponent
    },
    {
        path: 'binding', component: BindingComponent
    },
    {
        path: 'services', component: ServicesComponent
    },
    {
        path: 'observable', component: ObservableComponent
    },
    {
        path: '', redirectTo: '/attribute-directives', pathMatch: 'full'
    },
    {
        path: '**', redirectTo: '/attribute-directives'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        RouterModule.forRoot(appRoutes),
        InMemoryWebApiModule.forRoot(TaskData)
    ],
    declarations: [
        AppComponent,
        AttributeComponent,
        StructuralComponent,
        BindingComponent,
        ServicesComponent,
        ObservableComponent
    ],
    bootstrap: [AppComponent],
    providers: [
        TasksService,
        ObservableTaskService
    ]
})
export class AppModule { }

Update the app.component.html template to include a route for /observable

app.component.html

<h2>Angular Overview</h2>
<nav>
    <a routerLink="/attribute-directives" routerLinkActive="active">Attibute Directives</a>
    <a routerLink="/structural-directives">Structural Directives</a>
    <a routerLink="/binding">Data Binding</a>
    <a routerLink="/services">Services</a>
    <a routerLink="/observable">Observables and Http</a>
</nav>
<router-outlet></router-outlet>

Run the application and navigate to /observable to see Http interactions with the InMemoryWebApiModule

Click below image to see demonstration
observable

Back to Top

Although the Angular http client API returns an Observable<Response>, you can turn it into a Promise<Response>.

See this StackOverflow thread regarding differences between Promise and Observable.

Create the promise-task.service.ts. Notice how the only imported RxJS item is the toPromise operator.

promise-task.service.ts

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Task } from './task';

@Injectable()
export class PromiseTaskService {
    private tasksUrl = 'app/tasks';

    constructor(private http: Http) { }

    getTasks(): Promise<Task[]> {
        return this.http.get(this.tasksUrl)
            .toPromise()
            .then(this.extractData)
            .catch(this.handleError);
    }

    addTask(task: Task): Promise<Task> {
        let headers = new Headers({ 'Content-Type': 'application/json'});
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this.tasksUrl, JSON.stringify(task), options)
            .toPromise()
            .then(this.extractData)
            .catch(this.handleError);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body.data || { };
    }

    private handleError(error: Response | any) {
        let errMsg: string;
        if (error instanceof Response) {
            const body = error.json() || '';
            const err = body.error || JSON.stringify(body);
            errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
        } else {
            errMsg = error.message ? error.message : error.toString();
        }

        console.error(errMsg);
        return Promise.reject(errMsg);
    }
}

Create promise.component.ts. The only difference between this and observable.component.ts is the use of Promise rather than Observable.

promise.component.ts

import { Component, OnInit } from '@angular/core';
import { PromiseTaskService } from './promise-task.service';
import { Task } from './task';

@Component({
    moduleId: module.id,
    selector: 'promise-demo',
    templateUrl: 'promise.component.html',
    styleUrls: ['app.component.css']
})
export class PromiseComponent implements OnInit {
    errorMessage: string;
    tasks: Task[];
    task: Task = new Task();

    constructor(private taskService: PromiseTaskService) { }

    ngOnInit() {
        this.getTasks();
    }

    getTasks() {
        this.taskService.getTasks()
            .then(
            tasks => this.tasks = tasks,
            error => this.errorMessage = <any>error);
    }

    addTask() {
        if (!this.task.title) { return; }
        this.taskService.addTask(this.task)
            .then(
            task => {
                this.getTasks();
                this.task = new Task();
            },
            error => this.errorMessage = <any>error
            );
    }
}

Create the promise.component.html view template

promise.component.html

<h3>Http and Promises</h3>
<hr class="gradient">
<h4>Tasks</h4>
<ul>
  <li *ngFor="let tsk of tasks">{{tsk.title}} - {{tsk.completed}}</li>
</ul>

<label>Task Title: <input [(ngModel)]="task.title" placeholder="title"/></label>
<label>Task Completed: <input type="checkbox" [(ngModel)]="task.completed" /></label>
<button (click)="addTask()">Add Task</button>

<p class="red" *ngIf="errorMessage">{{errorMessage}}</p>

In app.module.ts, import the component and service classes, add a promise path that points to PromiseComponent in the appRoutes collection, add PromiseComponent to the declarations metadata array, and add PromiseTaskService to the providers metadata array

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { HttpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component';
import { AttributeComponent } from './attribute.component';
import { StructuralComponent } from './structural.component';
import { BindingComponent } from './binding.component';
import { ServicesComponent } from './services.component';
import { ObservableComponent } from './observable.component';
import { PromiseComponent } from './promise.component';
import { TasksService } from './tasks.service';
import { ObservableTaskService } from './observable-task.service';
import { PromiseTaskService } from './promise-task.service';
import { TaskData } from './task-data';

const appRoutes: Routes = [
    {
        path: 'attribute-directives', component: AttributeComponent
    },
    {
        path: 'structural-directives', component: StructuralComponent
    },
    {
        path: 'binding', component: BindingComponent
    },
    {
        path: 'services', component: ServicesComponent
    },
    {
        path: 'observable', component: ObservableComponent
    },
    {
        path: 'promise', component: PromiseComponent
    },
    {
        path: '', redirectTo: '/attribute-directives', pathMatch: 'full'
    },
    {
        path: '**', redirectTo: '/attribute-directives'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        RouterModule.forRoot(appRoutes),
        InMemoryWebApiModule.forRoot(TaskData)
    ],
    declarations: [
        AppComponent,
        AttributeComponent,
        StructuralComponent,
        BindingComponent,
        ServicesComponent,
        ObservableComponent,
        PromiseComponent
    ],
    bootstrap: [AppComponent],
    providers: [
        TasksService,
        ObservableTaskService,
        PromiseTaskService
    ]
})
export class AppModule { }

In app.component.html, create a link for the new component

app.component.html

<h2>Angular Overview</h2>
<nav>
    <a routerLink="/attribute-directives" routerLinkActive="active">Attibute Directives</a>
    <a routerLink="/structural-directives">Structural Directives</a>
    <a routerLink="/binding">Data Binding</a>
    <a routerLink="/services">Services</a>
    <a routerLink="/observable">Observables and Http</a>
    <a routerLink="/promise">Promises and Http</a>
</nav>
<router-outlet></router-outlet>

Run the application, and you'll see that although the implementation of the services behind both the promise and observable components is different, they are both still able to send and retrieve data

Click below image to see demonstration
promise

Back to Top

If the API resource that you are trying to access is on a remote server with a different origin (origin is the combination of URI scheme, hostname, and port number), then XMLHttpRequests using the Http service are not possible.

Modern browsers do allow XHR requests to servers from a different origin if the server supports the CORS protocol. If the server requires user credentials, you'll enable them in the request headers.

Some servers do not support CORS, but do support an older, read-only alternative called JSONP.

See this StackOverflow thread for a detailed explanation.

Create wikipedia.service.ts. The URLSearchParams class allows you to condition the query string in the way that the public API expects to receive data

wikipedia.service.ts

import { Injectable } from '@angular/core';
import { Jsonp, URLSearchParams } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class WikipediaService {
    constructor(private jsonp: Jsonp) { }

    search (term: string) {
        let wikiUrl = 'http://en.wikipedia.org/w/api.php';

        let params = new URLSearchParams();
        params.set('search', term);
        params.set('action', 'opensearch');
        params.set('format', 'json');
        params.set('callback', 'JSONP_CALLBACK');

        return this.jsonp
            .get(wikiUrl, { search: params })
            .map(response => <string[]>response.json()[1]);
    }
}

Create wiki.component.ts. To prevent the search() function from being called everytime the input value changes, the items observable is setup as a Subject with debounceTime, distinctUntilChanged and switchMap configured to delay the actual search service function from executing until the value has stopped changing for at least .3 seconds.

Subject - Represents an object that is both an observable sequence as well as an observer. Each notification is broadcasted to all subscribed observers. This class inherits both from the Rx.Observable and Rx.Observer classes.

debounceTime - Discard emitted values that take less than the specified time between output.

distinctUntilChanged - Only emit when the current value is different than the last.

switchMap - Map to observable, complete previous inner observable, emit values.

Note that the WikipediaService is added to the providers array in the component rather than the module. If the service is only used by a single component, you don't need to expose it to the entire module. You can instead merely expose it to the component itself.

wiki.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';

import { Subject } from 'rxjs/Subject';

import { WikipediaService } from './wikipedia.service';

@Component({
    moduleId: module.id,
    selector: 'wiki-demo',
    templateUrl: 'wiki.component.html',
    styleUrls: ['app.component.css'],
    providers: [WikipediaService]
})
export class WikiComponent implements OnInit {
    items: Observable<string[]>;
    private searchTermStream = new Subject<string>();

    constructor(private wikipediaService: WikipediaService) { }

    search(term: string) { this.searchTermStream.next(term); }

    ngOnInit() {
        this.items = this.searchTermStream
            .debounceTime(300)
            .distinctUntilChanged()
            .switchMap((term: string) => this.wikipediaService.search(term));
    }
}

Create wiki.component.html template view for the component

wiki.component.html

<h3>Jsonp</h3>
<hr class="gradient">
<p>
    Search Wikipedia when typing stops
</p>
<input #term (keyup)="search(term.value)" />
<ul>
    <li *ngFor="let item of items | async">{{item}}</li>
</ul>

In app.module.ts, import the WikiComponent class, add a jsonp route that points to WikiComponent in the appRoutes collection, and add WikiComponent to the declarations metadata array

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { HttpModule, JsonpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component';
import { AttributeComponent } from './attribute.component';
import { StructuralComponent } from './structural.component';
import { BindingComponent } from './binding.component';
import { ServicesComponent } from './services.component';
import { ObservableComponent } from './observable.component';
import { PromiseComponent } from './promise.component';
import { WikiComponent } from './wiki.component';
import { TasksService } from './tasks.service';
import { ObservableTaskService } from './observable-task.service';
import { PromiseTaskService } from './promise-task.service';
import { TaskData } from './task-data';

const appRoutes: Routes = [
    {
        path: 'attribute-directives', component: AttributeComponent
    },
    {
        path: 'structural-directives', component: StructuralComponent
    },
    {
        path: 'binding', component: BindingComponent
    },
    {
        path: 'services', component: ServicesComponent
    },
    {
        path: 'observable', component: ObservableComponent
    },
    {
        path: 'promise', component: PromiseComponent
    },
    {
        path: 'jsonp', component: WikiComponent
    },
    {
        path: '', redirectTo: '/attribute-directives', pathMatch: 'full'
    },
    {
        path: '**', redirectTo: '/attribute-directives'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        JsonpModule,
        RouterModule.forRoot(appRoutes),
        InMemoryWebApiModule.forRoot(TaskData)
    ],
    declarations: [
        AppComponent,
        AttributeComponent,
        StructuralComponent,
        BindingComponent,
        ServicesComponent,
        ObservableComponent,
        PromiseComponent,
        WikiComponent
    ],
    bootstrap: [AppComponent],
    providers: [
        TasksService,
        ObservableTaskService,
        PromiseTaskService
    ]
})
export class AppModule { }

In app.component.html, add a link to the /jsonp route

app.component.html

<h2>Angular Overview</h2>
<nav>
    <a routerLink="/attribute-directives" routerLinkActive="active">Attibute Directives</a>
    <a routerLink="/structural-directives">Structural Directives</a>
    <a routerLink="/binding">Data Binding</a>
    <a routerLink="/services">Services</a>
    <a routerLink="/observable">Observables and Http</a>
    <a routerLink="/promise">Promises and Http</a>
    <a routerLink="/jsonp">Jsonp</a>
</nav>
<router-outlet></router-outlet>

Click below image to see demonstration
jsonp

Back to Top

See Component Interaction for an in depth look at communicating between components

Back to Top

Before jumping into this sample, the app folder is beginning to get a bit crowded. So, I've taken the time to move components for each section into their own folders and update imports and URLs where appropriate. The file structure for the app folder is now as follows:

  • app
    • attribute
      • attribute.component.html
      • attribute.component.ts
    • binding
      • binding.component.html
      • binding.component.ts
    • observable
      • observable-task.service.ts
      • observable.component.html
      • observable.component.ts
    • promise
      • promise-task.service.ts
      • promise.component.html
      • promise.component.ts
    • services
      • services.component.html
      • services.component.ts
      • tasks.service.ts
    • structural
      • structural.component.html
      • structural.component.ts
    • wiki
      • wiki.component.html
      • wiki.component.ts
      • wikipedia.service.ts
    • app.component.ts
    • app.component.html
    • app.component.css
    • app.module.ts
    • task-data.ts
    • task.ts

The updates that need to be made are as follows:

  1. All of the .component.ts and .service.ts files contained in the new subfolders that import a class stored in the app folder need to change the reference from from ./{class] to from ../{class}
  2. All of the .component.ts files contained in the new subfolders that reference app.component.css in the styleUrls array need to change the reference to ../app.component.css
  3. import statements in app.module.ts that reference a service or component stored in a new subfolder need to change from from ./*.component and ./*.service to ./{folder}/*.component and ./{folder}/*.service
  4. The clean task in gulpfile.js has been updated to reflect the new directory structure. It is now as follows:

gulpfile.js

var
    gulp = require('gulp'),
    clean = require('gulp-clean');

gulp.task('clean', function () {
    return gulp.src(['src/app/**/*.js', 'src/app/**/*.map', 'src/main.js', 'src/main.js.map'])
    .pipe(clean({force: true}));
});

The following example will demonstrate the following capabilities:

Create a car.ts file in the app folder

car.ts

export class Car {
    public make: string;
    public model: string;
    public description: string;
    public url: string;
    public source: string;
}

Create a comment.ts file in the app folder

comment.ts

export class Comment {
    public name: string;
    public comment: string;
}

Update the task-data.ts file to provide an API route to cars

task-data.ts

import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Task } from './task';
import { Car } from './car';

export class TaskData implements InMemoryDbService {
  createDb() {
    let tasks: Array<Task> = [
      {
        title: 'First Task',
        completed: false,
        created_at: '',
        updated_at: ''
      },
      {
        title: 'Second Task',
        completed: false,
        created_at: '',
        updated_at: ''
      },
      {
        title: 'Third Task',
        completed: false,
        created_at: '',
        updated_at: ''
      },
      {
        title: 'Fourth Task',
        completed: false,
        created_at: '',
        updated_at: ''
      }
    ];

    let cars: Array<Car> = [
      {
        make: 'McLaren',
        model: 'P1',
        description: `
        The McLaren P1 is a British limited-production plug-in hybrid sports car produced by McLaren. 
        The concept car was capable of reaching speeds of 218 mph (351 km/h) with the limiter on. 
        The P1 features a 3.8-litre twin-turbo V8 petrol engine. The twin turbos boost the petrol engine 
        at 1.4 bar to deliver 727 bhp (737 PS; 542 kW) and 531 lb·ft (720 N·m) of torque at 7,500 rpm, combined 
        with an in-house developed electric motor producing 177 bhp (179 PS; 132 kW) and 192 lb·ft (260 N·m). 
        With both engine and the electric motor, the P1 has a total power and torque output of 904 bhp (917 PS; 674 kW) 
        and 723 lb·ft (980 N·m) of torque respectively.
        `,
        // tslint:disable-next-line:max-line-length
        url: 'http://www.topgear.com/sites/default/files/styles/16x9_1280w/public/cars-car/image/2015/02/buyers_guide_-_mclaren_p1_2014_-_front_quarter.jpg?itok=gSl3mI9a',
        source: 'https://en.wikipedia.org/wiki/McLaren_P1'
      },
      {
        make: 'Porsche',
        model: '918 Spyder',
        description: `
        The Porsche 918 Spyder is a mid-engined plug-in hybrid sports car by Porsche. 
        The Spyder is powered by a naturally aspirated 4.6-litre V8 engine, developing 608 metric horsepower (447 kW), 
        with two electric motors delivering an additional 279 metric horsepower (205 kW) for a combined output of 887 
        metric horsepower (652 kW). The 918 Spyder's 6.8 kWh lithium-ion battery pack delivers an all-electric range 
        of 19 km (12 mi) under EPA's five-cycle tests. The car has a top speed of around 340 km/h (210 mph).
        `,
        url: 'http://www.topgear.com/sites/default/files/styles/16x9_1280w/public/news/image/2015/04/Large%20Image_9581.jpg?itok=aQqGWV34',
        source: 'https://en.wikipedia.org/wiki/Porsche_918_Spyder'
      },
      {
        make: 'Lamborghini',
        model: 'Veneno',
        description: `
        The Lamborghini Veneno is a limited production supercar based on the Lamborghini Aventador and was built to celebrate 
        Lamborghini’s 50th anniversary. When introduced in 2013 at a price of US$4,500,000, it was the most expensive production 
        car in the world. he prototype, Car Zero, is finished in grey and includes an Italian flag vinyl on both sides of the car. 
        The engine is a development of the Aventador's 6.5 L V12 and produces 750 PS (552 kW; 740 bhp).
        `,
        url: 'http://www.topgear.com/sites/default/files/styles/16x9_1280w/public/news/image/2015/04/Large%20Image_9665.jpg?itok=Bqxc5GrG',
        source: 'https://en.wikipedia.org/wiki/Lamborghini_Aventador#Veneno'
      },
      {
        make: 'Ferrari',
        model: 'LaFerrari',
        description: `
        LaFerrari is the first mild hybrid from Ferrari, providing the highest power output of any Ferrari whilst decreasing fuel 
        consumption by 40 percent. LaFerrari's internal combustion engine is a mid-rear mounted Ferrari F140 65° V12 with a 6.3-litre 
        (6262 cc) capacity producing 800 PS (588 kW, 789 bhp) @ 9000 rpm and 700 N·m (520 lbf·ft) of torque @ 6,750 rpm, supplemented 
        by a 163 PS (120 kW; 161 bhp) KERS unit (called HY-KERS), which will provide short bursts of extra power.[25] The KERS system 
        adds extra power to the combustion engine's output level for a total of 963 PS (708 kW; 950 bhp) and a combined torque of 
        900 N·m (664 lb·ft).
        `,
        // tslint:disable-next-line:max-line-length
        url: 'http://www.topgear.com/sites/default/files/styles/16x9_1280w/public/cars-road-test/image/2015/04/Large%20Image_138.jpg?itok=mGvfDq3x',
        source: 'https://en.wikipedia.org/wiki/LaFerrari'
      }
    ];

    return {tasks, cars};
  }
}

In the app folder, create a folder named car, and create a car.service.ts file

car.service.ts

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Car } from '../car';
import { Comment } from '../comment';

@Injectable()
export class CarService {
    private carsUrl = 'app/cars';

    comments = Array<Comment>();

    constructor(private http: Http) { }

    getCars(): Observable<Car[]> {
        return this.http.get(this.carsUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }

    addCar(car: Car): Observable<Car> {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this.carsUrl, JSON.stringify(car), options)
            .map(this.extractData)
            .catch(this.handleError);
    }

    addComment(comment: Comment) {
        this.comments.push(comment);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body.data || { };
    }

    private handleError(error: Response | any) {
        let errMsg: string;
        if (error instanceof Response) {
            const body = error.json() || '';
            const err = body.error || JSON.stringify(body);
            errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
        } else {
            errMsg = error.message ? error.message : error.toString();
        }
        console.error(errMsg);
        return Observable.throw(errMsg);
    }
}

For some simple styling for these components, add a car.component.css file

car.component.css

.car-card {
  width: 800px;
  background-color: #eee;
  padding-top: 5px;
  background-color: #ddd;
}

.car-card > p {
  padding: 4px;
  margin-left: 16px;
  margin-right: 16px;
  margin-top: 0px;
  color: #333;
}

.car-card > img {
  width: 768px;
  margin-left: 16px;
  margin-bottom: 0px;
  padding-bottom: 0px;
}

.car-card > h4 {
  margin-left: 15px;
}

.car-card > h4 > a {
  background-color: #1e90ff;
  color: #eee;
}

.car-card > h4 > a:hover {
  background-color: #166cbf;
}

The first component that will be created is a CarDetailsComponent. This component will be responsible for rendering the details of the Car class. It contains an @Input() binding to a car property of type Car. This component's car property will be received by the parent component by iterating over an array of cars using NgFor

car-details.component.ts

import { Component, Input } from '@angular/core';
import { Car } from '../car';

@Component({
    moduleId: module.id,
    selector: 'car-details',
    templateUrl: 'car-details.component.html',
    styleUrls: ['../app.component.css', 'car.component.css']
})
export class CarDetailsComponent {
    @Input() car: Car;
}

Create a car-details.component.html template view for the component. Notice how the href and src properties are set using attribute binding

car-details.component.html

<div class="car-card">
  <h4><a [href]="car.source" target="_blank">{{car.make}} {{car.model}}</a></h4>
  <img [src]="car.url" />
  <p>
    {{car.description}}
  </p>
</div>

Create a car-creator.component.ts file. This component defines an EventEmitter<Car> that the parent component will subscribe to. When the addCar() function is executed, the parent component will receive the car property from this component and execute the logic linked to the event

car-creator.component.ts

import { Component, EventEmitter, Output } from '@angular/core';
import { Car } from '../car';

@Component({
    moduleId: module.id,
    selector: 'car-creator',
    templateUrl: 'car-creator.component.html',
    styleUrls: ['../app.component.css', 'car.component.css']
})
export class CarCreatorComponent {
    car: Car = new Car();
    @Output() onAddCar = new EventEmitter<Car>();

    addCar() {
        if (
            !this.car.make ||
            !this.car.model ||
            !this.car.description ||
            !this.car.source ||
            !this.car.url
        ) { return; }
        this.onAddCar.emit(this.car);
        this.car = new Car();
    }
}

Create the car-creator.component.html template view for the component

car-creator.component.html

<h4>Add Car</h4>
<div>
  <label>Make: <input [(ngModel)]="car.make" placeholder="make"></label>
  <label>Model: <input [(ngModel)]="car.model" placeholder="model"></label>
  <label>Source: <input [(ngModel)]="car.source" placeholder="www.car.com"></label>
  <label>Image URL: <input [(ngModel)]="car.url" placeholder="www.car.com/image.png"></label>
</div>
<div>
  <p>Description</p>
  <textarea rows="5" cols="100" [(ngModel)]="car.description"></textarea>
</div>
<button (click)="addCar()">Add Car</button>

Create a car-comments.component.ts file. The component has a comment property of type Comment that is bound to the input elements in its view template. When the addComment() function executes, the comment is passed into the carService.addComment() function, which in turn pushes the comment into the service's comments array. The parent component displays the comments from the service using an NgFor directive

car-comments.components.ts

import { Component } from '@angular/core';
import { CarService } from './car.service';
import { Comment } from '../comment';

@Component({
    moduleId: module.id,
    selector: 'car-comments',
    templateUrl: 'car-comments.component.html',
    styleUrls: ['../app.component.css', 'car.component.css']
})
export class CarCommentsComponent {
    comment: Comment = new Comment();

    constructor(private carService: CarService) { }

    addComment() {
        if (!this.comment.name || !this.comment.comment) {
            return;
        }

        this.carService.addComment(this.comment);

        this.comment = new Comment();
    }
}

Create the car-comments.component.html view template

car-comments.component.html

<h4>Add Comment</h4>
<div>
    <p>Name</p>
    <input [(ngModel)]="comment.name" />
</div>
<div>
    <p>Comment</p>
    <textarea rows="5" cols="100" [(ngModel)]="comment.comment"></textarea>
</div>
<button (click)="addComment()">Add Comment</button>

Now that the child elements have been created, the main component can be created. Add a cars.component.ts file.

When the component initializes, the cars array is set using the getCars() function. This in turn executes an API call through the carService.getCars() function.

Also when initialized, the comments array is set to the carService.comments array. Anytime the carService.comments array is updated by the CarCommentsComponent, the comments array will reflect the update.

Finally, the addCar() function receives is executed whenever the CarCreatorComponent emits its onAddCar event. The car property is received from the event, and passed into the carService.addCar() function

cars.component.ts

import { Component, OnInit } from '@angular/core';
import { CarService } from './car.service';
import { CarDetailsComponent } from './car-details.component';
import { Car } from '../car';
import { Comment } from '../comment';

@Component({
    moduleId: module.id,
    selector: 'cars',
    templateUrl: 'cars.component.html',
    styleUrls: ['../app.component.css', 'car.component.css'],
    providers: [CarService],
    entryComponents: [CarDetailsComponent]
})
export class CarsComponent implements OnInit {
    errorMessage: string;
    cars: Car[];
    car: Car = new Car();
    comments: Array<Comment>;

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.getCars();
        this.comments = this.carService.comments;
    }

    getCars() {
        this.carService.getCars()
            .subscribe(
            cars => {
                this.cars = cars;
            },
            error => this.errorMessage = <any>error
            );
    }

    addCar(car: Car) {
        this.carService.addCar(car)
            .subscribe(
            () => {
                this.getCars();
            },
            error => this.errorMessage = <any>error
            );
    }
}

Create the cars.component.html view template for the component. Notice how the onAddCar event in the <car-creator> component is set to the main component's addCar() function. Also, the car property for the <car-details> component is bound to the value of the current car iteration of the NgFor loop

cars.component.html

<div>
  <car-creator (onAddCar)="addCar($event)"></car-creator>
  <h4>Cars</h4>
  <car-details *ngFor="let c of cars" [car]="c"></car-details>
  <car-comments></car-comments>
  <div *ngIf="comments.length > 0">
    <h4>Comments</h4>
    <div *ngFor="let cmnt of comments">
      <p><strong>{{cmnt.name}}</strong></p>
      <p>{{cmnt.comment}}</p>
    </div>
  </div>
</div>

In app.module.ts, import the components, create a complex-component route in the appRoutes array, and add the components to the declarations metadata array

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { HttpModule, JsonpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component';
import { AttributeComponent } from './attribute/attribute.component';
import { StructuralComponent } from './structural/structural.component';
import { BindingComponent } from './binding/binding.component';
import { ServicesComponent } from './services/services.component';
import { ObservableComponent } from './observable/observable.component';
import { PromiseComponent } from './promise/promise.component';
import { CarsComponent } from './car/cars.component';
import { CarDetailsComponent } from './car/car-details.component';
import { CarCreatorComponent } from './car/car-creator.component';
import { CarCommentsComponent } from './car/car-comments.component';
import { WikiComponent } from './wiki/wiki.component';
import { TasksService } from './services/tasks.service';
import { ObservableTaskService } from './observable/observable-task.service';
import { PromiseTaskService } from './promise/promise-task.service';
import { TaskData } from './task-data';

const appRoutes: Routes = [
    {
        path: 'attribute-directives', component: AttributeComponent
    },
    {
        path: 'structural-directives', component: StructuralComponent
    },
    {
        path: 'binding', component: BindingComponent
    },
    {
        path: 'services', component: ServicesComponent
    },
    {
        path: 'observable', component: ObservableComponent
    },
    {
        path: 'promise', component: PromiseComponent
    },
    {
        path: 'jsonp', component: WikiComponent
    },
    {
        path: 'complex-components', component: CarsComponent
    },
    {
        path: '', redirectTo: '/attribute-directives', pathMatch: 'full'
    },
    {
        path: '**', redirectTo: '/attribute-directives'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        JsonpModule,
        RouterModule.forRoot(appRoutes),
        InMemoryWebApiModule.forRoot(TaskData)
    ],
    declarations: [
        AppComponent,
        AttributeComponent,
        StructuralComponent,
        BindingComponent,
        ServicesComponent,
        ObservableComponent,
        PromiseComponent,
        WikiComponent,
        CarsComponent,
        CarDetailsComponent,
        CarCreatorComponent,
        CarCommentsComponent
    ],
    bootstrap: [AppComponent],
    providers: [
        TasksService,
        ObservableTaskService,
        PromiseTaskService
    ]
})
export class AppModule { }

Finally, add a link to /complex-components in app.component.html

app.component.html

<h2>Angular Overview</h2>
<nav>
    <a routerLink="/attribute-directives" routerLinkActive="active">Attibute Directives</a>
    <a routerLink="/structural-directives">Structural Directives</a>
    <a routerLink="/binding">Data Binding</a>
    <a routerLink="/services">Services</a>
    <a routerLink="/observable">Observables and Http</a>
    <a routerLink="/promise">Promises and Http</a>
    <a routerLink="/jsonp">Jsonp</a>
    <a routerLink="/complex-components">Complex Components</a>
</nav>
<router-outlet></router-outlet>

That was a lot of work! Run the application and see how the each component functions in the context of the parent component.

Click below image for demonstration
complex-components

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment