ReactのチュートリアルをAngularでやってみた

angular Angular

Reactのチュートリアルを一通りやり終えたので、Angularでやったらどうなるか試してみた。
ついでなのでStackBlitzを使ってみることにした。StackBlitzについてはこちら

プロジェクト作成

  1. ダッシュボードにてAngularのアイコンをクリックするとプロジェクトが作成される。
  2. appフォルダ上で右クリック→”Angular Generator”→”Component”を選択する。
  3. 上部にコンポーネント名を入力すると、コンポーネントが作成されるので、以下のコンポーネントを作成する。
  • game
  • board
  • square
Angularアイコンをクリック
Angularアイコンをクリック
プロジェクトが作成される
プロジェクトが作成されると、自動的に実行される。ソースを修正すると即時反映される。
Angular GeneratorからComponentを選択
Angular Generator→Componentを選択
コンポーネント名を入力
コンポーネント名を入力してEnter
  1. app.module.ts に作成したコンポーネントを追加する
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
// ↓ 追加
import { BoardComponent } from './board/board.component';
import { GameComponent } from './game/game.component';
import { SquareComponent } from './square/square.component';
// ↑ 追加

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [
    AppComponent,
    HelloComponent,
// ↓ 追加
    BoardComponent,
    GameComponent,
    SquareComponent,
// ↑ 追加
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
  1. htmlにそれぞれのコンポーネントを表示できるように記述する。
<app-game></app-game>
<p>game</p>
<app-board></app-board>

<p>board</p>
<app-square></app-square>

<p>square</p>

squareコンポーネント

squareコンポーネントは、valueを親コンポーネントからもらって、クリックしたらイベントを親コンポーネントに返す。

Reactではpropsで行なっていたが、Angularでは@Input、@Outputを使用する。

  @Output() clicked = new EventEmitter<number>();

・・・

    this.clicked.emit();

子コンポーネント側で@Output() と宣言した変数は、親コンポーネントで使えるイベントとなる。

emitされた値を親側で受け取ることができる。

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; // 変更

@Component({
  selector: 'app-square',
  templateUrl: './square.component.html',
  styleUrls: ['./square.component.css'],
})
export class SquareComponent implements OnInit {
  @Input() value: string; // 追加
  @Output() clicked = new EventEmitter<number>(); // 追加

  constructor() {}

  ngOnInit() {}

  // ↓ 追加
  buttonClick() {
    this.clicked.emit();
  }
  // ↑ 追加
}

<!-- ↓ 変更-->
<button [className]="'square'" (click)="buttonClick()">
  {{ value }}
</button>
<!-- ↑ 変更-->

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

boardコンポーネント

boardコンポーネントも親コンポーネントからsquare(9マス分のvalue)を受け取り、squareコンポーネントのクリックイベントを親コンポーネントに返す。

  1. squareコンポーネントの@Input, @Outputに合わせて変更する。
<!-- ↓ 変更-->
<div [className]="'board-row'">
  <app-square [value]="" (clicked)="onClicked(0)"></app-square>
</div>
<!-- ↑ 変更-->

  1. gameコンポーネントとやり取りするための記述をする。
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; // 変更

@Component({
  selector: 'app-board',
  templateUrl: './board.component.html',
  styleUrls: ['./board.component.css'],
})
export class BoardComponent implements OnInit {
  @Input() squares: string[]; // 追加
  @Output() clicked = new EventEmitter<number>(); // 追加

  constructor() {}

  ngOnInit() {}
  // ↓ 追加
  onClicked(event: number) {
    this.clicked.emit(event);
  }
  // ↑ 追加
}
  1. 9マス分用意する。
<div *ngFor="let i of [0, 1, 2]" [className]="'board-row'">
  <app-square
    *ngFor="let j of [0, 1, 2]"
    [value]=""
    (clicked)="onClicked(3 * i + j)"
  ></app-square>
</div>

valueはgameコンポーネントを修正後に修正。

gameコンポーネント

boardコンポーネントに9マス分のvalueを配列にして引き渡し、クリックイベントを受け取る。

  1. boardコンポーネントの@Input、@Outputに合わせて変更する。タイムトラベル用の記述も併せて行う。
import { Component, OnInit } from '@angular/core';

type squareType = {
  squares: string[];
};

@Component({
  selector: 'app-game',
  templateUrl: './game.component.html',
  styleUrls: ['./game.component.css'],
})
export class GameComponent implements OnInit {
  history: squareType[] = [{ squares: Array(9).fill(null) }];
  status: string = '';
  stepNumber: number = 0;
  xIsNext: boolean = true;

  constructor() {}

  ngOnInit() {}
  jumpTo(num: number) {
    this.stepNumber = num;
    this.xIsNext = num % 2 === 0;
  }
  onClicked(event: number) {}
  trackFn(index: any, move: squareType) {
    return index;
  }
}
<div [className]="'game'">
  <div [className]="'game-board'">
    <app-board
      [squares]="history[stepNumber].squares"
      (clicked)="onClicked($event)"
    ></app-board>
  </div>
  <div [className]="'game-info'">
    <div [className]="'status'">{{ status }}</div>
    <ol>
      <li *ngFor="let step of history; index as move; trackBy: trackFn">
        <button *ngIf="move === 0" (click)="jumpTo(move)">
          Go to game start
        </button>
        <button *ngIf="move !== 0" (click)="jumpTo(move)">
          Go to move #{{ move }}
        </button>
      </li>
    </ol>
  </div>
</div>
ol,
ul {
  padding-left: 30px;
}
.status {
  margin-bottom: 10px;
}
.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
  1. ngOnInit()で、statusの初期値を設定する。
  ngOnInit() {
    this.status = 'Next player: ' + (this.xIsNext ? 'X' : 'O');
  }
  1. calculateWinner()を実装する。
  private calculateWinner(squares: string[]) {
    const lines = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ];
    for (let i = 0; i < lines.length; i++) {
      const [a, b, c] = lines[i];
      if (
        squares[a] &&
        squares[a] === squares[b] &&
        squares[a] === squares[c]
      ) {
        return squares[a];
      }
    }
    return null;
  }
  1. onClicked()を実装する。
    const his = this.history.slice(0, this.stepNumber + 1);
    const sq = his[his.length - 1].squares.slice();
    if (this.calculateWinner(sq) || sq[event]) {
      return;
    }
    sq[event] = this.xIsNext ? 'X' : 'O';
    this.history = this.history.concat([{ squares: sq }]);
    this.stepNumber = this.history.length - 1;;

    const winner = this.calculateWinner(this.history[this.stepNumber].squares);

    if (winner) {
      this.status = 'Winner: ' + winner;
    } else {
      this.xIsNext = !this.xIsNext;
      this.status = 'Next player: ' + (this.xIsNext ? 'X' : 'O');
    }

boardコンポーネント 再び

gameコンポーネントから受け取ったsquaresを使用して、値をsquareコンポーネントに引き渡す。

<div *ngFor="let i of [0, 1, 2]" [className]="'board-row'">
  <app-square
    *ngFor="let j of [0, 1, 2]"
    [value]="squares[3 * i + j]" // 変更
    (clicked)="onClicked(3 * i + j)"
  ></app-square>
</div>

完成!

コメント

タイトルとURLをコピーしました