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);
      }

}

results matching ""

    No results matching ""