読者です 読者をやめる 読者になる 読者になる

l08084のブログ

プログラマーになりたい

Angular + Typescriptをブラウザでデバッグしたい

はじめに

Angularプロジェクトをブラウザでデバッグする際に、TypescriptファイルにBreakPointを設定する方法がわからなかったので調べた。

環境

  • Angular: 2.4.7
  • angular-cli: 1.0.0-beta.28.3
  • webpack: 2.2.0
  • Google Chrome: 53.0.2785.116
  • 各種設定ファイルは、Angular CLIでプロジェクト作成後、デフォルト設定のまま
  • エディタはVisual Studio Code(1.7.2)を使用

デバッグ方法

  1. Angular CLIで雛形プロジェクトを作成した後、ng serveコマンドで開発ビルド&実行する
  2. Chromeのdeveloper toolsを起動する
  3. Sourcesタブを選択 f:id:l08084:20170213222308p:plain

webpack://配下にTypescriptファイルが配置されているのでBreakPointを設定することができる。

画像の例では、src/app/app.component.ts ファイルにBreakPointを設定している。

なぜ私のAngular CLIはカレントディレクトリにプロジェクトを作成してくれないのか?

Angularプロジェクトの雛形作成に、generator-angular2-typescriptを使っていたのですが、最近Angular CLIに移行しました。

表題の件

Angular CLIでは、ng new [プロジェクト名]コマンドでプロジェクトの雛形をカレントディレクトに作成してくれるんですが、なぜかカレントディレクトリではなくホームディレクトにしかプロジェクトを作成してくれない!という事態が発生しました。

開発環境

  • OS:OS X EI Capitan(10.11.6)
  • angular-cli: 1.0.0-beta.28.3
  • node: 6.2.1

解決

ググって出てきた下記stackoverflowの記事のおかげで解決しました。

stackoverflow.com

Do you have a package.json file in your home/user directory? I experienced this issue, then I removed package.json and that stopped it.

ホームディレクトリに置いてあるpackage.jsonが原因じゃない?package.jsonを削除すれば、カレントディレクトリにプロジェクトが作成されると思うよ〜みたいなことを言っていますね。

案の定ホームディレクトリにいつ作ったか覚えてないpackage.jsonが置いてあったので、削除したら無事カレントディレクトリにプロジェクトが作成されるようになりました。

個人的にまぎらわしいCSSのセレクタ

イントロ

数あるCSSセレクタのうち、意味がごっちゃになりやすいものをメモした。

タイプセレクタとclassセレクタの併用

次の例では、タイプセレクタliとclassセレクタ.itemを続けて記述している(間に半角スペースを含めてはいけない)。<li>タグのclass="item"の要素のみにスタイルが適用される。

li.item {
  font-weight: bold;
}
<p class="item">更新のお知らせ</p>
<ul>
  <li>おまけ</li>
  <!--下記要素のみにスタイルが適用される-->
  <li class="item">ハッピーセット</li>
</ul>

子孫コンビネータ(子孫セレクタ)

複数のセレクタを組み合わせて、ある要素の子要素または孫要素を選択するのが子孫コンピネータ。 次の例では、<ul>に含まれる<li>にだけマッチしてスタイルが適用される。

ul li {
  font-weight: bold;
}
<ul>
  <!--style適用範囲 start-->
  <li>ハッピーセット</li>
  <li>ラッキーセット</li>
  <!--style適用 end-->
</ul>
<ol>
  <li>妖怪ミニカー</li>
  <li>妖怪グラス</li>
</ol>

直下セレクタ(子セレクタ)

複数のセレクタを組み合わせて、ある要素の子要素のみを選択するのが子孫コンピネータ。 次の例では、<ul>に含まれる<li>にだけマッチしてスタイルが適用される。

ul > li {
  font-weight: bold;
}
<ul>
  <!--style適用範囲 start-->
  <li>ハッピーセット</li>
  <li>ラッキーセット</li>
  <!--style適用 end-->
</ul>
<ol>
  <li>妖怪ミニカー</li>
  <li>妖怪グラス</li>
</ol>

セレクタのグループ化

セレクタをグループ化すると、1つのスタイル宣言に対して複数のセレクタを割り当てることができる。次の例では、<p><li>の両方にマッチしてスタイルが適用される。

