exhtml
2/24/2018 - 12:40 PM

Angular 2 Tutorial

// SERVICES

/*

Components shouldn't fetch or save data directly. They should focus on presenting data and delegate data access to a service.
Our service could get hero data from anywhere—a web service, local storage, or a mock data source. Removing data access from components means you can change your mind about the implementation anytime, without touching any components. They don't know how the service works.

We'll create a HeroService that all application classes can use to get heroes. 
Instead of creating that service with 'new', you'll rely on Angular dependency injection to inject it into the HeroesComponent constructor.

Services are a great way to share information among classes that don't know each other. 
You'll create a MessageService and inject it in two places:
- HeroService, which uses the service to send a message.
- MessagesComponent, which displays that message.
*/

//Create the HeroService:
ng generate service hero
//Generates skeleton HeroService class in
//src/app/hero.service.ts

import { Injectable } from '@angular/core'; //imports the Angular Injectable symbol

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of'; // we will simulate getting data from the server with the RxJS of() function.

import { Hero } from './hero'; // import our model
import { HEROES } from './mock-heroes'; // import our mock data
import { MessageService } from './message.service'; // import our message service

@Injectable() //annotates the class with the @Injectable() decorator --tells Angular that this service might itself have injected dependencies (whether it does or it doesn't, it's good practice to keep the decorator).
export class HeroService {

  constructor(
    private messageService: MessageService //declares a private messageService property. Angular will inject the singleton MessageService into that property when it creates the HeroService. This is a typical "service-in-service" scenario: you inject the MessageService into the HeroService which is injected into the HeroesComponent.
  ) { }

  // Method to get and return our mock heroes
  getHeroes(): Observable<Hero[]> {
    // Todo: send the message _after_ fetching the heroes
    this.messageService.add('HeroService: fetched heroes'); // send a message when the heroes are fetched.
    return of(HEROES); //of(HEROES) returns an Observable<Hero[]> that emits a single value, the array of mock heroes. In the HTTP tutorial, you'll call HttpClient.get<Hero[]>() which also returns an Observable<Hero[]> that emits a single value, an array of heroes from the body of the HTTP response.
  }
}


/*
You must provide the HeroService in the dependency injection system before Angular can inject it into the HeroesComponent.
There are several ways to provide the HeroService (each option has pros and cons) 
- in the HeroesComponent, 
- in the AppComponent, 
- in the AppModule (we choose this one for now, and is the popular choice --in fact you can tell it to angular CLI)
*/
ng generate service hero --module=app

//src/app/app.module.ts
providers: [ // add the service to the 'providers' array --this array tells Angular to create a single, shared instance of HeroService and inject into any class that asks for it.
    HeroService,
  ],
// The HeroService is now ready to plug into the HeroesComponent.


// Update HeroesComponent
//src/app/heroes/heroes.component.ts 

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

import { Hero } from '../hero';
import { HeroService } from '../hero.service'; // delete the HEROES import and import the HeroService instead.

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {

  selectedHero: Hero;

  heroes: Hero[]; // Replace the definition of the heroes property with a simple declaration.

  constructor(
    private heroService: HeroService
  ) { } // Inject the HeroService: add a private heroService parameter of type HeroService to the constructor. The parameter simultaneously defines a private 'heroService' property and identifies it as a 'HeroService' injection site. When Angular creates a HeroesComponent, the Dependency Injection system sets the heroService parameter to the singleton instance of HeroService.

  // getHeroes() inside the ngOnInit lifecycle hook and let Angular call ngOnInit at an appropriate time after constructing a HeroesComponent instance.
  ngOnInit() {
    this.getHeroes();
  } // While you could call getHeroes() in the constructor, that's not the best practice. Reserve the constructor for simple initialization such as wiring constructor parameters to properties. The constructor shouldn't do anything.

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  // method to retrieve the heroes from the service
  getHeroes(): void {

    // SYNCHRONOUS VERSION
    // this.heroes = this.heroService.getHeroes(); 

    // Assigns an array of heroes to the component's heroes property. The assignment occurs synchronously, as if the server could return heroes instantly or the browser could freeze the UI while it waited for the server's response.

    /*
    This will not work when we will fetch heroes from a remote server (asynchronous operation). The HeroService must wait for the server to respond, getHeroes() cannot return immediately with hero data, and the browser will not block while the service waits.

    HeroService.getHeroes() must have an 'asynchronous signature' of some kind:
    - It can take a callback
    - It could return a Promise
    - It could return an Observable (Angular's HttpClient methods return RxJS Observables)
     */

    // ASYNCHRONOUS (OBSERVABLE) VERSION
    this.heroService.getHeroes()
        .subscribe(heroes => this.heroes = heroes); // will return an Observable in part because it will eventually use the Angular HttpClient.get() method, that returns an Observable.
    // Here, we wait for the Observable to emit the array of heroes— which could happen now or several minutes from now. Then 'subscribe' passes the emitted array to the callback, which sets the component's heroes property. This asynchronous approach will work when the HeroService requests heroes from the server.
  }
}




// Message system

// 1) Add a MessagesComponent that displays app messages at the bottom of the screen.
// Create MessagesComponent
ng generate component messages
//The CLI creates the component files in the src/app/messages folder and declare MessagesComponent in AppModule.

//Modify the AppComponent template to display the generated MessagesComponent
//src/app/app.component.html
`
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
`

// 2) Create an injectable, app-wide MessageService for sending messages to be displayed
ng generate service message --module=app
//The --module=app option tells the CLI to provide this service in the AppModule,

//src/app/message.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class MessageService {
  messages: string[] = []; // expose its cache of messages

  // add a message to the cache
  add(message: string) {
    this.messages.push(message);
  }

  // clear the cache
  clear() {
    this.messages = [];
  }
}

// 3) Inject MessageService into the HeroService
// 4) Display a message when HeroService fetches heroes successfully
// (see hero.service.ts above)



// MessagesComponent should display all messages, including the message sent by the HeroService when it fetches heroes.
//src/app/messages/messages.component.ts
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';

@Component({
  selector: 'app-messages',
  templateUrl: './messages.component.html',
  styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {

  constructor(
    public messageService: MessageService //declares a public messageService property. Angular will inject the singleton MessageService into that property when it creates the HeroService. **The messageService property must be PUBLIC because you're about to bind to it in the template. Angular only binds to public component properties.
  ) {}

  ngOnInit() {
  }

}


// Bind to the MessageService
//src/app/messages/messages.component.html
`
<div *ngIf="messageService.messages.length">
  <h2>Messages</h2>
  <button class="clear"
          (click)="messageService.clear()">clear</button>
  <div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
`
// - The *ngIf only displays the messages area if there are messages to show.
// - *ngFor presents the list of messages in repeated <div> elements.
// - An Angular event binding binds the button's click event to MessageService.clear().
// Display a Heroes List

//Create mock data
//src/app/mock-heroes.ts

import { Hero } from './hero';

export const HEROES: Hero[] = [ //array of 'Hero'es
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];


// HeroesComponent class file and import the mock HEROES.
//src/app/heroes/heroes.component.ts (import HEROES)

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes'; // import mock data

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'] // component's private CSS styles --Styles and stylesheets identified in @Component metadata are scoped to that specific component, and apply only to the HeroesComponent and don't affect the outer HTML or the HTML in any other component.
})
export class HeroesComponent implements OnInit {

  heroes = HEROES;

  selectedHero: Hero; // There is no selected hero when the application starts


  constructor() { }

  ngOnInit() {
  }

  // method to assign the clicked hero from the template to the component's selectedHero
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}



//heroes.component.html (heroes template)
`(click)="onSelect(hero)"` //event binding syntax: onSelect() is a HeroesComponent method, and Angular calls it with the hero object displayed in the clicked <li>
`[class.selected]="hero === selectedHero"` //Angular class binding: add and remove a CSS class conditionally --apply the .selected class to the <li> when the user clicks it.
`
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="hero === selectedHero"
    (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
`

// When selectedHero is undefined, the ngIf removes the hero detail from the DOM. There are no selectedHero bindings to worry about.
// When the user picks a hero, selectedHero has a value and ngIf puts the hero detail into the DOM.

// When the user clicks a hero in the master list, the component should display the selected hero's details at the bottom of the page.
// you'll listen for the hero item click event and update the hero detail.
`<div *ngIf="selectedHero">` // only display the selected hero details if the selectedHero exists
`
<div *ngIf="selectedHero">

  <h2>{{ selectedHero.name | uppercase }} Details</h2>
  <div><span>id: </span>{{selectedHero.id}}</div>
  <div>
    <label>name:
      <input [(ngModel)]="selectedHero.name" placeholder="name">
    </label>
  </div>

</div>
`


// heroes.component.css
// ...


// THE HERO COMPONENT
// https://angular.io/tutorial/toh-pt1


// Create a Hero class (Model). Give it id and name properties
// src/app/hero.ts
export class Hero {
  id: number;
  name: string;
}

//Create the heroes component
`ng generate component heroes`

// HeroesComponent class file:
// * app/heroes/heroes.component.ts

import { Component, OnInit } from '@angular/core'; // import the Component symbol from the Angular core library
import { Hero } from '../hero'; // import the Hero model

// annotate the component class with @Component
// @Component is a decorator function - specifies the Angular metadata for the component
@Component({
  selector: 'app-heroes', // component's CSS element selector --matches the name of the HTML element that identifies this component within a parent component's template.
  templateUrl: './heroes.component.html', // component's template file
  styleUrls: ['./heroes.component.css'] // component's private CSS styles
})
export class HeroesComponent implements OnInit {

  // define a 'hero' property of type Hero
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };

  constructor() { }

  // ngOnInit lifecycle hook [https://angular.io/guide/lifecycle-hooks#oninit] is called shortly after creating a component
  ngOnInit() {
    // it's a good place to put initialization logic
  }

}

// * app/heroes/heroes.component.html
// we use the built-in UppercasePipe (filters)
`
<h2>{{ hero.name | uppercase }} Details</h2> 
<div><span>id: </span>{{hero.id}}</div>
`
// [(ngModel)] is Angular's two-way data binding syntax.
`
<div>
    <label>name:
      <input [(ngModel)]="hero.name" placeholder="name">
    </label>
</div>
`


// add it to the template of the shell AppComponent.
// * src/app/app.component.html
`
<h1>{{title}}</h1>
<app-heroes></app-heroes>
`

// * src/app/app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public title = 'Tour of Heroes';
}

// Angular needs to know how the pieces of your application fit together and what other files and libraries the app requires. This information is called metadata
// Some of the metadata is in the @Component decorators that you added to your component classes. 
// Other critical metadata is in @NgModule decorators.

// src/app/app.module.ts 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // import the FormsModule symbol from the @angular/forms library --NgModel lives here

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';

// The most important @NgModule decorator annotates the top-level AppModule class.
@NgModule({
  // every component must be declared in exactly one NgModule, and we declare them here
  declarations: [
    AppComponent,
    HeroesComponent
  ],
  // 'imports' array contains a list of external modules that the app needs.
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }