22. Advanced Angular

ViewEncapsulation.Emulated (styles are scoped to component and do not leak outwards, default):

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

@Component({
  selector: 'app-highlight',
  template: `
              <mat-card>
                <div class="highlight">
                the will to win is nothing without the will to prepare
                </div>
              </mat-card>
  `,
  styles: [`
      mat-card {
        width: 600px;
      }
      .highlight {
        border: 3px solid red;
        background-color: yellow;
        text-align: center;
        margin-bottom: 20px;
      }
    `],
encapsulation: ViewEncapsulation.Emulated
})
export class HighlightComponent { }

ViewEncapsulation.None (styles apply to all elements on the page, upward and downward, like global styles):

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

@Component({
  selector: 'app-highlight',
  template: `
              <mat-card>
                <div class="highlight">
                the will to win is nothing without the will to prepare
                </div>
              </mat-card>
  `,
  styles: [`
      mat-card {
        width: 600px;
      }
      .highlight {
        border: 3px solid red;
        background-color: yellow;
        text-align: center;
        margin-bottom: 20px;
      }
    `],
encapsulation: ViewEncapsulation.None
})
export class HighlightComponent { }

ViewEncapsulation.Native Shadow DOM, everything inside #shadow-root element is encapsulated and isolated from the rest of the page:

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

@Component({
  selector: 'app-highlight',
  template: `
              <mat-card>
                <div class="highlight">
                the will to win is nothing without the will to prepare
                </div>
              </mat-card>
  `,
  styles: [`
      mat-card {
        width: 600px;
      }
      .highlight {
        border: 3px solid red;
        background-color: yellow;
        text-align: center;
        margin-bottom: 20px;
      }
    `],
encapsulation: ViewEncapsulation.Native
})
export class HighlightComponent { }
import { Directive, ElementRef, Input, HostListener } from '@angular/core';

@Directive({
  selector: '[appPopup]',
  exportAs: 'appPopup'
})
export class PopupDirective {
@Input() message: string;

  constructor(private el: ElementRef) {
    console.log(el);
  }

  @HostListener('click') displayMessage(): void {
    alert(this.message);
  }

}

Host for Popup Directive:

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


@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                Angular 5 Tour Of Technologies
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">
          <div>
                <p appPopup #popup1="appPopup" message="clicked p tag">this should be bound to our popup directive</p>
                <mat-icon appPopup #popup2="appPopup" message="clicked icon tag">favorite</mat-icon>
                <button mat-raised-button color="warn" (click)="popup1.displayMessage()">
                  Display popup for p element
                </button>
                <button mat-raised-button color="primary" (click)="popup2.displayMessage()">
                  Display popup for icon element
                </button>
          </div>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    `]
})
export class AppComponent {
  title = 'Tour of Technologies';

}

MessageComponent Content Projection:

import { Component, OnInit, Input, HostBinding, ViewEncapsulation } from '@angular/core';

@Component({
  selector: '[appMessage]',
  template: `
            <div class="header">
                {{ header }}
            </div>
            <mat-card class="size">
                <ng-content></ng-content>
            </mat-card>
  `,
  styles: [`
        .size {
          width: 600px;
        }
    `],
  encapsulation: ViewEncapsulation.Emulated
})
export class MessageComponent implements OnInit {
  @Input() header: string;
  @HostBinding('attr.class') cssClass = 'mat-display-1';

  ngOnInit() {
    console.log('header', this.header);
  }

}

Host Component:

  <div class="app-content">
            <div appMessage header="My Message">
                I come to bring you love, peace and Open Source Software.
            </div>
          <app-princess></app-princess>
  </div>

Let's view the results in our Chromium Web Browser:

OnInit/OnDestroy Component:

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-life-cycle-hooks',
  template: `
        <div>
            <mat-icon>home</mat-icon> Init/Destroy
        </div>
  `,
  styles: [``]
})
export class LifeCycleHooksComponent implements OnInit, OnDestroy {

  constructor() { }

  ngOnInit() {
    console.log('OnInit invoked');
  }

  ngOnDestroy() {
    console.log('OnDestroy invoked');
  }

}

Host Component:

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


@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                Angular 5 Tour Of Technologies
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">
          <mat-card>
          <h4>OnInit and OnDestroy</h4>
          <button mat-raised-button color="primary" (click)="toggle()">Toggle</button>
          <app-life-cycle-hooks *ngIf="display"></app-life-cycle-hooks>
          </mat-card>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    `]
})
export class AppComponent {
  title = 'Tour of Technologies';
  display = false;

