5. Components & Directives
A simple component with interpolation:
import { Component } from '@angular/core';
export class Book {
constructor(public title: string, public date: Date, public author: string) {}
}
@Component({
selector: 'app-article',
template: `
<h1>{{ book.title }}</h1>
<p>{{ book.date | date:'medium' }}</p>
<h3>Written by: {{ book.author }}</h3>
`,
styles: [`h3 { font-style: italic; }`]
})
export class ArticleComponent {
book = new Book('Angular 4 Love Affair', new Date(), 'Nils-Holger Nägele');
}
Parent child component communication with @Input():
Child component:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-attribution',
template: `
<h3>Written by: {{ author }}</h3>
`,
styles: [`h3 { font-style: italic; }`]
})
export class AttributionComponent {
@Input() author: string;
}
Parent component:
import { Component } from '@angular/core';
export class Book {
constructor(public title: string, public date: Date, public author: string) {}
}
@Component({
selector: 'app-article',
template: `
<h1>{{ book.title }}</h1>
<p>{{ book.date | date:'medium' }}</p>
<app-attribution [author]="book.author"></app-attribution>
`
})
export class ArticleComponent {
book = new Book('Angular 4 Love Affair', new Date(), 'Nils-Holger Nägele');
}
Prefix attribute with bind- for identical behavior:
<app-attribution bind-author="book.author"></app-attribution>
Bind to native attribute:
import { Component } from '@angular/core';
@Component({
selector: 'app-princess',
template: `
<img src="{{ imageUrl }}" style="width:60rem; border-radius:50%">
<i class="fa fa-heart fa-2x myColor" aria-hidden="true"> My Polymer Princess</i>
`,
styles: ['.myColor { color: red; }']
})
export class PrincessComponent{
imageUrl = '../assets/keras6.jpg';
}
Event Binding:
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<h1>{{ title }}</h1>
<p>Shares: {{ shareCount }}</p>
<button (click)="share($event)">Share</button>
`
})
export class ArticleComponent {
title = 'The will to win is nothing without the will to prepare.';
shareCount = 0;
share(event: Event): void {
console.log(event); // Mouse Event
this.shareCount++;
}
}
Prefixing on- to event attribute provides identical behavior:
<button on-click="share($event)">Share</button>
Child to parent component communication with EventEmitter:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-text-editor',
template: `
<textarea (keyup)="wordCount($event)"></textarea>
`
})
export class TextEditorComponent {
@Output() updateCount = new EventEmitter<number>();
wordCount(event): void {
this.updateCount.emit((event.target.value.match(/\S+/g) || []).length);
}
}
@Component({
selector: 'app-article',
template: `
<h1>{{ title }}</h1>
<p>Word count: {{ wordCount }}</p>
<app-text-editor (updateCount)="updateWordCount($event)"></app-text-editor>
`
})
export class ArticleComponent {
title = 'I have met my hero, he is me.';
wordCount = 0;
updateWordCount(count: number): void {
this.wordCount = count;
}
}
Directives: attach behavior to DOM elements:
import { Directive, HostListener } from '@angular/core';
@Directive({
selector: '[appClickToReveal]'
})
export class ClickToRevealDirective {
@HostListener('click', ['$event.target'])
// @HostListener('mouseover', ['$event.target'])
reveal(target) {
target.style['white-space'] = 'normal';
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<h1 appClickToReveal>{{ title }}</h1>
`,
styles: [`
h1 { text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 350px; }`
]
})
export class ArticleComponent {
title = `I have met my hero, he is me. Somebody may beat me but
they are going to have to bleed to do it.`;
}
ngContent to project nested content
import { Component } from '@angular/core';
@Component({
selector: 'app-ad-section',
template: `
<a href="https://ng-be.org/">{{ advertisementText }}</a>
<ng-content selection="p"></ng-content>
`
})
export class AdSectionComponent {
advertisementText = 'Come to NG-BE 2017 in December';
}
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<app-ad-section>
<p>I'm going to work that it's a pure guts race at the end, and if it is.
I am the only one who can win it.</p>
<p>I've learned that finishing a marathon isn't just an athelectic achievement.
It's a state of mind; a state of mind that says anything is possible.</p>
</app-ad-section>
`
})
export class ArticleComponent {
title = `I have met my hero, he is me. Somebody may beat me but
they are going to have to bleed to do it.`;
}
ngIf && ngFor structural directives:
import { Component } from '@angular/core';
export interface Technologies {
id: number;
name: string;
active: boolean;
}
@Component({
selector: 'app-article',
template: `
<div *ngFor="let technology of technologies; let i = index">
<h3 *ngIf="technology.active">
{{ i }}: {{ technology.name }}
</h3>
</div>
`
})
export class ArticleComponent {
technologies: Array<Technologies> = [
{ id: 1, name: 'Angular 5 RC1', active: true },
{ id: 2, name: 'Angular 4', active: false },
{ id: 3, name: 'Angular CLI', active: true },
{ id: 4, name: 'Angular Material', active: false }
];
}
Decompose to template directives on same element, like this:
template: `
<div template="ngFor let technology of technologies; let i = index">
<h3 template="ngIf technology.active">
{{ i }}: {{ technology.name }}
</h3>
</div>
`
Decompose template directive into wrapping <template> element, like this:
template: `
<template ngFor let-technology [ngForOf]="technologies" let-i="index">
<div>
<template [ngIf]="technology.active">
<h3>
{{ i }}: {{ technology.name }}
</h3>
</template>
</div>
</template>
`
Template Reference Variable:
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<input #inputTitle (keyup)="0">
<h2>{{ inputTitle.value }}</h2>
`
})
export class ArticleComponent {
}
Set property in component:
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<input #inputTitle (keyup)="setTitle(inputTitle.value)">
<h2>{{ theTitle }}</h2>
`
})
export class ArticleComponent {
theTitle = '';
setTitle(value: string): void {
this.theTitle = value;
}
}
Attribute property binding:
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<input #inputTitle (keydown.enter)="setTitle(inputTitle.value)"
(keyup)="checkCache(inputTitle.value)">
<h2 [style.color]="isCached ? 'red' : 'green'">{{ theTitle }}</h2>
`
})
export class ArticleComponent {
theTitle = '';
isCached = false;
setTitle(value: string): void {
this.theTitle = value;
}
checkCache(value: string): void {
this.isCached = value !== this.theTitle;
}
}
Lifecycle hooks:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-list',
template: `
<input (keyup.enter)="add($event)">
<app-article *ngFor="let title of titles; let i = index;"
[articleTitle]="title">
<button (click)="remove(i)">X</button>
</app-article>
`
})
export class ArticleListComponent {
titles: string[] = [];
add(event: any): void {
this.titles.push(event.target.value);
event.target.value = '';
}
remove(index: number): void {
this.titles.splice(index, 1);
}
}
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<h1>
<ng-content></ng-content>{{ articleTitle }}
</h1>
`
})
export class ArticleComponent implements OnInit, OnDestroy {
@Input() articleTitle: string;
ngOnInit() {
console.log('OnInit', this.articleTitle);
}
ngOnDestroy() {
console.log('OnDestroy', this.articleTitle);
}
}
Child component referencing parent component:
import { Component } from '@angular/core';
@Component({
selector: 'app-article',
template: `
<app-feedback [value]="likes"></app-feedback>
`
})
export class ArticleComponent {
likes = 0;
increment(): void {
this.likes++;
}
}
import { Component, Input } from '@angular/core';
import { ArticleComponent } from '../article/article.component';
@Component({
selector: 'app-feedback',
template: `
<h2>Number of likes: {{ value }}</h2>
<button (click)="likeTechnology()">Like this technology!</button>
`
})
export class FeedbackComponent {
@Input() value: number;
constructor(private articleComponent: ArticleComponent) { }
likeTechnology(): void {
this.articleComponent.increment();
}
}
ViewChild & forwardRef mutual parent child awareness:
import { Component, Input, Inject, forwardRef } from '@angular/core';
import { ArticleComponent } from '../article/article.component';
@Component({
selector: 'app-feedback',
template: `
<h2>Number of likes: {{ value }}</h2>
<button (click)="likeTechnology()" [disabled]="!likeEnabled">Like this technology!</button>
`
})
export class FeedbackComponent {
@Input() value: number;
likeEnabled = false;
constructor(@Inject(forwardRef(() => ArticleComponent ))private articleComponent: ArticleComponent) { }
likeTechnology(): void {
this.articleComponent.increment();
}
setLikeEnabled(enabledStatus: boolean): void {
this.likeEnabled = enabledStatus;
}
}
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { FeedbackComponent } from '../feedback/feedback.component';
@Component({
selector: 'app-article',
template: `
<input type="checkbox" (click)="changeLikesEnabled($event)">Enable Likes
<app-feedback [value]="likes"></app-feedback>
`
})
export class ArticleComponent implements AfterViewInit {
@ViewChild(FeedbackComponent) feedbackComponent: FeedbackComponent;
likes = 0;
constructor() {
console.log(this.feedbackComponent);
}
ngAfterViewInit() {
console.log(this.feedbackComponent);
}
increment(): void {
this.likes++;
}
changeLikesEnabled(event): void {
this.feedbackComponent.setLikeEnabled(event.target.checked);
}
}
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="row">
<h1>{{ title }}</h1>
<div class="col-md-6">
<app-article></app-article>
</div>
<div class="col-md-6">
<app-princess></app-princess>
</div>
</div>
`,
styles: ['']
})
export class AppComponent {
title = 'Angular 4 Love Affair';
}
ContentChild & forwardRef mutual parent child awareness:
import { Component, Inject, forwardRef } from '@angular/core';
import { ArticleComponent } from '../article/article.component';
@Component({
selector: 'app-feedback',
template: `
<h2>Number of likes: {{ value }}</h2>
<button (click)="likeTechnology()" [disabled]="!likeEnabled">Like this technology!</button>
`
})
export class FeedbackComponent {
value: number;
likeEnabled = false;
constructor(@Inject(forwardRef(() => ArticleComponent ))
private articleComponent: ArticleComponent) {
this.updateLikes();
}
updateLikes() {
this.value = this.articleComponent.likes;
}
likeTechnology(): void {
this.articleComponent.increment();
this.updateLikes();
}
setLikeEnabled(enabledStatus: boolean): void {
this.likeEnabled = enabledStatus;
}
}
import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { FeedbackComponent } from '../feedback/feedback.component';
@Component({
selector: 'app-article',
template: `
<input type="checkbox" (click)="changeLikesEnabled($event)">Enable Likes
<ng-content></ng-content>
`
})
export class ArticleComponent implements AfterContentInit {
@ContentChild(FeedbackComponent) feedbackComponent: FeedbackComponent;
likes = 0;
constructor() {
console.log(this.feedbackComponent);
}
ngAfterContentInit() {
console.log(this.feedbackComponent);
}
increment(): void {
this.likes++;
}
changeLikesEnabled(event): void {
this.feedbackComponent.setLikeEnabled(event.target.checked);
}
}