#この記事でやること
前回、[都市の登録]→[地図上にマーカー描画]の処理を実装したが、
非同期の登録処理が終わる前に[画面遷移]→[マーカー描画]が走ることで、
一度別画面に遷移しないとマーカーが表示されない状態になってしまった。
※過去記事
①Angular環境構築とひな形の生成
②コンポーネントとサービスの追加(登録画面)
③OpenweathermapのAPIを叩く
④Leafletで地図を表示する
今回はサービスの保持しているプロパティの更新を検知する仕組みを導入し、
更新処理が終わったタイミングでマーカー描画を行う仕組みに変更する
##参考にした記事
[Angular] サービスを使用してデータをコンポーネント間で共有する
※めっちゃ助かりました。ありがとうございます。
##Subjectを利用した更新通知を実装
今回やりたいことは、「サービスが持つプロパティの変更」を「コンポーネント(ts)で検知して後続処理を行う」なので、
任意のタイミングで他のコンポーネントにデータを送受信できるSubject
を使って更新メッセージのやり取りを行う
###サービスの修正
まずはサービス側に以下の修正を行う
・subjectのインポート
・更新メッセージを格納(通知)する領域を追加
・更新を通知する処理を実装
:
/* 追加*/
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
を追加する
:
/* 追加 */
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内で更新メッセージ)
↓ ↓
登録完了を検知 ↲
↓
マーカー描画
##(ついで①)マーカー画像をOpenweathermapのアイコンに変更
Openweatherで取得した気象情報の中にicon
があることに気づいたので、ついでに実装する
###city.ts
クラスの修正
アイコン画像URLを格納する領域を追加する
export class City {
id: number; /* 連番 */
name: string; /* 都市名 */
weather: string; /* 天気を取得して格納 */
/* 緯度経度を格納するプロパティ*/
location:
{lat: number, lon: number}
/* アイコンURL */
icon: string;
}
###登録処理city.service.ts
の修正
APIレスポンスからアイコンURLを取得する処理を追加
:
:
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のアイコンを利用する
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: '© <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を渡すように変更
:
:
:
private addMarker() {
/* 登録されている都市ごとにマーカーを作成*/
this.cities.map(city => {
this.leafletService.addMarker(city.name, city.weather, city.location.lat, city.location.lon, city.icon);
});
}
:
※よくみると曇りのアイコンになっている(赤枠部)
##(ついで②)すべてのマーカーが収まるように地図を動かす
マーカーの数が増えた場合にすべてが収まるように変更してみる
マーカーを配列として管理し、getBounds()
すればいい感じになるようだ
###leaflet.service.ts
クラスの修正
これまではマーカーは作りっぱなし(mapにaddToするだけ)だったため、
管理用の配列に格納し、getBouds()
してmapにfitBouds()
する
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: '© <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);
}
}
}
##次回の予定
登録した都市をどこにも保存しておらず、F5で消えてしまうので
次はMongoDBを用意して格納してみる
※関連記事
①Angular環境構築とひな形の生成
②コンポーネントとサービスの追加(登録画面)
③OpenweathermapのAPIを叩く
④Leafletで地図を表示する