  toggle(): void {
    this.display = !this.display;
  }

}

OnChanges:

import { Component, OnChanges, Input, SimpleChange } from '@angular/core';

@Component({
  selector: 'app-life-cycle-hooks',
  template: `
        <mat-card>
        <mat-card-header>
            <mat-card-title>{{ name }}</mat-card-title>
            <mat-card-subtitle>{{ comment }}</mat-card-subtitle>
        </mat-card-header>
        </mat-card>
  `,
  styles: [``]
})
export class LifeCycleHooksComponent implements OnChanges {
  @Input() name: string;
  @Input() comment: string;


  constructor() { }

  ngOnChanges(changes: {[propName: string]: SimpleChange}) {
        console.log('Changes', changes);
  }

}

Host Component:

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


@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                Angular 5 Tour Of Technologies
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">
          <h4>OnChanges</h4>
          <form>
            <mat-form-field>
              <input #namefld
                      matInput
                      (keyup)="setValues(namefld, commentfld)"
                      placeholder="Enter your name"
                      value="{{ name }}">
            </mat-form-field>

            <mat-form-field>
              <textarea #commentfld
                         matInput
                         (keyup)="setValues(namefld, commentfld)"
                        placeholder="Leave a comment">{{ comment }}
              </textarea>
            </mat-form-field>
          </form>
          <app-life-cycle-hooks [name]="name" [comment]="comment">
          </app-life-cycle-hooks>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    `]
})
export class AppComponent implements OnInit {
  title = 'Tour of Technologies';
  name = '';
  comment = '';

  ngOnInit() {
  this.name = 'Nils-Holger Nägele';
  this.comment = 'I love learning ...';
  }

  setValues(namefld, commentfld): void {
    this.name = namefld.value;
    this.comment = commentfld.value;
  }

}

appIf:

import { Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core';

@Directive({
  selector: '[appIf]'
})
export class IfDirective {
  @Input() set appIf(condition) {
    if (condition) {
      this.viewContainer.createEmbeddedView(this.template);
    } else {
      this.viewContainer.clear();
    }
  }
  constructor(private viewContainer: ViewContainerRef,
              private template: TemplateRef<any>) { }

}

Host Component:

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


@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                Angular 5 Tour Of Technologies
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">

          <mat-card>
            <button mat-raised-button color="warn" (click)="toggle()">
                Toggle
            </button>
            <div *appIf="display">
                this message is displayed if condition evaluates to true.
            </div>
          </mat-card>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    `]
})
export class AppComponent  {
  title = 'Tour of Technologies';
  display = true;

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

}

appFor:

import { Directive, IterableDiffer, IterableDiffers, ViewRef, ViewContainerRef,
          TemplateRef, ChangeDetectorRef, DoCheck, Input} from '@angular/core';

@Directive({
  selector: '[appFor]'
})
export class ForDirective implements DoCheck {
  private items: any;
  private differ: IterableDiffer<any>;

  private views: Map<any, ViewRef> = new Map<any, ViewRef>();

  constructor(private viewContainer: ViewContainerRef,
              private template: TemplateRef<any>,
              private differs: IterableDiffers) { }

  @Input() set appForOf(items) {
    this.items = items;
    if (this.items && !this.differ) {
      this.differ = this.differs.find(items).create();
    }
  }
  ngDoCheck(): void {
      if (this.differ) {
        const changes = this.differ.diff(this.items);
        if (changes) {
          changes.forEachAddedItem(change => {
            const view = this.viewContainer.createEmbeddedView(this.template,
                  { $implicit: change.item });
            this.views.set(change.item, view);
          });
          changes.forEachRemovedItem(change => {
              const view = this.views.get(change.item);
              const idx = this.viewContainer.indexOf(view);
              this.viewContainer.remove(idx);
              this.views.delete(change.item);
          });
        }
      }
  }

}

Host Component:

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