p, li {
  font-weight: bold;
}
<!--style適用範囲 start-->
<p>お知らせ</p>
<!--style適用 end-->
<ul>
  <!--style適用範囲 start-->
  <li>妖怪ミニカー</li>
  <li>妖怪グラス</li>
  <!--style適用 end-->
</ul>

下記の本を参考にした。

https://www.amazon.co.jp/dp/B00VUS6WOE/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

ngForm内に別ComponentのFormを含めるとvalueが取得できない

イントロ

ngForm内に、別コンポーネントで定義したテキストフィールドや、セレクトボックスを含めると、Sendボタンで送信するときに、valueを取得できないという問題に直面した。

開発環境

  • Angular@2.4.1

  • typescript@2.0.10

デモアプリケーション

  • テキストフィールドは、ngFormと別コンポーネントで定義している

  • コンソールを見ると、別コンポーネントで定義しているテキストフィールドのvalueも取得できていることがわかる

f:id:l08084:20161231221509p:plain

ngDefaultControlで解決

ngDefaultControlを入れたら解決した。理屈はわかってないので、後で調べる。

  • src/app/app.component.html
<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
  <div class="form-box">
    <input-text name="text1" [(ngModel)]="text1" ngDefaultControl></input-text>
    <input-text name="text2" [(ngModel)]="text2" ngDefaultControl></input-text>
    <input-text name="text3" [(ngModel)]="text3" ngDefaultControl></input-text>
    <button type="submit" class="btn btn-primary">Submit</button>
  </div>
</form>
  • src/components/input-text/input-text.component.ts
import { Component, Input } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'input-text',
  template: `
  <input type="text" class="form-control" [(ngModel)]="value">`,
  styleUrls: ['./input-text.component.scss']
})
export class InputTextComponent {
}

上記ソースコードは、下記URLに格納してます。

github.com

angular2-google-mapsで渋滞状況の表示

イントロ

前前回の記事で作成した、angular2-google-mapsのデモアプリに、渋滞状況表示のデモを追加した。

開発環境

  • Angular@2.4.1

  • angular2-google-maps@0.17.0

  • typescript@2.0.10

渋滞状況の表示(traffic layer)とは

道路の混雑状況を色で表示する(空いていれば緑、混雑していれば赤)、Google Mapの機能。

渋滞状況表示のデモ

  1. 「Set Traffic Layer」と記載されたボタンを押下

  2. Google Map上に渋滞状況が表示される

渋滞状況表示前 f:id:l08084:20161230162902p:plain

渋滞状況表示後 f:id:l08084:20161230162918p:plain

ソースコード

  • src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { MapService } from '../services/map.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  lat: number = 35.6329007;
  lng: number = 139.8782003;
  zoom: number = 15;
  address: string = '';
  trafficSwitch: number = 0;

  constructor(
    public mapService: MapService,
  ) {}

  public ngOnInit() {
    this.getAddress();
  }

  public geocoding(f: NgForm) {
    let self = this;

    this.mapService.geocoding(f.value.address).then(
      rtn => {
        let location = rtn[0].geometry.location;

        self.lat = location.lat();
        self.lng = location.lng();

        // call reverse geocoding
        self.getAddress();
      }
    );
  }

  public getAddress() {
    let self = this;

    this.mapService.reverseGeocoding(this.lat, this.lng).then(
      rtn => {
        self.address = rtn[0].formatted_address;
      }
    );
  }

  public setTrafficLayer() {
    this.trafficSwitch = 1 - this.trafficSwitch;
  }
}
  • src/app/app.component.html
<form #f="ngForm" (ngSubmit)="geocoding(f)" novalidate>
  <input type="text" name="address" placeholder="場所を入力してください...(例:品川駅)" ngModel>
</form>
<div class="address-panel">{{ address }}</div>
<div class="trafficButton" (click)="setTrafficLayer()">Set Traffic Layer</div>
<sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
  <sebm-google-map-marker [latitude]="lat" [longitude]="lng"></sebm-google-map-marker>
  <map-content #map [trafficSwitch]="trafficSwitch"></map-content>
