Chapter 03

UIフレームワーク編

たつきち
たつきち
2020.09.21に更新

現時点で下図のようなTodoアプリができています。

チュートリアルとしては十分かもしれませんが、現実にAngularでアプリを作るとなったら、ある程度美しい見た目にすることは必須条件ですよね。

なので、ここまでに作ったアプリにUIフレームワークを適用して見た目を美しくするということをやっていきたいと思います💪

1. 導入するUIフレームワークを選択する

Angular公式サイト内の こちらのページ に、サードパーティー製も含めた色々なツールチェーンが紹介されており、 UI Components の項を見るとUIフレームワークもたくさん紹介されています。

今回は僕の独断で Ant Design of Angular (ng-zorro-antd) を導入してみたいと思います。非常に豊富なUIコンポーネントが用意されており、見た目も抜群に美しいので個人的におすすめのフレームワークです👍

2. ng-zorro-antd をインストールする

ドキュメント に従って、以下のようにしてインストールします。

ng add ng-zorro-antd

途中いくつか質問が表示されます。今回は以下のように回答してください。

インストールが完了すると、以下のファイルに差分が表れます。

  • angular.json
  • package-lock.json
  • package.json
  • src/app/app.component.html
  • src/app/app.module.ts

package.json package-lock.json は単にパッケージがインストールされたことによる差分ですが、他の3ファイルは ng add コマンドを使ってインストールしたことによって自動で実行された処理の結果です。

ng add コマンドは、単にパッケージをインストールするだけでなく、そのパッケージがAngularが定める一定のルール( schematics )に則ったインストールスクリプトを持っている場合はそれを実行してくれるコマンドなのです👌(参考:Version 6 of Angular Now Available - Angular Blog

  • angular.json
  • src/app/app.module.ts

に対して自動で行われた変更の内容は、 ng-zorro-antd が持っているCSSファイルをビルドに組み込むようにしてくれているのと、Angularに対するロケール情報の設定など ng-zorro-antd を使い始めるために必要な各種設定をしてくれているだけなので、このままコミットしてしまって大丈夫です。

ただ、 src/app/app.component.html に対する変更だけはビューの内容を勝手にサンプルで置き換えてしまっているありがた迷惑な変更なので😅、これは間違ってコミットしてしまわないように要注意です。 src/app/app.component.html だけはもとに戻してからコードをコミットしましょう✋

また、 angular.json への更新はすでに ng serve 起動している開発用サーバーには反映されないので、一度 ng serve のプロセスを終了して、再度 ng serve し直す必要があります。

3. ng-zorro-antd のコンポーネントやディレクティブを使ってビューを修正する

この時点で、画面の見た目は以下のようになっています。

いくつかスタイルが当たっていますが、当然それだけではダメで、ビューのコードを ng-zorro-antd が提供してくれているコンポーネントやディレクティブを使ったコードに書き換えていく必要があります。

タイトルバーを設置する

PageHeader コンポーネントを使ってページトップにタイトルバーを設置してみましょう。

src/app/app.module.ts

+ import { NzPageHeaderModule } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
+     NzPageHeaderModule,
    ],

src/app/app.component.html

+ <nz-page-header nzTitle="AngularTodo"></nz-page-header>
+
  <app-task-list></app-task-list>

src/app/app.component.scss

+ nz-page-header {
+   background-color: #eee;
+   margin-bottom: 1rem;
+ }

ngModel を利用するために AppModuleFormsModule をインポートしたのとまったく同じように、 ng-zorro-antd が持っているコンポーネントを利用するために、必要なモジュール(この場合は NzPageHeaderModule をインポートすれば、ビューで <nz-page-header> を使えるようになるというわけです。

見た目はこうなりました。

リストの見た目を美しくする

次に、List コンポーネントを使ってリストの見た目を美しくしましょう。

src/app/app.module.ts

- import { NzPageHeaderModule } from 'ng-zorro-antd';
+ import { NzListModule, NzPageHeaderModule } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
      NzPageHeaderModule,
+     NzListModule,
    ],

src/app/app.component.scss

  nz-page-header {
    background-color: #eee;
    margin-bottom: 1rem;
  }
+
+ app-task-list {
+   display: block;
+   margin: 0 1rem;
+ }

src/app/task-list.component.html

- <ul>
+ <nz-list nzBordered>
-   <li *ngFor="let task of tasks">
+   <nz-list-item *ngFor="let task of tasks">
      <app-task-list-item [task]="task"></app-task-list-item>
-   </li>
+   </nz-list-item>
-   <li>
+   <nz-list-item>
      <app-task-form (addTask)="addTask($event)"></app-task-form>
-   </li>
+   </nz-list-item>
- </ul>
+ </nz-list>

src/app/task-list/task-list.component.scss

+ app-task-list-item, app-task-form {
+   width: 100%;
+ }

見た目はこうなりました。

チェックボックスの見た目を美しくする

Checkbox ディレクティブを使って、 TaskListItemComponent の見た目を美しくしましょう。

src/app/app.module.ts

- import { NzListModule, NzPageHeaderModule } from 'ng-zorro-antd';
+ import { NzCheckboxModule, NzListModule, NzPageHeaderModule } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
      NzPageHeaderModule,
      NzListModule,
+     NzCheckboxModule,
    ],

src/app/task-list-item/task-list-item.component.html