@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                Angular 5 Tour Of Technologies
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">
          <mat-card>
            <mat-list>
                <mat-list-item *appFor="let t of technologies">
                  {{ t.name }} is a {{ t.category }}
                  <button mat-raised-button color="warn" (click)="remove(t)">
                    x
                  </button>
                </mat-list-item>
            </mat-list>
            <form>
              <mat-form-field>
                    <input matInput #name placeholder="Name">
              </mat-form-field>
              <mat-form-field>
                   <textarea matInput #category placeholder="Category"></textarea>
              </mat-form-field>
              <button mat-raised-button color="primary" (click)="add(name, category)">
                Add
              </button>
            </form>
          </mat-card>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    `]
})
export class AppComponent implements OnInit  {
  title = 'Tour of Technologies';
  technologies: any[];


  ngOnInit() {
      this.technologies = [
        { name: 'Angular 5', category: 'Single Page Application Framework' },
        { name: 'Angular CLI 1.6', category: 'Tool to build, scaffold and maintain' },
        { name: 'Angular Material', category: 'CSS Framework' },
        { name: 'Angular Universal', category: 'Server Side Rendering' }
      ];
  }

  remove(technology) {
    const idx = this.technologies.indexOf(technology);
    this.technologies.splice(idx, 1);
    return false;
  }

  add(name, category) {
    this.technologies.push({ name: name.value, category: category.value });
    name.value = '';
    category.value = '';
  }

}

ToolTipDirective:

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

export class Overlay {
  private el: HTMLElement;
  constructor() {
    const elem = document.createElement('div');
    elem.className = 'tooltip';
    this.el = elem;
  }

  close() {
    this.el.hidden = true;
  }

  open(elem, text) {
    this.el.innerHTML = text;
    this.el.hidden = false;
    const rect = elem.nativeElement.getBoundingClientRect();
    this.el.style.left = rect.left + 'px';
    this.el.style.right = rect.top + 'px';
  }

  attach(target) {
    target.appendChild(this.el);
  }

  detach() {
    this.el.parentNode.removeChild(this.el);
  }

}

@Directive({
  selector: '[appToolTip]'
})
export class ToolTipDirective {
  @Input() appToolTip: string;

  constructor(private el: ElementRef, private overlay: Overlay) {
    this.overlay.attach(el.nativeElement);
  }

  @HostListener('mouseenter')
    onMouseEnter() {
      this.overlay.open(this.el, this.appToolTip);
    }

  @HostListener('mouseleave')
    onMouseLeave() {
      this.overlay.close();
    }

}

HostComponent:

import { Component } from '@angular/core';
import { Overlay } from './tool-tip.directive';

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>Sidenav Content here</p>
        </mat-sidenav>
        <div class="app-content">
          <mat-card>
            <div appToolTip="hello baby">My Number is 42</div>
          </mat-card>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  providers: [Overlay],
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    div[appToolTip] {
      width: 150px;
      height: 50px;
      cursor: pointer;
      color: red;
    }
    .tooltip {
      position: absolute;
      background: white;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';

}

ZippyComponent:

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

@Component({
  selector: 'app-zippy-header',
  template: `
      <header>{{ header }}</header>
  `,
  styles: [`
        header {
          cursor: pointer;
          border-bottom: 1px solid #ccc;
          font-size: 1.2em;
          background-color: #eee;
        }

    `]
})
export class ZippyHeaderComponent {
  @Input() header: string;

}

@Component({
  selector: 'app-zippy',
  template: `
          <section>
                <app-zippy-header
                        (click)="visible = !visible"
                        [header]="header">
                </app-zippy-header>
            <div *ngIf="visible">
                  <ng-content select="content"></ng-content>
            </div>
          </section>
  `,
  styles: [`
    section {
      width: 300px;
      border: 1px solid #ccc;
    }
    `]
})
export class ZippyComponent {
    @Input() header: string;
    visible = true;

}

Host Component:

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

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>For My Polymer Princess Carmen</p>
        </mat-sidenav>
        <div class="app-content">
          <mat-card>
            <app-zippy header="Header">
                <content>
                    The world is your oyster. Live life to the fullest.
                    Enjoy every second. Carpe Diem. Peace and Love.
                </content>
            </app-zippy>
          </mat-card>
          <app-princess></app-princess>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
      height: 200px;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';

}

Custom Component:

(function() {
  const template = document.createElement('template');

  template.innerHTML = `
    <style>
      :host {
        all: initial;
        display: block;
        contain: content;
        text-align: center;
        background: linear-gradient(to left, hotpink, transparent);
        max-width: 500px;
        margin: 0 auto;
        border-radius: 8px;
        transition: transform .2s ease-out;
      }

      :host([hidden]) {
        display: none;
      }

      button,
      span {
        font-size: 3rem;
        font-family: monospace;
        padding: 0 .5rem;
      }

      button {
        cursor: pointer;
        background: pink;
        color: black;
        border: 0;
        border-radius: 6px;
        box-shadow: 0 0 5px rgba(173, 61, 85, .5);
      }

      button:active {
        background: #ad3d55;
        color: white;
      }
    </style>
    <div>
      <button type="button" increment>+</button>
      <span></span>
      <button type="button" decrement>-</button>
    </div>
  `;

  class MyCounter extends HTMLElement {
    constructor() {
      super();

      this.increment = this.increment.bind(this);
      this.decrement = this.decrement.bind(this);

      this.attachShadow({ mode: 'open' });
      this.shadowRoot.appendChild(template.content.cloneNode(true));

      this.incrementBtn = this.shadowRoot.querySelector('[increment]');
      this.decrementBtn = this.shadowRoot.querySelector('[decrement]');
      this.displayVal = this.shadowRoot.querySelector('span');

      this.maxReached = new CustomEvent('maxReached');
      this.minReached = new CustomEvent('minReached');
    }

    connectedCallback() {
      this.incrementBtn.addEventListener('click', this.increment);
      this.decrementBtn.addEventListener('click', this.decrement);

      if (!this.hasAttribute('value')) {
        this.setAttribute('value', 1);
      }
    }

    increment() {
      const step = +this.step || 1;
      const newValue = +this.value + step;

      if (this.max) {
        if (newValue > +this.max) {
          this.value = +this.max;
          this.dispatchEvent(this.maxReached);
        } else {
          this.value = +newValue;
        }
      } else {
        this.value = +newValue;
      }
    }

    decrement() {
      const step = +this.step || 1;
      const newValue = +this.value - step;

      if (this.min) {
        if (newValue < +this.min) {
          this.value = +this.min;
          this.dispatchEvent(this.minReached);
        } else {
          this.value = +newValue;
        }
      } else {
        this.value = +newValue;
      }
    }

    static get observedAttributes() {
      return ['value'];
    }

    attributeChangedCallback(name, oldValue, newValue) {
      this.displayVal.innerText = this.value;
    }

    get value() {
      return this.getAttribute('value');
    }

    get step() {
      return this.getAttribute('step');
    }

    get min() {
      return this.getAttribute('min');
    }

    get max() {
      return this.getAttribute('max');
    }

    set value(newValue) {
      this.setAttribute('value', newValue);
    }

    set step(newValue) {
      this.setAttribute('step', newValue);
    }

    set min(newValue) {
      this.setAttribute('min', newValue);
    }

    set max(newValue) {
      this.setAttribute('max', newValue);
    }

    disconnectedCallback() {
      this.incrementBtn.removeEventListener('click', this.increment);
      this.decrementBtn.removeEventListener('click', this.decrement);
    }
  }

  window.customElements.define('my-counter', MyCounter);
})();

HostComponent:

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

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>For My Polymer Princess Carmen</p>
        </mat-sidenav>
        <div class="app-content">
        <mat-grid-list cols="2">
          <mat-grid-tile>
                  <mat-card>
                    <h3>Hello {{ name }}</h3>
                    <my-counter min="0" value="8" max="1024" step="2"></my-counter>
                  </mat-card>
          </mat-grid-tile>
          <mat-grid-tile>
                  <app-princess></app-princess>
          </mat-grid-tile>
        </mat-grid-list>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';
  name = 'Nils-Holger Nägele';
}

Let's view the results in Chromium:

TodoComponent:

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

interface Todo {
  completed: boolean;
  label: string;
}

@Component({
  selector: 'app-todo',
  template: `
        <mat-list>
              <h2>Good Morning {{ name }}!</h2>
              <mat-form-field>
                  <input #newTodo matInput placeholder="Enter a new todo">
              </mat-form-field>
              <button mat-raised-button color="accent"
                     (click)="add(newTodo.value); newTodo.value = ''">
                Add
              </button>
              <h3>What needs to be done?</h3>
              <mat-list-item
                            *ngFor="let todo of todos;
                             let idx = index;" [class.completed]="todo.completed">
                <mat-checkbox [checked]="todo.completed" (change)="toggle(idx)">
                  {{ todo.label }}
                </mat-checkbox>
              </mat-list-item>
        </mat-list>

  `,
  styles: [`
        .completed {
          text-decoration: line-through;
        }
    `],
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoComponent {

  todos: Todo[] = [
    { label: 'Create new Angular 5 Apps', completed: false },
    { label: 'Write new Angular 5 Tutorials', completed: false },
    { label: 'Write Angular 4 Love Affair Gitbook', completed: false },
    { label: 'Read + Code Angular Documentation', completed: false },
    { label: 'Make her a bunch', completed: false },
    { label: 'Save The World', completed: false }
  ];

  name = 'Nils-Holger';

  add(label) {
    if (!label) { return; }
    this.todos.push({ label: label, completed: false });
  }

  remove(idx) {
    this.todos.splice(idx, 1);
  }

  toggle(idx) {
    const todo = this.todos[idx];
    todo.completed = !todo.completed;
  }

}

AppComponent:

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

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>For My Polymer Princess Carmen</p>
        </mat-sidenav>
        <div class="app-content">
        <mat-grid-list cols="2">
          <mat-grid-tile>
                  <mat-card>
                    <app-todo></app-todo>
                  </mat-card>
          </mat-grid-tile>
          <mat-grid-tile>
                  <app-princess></app-princess>
          </mat-grid-tile>
        </mat-grid-list>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';

}

Let's have a look at the results in Chromium:

Inputs && Outputs:

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

interface Todo {
  completed: boolean;
  label: string;
}

@Component({
  selector: 'app-todo-input',
  template: `
      <mat-form-field>
          <input #newTodo matInput [placeholder]="placeholder">
      </mat-form-field>
      <button mat-raised-button color="accent"
          (click)="emit(newTodo.value); newTodo.value = ''">
          {{ buttonLabel }}
     </button>
  `,
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoInputComponent {
  @Input() placeholder: string;
  @Input() buttonLabel: string;
  @Output() add = new EventEmitter<string>();

  emit(label) {
    this.add.emit(label);
  }

}

@Component({
  selector: 'app-todo-list',
  template: `
  <mat-list>
        <mat-list-item
                *ngFor="let todo of todos;
                 let idx = index;" [class.completed]="todo.completed">
                 <mat-checkbox [checked]="todo.completed" (change)="toggle(idx)">
                 {{ todo.label }}
                 </mat-checkbox>
        </mat-list-item>
  </mat-list>
  `,
  styles: [`
        .completed {
          text-decoration: line-through;
        }
    `],
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoListComponent {
  @Input() todos: Todo[];
  @Output() toggleCompletion = new EventEmitter<Todo>();
  toggle(idx: number) {
    const todo = this.todos[idx];
    this.toggleCompletion.emit(todo);
  }
}

@Component({
  selector: 'app-todo',
  template: `
              <h2>Good Morning {{ name }}!</h2>
              <app-todo-input
                          [placeholder]="'Enter a new todo'"
                          [buttonLabel]="'add'"
                          (add)="add($event)">
              </app-todo-input>
              <h3>What needs to be done?</h3>
              <app-todo-list [todos]="todos" (toggleCompletion)="toggle($event)">
              </app-todo-list>

  `,
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoComponent {

  todos: Todo[] = [
    { label: 'Create new Angular 5 Apps', completed: false },
    { label: 'Write new Angular 5 Tutorials', completed: false },
    { label: 'Write Angular 4 Love Affair Gitbook', completed: false },
    { label: 'Read + Code Angular Documentation', completed: false },
    { label: 'Make her a bunch', completed: false },
    { label: 'Save The World', completed: false }
  ];

  name = 'Nils-Holger';

  add(label) {
    if (!label) { return; }
    this.todos.push({ label: label, completed: false });
  }

  toggle(todo: Todo) {
    todo.completed = !todo.completed;
  }

}

Content Projection:

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


@Component({
  selector: 'app-fancy-button',
  template: `
    <button mat-raised-button color="accent"><ng-content></ng-content></button>
  `
})
export class FancyButtonComponent { }

HostComponent:

  <mat-card>
        <app-fancy-button>Click <i>me</i> now!</app-fancy-button>
  </mat-card>

Let's view the results in Chromium:

Content projection multiple chunks:

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


@Component({
  selector: 'app-panel',
  template: `
        <mat-accordion>
        <mat-expansion-panel (opened)="panelOpenState = true"
                             (closed)="panelOpenState = false">
        <mat-expansion-panel-header>
        <mat-panel-title>
            <ng-content select=".panel-title"></ng-content>
        </mat-panel-title>
        <mat-panel-description>
            Currently I am {{ panelOpenState ? 'open' : 'closed' }}
        </mat-panel-description>
        </mat-expansion-panel-header>
        <ng-content select=".panel-content"></ng-content>
        </mat-expansion-panel>
        </mat-accordion>
  `
})
export class PanelComponent {
      panelOpenState = false;

}

Host Component:

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

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>For My Polymer Princess Carmen</p>
        </mat-sidenav>
        <div class="app-content">
        <mat-grid-list cols="2">
          <mat-grid-tile>
                  <mat-card>
                    <app-panel>
                    <span class="panel-title">Self Aware Panel</span>
                    <p class="panel-content">I have met my hero, he is me.</p>
                    </app-panel>
                  </mat-card>
          </mat-grid-tile>
          <mat-grid-tile>
                  <app-princess></app-princess>
          </mat-grid-tile>
        </mat-grid-list>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    mat-card {
      width: 400px;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';

}

Let's view the results in our Chromium Web Browser:

Immutable Data Structures, Change Detection Strategies:

Immutable:

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

import * as Immutable from 'immutable';

interface Todo {
  completed: boolean;
  label: string;
}

@Component({
  selector: 'app-todo-input',
  template: `
      <mat-form-field>
          <input #newTodo matInput [placeholder]="placeholder">
      </mat-form-field>
      <button mat-raised-button color="accent"
          (click)="emit(newTodo.value); newTodo.value = '';">
          {{ buttonLabel }}
     </button>
  `,
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoInputComponent implements DoCheck {
  @Input() placeholder: string;
  @Input() buttonLabel: string;
  @Output() add = new EventEmitter<string>();

  emit(label) {
    this.add.emit(label);
  }

  ngDoCheck() {
    console.log('Change detection runs in TodoInputComponent');
  }

}

@Component({
  selector: 'app-todo-list',
  template: `
  <mat-list>
        <mat-list-item
                *ngFor="let todo of todos; let idx = index;"
                [class.completed]="todo.get('completed')">
                 <mat-checkbox [checked]="todo.get('completed')" (change)="toggle(idx)">
                 {{ todo.get('label') }}
                 </mat-checkbox>
        </mat-list-item>
  </mat-list>
  `,
  styles: [`
        .completed {
          text-decoration: line-through;
        }
    `],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoListComponent implements DoCheck {
  @Input() todos;
  @Output() toggleCompletion = new EventEmitter<number>();
  toggle(idx: number) {
    this.toggleCompletion.emit(idx);
  }

  ngDoCheck() {
    console.log('Change detection runs in TodoListComponent');
  }

}

@Component({
  selector: 'app-todo',
  template: `
              <h2>Good Morning {{ name }}!</h2>
              <app-todo-input
                          [placeholder]="'Enter a new todo'"
                          [buttonLabel]="'add'"
                          (add)="add($event)">
              </app-todo-input>
              <h3>What needs to be done?</h3>
              <app-todo-list [todos]="todos" (toggleCompletion)="toggle($event)">
              </app-todo-list>

  `,
  encapsulation: ViewEncapsulation.Emulated
})
export class TodoComponent implements DoCheck {

  todos = Immutable.fromJS([
    { label: 'Create new Angular 5 Apps', completed: false },
    { label: 'Write new Angular 5 Tutorials', completed: false },
    { label: 'Write Angular 4 Love Affair Gitbook', completed: false },
    { label: 'Read + Code Angular Documentation', completed: false },
    { label: 'Make her a bunch', completed: false },
    { label: 'Save The World', completed: false }
  ]);

  name = 'Nils-Holger';

  add(label) {
    if (!label) { return; }
    this.todos = this.todos.push(Immutable.fromJS({ label: label, completed: false }));
  }

  toggle(idx: number) {
    this.todos = this.todos.update(idx, todo => {
        return Immutable.fromJS({
          label: todo.label,
          completed: !todo.completed
        });
    });
  }

  ngDoCheck() {
    console.log('Change detection runs in TodoComponent');
  }

}

Injector Basics:

import { NgModule, Component, Inject, InjectionToken, Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';

const BUFFER_SIZE = new InjectionToken<number>('buffer-size');

class Buffer {
  constructor(@Inject(BUFFER_SIZE) private size: number) {
    console.log(this.size);
  }

}

@Injectable()
class SocketService {
  constructor(private buffer: Buffer) { }

}

@Component({
  selector: 'app-injector-basics',
  template: `Open your browser's console`
})
class InjectorBasicsComponent {

