0
1

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 5 years have passed since last update.

Angular+Openweathermap+Leafret.js+MongoDBで天気予報WEBアプリを作る ⑤サービスが持つプロパティの更新を検知する

Last updated at Posted at 2019-10-13

#この記事でやること
 前回、[都市の登録]→[地図上にマーカー描画]の処理を実装したが、
 非同期の登録処理が終わる前に[画面遷移]→[マーカー描画]が走ることで、
 一度別画面に遷移しないとマーカーが表示されない状態になってしまった。

 ※過去記事
  ①Angular環境構築とひな形の生成
  ②コンポーネントとサービスの追加(登録画面)
  ③OpenweathermapのAPIを叩く
  ④Leafletで地図を表示する

 今回はサービスの保持しているプロパティの更新を検知する仕組みを導入し、
 更新処理が終わったタイミングでマーカー描画を行う仕組みに変更する

##参考にした記事
[Angular] サービスを使用してデータをコンポーネント間で共有する
※めっちゃ助かりました。ありがとうございます。

##Subjectを利用した更新通知を実装

 今回やりたいことは、「サービスが持つプロパティの変更」を「コンポーネント(ts)で検知して後続処理を行う」なので、
 任意のタイミングで他のコンポーネントにデータを送受信できるSubjectを使って更新メッセージのやり取りを行う

###サービスの修正
 まずはサービス側に以下の修正を行う
  ・subjectのインポート
  ・更新メッセージを格納(通知)する領域を追加
  ・更新を通知する処理を実装

src/app/city/city.service.ts
 :
/* 追加*/
import { Subject } from 'rxjs';
 :
 :
export class CityService {

  cities: City[];

  /* 通知の仕組み追加 */
  // notice$を更新するとsubscribeしている個所が発火する
  private subject = new Subject<string>();
  public notice$ = this.subject.asObservable();

  public addCity(city: City): void {
    var id: number;
    id = this.cities.length + 1;
    /* idを設定 */
    city.id = id;

    /* OpenweathermapのAPIを呼び出す */
    var apiEndPoint: string = this.environment.baseUrl
    + 'weather?q=' + city.name
    + '&appid=' + this.environment.appId;

    this.service.sendHttpRequest(apiEndPoint)
    .subscribe(res => {
      var weather = res.weather[0].description;
      // 天気を設定
      city.weather = weather;

      // 緯度経度を設定
      var coord = res.coord;
      city.location = {lat:coord.lat, lon:coord.lon};

      // 配列に追加
      this.cities.push(city);

      /* 登録処理完了時に更新メッセージ領域の文字列を更新*/
      this.updateNotice('city pushued')

      }, err => {
        console.log(err);
      }, () => {
      });
  }

  /* 更新メッセージを反映させる処理 */
  private updateNotice(update: string) {
    console.log('data update!');
    this.subject.next(update);
  }
}

これで登録処理addCityが完了したタイミングで通知を飛ばすことができる。

###コンポーネントの修正(subscribeする)
続いてdashboard.component.ts側に監視する仕組みsubscribeを追加する

src/app/dashboard/dashboard.component.ts
 :
/* 追加 */
import { Subscription } from 'rxjs';
 :
 :
export class DashboardComponent implements OnInit {
  cities: City[];
  /* 追加 */
  private subscription: Subscription;
 :
 :
  ngOnInit() {
    this.cities = this.cityService.getCities();

    /* 地図を生成 */
    this.leafletService.createMap();

    /* サービス側プロパティの更新(都市の追加)を検知 */
    this.subscription = this.cityService.notice$.subscribe(
      msg => {
        console.log('Data Updated Detection.');
        // マーカーを描画する
        this.addMarker();
      });
  }

  private addMarker() {
    /* 登録されている都市ごとにマーカーを作成*/
    this.cities.map(city => {
      this.leafletService.addMarker(city.name, city.weather, city.location.lat, city.location.lon);
    });
  }

###結果
 登録完了を待ってマーカー描画処理を行うことができた!

 登録処理の実行     →
    ↓          ↓
 画面遷移&マップ描画  Openweather問い合わせ
    ↓          ↓
    ↓        登録完了を通知(city.service内で更新メッセージ)
    ↓          ↓
 登録完了を検知      ↲
    ↓         
 マーカー描画

画面
image.png

image.png

##(ついで①)マーカー画像をOpenweathermapのアイコンに変更
 Openweatherで取得した気象情報の中にiconがあることに気づいたので、ついでに実装する

###city.tsクラスの修正
 アイコン画像URLを格納する領域を追加する

src/app/city/city.ts
export class City {
    id: number;       /* 連番 */
    name: string;     /* 都市名 */
    weather: string;  /* 天気を取得して格納 */