</sebm-google-map>
  • src/components/map/map-content.component.ts
import { Component, Input } from '@angular/core';

import { GoogleMapsAPIWrapper } from 'angular2-google-maps/core';

declare var google: any;

/**
 * MapContentComponent
 * 
 * @export
 * @class MapContentComponent
 */
@Component({
  selector: 'map-content',
  template: ''
})
export class MapContentComponent {
  private trafficLayer: any;

  @Input() public set trafficSwitch(trafficSwitch: number) {
    this.trafficToggle(trafficSwitch);
  }

  /**
   * Creates an instance of MapContentComponent.
   * 
   * @param {GoogleMapsAPIWrapper} mapApiWrapper
   * 
   * @memberOf MapContentComponent
   */
  constructor(
    private mapApiWrapper: GoogleMapsAPIWrapper,
  ) {
    // none
  }

  /**
   * Set traffic layer
   * 
   * @param {number} state
   * 
   * @memberOf MapContentComponent
   */
  public trafficToggle(state: number) {
    let self = this;
    this.mapApiWrapper.getNativeMap()
      .then((map) => {
        self.trafficLayer = self.trafficLayer ? self.trafficLayer : new google.maps.TrafficLayer();
        let targetMap = state === 1 ? map : null;

        self.trafficLayer.setMap(targetMap);
      });
  }

}

GoogleMapsAPIWrapperのgetNativeMap()を呼び出して、mapオブジェクトを取得しているところがポイント

デモアプリケーションは、下記URLに格納してます。

github.com

参考サイト

waox.main.jp

angular2-google-mapsでリバースジオコーディング

イントロ

前回の記事で作成した、angular2-google-mapsのデモアプリに、リバースジオコーディングのデモを追加した。

開発環境

  • Angular@2.4.1

  • angular2-google-maps@0.17.0

  • typescript@2.0.10

リバースジオコーディングとは

本ブログでは、緯度、経度から住所を算出することを指しています。

リバースジオコーディングのデモ

  1. Google Mapの中心の座標(緯度、経度)を取得

  2. 座標を住所に変換(リバースジオコーディング)

  3. 住所を白枠内に表示

f:id:l08084:20161229204827p:plain

ソースコード

  • src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { MapService } from '../services/map.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  lat: number = 35.6329007;
  lng: number = 139.8782003;
  zoom: number = 15;
  address: string = '';

  constructor(
    public mapService: MapService,
  ) {}

  public ngOnInit() {
    this.getAddress();
  }

  public geocoding(f: NgForm) {
    let self = this;

    this.mapService.geocoding(f.value.address).then(
      rtn => {
        let location = rtn[0].geometry.location;

        self.lat = location.lat();
        self.lng = location.lng();

        // call reverse geocoding
        self.getAddress();
      }
    );
  }

  public getAddress() {
    let self = this;

    this.mapService.reverseGeocoding(this.lat, this.lng).then(
      rtn => {
        self.address = rtn[0].formatted_address;
      }
    );
  }
}

getAddress()で、mapServiceクラスに座標を渡して、結果の住所情報を受け取っている。

  • src/services/map.service.ts
import { Injectable } from '@angular/core';

import { MapsAPILoader } from 'angular2-google-maps/core';

declare let google: any;

@Injectable()
export class MapService {
    private geocoder: any = null;

    constructor(
        private mapsAPILoader: MapsAPILoader,
    ) { }

    public geocoding(address: string): Promise<any> {
        return this.mapsAPILoader.load().then(() => {
            this.geocoder = new google.maps.Geocoder();

            return new Promise((resolve, reject) => {
                this.geocoder.geocode({ 'address': address }, (result: any, status: any) => {
                    if (status === google.maps.GeocoderStatus.OK) {
                        resolve(result);
                    } else {
                        reject(status);
                    }
                });
            });
        });
    }

    public reverseGeocoding(lat: number, lng: number): Promise<any> {
        let latlng = { lat: lat, lng: lng };

        return this.mapsAPILoader.load().then(() => {

            this.geocoder = new google.maps.Geocoder();
            return new Promise((resolve, reject) => {
                this.geocoder.geocode({ 'location': latlng }, (result: any, status: any) => {
                    if (status === google.maps.GeocoderStatus.OK) {
                        resolve(result);
                    } else {
                        reject(status);
                    }
                });
            });
        });
    }

}

