15. NgModules
AppModule (minimal):
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [ AppComponent ],
imports: [ BrowserModule ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
AppComponent (minimal):
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
`
})
export class AppComponent {
title = 'Tour Of Technologies';
}
Main (Compile Just In Time) dynamic in the browser compilation with Angular Compiler:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);
Compile Ahead of Time compiles the app at build time:
ng serve --aot
ng build --aot
HighlightDirective:
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[appHighlight]'})
/** highlight attached element in red and set font to white **/
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = '#ff4d4d';
el.nativeElement.style.color = '#ffffff';
console.log(`* AppRoot highlight called for ${el.nativeElement.tagName}`);
}
}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="container">
<div class="left">
<h1 appHighlight>{{ title }}</h1>
</div>
<div class="right">
<img class="img-round" [src]="imageUrl" alt="princess image">
</div>
</div>
`
})
export class AppComponent {
imageUrl = '../assets/polymer3.jpg';
title = 'Tour Of Technologies';
}
Let's have a look at the results in our Chromium Browser:

TitleComponent:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-title',
template: `
<h1 appHighlight>{{ title }} {{ subtitle }}</h1>
`
})
export class TitleComponent {
@Input() subtitle = '';
title = 'Tour Of Technologies';
}
UserService:
import { Injectable } from '@angular/core';
@Injectable()
/** dummy version of an authenticated user service **/
export class UserService {
userName = 'Nils-Holger Nägele';
}
AwesomePipe (Shared):
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'appAwesome'
})
/** precede the input string with the word awesome **/
export class AwesomePipe implements PipeTransform {
transform(phrase: string) {
return phrase ? 'Awesome ' + phrase : '';
}
}
HighlightDirective (Shared):
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[appHighlight]'})
/** highlight attached element in red and set font to white **/
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = '#ff4d4d';
el.nativeElement.style.color = '#ffffff';
console.log(`* Shared highlight highlight called for ${el.nativeElement.tagName}`);
}
}
import { Component, OnInit } from '@angular/core';
import { Contact, ContactService } from './contact.service';
import { UserService } from '../core/user.service';
@Component({
selector: 'app-contact',
template: `
<h2>Contact of {{ userName }}</h2>
<div *ngIf="msg" class="msg">{{ msg }}</div>
<form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm">
<h3 appHiglight>{{ contact.name | appAwesome }}</h3>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="contact.name" name="name" #name="ngModel">
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
</div>
<br>
<button type="submit" class="btn btn-default"
[disabled]="!contactForm.form.valid">Save</button>
<button type="button" class="btn" (click)="next()"
[disabled]="!contactForm.form.valid">Next Contact</button>
<button type="button" class="btn" (click)="newContact()">
New Contact
</button>
</form>
`,
styles: [`
.ng-valid[required] {
border-left: 5px solid #42A948;
}
.ng-invalid {
border-left: 5px solid #a94442;
}
.alert {
padding: 15px;
margin: 8px 0;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.msg {
color: blue;
background-color: whitesmoke;
border: 1px solid transparent;
border-radius: 4px;
margin-bottom: 20px;
}
`]
})
export class ContactComponent implements OnInit {
contact: Contact;
contacts: Contact[];
msg = 'Loading contacts ...';
userName = '';
constructor(private contactService: ContactService, userService: UserService) {
this.userName = userService.userName;
}
ngOnInit() {
this.contactService.getContacts().then(contacts => {
this.msg = '';
this.contacts = contacts;
this.contact = contacts[0];
});
}
next() {
let idx = 1 + this.contacts.indexOf(this.contact);
if ( idx >= this.contacts.length) { idx = 0; }
this.contact = this.contacts[idx];
}
onSubmit() {
this.displayMessage('Saved ' + this.contact.name);
}
newContact() {
this.displayMessage('New Contact');
this.contact = { id: 42, name: '' };
this.contacts.push(this.contact);
}
/** display message briefly, then remove it **/
displayMessage(msg: string) {
this.msg = msg;
setTimeout(() => this.msg = '', 2000);
}
}
import { Injectable } from '@angular/core';
export class Contact {
constructor(public id: number, public name: string) { }
}
const CONTACTS: Contact[] = [
new Contact(21, 'Superman'),
new Contact(22, 'Spiderman'),
new Contact(23, 'Batman')
];
const FETCH_LATENCY = 500;
@Injectable()
export class ContactService {
getContacts(): Promise<Contact[]> {
return new Promise<Contact[]>(resolve => {
setTimeout(() => {
resolve(CONTACTS);
}, FETCH_LATENCY);
});
}
getContact(id: number | string) {
return this.getContacts()
.then(technologies => technologies.find(technology => technology.id === +id));
}
}
// exact copy of shared/highlight directive except for background color and message
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appHighlight], input'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = '#ffb3b3';
el.nativeElement.style.color = '#ffffff';
console.log('Contact highlight called for ${el.nativeElement.tagName}');
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from '../shared/awesome.pipe';
import { ContactComponent } from './contact.component';
import { HighlightDirective } from './highlight.directive';
import { ContactService } from './contact.service';
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
exports: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
AppModule:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { PrincessComponent } from './princess.component';
import { HighlightDirective } from './shared/highlight.directive';
import { TitleComponent } from './core/title.component';
import { UserService } from './core/user.service';
import { ContactModule } from './contact/contact.module';
@NgModule({
declarations: [
AppComponent,
PrincessComponent,
HighlightDirective,
TitleComponent
],
imports: [ BrowserModule, ContactModule ],
providers: [ UserService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Let's view the results in our Chromium Brother:

import { Injectable } from '@angular/core';
export class Contact {
constructor(public id: number, public name: string) { }
}
const CONTACTS: Contact[] = [
new Contact(21, 'Superman'),
new Contact(22, 'Spiderman'),
new Contact(23, 'Batman'),
new Contact(24, 'Wonderwoman')
];
const FETCH_LATENCY = 500;
@Injectable()
export class ContactService {
getContacts(): Promise<Contact[]> {
return new Promise<Contact[]>(resolve => {
setTimeout(() => {
resolve(CONTACTS);
}, FETCH_LATENCY);
});
}
getContact(id: number | string) {
return this.getContacts()
.then(technologies => technologies.find(technology => technology.id === +id));
}
}
// exact copy of shared/highlight directive except for background color and message
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appHighlight], input'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = '#ffb3b3';
el.nativeElement.style.color = '#ffffff';
console.log('Contact highlight called for ${el.nativeElement.tagName}');
}
}
import { Component, OnInit } from '@angular/core';
import { Contact, ContactService } from './contact.service';
import { UserService } from '../core/user.service';
@Component({
selector: 'app-contact',
template: `
<h2>Contact of {{ userName }}</h2>
<div *ngIf="msg" class="msg">{{ msg }}</div>
<form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm">
<h3 appHiglight>{{ contact.name | appAwesome }}</h3>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="contact.name" name="name" #name="ngModel">
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
</div>
<br>
<button type="submit" class="btn btn-default"
[disabled]="!contactForm.form.valid">Save</button>
<button type="button" class="btn" (click)="next()"
[disabled]="!contactForm.form.valid">Next Contact</button>
<button type="button" class="btn" (click)="newContact()">
New Contact
</button>
</form>
`,
styles: [`
.ng-valid[required] {
border-left: 5px solid #42A948;
}
.ng-invalid {
border-left: 5px solid #a94442;
}
.alert {
padding: 15px;
margin: 8px 0;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.msg {
color: blue;
background-color: whitesmoke;
border: 1px solid transparent;
border-radius: 4px;
margin-bottom: 20px;
}
`]
})
export class ContactComponent implements OnInit {
contact: Contact;
contacts: Contact[];
msg = 'Loading contacts ...';
userName = '';
constructor(private contactService: ContactService, userService: UserService) {
this.userName = userService.userName;
}
ngOnInit() {
this.contactService.getContacts().then(contacts => {
this.msg = '';
this.contacts = contacts;
this.contact = contacts[0];
});
}
next() {
let idx = 1 + this.contacts.indexOf(this.contact);
if ( idx >= this.contacts.length) { idx = 0; }
this.contact = this.contacts[idx];
}
onSubmit() {
this.displayMessage('Saved ' + this.contact.name);
}
newContact() {
this.displayMessage('New Contact');
this.contact = { id: 42, name: '' };
this.contacts.push(this.contact);
}
/** display message briefly, then remove it **/
displayMessage(msg: string) {
this.msg = msg;
setTimeout(() => this.msg = '', 2000);
}
}
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ContactComponent } from './contact.component';
@NgModule({
imports: [RouterModule.forChild([ { path: 'contact', component: ContactComponent }])],
exports: [ RouterModule ]
})
export class ContactRoutingModule { }
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { ContactRoutingModule } from './contact-routing.module';
import { ContactComponent } from './contact.component';
import { ContactService } from './contact.service';
@NgModule({
imports: [ SharedModule, ContactRoutingModule ],
declarations: [ ContactComponent ],
providers: [ ContactService ]
})
export class ContactModule { }
UserService Core Module v4:
import { Injectable, Optional } from '@angular/core';
let nextId = 1;
export class UserServiceConfig {
userName = 'Laura Honey';
}
@Injectable()
export class UserService {
id = nextId++;
private _userName = 'Nils-Holger Nägele';
constructor(@Optional() config: UserServiceConfig) {
if (config) { this._userName = config.userName; }
}
get userName() {
const suffix = this.id > 1 ? ` **** ${this.id} ****` : '';
return this._userName + suffix;
}
}
TitleComponent Core Module v4:
import { Component, Input } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-title',
template: `
<h1 appHighlight>{{ title }} {{ subtitle }}</h1>
<p *ngIf="user">
<i>Welcome, {{ user }}</i>
</p>
`
})
export class TitleComponent {
@Input() subtitle = '';
title = 'Tour Of Technologies';
user = '';
constructor(userService: UserService) {
this.user = userService.userName;
}
}
CoreModule Core Module v4:
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TitleComponent } from './title.component';
import { UserService, UserServiceConfig } from './user.service';
@NgModule({
imports: [ CommonModule ],
declarations: [ TitleComponent ],
exports: [ TitleComponent ],
providers: [ UserService ]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(`
CoreModule is already loaded. Import it in AppModule only.
`);
}
}
static forRoot(config: UserServiceConfig): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [{provide: UserServiceConfig, useValue: config}]
};
}
}
CrisisService CrisisModule v4:
import { Injectable } from '@angular/core';
export class Crisis {
constructor(public id: number, public name: string) { }
}
const CRISES: Crisis[] = [
new Crisis(1, 'Angular has a performance problem'),
new Crisis(2, 'Angular CLI isn\'t progressing fast enough'),
new Crisis(3, 'Angular is not innovating fast enough'),
new Crisis(4, 'Angular is not on the first page of GitHub Star Count')
];
const FETCH_LATENCY = 500;
@Injectable()
export class CrisisService {
getCrises() {
return new Promise<Crisis[]>(resolve => {
setTimeout(() => { resolve(CRISES); }, FETCH_LATENCY);
});
}
getCrisis(id: number | string) {
return this.getCrises()
.then(technologies => technologies.find(technology => technology.id === +id));
}
}
CrisisListComponent CrisisModule v4:
import { Component, OnInit } from '@angular/core';
import { Crisis, CrisisService } from './crisis.service';
@Component({
template: `
<h3 appHighlight>Crisis List</h3>
<div *ngFor="let crisis of crises | async">
<a routerLink="{{'../' + crisis.id}}" class="default">
{{ crisis.id }} - {{ crisis.name }}
</a>
</div>
`
})
export class CrisisListComponent implements OnInit {
crises: Promise<Crisis[]>;
constructor(private crisisService: CrisisService) { }
ngOnInit() {
this.crises = this.crisisService.getCrises();
}
}
CrisisDetailComponent CrisisModule v4:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
template: `
<h3 appHighlight>Crisis Detail</h3>
<div>Crisis id: {{ id }}</div>
<br>
<a routerLink="../list">Crisis List</a>
`
})
export class CrisisDetailComponent implements OnInit {
id: number;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
}
}
CrisisRoutingModule CrisisModule v4:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CrisisListComponent } from './crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail.component';
const routes: Routes = [
{ path: '', redirectTo: 'list', pathMatch: 'full' },
{ path: 'list', component: CrisisListComponent },
{ path: ':id', component: CrisisDetailComponent }
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class CrisisRoutingModule { }
CrisisModule CrisisModule v4:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CrisisListComponent } from './crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail.component';
import { CrisisService } from './crisis.service';
import { CrisisRoutingModule } from './crisis-routing.module';
@NgModule({
imports: [ CommonModule, CrisisRoutingModule ],
declarations: [ CrisisListComponent, CrisisDetailComponent ],
providers: [ CrisisService ]
})
export class CrisisModule { }
AwesomePipe Shared Module v4:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'appAwesome'
})
/** precede the input string with the word awesome **/
export class AwesomePipe implements PipeTransform {
transform(phrase: string) {
return phrase ? 'Awesome ' + phrase : '';
}
}
HighlightDirective Shared Module v4:
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[appHighlight]'})
/** highlight attached element in red and set font to white **/
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = '#ff4d4d';
el.nativeElement.style.color = '#ffffff';
console.log(`* Shared highlight highlight called for ${el.nativeElement.tagName}`);
}
}
SharedModule Shared Module v4:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [ CommonModule ],
declarations: [ AwesomePipe, HighlightDirective ],
exports: [ AwesomePipe, HighlightDirective, CommonModule, FormsModule ]
})
export class SharedModule { }
TechnologyService TechnologyModule v4:
import { Injectable } from '@angular/core';
export class Technology {
constructor(public id: number, public name: string) { }
}
const TECHNOLOGIES: Technology[] = [
new Technology(11, 'Angular 5'),
new Technology(12, 'Angular Material 5'),
new Technology(13, 'Angular CLI 1.5'),
new Technology(14, 'Angular Universal'),
new Technology(15, 'Angular Service Worker'),
new Technology(16, 'Ionic 3')
];
const FETCH_LATENCY = 500;
@Injectable()
export class TechnologyService {
getTechnologies() {
return new Promise<Technology[]>(resolve => {
setTimeout(() => { resolve(TECHNOLOGIES); }, FETCH_LATENCY);
});
}
getTechnology(id: number | string) {
return this.getTechnologies()
.then(technologies => technologies.find(t => t.id === +id));
}
}
TechnologyComponent TechnologyModule v4:
import { Component } from '@angular/core';
import { TechnologyService } from './technology.service';
import { UserService } from '../core/user.service';
@Component({
template: `
<h2>Technologies of {{ userName }}</h2>
<router-outlet></router-outlet>
`,
providers: [ TechnologyService ]
})
export class TechnologyComponent {
userName = '';
constructor(userService: UserService) {
this.userName = userService.userName;
}
}
TechnologyListComponent TechnologyModule v4:
import { Component, OnInit } from '@angular/core';
import { Technology, TechnologyService } from './technology.service';
@Component({
selector: 'app-technology-list',
template: `
<h3 appHighlight>Technology List</h3>
<div *ngFor="let technology of technologies | async">
<a routerLink="{{ technology.id }}" class="default">{{ technology.id }} - {{ technology.name }}</a>
</div>
`
})
export class TechnologyListComponent implements OnInit {
technologies: Promise<Technology[]>;
constructor(private technologyService: TechnologyService) { }
ngOnInit() {
this.technologies = this.technologyService.getTechnologies();
}
}
TechnologyDetailComponent TechnologyModule v4:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Technology, TechnologyService } from './technology.service';
@Component({
template: `
<h3 appHighlight>Technology Detail</h3>
<div *ngIf="technology">
<div>Id: {{ technology.id }}</div><br>
<label>Name:
<input [(ngModel)]="technology.name">
</label>
</div>
<br>
<a routerLink="../">Technology List</a>
`
})
export class TechnologyDetailComponent implements OnInit {
technology: Technology;
constructor(private route: ActivatedRoute, private technologyService: TechnologyService) { }
ngOnInit() {
const id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
this.technologyService.getTechnology(id).then(technology => this.technology = technology);
}
}
TechnologyRoutingModule TechnologyModule v4:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TechnologyComponent } from './technology.component';
import { TechnologyListComponent } from './technology-list.component';
import { TechnologyDetailComponent } from './technology-detail.component';
const routes: Routes = [
{ path: '', component: TechnologyComponent,
children: [
{ path: '', component: TechnologyListComponent },
{ path: ':id', component: TechnologyDetailComponent }
]
}
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class TechnologyRoutingModule { }
TechnologyModule TechnologyModule v4:
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { TechnologyComponent } from './technology.component';
import { TechnologyDetailComponent } from './technology-detail.component';
import { TechnologyListComponent } from './technology-list.component';
import { TechnologyRoutingModule } from './technology-routing.module';
@NgModule({
imports: [ SharedModule, TechnologyRoutingModule ],
declarations: [ TechnologyComponent, TechnologyDetailComponent, TechnologyListComponent ]
})
export class TechnologyModule { }
AppModule v4:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { PrincessComponent } from './princess.component';
import { ContactModule } from './contact/contact.module';
import { CoreModule } from './core/core.module';
import { AppRoutingModule } from './app.routing.module';
@NgModule({
declarations: [
AppComponent,
PrincessComponent
],
imports: [
BrowserModule,
ContactModule,
CoreModule.forRoot({ userName: 'Miss Polymer Princess'}),
AppRoutingModule
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule { }
AppRoutingModule v4:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
export const routes: Routes = [
{ path: '', redirectTo: 'contact', pathMatch: 'full' },
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'technologies', loadChildren: 'app/technology/technology.module#TechnologyModule' }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule { }
AppComponent v4:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="container">
<div class="left">
<app-title [subtitle]="subtitle"></app-title>
<nav>
<a routerLink="contact" routerLinkActive="active">Contact</a>
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
<a routerLink="technologies" routerLinkActive="active">Technologies</a>
</nav>
<router-outlet></router-outlet>
</div>
<div class="right">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
subtitle = '(v4)';
}
Let's have a look at the running app in Chromium:


