Tutorial kali ini, kita akan melanjutkan tutorial aplikasi Tour of Heroes untuk menampilkan list heroes, dan memungkinkan user untuk memilih hero dan menampilkan detail hero.

Akhir dari tutorial ini dapat kalian lihat di sini live example / downloadable example.

Displaying heroes

Untuk menampilkan list dari heroes, kita harus menambahkan data heroes di view template.

Create heroes

Buat 10 heroes dalam array.

src/app/app.component.ts (hero array)

const HEROES: Hero[] = [
  { 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' }
];

Array heroes merupakan tipe dari Hero, sebuah class yang telah kita definisikan di tutorial sebelumnya.

Expose heroes

Buat public property di dalam class AppComponent yang meng-expose heroes agar dapat kita binding.

app.component.ts (hero array property)

heroes = HEROES;

Type heroes tidak terdefinisi karena TypeScipt mengambilnya dari dari array HEROES.

Data hero terpisah dari class implementasi karena nanti pada akhirnya nama hero akan di ambil dari data service.

Menampilkan nama hero di dalam template

Untuk menampilkan nama heroes pada unordered list, masukan code HTML di bawah ini di bawah title dan di atas hero detail.

app.component.ts (heroes template)

<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

Dari html ini kita bisa mengisi template dengan nama hero.

List heroes dengan ngFor

Tujuan dari bagian ini adalah unuk bind array dari heroes di dalam component template, dan menampilkannya secara individually.

Modifikasi tag <li> dengan menambahkan built-in directive *ngFor.

app.component.ts (ngFor)

<li *ngFor="let hero of heroes">

Baca lebih lanjut dengan ngFor dan input variabel template di sesi menampilkan array dengan *ngFor, Displaying Data, sesi ngFor, dan Template Syntax.

Didalam tag <li>, tambahkan content dengan menggunakan template hero variabel untuk menampilkan property hero.

app.component.ts (ngFor template)


<li *ngFor="let hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

Ketika browser me-refresh, list heroes akan tampil.

Style heroes

Untuk menambahkan style dari component kita, set styles property dalam @Component decorator dengan class CSS di bawah ini.

src/app/app.component.ts (styles)

styles: [`
  .selected {
    background-color: #CFD8DC !important;
    color: white;
  }
  .heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
  }
  .heroes li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 4px;
  }
  .heroes li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .heroes li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .heroes .text {
    position: relative;
    top: -3px;
  }
  .heroes .badge {
    display: inline-block;
    font-size: small;
    color: white;
    padding: 0.8em 0.7em 0 0.7em;
    background-color: #607D8B;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -4px;
    height: 1.8em;
    margin-right: .8em;
    border-radius: 4px 0 0 4px;
  }
`]

Jangan lupa menggunakan notasi backtick untuk membuat multi-line string.

Menambah style membuat file menjadi panjang. Di tutorial selanjutnya kita akan memindahkan style ke file terpisah.

Ketika kita menambahkan style di dalam component, mereka akan terbatas hanya sebatas component tersebut. Style di atas hanya untuk AppComponent dan tidak berefek pada HTML di luar component tersebut.

Template untuk menampilkan heroes akan terlihat seperti berikut ini

src/app/app.component.ts (styled heroes)


<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

Memilih hero

Aplikasi saat ini dapat menampilkan list hero dengan baik dengan single hero dalam details view. Tapi list dan detail view tidak saling terkoneksi. Ketika user memilih hero dari form list, hero yang di pilih harus menampilkan detail view. UI pattern ini di sebut dengan “master/detail”. Di dalam kasus, master adalah heroes list dan detail adalah detail hero yang di pilih.

Selanjutnya kita akan mengkoneksikan master ke detail dengan component selectedHero, dengan event click.

Menangani click event

Tambahkan event click binding pada tag <li> seperti ini.

app.component.ts (template excerpt)

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  ...
</li>

Ekspresi onSelect(hero) memanggil method pada AppComponent yaitu onSelect(), passing variabel template input hero sebagai argument.

Pelajari lebih lanjut mengenai event binding pada User Input dan sesi Event binding pada page Template Syntax.

Tambahkan click handler untuk mengakses selected hero

Kita tidak perlu membuat property hero karena kita tidak menampilkan single hero, kita menampilkan list heroes. Tapi user dapat memilih salah satu heroes dengan mengkliknya. Jadi ganti property hero dengan property selectedHero.

src/app/app.component.ts (selectedHero)

selectedHero: Hero;

Tambahkan method onSelect() yang mengatur property selectedHero kepada hero yang user click.

src/app/app.component.ts (onSelect)

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

Bind property selectedHero yang baru dengan code di bawah ini.

app.component.ts (template excerpt)

<h2> details!</h2>
<div><label>id: </label></div>
<div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>

Sembunyikan empty detail dengan ngIF

Ketika aplikasi di load, selectedHero tidak terdifinisi. Hero yang di pilih di inisiasi ketika user mengklik nama hero. Angular tidak bisa menampilkan property selectedHero yang tidak terfinisi dan menampilkan error yang terlihat pada console browser.

EXCEPTION: TypeError: Cannot read property ‘name’ of undefined in [null]

Tambahkan built-in directive ngIf dan set pada property selectedHero pada component.

src/app/app.component.ts (ngIf)


<div *ngIf="selectedHero">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  </div>
</div>

Sekarang aplikasi tidak lagi error dan list nama kembali muncul pada browser.

Ketika tidak ada hero yang di pilih, directive ngIf membuang detail hero HTML dari DOM.

Ketika user memilih hero, selectedHero menjadi terdefinisi dan ngIf mengambil content detail hero dan menampilkannya pada DOM.

Baca lebih lanjut mengenai ngIf dan ngFor dalam halaman Structural Directives dan sesi Built-in directive dari halaman Template Syntax.

Style selected hero

Dalam styles metadata yang sudah kita buat di atas, disana ada CSS class dengan nama selected. Untuk membuat hero yang di pilih lebih terlihat, kita akan memakai class selected pada tag <li> ketika user mengklik pada nama hero. Sebagai contoh, ketika user klik “Magneta”, warna background akan menjadi seperti ini.

style selected hero.

Di dalam template, tambahkan [class.selected] binding dengan <li>.

app.component.ts (setting the CSS class)

[class.selected]="hero === selectedHero"

Ketika ekspresi (hero === selectedHero) bernilai true, Angular menambahkan CSS class selected. Ketika ekspresi bernilai false, Angular menghilangkan class selected.

Final version dari tag <li> akan terlihat seperti ini.

app.component.ts (styling each hero)


<li *ngFor="let hero of heroes"
  [class.selected]="hero === selectedHero"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

Setelah “Magneta”, list akan terlihat seperti ini.

list hero

Berikut adalah file app.component.ts yang lengkap.

src/app/app.component.ts


import { Component } from '@angular/core';
export class Hero {
  id: number;
  name: string;
}
const HEROES: Hero[] = [
  { 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' }
];
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <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>
    <div *ngIf="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="selectedHero.name" placeholder="name"/>
      </div>
    </div>
  `,
  styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

Apa saja yang sudah kita pelajari?

Di tutorial selanjutnya, kita akan memisahkan aplikasi ke pada subcomponent dan membuat mereka dapat bekerja secara bersamaan.

Referensi