Angular と Ignite UI for Angular でクロスプラットフォームアプリを作る その 2

[前回](https://qiita.com/kenakamu/items/f256a154e5caf5c55c54) は簡単な CRUD ができるアプリを作りましたが、今回はそこから色々手を加えていきます。やりながら書いているのでどうなるかは分かりません。

# メニューから複数の機能を利用する
現在はユーザーの一覧から色々操作するだけですが、他の機能も加えることを考えます。

## ナビゲーションドロワーを利用する
Ignite UI の NavDrawer という、左右から飛び出るメニューを作れる機能を使って、実装します。

1\. まずはモジュールの追加からします。モジュール名が NavigationDrawerModule であり、Igx から始まりませんが、[GitHub の Issue](https://github.com/IgniteUI/igniteui-angular/issues/512) に上がっているのでそのうち IgxNavigationDrawerModule になるでしょう。

```typescript:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// Ignite UI モジュールの参照
import {
  IgxNavbarModule, IgxIconModule, IgxScrollModule, IgxAvatarModule,
  IgxLabelModule, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule,
  IgxToastModule, IgxDialogModule, IgxCheckboxModule, IgxSwitchModule, IgxSliderModule,
  IgxRadioModule, NavigationDrawerModule } from 'igniteui-angular/main';

// Hammer
import "hammerjs";
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { UserService } from './user/user.service';
import { NewComponent } from './new/new.component';

@NgModule({
  declarations: [
    AppComponent,
    ListComponent,
    DetailComponent,
    NewComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    FormsModule,
    IgxNavbarModule, // Ignite UI Navbar モジュールの追加
    IgxIconModule, // Ignite UI Icon モジュールの追加
    IgxScrollModule, // Ignite UI Scroll モジュールの追加
    IgxAvatarModule, // Ignite UI Avatar モジュールの追加
    IgxLabelModule, // Ignite UI Label モジュールの追加
    IgxInput, // Ignite UI Input の追加
    IgxDatePickerModule, // Ignite UI DatePicker モジュールの追加
    IgxButtonModule, // Ignite UI Button モジュールの追加
    IgxRippleModule, // Ignite UI Ripple モジュールの追加
    IgxToastModule, // Ignite UI Toast モジュールの追加    
    IgxDialogModule,　// Ignite UI Dialog モジュールの追加
    IgxCheckboxModule, // Ignite UI Checkbox モジュールの追加
    IgxSwitchModule, // Ignite UI Switch モジュールの追加
    IgxSliderModule,　// Ignite UI Slider モジュールの追加
    IgxRadioModule,　// Ignite UI Slider モジュールの追加
    NavigationDrawerModule // Ignite UI Drawer モジュールの追加
  ],
  providers: [UserService], // サービスをプロバイダに追加
  bootstrap: [AppComponent]
})
export class AppModule { }
```
2\. メニューの追加先は app モジュールにしてみます。ドロワーの追加とメニューボタンクリック時にドロワーをトグルするようにします。尚、pinThreashold を false にしているのは、画面サイズが 1024 以上の場合に、メニュー以外がグレーになる効果が得られないためです。また app-list は削除しています。

```html:src/app/app.component.html
<igx-navbar [title]="title" actionButtonIcon="menu" (onAction)="drawer.toggle()">
  <igx-icon name="add" (click)="onClickAdd()"></igx-icon>
</igx-navbar>
<!-- ドロワーの追加 -->
<igx-nav-drawer #drawer [pinThreshold]="false" width="280px">
  <div class="ig-drawer-content">
    <nav class="nav">
      <!-- ヘッダー -->
      <span class="nav-item header">menus</span>
      <!-- メニューの追加 -->
      <span class="nav-item" [routerLinkActive]="'active'" routerLink="/">
        <igx-icon fontSet="material" name="list"></igx-icon>
        <span>List</span>
      </span>
      <span class="nav-item" [routerLinkActive]="'active'" routerLink="grid">
        <igx-icon fontSet="material" name="grid_on"></igx-icon>
        <span>Grid</span>
      </span>
    </nav>
  </div>
</igx-nav-drawer>

<router-outlet></router-outlet>
```

3\. 既定ではあまり奇麗に表示されないため、CSS を追加します。[GitHub の CSS](https://github.com/IgniteUI/igniteui-angular/blob/master/demos/app/app.component.css)をそのまま使っています。NavDrawer のアイテムや CSS は NavDrawer フォルダではなく、app.component レベルにあります。

```css:src/app/app.component.css
.ig-drawer-content {
    background: #fff;
}

.ig-drawer-content ::-webkit-scrollbar {
    width: 3px;
    background: #e4e4e4;
}

.ig-drawer-content ::-webkit-scrollbar-thumb {
    background: #ec6f74;
    border-radius: 0;
}

.nav {
    position: absolute;
    width: 100%;
    height: 100%;
    border-right: 1px solid #e4e4e4;
    overflow-y: scroll;
    overflow-x: hidden;
}

.nav-item {
    display: flex;
    flex: 1 0 100%;
    flex-flow: row nowrap;
    color: #383838;
    max-height: 48px;
    padding: 12px 16px;
    cursor: pointer;
    font-size: 14px;
    align-items: center;
    font-weight: 600;
    user-select: none;
    outline: transparent;
    white-space: nowrap;
}

.nav-item.active {
    background-color: #fcc5de;
}

.nav-item:hover {
    background-color: #f0f0f0;
}

.nav-item > * + * {
    margin-left: 32px;
}

.nav-item.header {
    font-size: 16px;
    color: #9c9c9c;
    font-weight: 400;
    cursor: initial;
}

.nav-item.header:hover {
    background-color: transparent;
}
```

4\. 保存して動作を確認します。
<img width="866" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/a745d433-a000-5d88-237d-4adb0fcf00b1.png">

5\. メニューは出ていますが、画面の一番上からでていてメニューを隠してしますので、もう少し CSS を追加して挙動を変えます。

```css:src/app/app.component.css
/*上記のものに以下を追加 */
igx-nav-drawer >>> .ig-nav-drawer-overlay,
igx-nav-drawer >>> .ig-nav-drawer {
 margin-top: 56px;
}
```
6\. これでメニューを隠さなくなりました。
<img width="866" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/337d6dd0-cbb1-6260-e32a-0614f58103dc.png">

## Angular の機能でリストのモジュール化
現在は app.module.html にリストの機能があり、ルーティングで詳細や新規画面を出しています。しかし他の機能を使う際にはリストが不要であるため、リスト自体をモジュール化しましょう。

1\. まず見やすくするため、new と detail フォルダを list フォルダに移します。
<img width="138" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/448811f8-9c9f-cc25-aa9b-a8324c53825d.png">

2\. 次に src/app/list 配下に list.module.ts を作成し、コンポーネントやモジュールの情報を追加します。

```typescript:src/app/list/list.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';

import { ListComponent } from './list.component';
import { DetailComponent } from './detail/detail.component';
import { NewComponent } from './new/new.component';

// Ignite UI モジュールの参照
import {
    IgxIconModule, IgxScrollModule, IgxAvatarModule,
    IgxLabelModule, IgxInput, IgxDatePickerModule, IgxButtonModule, IgxRippleModule,
    IgxToastModule, IgxDialogModule, IgxCheckboxModule, IgxSwitchModule, IgxSliderModule,
    IgxRadioModule
} from 'igniteui-angular/main';

const listRoutes: Routes = [
    {
        // 親パスと子パスを設定
        path: 'list', component: ListComponent, children: [
            // detail をパスとして指定したら Detail コンポーネントを表示。idがパラメーター
            { path: 'detail/:id', component: DetailComponent },
            // new をパスとして指定したら New コンポーネントを表示
            { path: 'new', component: NewComponent }
        ]
    }
];

@NgModule({
    declarations: [
        ListComponent,
        DetailComponent,
        NewComponent
    ],
    imports: [
        // ルートモジュールを子パスを含めて登録
        RouterModule.forChild(listRoutes),
        CommonModule,
        FormsModule,
        IgxScrollModule, // Ignite UI Scroll モジュールの追加
        IgxAvatarModule, // Ignite UI Avatar モジュールの追加
        IgxLabelModule, // Ignite UI Label モジュールの追加
        IgxInput, // Ignite UI Input の追加
        IgxDatePickerModule, // Ignite UI DatePicker モジュールの追加
        IgxButtonModule, // Ignite UI Button モジュールの追加
        IgxRippleModule, // Ignite UI Ripple モジュールの追加
        IgxToastModule, // Ignite UI Toast モジュールの追加    
        IgxDialogModule,　// Ignite UI Dialog モジュールの追加
        IgxCheckboxModule, // Ignite UI Checkbox モジュールの追加
        IgxSwitchModule, // Ignite UI Switch モジュールの追加
        IgxSliderModule,　// Ignite UI Slider モジュールの追加
        IgxRadioModule,　// Ignite UI Slider モジュールの追加
    ],
    exports: [RouterModule]
})
export class ListModule { }
```

3\. アプリで リストモジュールが使えるように app.module.ts に追加するとともに、リストモジュールでしか使わない Ignite UI のモジュールは、一旦削除します。※また後から必要になって追加しますが、それはまたその時に。

```typescript:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { ListModule } from './list/list.module';
import { AppComponent } from './app.component';

// Ignite UI モジュールの参照
import {
  IgxNavbarModule, IgxIconModule, NavigationDrawerModule } from 'igniteui-angular/main';

// Hammer
import "hammerjs";
import { UserService } from './user/user.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    FormsModule,
    ListModule,
    IgxNavbarModule, // Ignite UI Navbar モジュールの追加
    IgxIconModule, // Ignite UI Icon モジュールの追加   
    NavigationDrawerModule // Ignite UI Drawer モジュールの追加
  ],
  providers: [UserService], // サービスをプロバイダに追加
  bootstrap: [AppComponent]
})
export class AppModule { }
```

4\. new と detail のパスを変更したことで影響を受ける import のパスを修正します。

```typescript:src/app/list/new/new.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { User, Gender } from '../../models/user';
import { UserService } from '../../user/user.service';
import { IgxLabel, IgxInput, IgxAvatar, IgxToast, IgxDialog, IgxCheckbox, IgxSwitch, IgxSlider, IgxRadio } from 'igniteui-angular/main';
import { Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';

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

  constructor(private userService: UserService, private router: Router) {   
  }

  @ViewChild('toast') toast: IgxToast;
  public user: User;
  public gender: string[];

  ngOnInit() {
    this.user = new User("", "", 0, null, Gender.Other, 0, true);
    // Gender を配列から取得
    let genderValues = Object.keys(Gender);
    this.gender = genderValues.slice(genderValues.length / 2);
  }

  public loadImage(input: HTMLInputElement): void {
    if (!input.value) {
      return;
    }

    let reader = new FileReader();
    // Callback when file read.
    reader.onload = () => {
      input.value = "";
      this.user.image = reader.result;
    }

    reader.readAsDataURL(input.files[0]);
  }

  public create() {
    this.userService.add(this.user).subscribe(() => {
      // 作成したらトーストを出してルートに遷移
      this.toast.show();
      this.router.navigate([`/`]);
    });
  }
}
```
```typescript:src/app/list/detail/detail.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { IgxLabel, IgxInput, IgxAvatar, IgxToast, IgxDialog } from 'igniteui-angular/main';
import { UserService } from '../../user/user.service';
import { User } from '../../models/user';
import { Router } from '@angular/router';

@Component({
  selector: 'app-detail',
  templateUrl: './detail.component.html',
  styleUrls: ['./detail.component.css']
})

export class DetailComponent implements OnInit {

  @ViewChild('toast') toast: IgxToast;
  public user: User;
  constructor( private route: ActivatedRoute, private router: Router,private userService: UserService ) {
  }

  ngOnInit() {
    // パラメーターの変更をモニタ
    this.route.params.subscribe(params => {
      this.userService.getUser(params.id).subscribe(
        (user) => {this.user = user;}
      );
    });
  }

  public save(){
    this.userService.save(this.user).subscribe(()=>{
      this.toast.show();
    });    
  }

  public delete(){
    this.userService.delete(this.user).subscribe(()=>{
      this.toast.message = "deleted";
      this.toast.show();
      this.router.navigate([`/`]);
    })
  }
}
```

5\. ルーティングが変わって new は /list/new に、detail は /list/detail/:id になっているので、それぞれ影響箇所を変えます。

```typescript:src/app/list/list.component.ts
import { Component, OnInit, EventEmitter } from '@angular/core';
import { IgxScroll, IgxScrollEvent, IgxAvatar } from "igniteui-angular/main";
import { User } from '../models/user';
import { UserService } from '../user/user.service';
import { Router } from '@angular/router';

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

  constructor(private userService: UserService, private router: Router) {
    this.load();
  }

  public users: User[] = new Array<User>();
  public visibleUsers: User[];
  public visibleUsersCount: number = 8;

  ngOnInit() {
    // 変更通知を受け取ったら再度データをロード
    this.userService.userUpdateSource$.subscribe(
      (user)=>{this.load();}
    )
  }

  public load():void{    
     // ユーザーをサービスから取得。非同期のため subscribe でデータ受け取り。
    this.userService.getUsers().subscribe(
      (users) => {
        this.users = users;
        this.visibleUsers = this.users.slice(0, this.visibleUsersCount);
      }
    );
  }

  private onItemSelect(user: User): void {
    // list/detail にナビゲート
    this.router.navigate([`/list/detail/${user.id}`]);
  }

  private updateList($event: IgxScrollEvent): void {    
    this.visibleUsers = this.users.slice($event.currentTop, $event.currentTop + this.visibleUsersCount);
  }
}
```
```typescript:src/app/app.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';
import { User } from './models/user';
import { Router } from '@angular/router';
import { IgxNavbar, IgxIcon, NavigationDrawer } from 'igniteui-angular/main';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  selectedUser: User;

  title = 'Ignite Ui App';

  constructor(private router: Router) {
  }

  onClickAdd(){
    // list/new を呼び出し
    this.router.navigate(['/list/new']);
  }
}
```

6\. list.component.html で router-outlet を入れます。それに伴い、CSS も更新します。

```html:src/app/list/list.component.html
<igx-scroll #scroll (onScroll)="updateList($event)" 
    [visibleItemsCount]="visibleUsersCount" 
    [itemHeight]="70" 
    [totalItemsCount]="users.length">
    <ul class="list">
        <li class="list-item" *ngFor="let user of visibleUsers" (click)="onItemSelect(user)">
            <igx-avatar class="list-item-image" roundShape="true" src="{{user.image}}"></igx-avatar>
            <h5 class="list-item-value">{{user.name}}</h5>
        </li>
    </ul>
</igx-scroll>

<router-outlet></router-outlet>
```
```css:src/app/list/list.component.css
.list {
    width: 250px;
}

.list-item {
    list-style: none;
    height: 64px;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;    
}

.list-item-image, .list-item-value {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;
}

igx-scroll, router-outlet {
    float: left;
}
```
7\. 最後に app レベルのルーティングを修正します。

```typescript:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListComponent } from './list/list.component';

const routes: Routes = [
  // 既定はリストコンポーネントを表示
  { path: '', component: ListComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```
8\. すべて保存して動作が変わっていないことを確認します。結構広範囲にわたって変更しましたが、これでリストモジュールにすべてを一旦移動できました。

# データグリッドの利用
リストではレコードの操作を中心に作ったので、グリッドはユーザーの一覧を柔軟に表示します。Ignite UI には柔軟で高機能なデータグリッド機能があるので、そちらを使います。

1\. グリッドはモジュールではなくはコンポーネントとして追加するので、以下のコマンドを実行します。

```shell
ng generate component grid
```

2\. 次にモジュールの追加をします。データグリッドはこれまでと異なり、.forRoot() が必要になります。

```typescript:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { ListModule } from './list/list.module';
import { AppComponent } from './app.component';
import { GridComponent } from './grid/grid.component';
import { UserService } from './user/user.service';

// Ignite UI モジュールの参照
import {
  IgxNavbarModule, IgxIconModule, NavigationDrawerModule,
  IgxGridModule, } from 'igniteui-angular/main';

// Hammer
import "hammerjs";

@NgModule({
  declarations: [
    AppComponent,
    GridComponent    
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    FormsModule,
    ListModule,
    IgxNavbarModule, // Ignite UI Navbar モジュールの追加
    IgxIconModule, // Ignite UI Icon モジュールの追加   
    NavigationDrawerModule, // Ignite UI Drawer モジュールの追加
    IgxGridModule.forRoot(), // Ignite UI Grid モジュールの追加
  ],
  providers: [UserService], // サービスをプロバイダに追加
  bootstrap: [AppComponent]
})
export class AppModule { }
```

3\. Grid コンポーネントにデータグリッドを追加します。まずはデータグリッドが備える自動列生成 [autoGenerate] でグリッドを作ってみます。

```html:src/app/grid/gird.component.html
<igx-grid [data]="users" [autoGenerate]="true" [paging]="true" [perPage]="8">
</igx-grid>
```
```typescript:src/app/grid/grid.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../models/user';
import { UserService } from '../user/user.service';

@Component({
  selector: 'app-grid',
  templateUrl: './grid.component.html',
  styleUrls: ['./grid.component.css']
})

export class GridComponent implements OnInit {

  private users: User[] = new Array<User>();
  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userService.getUsers().subscribe((users) => this.users = users);
  }
}
```
4\. Grid コンポーネントにアクセスできるように、ルーティングを更新します。

```typescript:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListComponent } from './list/list.component';
import { GridComponent } from './grid/grid.component';

const routes: Routes = [
  // 既定はリストコンポーネントを表示
  { path: '', component: ListComponent },
  { path: 'grid', component: GridComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

5\. すべて保存したら、http://localhost:4200/grid にアクセスします。列が自動で生成されていたら成功です。

<img width="765" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/024fbc7b-078f-bf76-eca8-ea197d899449.png">

6\. 次に列を個別に追加していきます。またアバターなどで見た目も変えましょう。まずモジュールの読み込みから

```typescript:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { ListModule } from './list/list.module';
import { AppComponent } from './app.component';
import { GridComponent } from './grid/grid.component';
import { UserService } from './user/user.service';

// Ignite UI モジュールの参照
import {
  IgxNavbarModule, IgxIconModule, NavigationDrawerModule,
  IgxGridModule, IgxAvatarModule } from 'igniteui-angular/main';

// Hammer
import "hammerjs";

@NgModule({
  declarations: [
    AppComponent,
    GridComponent    
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    FormsModule,
    ListModule,
    IgxNavbarModule, // Ignite UI Navbar モジュールの追加
    IgxIconModule, // Ignite UI Icon モジュールの追加   
    NavigationDrawerModule, // Ignite UI Drawer モジュールの追加
    IgxGridModule.forRoot(), // Ignite UI Grid モジュールの追加
    IgxAvatarModule, // Ignite UI Avatar モジュールの追加
  ],
  providers: [UserService], // サービスをプロバイダに追加
  bootstrap: [AppComponent]
})
export class AppModule { }
```

7\. Grid コンポーネントを変更します。列の指定には igx-column を使い、また詳細を変更したい場合は Angular の機能である ng-template で中身を調整可能です。

```typescript:src/app/grid/grid.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../models/user';
import { UserService } from '../user/user.service';
import { IgxIcon, IgxAvatar } from 'igniteui-angular/main';

@Component({
  selector: 'app-grid',
  templateUrl: './grid.component.html',
  styleUrls: ['./grid.component.css']
})

export class GridComponent implements OnInit {

  private users: User[] = new Array<User>();
  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userService.getUsers().subscribe((users) => this.users = users);
  }
}
```
```html:src/app/grid/grid.component.html
<igx-grid #grid [data]="users" [autoGenerate]="false" [paging]="true" [perPage]="6">
  <igx-column width="80px" field="userRank" header="Rank" dataType="number" sortable="true"></igx-column>
  <igx-column width="120px" field="image">
    <ng-template igxCell let-col>
      <igx-avatar size="medium" roundShape="true" src="{{col}}"></igx-avatar>
    </ng-template>
  </igx-column>
  <igx-column field="name" header="User Name" dataType="string" editable="true"></igx-column>
  <igx-column field="birthdate" header="BirthDate" dataType="Date">
    <ng-template igxCell let-col>
      {{col.toDateString()}}
    </ng-template>
  </igx-column>
  <igx-column width="120px" field="gender" header="Gender" dataType="Gender"></igx-column>
  <igx-column width="120px" field="isAdmin" header="Is Admin" dataType="boolean">
    <ng-template igxCell let-col>
      <igx-icon *ngIf="col" fontSet="material" name="star" color="red"></igx-icon>
      <igx-icon *ngIf="!col" fontSet="material" name="star_border"></igx-icon>
    </ng-template>
  </igx-column>
</igx-grid>
```

8\. すべてを保存すると以下のようにグリッドが表示されます。
<img width="564" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/892d9578-8b43-0879-5993-0428068311f7.png">

9\. ソートを有効にした Rank ヘッダーをクリックすると並べ替えが行えます。また編集可能にしたユーザー名をダブルクリックすると名前を変えることが出来ます。ただしここでは保存機能はつけていないので実際は機能しません。

その他グリッドでは検索もできます。

# カードの利用
データグリッドやリストはあくまでテーブル形式ですが、Ignite UI のカードを使うとタイル的な UI を簡単に実現できます。

1\. まずカード用のコンポーネントを追加します。

```shell
ng generate component card
```

2\. 次にモジュールを追加します。

```typescript:src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { ListModule } from './list/list.module';
import { AppComponent } from './app.component';
import { GridComponent } from './grid/grid.component';
import { CardComponent } from './card/card.component';
import { UserService } from './user/user.service';

// Ignite UI モジュールの参照
import {
  IgxNavbarModule, IgxIconModule, NavigationDrawerModule,
  IgxGridModule, IgxAvatarModule, IgxCardModule
} from 'igniteui-angular/main';

// Hammer
import "hammerjs";

@NgModule({
  declarations: [
    AppComponent,
    GridComponent,
    CardComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    FormsModule,
    ListModule,
    IgxNavbarModule, // Ignite UI Navbar モジュールの追加
    IgxIconModule, // Ignite UI Icon モジュールの追加   
    NavigationDrawerModule, // Ignite UI Drawer モジュールの追加
    IgxGridModule.forRoot(), // Ignite UI Grid モジュールの追加
    IgxAvatarModule, // Ignite UI Avatar モジュールの追加
    IgxCardModule, // Ignite UI Card モジュールの追加
  ],
  providers: [UserService], // サービスをプロバイダに追加
  bootstrap: [AppComponent]
})
export class AppModule { }
```

3\. ルーティングにカードコンポーネントを追加します。また既定はリストですが、明示的にリストを指定できるようにもしておきます。

```typescript:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ListComponent } from './list/list.component';
import { GridComponent } from './grid/grid.component';
import { CardComponent } from './card/card.component';

const routes: Routes = [
  // 既定はリストコンポーネントを表示
  { path: '', component: ListComponent },
  { path: 'list', component: ListComponent },
  { path: 'grid', component: GridComponent },
  { path: 'card', component: CardComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

4\. 各ユーザーのカード用の画像を指定したいので、ユーザーモデルを拡張します。またユーザーサービスで画像を返すように設定します。

```typescript:src/modes/user.ts
export class User {
    public image: string
    public name: string
    public id: number
    public birthdate: Date
    public gender: Gender
    public userRank: number
    public isAdmin: boolean
    public backgroundImage: string
    
    constructor(image: string, name: string, id: number, birthdate: Date,
    gender: Gender, userRank: number, isAdmin: boolean, backgroundImage: string ) {
        this.image = image;
        this.name = name;
        this.id = id;
        this.birthdate = birthdate;
        this.gender = gender;
        this.userRank = userRank;
        this.isAdmin = isAdmin;
        this.backgroundImage = backgroundImage;
    }
}

export enum Gender {
    Male = 1,
    Female,
    Other,
}
```
```typescript:src/user/user.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { User, Gender } from '../models/user'
import { Subject } from 'rxjs/Subject';

@Injectable()
export class UserService {

  private users: Array<User>;
  // 通知用に一つ Subject を用意
  private userUpdate = new Subject<string>()
  public userUpdateSource$ = this.userUpdate.asObservable();

  constructor() {
    this.users = new Array<User>();
    for (let i = 1; i <= 22; i++) {
      let birthdate = new Date(2018, 0, i);
      let backgrounImage = "http://www.infragistics.com/angular-demos/assets/images/card/media/ny.jpg";
      if( i % 4 == 1){
        backgrounImage = "http://www.infragistics.com/angular-demos/assets/images/card/media/yosemite.jpg";
      }
      else if (i%4 == 2){
        backgrounImage = "http://www.infragistics.com/angular-demos/assets/images/card/media/monuments.jpg";
      }
      else if (i%4 == 3){
        backgrounImage = "http://www.infragistics.com/angular-demos/assets/images/card/media/the_red_ice_forest.jpg";
      }
      this.users.push(new User(
        `http://www.infragistics.com/angular-demos/assets/images/avatar/${i}.jpg`,
        "User: " + i,
        i,
        birthdate,
        Gender.Other,
        i,
        true,
        backgrounImage
      ));
    }
  }

  getUsers(): Observable<User[]> {
    return of(this.users)
  }

  getUser(id: number): Observable<User> {
    return of(this.users.find(x => x.id === +id));
  }

  add(user: User): Observable<boolean> {
    this.users.push(user);
    // 追加したことを通知
    this.userUpdate.next("updated");
    return of(true);
  }

  save(user: User): Observable<boolean> {
    let index = this.users.indexOf(user);
    if (index !== -1) {
      this.users[index] = user;
      return of(true);
    }
    else {
      return of(false);
    }
  }

  delete(user: User): Observable<boolean> {
    let index = this.users.indexOf(user);
    if (index !== -1) {
      this.users.splice(index, 1);
      // 削除したことを通知
      this.userUpdate.next("updated");
      return of(true);
    }
    else {
      return of(false);
    }
  }
}
```
5\. カードコンポーネントを更新してカードを表示します。今回も Ignite UI のコントロールに対して直接 CSS を指定してカスタマイズしています。

```typescript:src/app/card/card.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../models/user';
import { UserService } from '../user/user.service';
import { IgxAvatar } from 'igniteui-angular/main';
@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {

  private users: User[] = new Array<User>();
  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userService.getUsers().subscribe((users) => this.users = users);
  }
  private openUrl(url: string): void {
    window.location.href = url;
  }
}
```
```html:src/app/card/card.component.html
<div class="card">
  <div class="card-item" *ngFor="let user of users">
    <igx-card>
      <div style="overflow: hidden">
        <img width="100%" height="100%" src="{{user.backgroundImage}}">
      </div>
      <igx-card-header>
        <igx-avatar class="list-item-image" roundShape="true" src="{{user.image}}"></igx-avatar>
        <h2 class="list-item-value">{{user.name}}</h2>
      </igx-card-header>
      <igx-card-content>
        Rank:{{user.userRank}}
        <br/> Birthday: {{user.birthdate.toDateString()}}

      </igx-card-content>
      <igx-card-actions>
        <button igxButton igxRipple>Like</button>
        <button igxButton igxRipple>Share</button>
      </igx-card-actions>
    </igx-card>
  </div>