  constructor(private socketService: SocketService) {
    console.log(socketService);

  }

}

@NgModule({
  imports: [ CommonModule],
  declarations: [ InjectorBasicsComponent ],
  exports: [ InjectorBasicsComponent ],
  providers: [{ provide: BUFFER_SIZE, useValue: 42 }, Buffer, SocketService]
})
export class InjectorBasicsModule { }

forwardRef:

import { NgModule, Component, Inject,
         InjectionToken, Injectable, forwardRef } from '@angular/core';
import { CommonModule } from '@angular/common';

const BUFFER_SIZE = new InjectionToken<number>('buffer-size');

@Injectable()
class SocketService {
  constructor(@Inject(forwardRef(() => Buffer)) private buffer: Buffer) { }

}

class Buffer {
  constructor(@Inject(BUFFER_SIZE) private size: number) {
    console.log(this.size);
  }

}

Developer Class:

export class Developer {
  public id: number;
  public githubHandle: string;
  public avatarUrl: string;
  public realName: string;
  public email: string;
  public technology: string;
  public popular: boolean;
}

DeveloperCollection Class:

import { Developer } from './developer';

export class DeveloperCollection {
  private developers: Developer[] = [];

  getUserByGitHubHandle(username: string): Developer {
    return this.developers.filter(u => u.githubHandle === username)
                          .pop();
  }