    /* 緯度経度を格納するプロパティ*/
    location: 
      {lat: number, lon: number}

    /* アイコンURL */
    icon: string;
  }

###登録処理city.service.tsの修正
 APIレスポンスからアイコンURLを取得する処理を追加

src/app/city/city.service.ts
 :
 :
  public addCity(city: City): void {
 :
 :
    /* OpenweathermapのAPIを呼び出す */
    var apiEndPoint: string = this.environment.baseUrl
    + 'weather?q=' + city.name
    + '&appid=' + this.environment.appId;

    this.service.sendHttpRequest(apiEndPoint)
    .subscribe(res => {
      var weather = res.weather[0];
      // 天気を設定
      city.weather = weather.description;

      // 緯度経度を設定
      var coord = res.coord;
      city.location = {lat:coord.lat, lon:coord.lon};

      // アイコンURLを取得
      city.icon = 'http://openweathermap.org/img/wn/' + 
weather.icon + '@2x.png'

      // 配列に追加
      this.cities.push(city);
      this.updateNotice('city pushued:' + city.name)
 :
 :

###マーカー描画処理の変更
 leaflet.service.tsのマーカー描画処理を変更し、Openweatherのアイコンを利用する

src/app/common/leaflet.service.ts

import { Injectable } from '@angular/core';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class LeafletService {

  map:any;
  constructor() { }

  createMap() {
    this.map = L.map('map').setView([33.584261,130.403789], 13);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(this.map);
  }

  addMarker(name: string, weather: string, lat: number, lon: number, icon: string) {
    /* icon情報を追加 */
    var iconOpt = L.icon({
      iconUrl: icon,
      iconRetinaUrl: icon,
      iconSize: [75, 75],
      iconAnchor: [25, 25],
      popupAnchor: [0, 0],
    });
    // マーカー作成
    L.marker([lat, lon], { icon: iconOpt } )
    .bindPopup(name + ':\n' + weather)
    .addTo(this.map);
  }
}

###コンポーネントの修正
 マーカー描画処理にアイコンURLを渡すように変更

src/app/dashboard/dashboard.component.ts
 :
 :
 :
  private addMarker() {
    /* 登録されている都市ごとにマーカーを作成*/
    this.cities.map(city => {
      this.leafletService.addMarker(city.name, city.weather, city.location.lat, city.location.lon, city.icon);
    });
  }
 :

画面
Openstreetmapのアイコンに変更できた!
2019-10-13_15h07_26.png

※よくみると曇りのアイコンになっている(赤枠部)

##(ついで②)すべてのマーカーが収まるように地図を動かす
マーカーの数が増えた場合にすべてが収まるように変更してみる
マーカーを配列として管理し、getBounds()すればいい感じになるようだ

###leaflet.service.tsクラスの修正
 これまではマーカーは作りっぱなし(mapにaddToするだけ)だったため、
 管理用の配列に格納し、getBouds()してmapにfitBouds()する

src/app/common/leaflet.service.ts
import { Injectable } from '@angular/core';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class LeafletService {
  map:any;
  /* マーカー管理用プロパティ追加 */
  markers:L.FeatureGroup;

  constructor() {
    /* 初期化 */
    this.markers = new L.FeatureGroup();
   }

  createMap() {
    this.map = L.map('map').setView([33.584261,130.403789], 13);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(this.map);
  }

  addMarker(name: string, weather: string, lat: number, lon: number, icon: string) {
    // icon情報
    var iconOpt = L.icon({
      iconUrl: icon,
      iconRetinaUrl: icon,
      iconSize: [75, 75],
      iconAnchor: [25, 25],
      popupAnchor: [0, 0],
    });

    // マーカー作成
    this.markers.addLayer(
    L.marker( [lat, lon], { icon: iconOpt } ).bindPopup(name + ':\n' + weather)
    )

    this.markers.addTo(this.map);
    // マーカー追加後にフィット処理を呼び出す
    this.changeBounds();
  }

  changeBounds() {
    // ズームサイズが大きすぎる場合は下げる
    this.map.fitBounds(this.markers.getBounds());
    console.log(this.map.getZoom());
    if (this.map.getZoom() > 15) {
      this.map.setZoom(13);
    }
  }
}

###画面
いい感じになった
image.png

##次回の予定
 登録した都市をどこにも保存しておらず、F5で消えてしまうので
 次はMongoDBを用意して格納してみる

 ※関連記事
  ①Angular環境構築とひな形の生成
  ②コンポーネントとサービスの追加(登録画面)
  ③OpenweathermapのAPIを叩く
  ④Leafletで地図を表示する

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?