</div>
```
```css:src/app/card/card.component.css
.card {
    display: flex;
    flex-flow: row wrap;
}

.card-item{
    max-width:400px;
    padding:10px;
}

igx-card-header {
    background: #f8f8ff;
}

igx-card-content {
    background: gray;
    color:white;
}
```
6\. 最後にナビゲーションドロワーを更新してカードに対するリンクを作成します。また今回はメニューを選ぶとドロワーを閉じるようにしてみました。

```html:src/app/app.component.html
<igx-navbar [title]="title" actionButtonIcon="menu" (onAction)="drawer.toggle()">
  <igx-icon name="add" (click)="onClickAdd()"></igx-icon>
</igx-navbar>
<!-- ドロワーの追加 -->
<igx-nav-drawer #drawer [pinThreshold]="false" width="280px">
  <div class="ig-drawer-content">
    <nav class="nav">
      <!-- ヘッダー -->
      <span class="nav-item header">menus</span>
      <!-- メニューの追加 -->
      <span class="nav-item" [routerLinkActive]="'active'" routerLink="list" (click)="drawer.close();">
        <igx-icon fontSet="material" name="list"></igx-icon>
        <span>List</span>
      </span>
      <span class="nav-item" [routerLinkActive]="'active'" routerLink="grid" (click)="drawer.close();">
        <igx-icon fontSet="material" name="grid_on"></igx-icon>
        <span>Grid</span>
      </span>
      <span class="nav-item" [routerLinkActive]="'active'" routerLink="card" (click)="drawer.close();">
        <igx-icon fontSet="material" name="view_module"></igx-icon>
        <span>Card</span>
      </span>
    </nav>
  </div>
</igx-nav-drawer>

<router-outlet></router-outlet>
```

7\. 全て保存して、http://localhost:4200/card にアクセスすると、画面が以下のように更新されます。メニューの挙動も確認してください。

<img width="739" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/d29287ce-e9ea-cb12-a412-eb0948fd4adf.png">

# モバイルの検討
モバイルになると画面サイズが変わりますが、Ignite UI はレスポンシブ対応しているため、結構リサイズには柔軟に対応が可能です。モバイルで面白そうなコントロールとしてはタブバーコントロールがあります。[Tab Bar](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tabbar.html)

ネイティブ開発する際は、Ionic 等と組み合わた開発になると思いますが、また別の機会に見てみます。

# まとめ
Ignite UI を見てきましたが個人的には気に入っています。Ignite UI は Angular 以外にも JavaScript 版があります。また Xamarin、MVC、WPF、iOS、Android 用のコントロールなどもあるようですので、試したらまた記事にします。

## 参考
[Infragistics Ultimate: 開発ツールキット全般](https://jp.infragistics.com/products/ultimate)
[Infragistics 開催イベント](https://jp.infragistics.com/events/)