reverseGeocoding()で、リバースジオコーディングの処理を実施している

  • src/app/app.component.html
<form #f="ngForm" (ngSubmit)="geocoding(f)" novalidate>
  <input type="text" name="address" placeholder="場所を入力してください...(例:品川駅)" ngModel>
</form>
<div class="address-panel">{{ address }}</div>
<sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
  <sebm-google-map-marker [latitude]="lat" [longitude]="lng"></sebm-google-map-marker>
</sebm-google-map>

デモアプリケーションは、下記URLに格納してます。

github.com

angular2-google-mapsでジオコーディング

最近仕事でGoogle Map周りを触ることが多いので、下記ライブラリを使って、ジオコーディング処理のデモアプリを作成してみた。

github.com

開発環境
  • Angular@2.4.1
  • typescript@2.0.10

ジオコーディングとは

Google Maps API公式サイトによると、住所を地理的座標(緯度、経度)に変換する処理とのこと。

ジオコーディングデモアプリ概要

入力した場所情報をジオコーディングで緯度と経度に変換後、GoogleMapに渡すといった処理をAngularを使って書いている。

GoogleMapで表示したい場所をテキストボックスに入力してEnterすると。。。
f:id:l08084:20161229160340p:plain

GoogleMapの中心が、テキストボックスに入力した地点に移動する。という仕様
f:id:l08084:20161229160450p:plain

デモアプリソースコード解説

angular2-google-mapsの導入方法は、公式サイトのGetting Startedがわかりやすいので割愛する。

  • src/app/app.component.ts
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { MapService } from '../services/map.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  lat: number = 35.6329007;
  lng: number = 139.8782003;
  zoom: number = 15;

  constructor(
    public mapService: MapService,
  ) {}

  public geocoding(f: NgForm) {
    let self = this;

    this.mapService.geocoding(f.value.address).then(
      rtn => {
        let location = rtn[0].geometry.location;

        self.lat = location.lat();
        self.lng = location.lng();
      }
    );
  }
}

テキストボックスに入力した場所情報を、MapServiceに渡した後、MapServiceからジオコーディング結果として渡された緯度と経度を受け取り、HTML上のGoogleMapに渡している。

  • src/app/app.component.ts
<form #f="ngForm" (ngSubmit)="geocoding(f)" novalidate>
  <input type="text" name="address" placeholder="場所を入力してください...(例:品川駅)" ngModel>
</form>
<sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
  <sebm-google-map-marker [latitude]="lat" [longitude]="lng"></sebm-google-map-marker>
</sebm-google-map>
  • src/services/map.service.ts
import { Injectable } from '@angular/core';

import { MapsAPILoader } from 'angular2-google-maps/core';

declare let google: any;

@Injectable()
export class MapService {
    private geocoder: any = null;

    constructor(
        private mapsAPILoader: MapsAPILoader,
    ) { }

    public geocoding(address: string): Promise<any> {
        return this.mapsAPILoader.load().then(() => {
            this.geocoder = new google.maps.Geocoder();

            return new Promise((resolve, reject) => {
                this.geocoder.geocode({ 'address': address }, (result: any, status: any) => {
                    if (status === google.maps.GeocoderStatus.OK) {
                        resolve(result);
                    } else {
                        reject(status);
                    }
                });
            });
        });
    }

}

・src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';

import { AgmCoreModule } from 'angular2-google-maps/core';

import { MapService } from '../services/map.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AgmCoreModule.forRoot({
      apiKey: 'YourAPIKey'
    })
  ],
  providers: [
    MapService,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

mapsAPILoader.load()を使用すると、「google is not undefined〜」的なエラーが発生しないので良いです。

作成したデモアプリは、Githubにあげているので、取得したGoogle mapsのAPIKEYを設定すれば動かすこともできます。

手順
  1. GoogleMapのAPIKEYを取得して、src/app/app.module.tsに設定
  1. npm installコマンド実施
  1. ng serveコマンド実施
  1. ブラウザを開いて、http://localhost:4200/ を入力する


github.com

参考サイト

github.com

mae.chab.in