1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Microsoft] 4. サービスを追加 - Angularチュートリアル Tour of Heroes を Blazor で再実装する

Last updated at Posted at 2020-07-04

サービスを追加

フェイクデータを表示するHeroesコンポーネントを作成しました。
リファクタリングします。


なにゆえにサービスクラスを作成するのか

テストのしやすさから考えると、アプリを構成する部品はそれぞれ関心が分離している方がよいです。
コンポーネントがデータの保存方法について関知しないようにします。

プロジェクトを分けるか分けないか

サービスクラスを作成するにあたり、プロジェクトをどのようにするかが悩ましいです。

今は以下の2つのプロジェクトがあります。

  • BlazorTourOfHeroes WASMプロジェクト
  • BlazorTourOfHeroes.Tests 上記のユニットテストプロジェクト

WASMプロジェクト以外からも使うのであれば、別プロジェクトを用意しないとですね。
今はプロジェクトを分けずに進めます。

HeroServiceを作成する

テスト時にモック化しやすいように、インターフェースとコンクリートクラスを作成します。

Service/IHeroService.cs
namespace BlazorTourOfHeroes.Service
{
    public interface IHeroService
    {
    }
}
Service/HeroService.cs
namespace BlazorTourOfHeroes.Service
{
    public class HeroService : IHeroService
    {
    }
}

ヒーローデータを返すメソッドを作成する

実際には、WebサービスやLocalStorageからデータを返します。
ここではモックです。

IHeroService.cs
List<Hero> GetHeroes();
HeroService.cs
public List<Hero> GetHeroes() => MockHeroes.Create();

DIコンテナへ登録する

コンポーネントヘインジェクトできるように、DIコンテナへ登録します。

DIコンテナへの登録は、Program.csファイルの中で行います。

Program.cs
builder.Services.AddSingleton<IHeroService, HeroService>();

Heroesコンポーネントクラスを更新する

作成したサービスを使うようにコンポーネントを更新します。

インスタンス変数を宣言のみにします。

Heroes.razor
private List<Hero> heroes;

HeroServiceをインジェクトする

DIコンテナへ登録したHeroServiceをコンポーネントで使用できるようにします。

Heroes.razor
@using BlazorTourOfHeroes.Service
@inject IHeroService HeroService

@inject の引数ひとつめが型、ふたつめが変数名です。

GetHeroesメソッドを追加する

サービスからヒーロー達を読み込むメソッドを追加します。

Heroes.razor
private void GetHeroes()
{
    heroes = HeroService.GetHeroes();
}

作成したメソッドをOnInitializedメソッドから呼び出すようにする

コンポーネントの準備が整うと OnInitialized メソッドが呼ばれます。このなかでヒーロー達を読み込むようにします。

Hero.razor
protected override void OnInitialized()
{
    GetHeroes();
}

非同期化

現実世界のアプリでは、ヒーロー達の読み込みがいつ終わるかわかりません。
いつ終わるかわからないものを待つことはできないので、非同期化します。

HeroServiceを非同期にする

GetHeroesメソッドを変更してTaskを返すようにします。

IHeroService.cs
Task<List<Hero>> GetHeroes();
HeroService.cs
public Task<List<Hero>> GetHeroes() =>
    Task.FromResult(MockHeroes.Create());

awaitする

GetHeroesGetHeroesAsyncに書き換えて、awaitするようにします。

Heroes.razor
private async Task GetHeroesAsync()
{
    heroes = await HeroService.GetHeroes();
}

OnInitializedAsyncに書き換える

OnInitializedの非同期版であるOnInitialiedAsyncを使うようにします。

Heroes.razor
protected override async Task OnInitializedAsync()
{
    await GetHeroesAsync();
}

メッセージを表示する

ここでは次のことを行います。

  • Messageコンポーネントを作成し、画面下部にアプリケーションからのメッセージを表示します。
  • MessageServiceを作成します。
  • MessageServiceHeroServiceヘインジェクトします。
  • HeroServiceがヒーロ達を読み込んだら、メッセージを表示するようにします。

Messageコンポーネントを作成する

dotnetコマンドを使ってMessageコンポーネントを作成します。

dotnet new razorcomponent -o BlazorTourOfHeroes/Shared -n Message