  getUserById(id: number): Developer {
    return this.developers.filter(u => u.id === id).pop();
  }

  addDeveloper(dev: Developer): void {
    this.developers.push(dev);
  }

  getAll(): Developer[] {
    return this.developers;
  }

}

Route Configurations:

const routeModule = RouterModule.forRoot([
    {
      path: '',
      redirectTo: 'home',
      pathMatch: 'full'
    },
    {
      path: 'home',
      component: HomeComponent
    },
    {
      path: 'dev-add',
      component: AddDeveloperComponent
    },
    {
      path: 'add-dev',
      redirectTo: 'dev-add'
    }
]);
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
          <mat-toolbar color="primary">
            <button (click)="sidenav.toggle()" mat-mini-fab><mat-icon>menu</mat-icon></button>
            <span class="fill-remaining-space"></span>
            <span>
                {{ title }}
            </span>
          </mat-toolbar>
      <mat-sidenav-container>
        <mat-sidenav #sidenav mode="side" class="app-sidenav">
          <p>For My Polymer Princess Carmen</p>
        </mat-sidenav>
        <div class="app-content">
        <mat-grid-list cols="2" rowHeight="200px">
          <mat-grid-tile>
                  <nav mat-tab-nav-bar>
                    <a mat-tab-link [routerLink]="['home']">Home</a>
                    <a mat-tab-link [routerLink]="['dev-add']">Add Developer</a>
                  </nav>
          </mat-grid-tile>
          <mat-grid-tile rowspan="2">
                  <app-princess></app-princess>
          </mat-grid-tile>
          <mat-grid-tile colspan="1">
                <router-outlet></router-outlet>
          </mat-grid-tile>
        </mat-grid-list>
        </div>
      </mat-sidenav-container>
  `,
  styles: [`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
    }
    `]
})
export class AppComponent  {
  title = 'Angular 5 Tour Of Technologies';

}

EmailValidator:

import { Directive } from '@angular/core';
import { NG_VALIDATORS } from '@angular/forms';

function validateEmail(emailControl) {
  if (!emailControl.value || /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) {
    return null;
  } else {
    return { 'invalidEmail': true };
  }
}
@Directive({
  selector: '[appEmailValidator]',
  providers: [{
    provide: NG_VALIDATORS,
    multi: true,
    useValue: validateEmail
  }]
})
export class EmailValidatorDirective { }

results matching ""

    No results matching ""