- <label class="{{ task.done ? 'done' : '' }}">
-   <input type="checkbox" [(ngModel)]="task.done">
+ <label nz-checkbox [(ngModel)]="task.done" class="{{ task.done ? 'done' : '' }}">
    {{ task.title }}
    <span *ngIf="task.deadline">(期日:{{ task.deadline|date:'yyyy/MM/dd' }})</span>
    <span *ngIf="isOverdue(task)" class="overdue">期日超過</span>

見た目はこうなりました。(微妙な変化ですが)

タグを使って期日の表示や 期日超過 のラベルを美しくする

Tag コンポーネントを使って、期日の表示や 期日超過 のラベルを美しくしましょう。

src/app/app.module.ts

- import { NzCheckboxModule, NzListModule, NzPageHeaderModule } from 'ng-zorro-antd';
+ import { NzCheckboxModule, NzListModule, NzPageHeaderModule, NzTagModule } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
      NzPageHeaderModule,
      NzListModule,
      NzCheckboxModule,
+     NzTagModule,
    ],

src/app/task-list-item/task-list-item.component.html

- <label nz-checkbox [(ngModel)]="task.done" class="{{ task.done ? 'done' : '' }}">
-   {{ task.title }}
-   <span *ngIf="task.deadline">(期日:{{ task.deadline|date:'yyyy/MM/dd' }})</span>
-   <span *ngIf="isOverdue(task)" class="overdue">期日超過</span>
- </label>
+ <div class="left">
+   <label nz-checkbox [(ngModel)]="task.done" class="{{ task.done ? 'done' : '' }}">
+     {{ task.title }}
+   </label>
+ </div>
+ 
+ <div class="right">
+   <nz-tag nzColor="error" *ngIf="isOverdue(task)">期日超過</nz-tag>
+   <nz-tag nzColor="default" *ngIf="task.deadline">期日:{{ task.deadline|date:'yyyy/MM/dd' }}</nz-tag>
+ </div>

src/app/task-list-item/task-list-item.component.scss

+ .left {
+   float: left;
+ }
+ 
+ .right {
+   float: right;
+   :last-child {
+     margin-right: 0;
+   }
+ }
+ 
  .done {
-   color: gray;
+   color: lightgray;
    text-decoration: line-through;
  }
- 
- .overdue {
-   color: darkred;
- }

見た目はこうなりました。

タスク追加フォームの見た目を美しくする

InputDatePickerButton のコンポーネント群 を使って、タスク追加フォームの見た目を美しくしましょう。

src/app/app.module.ts

- import { NzCheckboxModule, NzListModule, NzPageHeaderModule, NzTagModule } from 'ng-zorro-antd';
+ import {
+   NzButtonModule,
+   NzCheckboxModule,
+   NzDatePickerModule,
+   NzInputModule,
+   NzListModule,
+   NzPageHeaderModule,
+   NzTagModule,
+ } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
      NzPageHeaderModule,
      NzListModule,
      NzCheckboxModule,
      NzTagModule,
+     NzInputModule,
+     NzDatePickerModule,
+     NzButtonModule,
    ],

src/app/task-form/task-form.component.html

- <input type="text" [(ngModel)]="newTask.title" #title="ngModel" required>
- <input type="date" [(ngModel)]="newTask.deadline">
- <button (click)="submit()" [disabled]="title.invalid">追加</button>
- <br>newTaskの値: {{ newTask|json }}
+ <nz-input-group nzCompact>
+   <input nz-input placeholder="To do" [(ngModel)]="newTask.title" name="title" #title="ngModel" autofocus required>
+   <nz-date-picker [(ngModel)]="newTask.deadline"></nz-date-picker>
+   <button nz-button nzType="primary" (click)="submit()" [disabled]="title.invalid">追加</button>
+ </nz-input-group>

src/app/task-form/task-form.component.scss

+ nz-input-group {
+   input {
+     width: 40%;
+   }
+   nz-date-picker {
+     width: 40%;
+   }
+   button {
+     margin-left: 3%;
+     width: 17%;
+   }
+ }

見た目はこうなりました。

グリッドレイアウトを導入する

ここまでで十分美しくなったように思えますが、実は、大画面で表示するとこんなふうにすごく間延びした表示になってしまいます。

なので、Grid を使って適切にグリッドレイアウトを実装して、大画面でもちょうどいいサイズで表示されるようにしましょう。

src/app/app.module.ts

  import {
    NzButtonModule,
    NzCheckboxModule,
    NzDatePickerModule,
+   NzGridModule,
    NzInputModule,
    NzListModule,
    NzPageHeaderModule,
    NzTagModule,
  } from 'ng-zorro-antd';

  // ...

    imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule,
      BrowserAnimationsModule,
      NzPageHeaderModule,
      NzListModule,
      NzCheckboxModule,
      NzTagModule,
      NzInputModule,
      NzDatePickerModule,
      NzButtonModule,
+     NzGridModule,
    ],

src/app/app.component.html

  <nz-page-header nzTitle="AngularTodo"></nz-page-header>
  
- <app-task-list></app-task-list>
+ <div nz-row>
+   <div nz-col nzXs="24" nzLg="18" nzXl="15" nzXXl="10">
+     <app-task-list></app-task-list>
+   </div>
+ </div>

見た目はこうなりました。

いい感じですね!

4. Before/After

Before After

非常に美しくなりました!🙌