Indexページを編集して、Messageコンポーネントを表示するようにします。

Index.razor
<h1>Tour of Heroes</h1>

<Heroes></Heroes>
<Message></Message>

MessageServiceを作成する

インターフェースとコンクリートクラスを作成します。

IMessageService.cs
using System;
using System.Collections.Generic;

namespace BlazorTourOfHeroes.Service
{
    public interface IMessageService
    {
        IEnumerable<string> Messages { get; }
        void Add(string message);
        void Clear();
    }
}
MessageService.cs
using System;
using System.Collections.Generic;

namespace BlazorTourOfHeroes.Service
{
    public class MessageService : IMessageService
    {
        private readonly List<string> messages = new List<string>();

        public IEnumerable<string> Messages
        {
            get
            {
                return messages;
            }
        }

        public void Add(string message)
        {
            messages.Add(message);
        }

        public void Clear()
        {
            messages.Clear();
        }
    }
}

MessageServiceをDIコンテナへ登録する

DIコンテナへ登録します。

Program.cs
builder.Services.AddSingleton<IMessageService, MessageService>();

HeroServiceヘインジェクトする

コンポーネントではない場合は、コンストラクタインジェクションになります。

HeroService.cs
private readonly IMessageService messageService;

public HeroService(IMessageService messageService)
{
    this.messageService = messageService;
}

HeroServiceからメッセージを送る

GetHeroesメソッドを変更します。

HeroesService.cs
public Task<List<Hero>> GetHeroes()
{
    // TODO: ヒーロー達を取得した __後で__ メッセージを送るようにする
    messageService.Add("HeroService: fetched heroes");
    return Task.FromResult(MockHeroes.Create());
}

HeroServiceからのメッセージを表示する

Messageコンポーネントを変更してメッセージを表示するようにします。

OnInitializedの中で、MessageServiceの状態変化を受け取るハンドラStateHasChangedを登録しています。
登録を解除できるように、IDisposableを実装しています。

Message.razor
@using BlazorTourOfHeroes.Service
@inject IMessageService MessageService

@if (MessageService.Messages.Count() > 0) {
    <h2>Messages</h2>
    <button class="clear" @onclick="MessageService.Clear">Clear</button>
    @foreach (var message in MessageService.Messages)
    {
        <div>@message</div>
    }
}

Heroコンポーネントにメッセージを追加する

ヒーロー選択時に、メッセージを追加するようにします。

Heroes.razor
private void OnSelect(Hero hero)
{
    selectedHero = hero;
    MessageService.Add($"HeroesComponent: Selected hero id={hero.Id}");
}

HeroServiceの状態変化をコンポーネントで受け取る

Heroesコンポーネントからメッセージを追加していますが、Messageコンポーネントに反映されません。
これを解決するには、2つやることがあります。

  • MessageServiceから状態変化を通知する。
  • MessageコンポーネントでMessageServiceの状態変化通知を受け取る。

HeroServiceから状態変化を通知する

状態変化を通知するイベントを定義します。

IMessageService.cs
event Action OnChange;

状態が変化したとき、イベントハンドラを起動するようにします。

MessageService.cs
public event Action OnChange;

public void Add(string message)
{
    messages.Add(message);
    NotifyChange();
}

public void Clear()
{
    messages.Clear();
    NotifyChange();
}

// ハンドラが登録されていれば変更を通知
private void NotifyChange() => OnChange?.Invoke();

MessageコンポーネントでMessageServiceの状態変化通知を受け取る

OnInitializedのなかで、StateHasChangedハンドラを登録します。
コンポーネント廃棄時にハンドラを登録解除するため、IDisposableを実装します。
Disposeのなかで登録解除します。

Message.razor
@using BlazorTourOfHeroes.Service
@implements IDisposable
@inject IMessageService MessageService

@if (MessageService.Messages.Count() > 0) {
    <h2>Messages</h2>
    <button class="clear" @onclick="MessageService.Clear">Clear</button>
    @foreach (var message in MessageService.Messages)
    {
        <div>@message</div>
    }
}

@code {
    protected override void OnInitialized()
    {
        MessageService.OnChange += StateHasChanged;
    }

    public void Dispose()
    {
        MessageService.OnChange -= StateHasChanged;
    }
}

こんなんできました

toh.gif

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?