君は心理学者なのか?

大学時代に心理学を専攻しなぜかプログラマになった、サイコ(心理学)プログラマかろてんの雑記。

Angular2/4にてbootstrapのmodalウィンドウを開き、閉じた時に親コンポーネントに値を反映させる

Angular2/4を使ってmodalウィンドウを表示する

bootstrap標準のmodalウィンドウを、

Angularから開こうと頑張った話です。

bootstrapとAngular以外は使っておりません。

0. 準備

0-1. プロジェクトの作成

新規プロジェクトを作成し、サーバをスタートさせます。

ng new modal
ng serve

0-2. ngx-bootstrapのパッケージをinstall

npm install ngx-bootstrap --save

0-3. 必要なmoduleを読み込む

// app.module.ts
import { FormsModule } from '@angular/forms'; // 追加
import { ModalModule } from 'ngx-bootstrap'; // 追加

0-4. 必要なmoduleをimportする

@NgModule({
  imports: [
    ModalModule.forRoot(), // 追加
    FormsModule // 追加
  ],  
})

0-5. bootstrap.min.cssを読み込む

<!-- index.html -->
...
 <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
...

1. ダイアログコンポーネントの作成と編集

1-1. ダイアログコンポーネントの作成

ng g component dialog

1-2. dialog.component.htmlを編集

<!-- dialog.component.html -->
<div class="modal fade" bsModal #lgModal="bs-modal" id="sampleModal" tabindex="-1">
  <div class="modal-dialog modal-sm">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" (click)="lgModal.hide()"><span>×</span></button>
        <h4 class="modal-title">テスト</h4>
      </div>
      <div class="modal-body">
        <input type="text" [(ngModel)]="dialogData">
      </div>
      <div class="modal-footer">   
        <button type="button" class="btn btn-default" (click)="lgModal.hide()" data-dismiss="modal">キャンセル</button>
        <button type="button" class="btn btn-info" (click)="onClose()" data-dismiss="modal">決定</button>
      </div>
    </div>
  </div>
</div>

1-3. dialog.component.tsを編集

import {
  Component,
  OnInit,
  ViewChild,
  Input,
  Output,  
  EventEmitter
} from '@angular/core';
import {ModalDirective} from "ngx-bootstrap";

export class DialogComponent implements OnInit {
  /** modal要素を取得 */
  @ViewChild("lgModal") modalRef :ModalDirective;
  /** 親からdialogDataを受け取る */
  @Input() dialogData;
  /** 親へのイベント通知用 */
  @Output() clickCloseButton = new EventEmitter<any>();

  /** modal内で扱うデータ */
  public data;
...
  /** 親コンポーネントから呼び出される */
  openDialog()
  {
    /** ダイアログを開いた時、親データから値を取り直す */
    this.data = this.dialogData;
    this.modalRef.show();
  }

  /** モーダルの閉じるボタンを押した時に呼び出される */
  onClose()
  {
    this.modalRef.hide();
    /** 親コンポーネントへイベントを通知 */
    this.clickCloseButton.emit({data: this.dialogData});
  }
}

2. appコンポーネントの編集

2-1. app.component.htmlを編集

<!-- app.component.html -->
{{parentData}}
<button (click)="openDialog()" class="btn btn-primary">ダイアログを開く</button>
<!-- ダイアログコンポーネント -->
<app-dialog [dialogData]="parentData" (clickCloseButton)="getDialogData($event)" #dialog></app-dialog>

コンポーネントから、

子であるダイアログコンポーネントにparentDataをわたしています。

また、

コンポーネントから親コンポーネントにclickCloseというイベントをemitし、

getDialogDataという関数で受け取っています。

2-2. app.component.tsを編集

import {
  Component,
  ViewChild // 追加
} from '@angular/core';
import { DialogComponent } from './dialog/dialog.component'; // 追加

  /** ここで子コンポーネントであるダイアログコンポーネントを取得しています */
  @ViewChild("dialog") dialogComponent: DialogComponent;
  parentData :string = "hello";

  openDialog() {
    this.dialogComponent.openDialog();
  }

  getDialogData(e)
  {
    /** 親コンポーネントのデータ書き換えを行います */
    this.parentData = e.data;
  }

結果

f:id:karoten512:20171206193533g:plain

参考

【AngularでModal設定】Angular2でダイアログを開く方法 | 思いのままを綴る

お世話になりました。

ソースコード

github.com

最近の出来事を元に脚本を書いてみる。その5

はじめに

この話はいい感じにフィクションです。

登場人物

わたし(26)…会社員

ボーカル(20代後半)

ギター(20代後半)

M

 時刻は22時を回ったところ。

 吉祥寺サンロードは、まるで一方通行になってしまったみたいだ。

 人々はみんな、駅の方へ向かって歩いている。

SE

 カッティングで弾かれているアコースティックギター

M

 ふとギターとすごくいい声が聞こえてきた。

 ハスキーな男の声。

 すこし路地に入ると、いた。20代後半くらいだろうか。

 アフロの兄ちゃんが、朗々と声を張り上げてうたっていた。

M

 足をとめると、兄ちゃんにウィンクされた。

 そのまま曲が終わり、2,3人の観客からまばらな拍手がおくられる。

兄ちゃん「いい夜だねぇ!」

M

 私は曖昧な笑顔を返した。

 兄ちゃんがダンボールを出してくれる。そこに座る私。

 ダンボール on 路上。ひんやりしている。

兄ちゃん「今日何してたの?」

私「吉祥寺で買い物を」

兄ちゃん「あ〜!俺と一緒だ!」

M

 周りから「うそでしょ」とからかわれる兄ちゃん。

 おちゃめな人だ。

私「バンド名なんていうんすか?」

兄ちゃん「村上製作所!」

私「村田製作所から来てるんですか?」

兄ちゃん「そーそー! まあ俺の方が先だけどね!!」

M

そういって豪快に笑う。つられて私も笑ってしまった。

??「おーい!」

兄ちゃん「おお!何してたん?」

??「エフェクターをさ、買ってた」

兄ちゃん「あ、こいつうちのギター!」

私「どうも」

ギターの人「どもども」

M

 ギターの人はおもむろにギターを取り出した。セッションするらしい。

M

 深夜のサンロードに、

 アコースティックギターと少しディストーションがかかったエレキギター

 朗々としたハスキーボイス。

 確かに、良い夜かもしれない。

M

 曲が終わった。先ほどまで楽しそうにうたっていた兄ちゃんが、

 今度は深刻そうな顔をしている。

兄ちゃん「あのさ」

兄ちゃん「今、今言っていい?いいんかなー?
    
     このタイミングで言っていいんかなー?」

ギターの人「なんだよ、早く言えよ」

兄ちゃん「……マサちゃん転勤だって。。。」

ギターの人「。。。えぇーーーー!」

M

 サンロードに困惑した声が響き渡った。

ギターの人「ドラムいないとキツイよな。。。」

兄ちゃん「……探さないとな。。。」

M

 ちょっと笑ってしまった。

 僕は学生時代にバンドでドラムをやっていたからだ。

 この状況、あの展開に似ている。

 とつぜんメンバーの一人が負傷してギターが弾けなくなり、

 途方に暮れるメンバーの前にあらわれる主人公。

 普段はうだつのあがらない主人公が、そのときだけスターになる。

私「さっきの曲、途中で拍子が変わっておしゃれでしたね」

兄ちゃん「いや~!もうあそこ二度とやんない!

  難しいんだよね!」

M

 兄ちゃんはあれ、と首をかしげた

兄ちゃん「……ちょっとまって。君音楽やってたっしょ」

私「……昔ちょっとやってました」

兄ちゃん「やっぱそうだよな!あそこ普通の人はわかんねえもん」

M

 ここで「ドラムやってました」といったらどうなるんだろう。

私「……じゃあ、私はそろそろ」

兄ちゃん「お。帰る?」

M

手をぶんぶん振る兄ちゃんと、ギターの人。

兄ちゃん「いい夢見ろよ!」

ギターの人「またね〜!」

M

 僕はそのまま帰宅した。

【Git】時よ戻れ!〜RPGゲームの「セーブ」とGitの「commit」の比較から、commitとは何かについて考えてみる〜

f:id:karoten512:20171203194923j:plain

commitとは結局なんなのか

結論

commitとは、

どのセーブ地点にも戻ることができる

高機能な「セーブ」

解説

RPGのセーブとGitのcommitを比較してみます。

RPGの「セーブ」

f:id:karoten512:20171203182706g:plain

ここに冒険の書v0.1があります。

冒険が進んだので、冒険の書v0.2として保存しようとしました。

基本、セーブは上書きなので、

一度保存してしまうと前のデータには戻ることが出来ません。

Gitの「commit」

Gitのcommitは上書きではなく、

「写真をとる」イメージです。

git commit

とすると、パシャッとファイルの写真を取ってくれます。

f:id:karoten512:20171203183651g:plain

冒険が進み、冒険の書v0.2としてコミットすると、

こんな感じになります。

f:id:karoten512:20171203184134g:plain

お気づきでしょうか。

そう、前の「冒険の書v0.1」も写真としてしっかり残っているのです。

残っていると、そのファイルを復活させる(=戻る)ことができます。

セーブとcommitの違い

ズバリ、

戻れるか戻れないか

という違いです。

まずはこの点を抑えておきましょう。

commitは本当に戻れるのか

本当にcommitは戻れるのでしょうか。

ちょっと試してみましょう。

準備

以下のように、冒険の書を作成し、適当に3つコミットします。

f:id:karoten512:20171203194133g:plain

戻してみる

では、冒険の書v0.2に戻してみましょう。

git checkout ハッシュ番号

を使います。

f:id:karoten512:20171203200024g:plain

戻れた。

戻れることのメリット

  • やりなおせる

バグを出しまくっても、どうしようもないコードを書きまくっても、

いつでも戻ってやりなおせる。

  • 安心感

やりなおせるということから、安心感が生まれる。

まとめ

Gitは戻れる。便利。

f:id:karoten512:20171203194336p:plain

どんどんコミットしていきましょう。

Angular2/4のngOnChangesはオブジェクトプロパティが変わったことを検知しない〜Lifecycle HooksのngOnChages, ngDoCheckの違い〜

ngOnChangesを使って、子に渡したプロパティが変わったことを検知する

コンポーネント

/** app.component.ts */
export class AppComponent {
  public datas = 1;
  onClick()
  {
    this.datas ++;
  }
}
<!-- app.component.html -->
<button (click)="onClick()">押してください</button>
<app-child [parentProperty]='datas'></app-child>

コンポーネント

<!-- child/child.component.ts -->
export class ChildComponent implements OnInit, OnChanges {
  @Input() parentProperty;
  constructor() { } 
  ngOnInit() {
  }
  ngOnChanges() {
    console.log('changes');
  }
}

結果

clickするたび、changesが表示される。

ngOnChangesを使って、子に渡したオブジェクトのプロパティが変わったことを検知する

今度は子Componentにオブジェクトをわたして、

そのオブジェクトのプロパティを変更してみる。

<!-- app.component.ts -->
export class AppComponent {
  public datas = {
    data1: 1,
    data2: 2
  };
  onClick()
  {
    this.datas.data1 ++;
  }
}

結果

changesが表示されない。

オブジェクトプロパティの変更は検知されない!

stackoverflowで検索

angular - In angular2, how to get onChanges for properties changed on an object sent in for an @Input - Stack Overflow

Q.
How can I detect if there is a change
to any property on settings?

どうやったらオブジェクトプロパティの変更を検知できますか?

A.
Angular will only notice
if the object has been changed to a different object
(i.e., the object reference changed),
so ngOnChanges() can't be used to solve your problem. 
You could implement the ngDoCheck() method in your MyDirective class.

Angularはオブジェクト自体が変更された時(参照先が変わった時)に検知します。 なのでngOnChangesではダメです。 ngDoCheckを実装してください。

ngDoCheckを実装してみる

<!-- child/child.component.ts -->
export class ChildComponent implements OnInit, OnChanges {
  @Input() parentProperty;
  constructor() { } 
  ngOnInit() {
  }
  ngOnChanges() {
    console.log('changes');
  }
  ngDoCheck() {
    console.log('do check');
  }
}

do checkが表示された。

オブジェクトプロパティの変更が検知されている。

Angular2/4にて、Cannot find name SimpleChangesエラーが出た時の対処法

Angular2/4にて、Cannot find name SimpleChangesエラー

変更検知をしたかったので、以下の記述をしたところ、

Cannot find name 'SimpleChanges'.

というコンパイルエラーが出た。

export class ChildComponent implements OnInit, OnChanges {
  @Input() parentProperty;
  private formattedData;
  constructor() { } 
  ngOnInit() {
  }
  ngOnChanges(changes :SimpleChanges) {
    console.log('changes');
  }
}

解決方法

import { SimpleChanges } from '@angular/core';

を追加してやればOK.

参考

stackoverflow.com

stackoverflowさん、いつもありがとうございます。

Angular2/4の*ngIfと[hidden]の使い分け/違い〜Component自体は削除せず非表示にするにはどうしたらよいか〜

結論

*ngIfを使うとComponent自体消えてしまうが、

[hidden]を使うと非表示になるだけでComponentはそのまま残る。

*ngIfの場合

<app-sample *ngIf="showFlg"></app-sample>

[hidden]の場合

<app-sample [hidden]="showFlg"></app-sample>

いきさつ

Angular2/4にて、こんなことがあった

  • 2つのComponentA、ComponentBがある

  • タブAがアクティブの時ComponentA、

    タブBがアクティブの時ComponentBが表示される

  • ComponentAの値の変化に応じて、ComponentBの値を変化させる

この時、はじめはこのように実装しようと思った。

初めに考えた方法

*ngIfを使って実装してみた。

<button (click)="onClick()">タブ切り替えボタン</button>
<app-a *ngIf="showFlg"></app-a>
<app-b *ngIf="!showFlg"></app-b>
export class AppComponent {
  public showFlg :boolean = true;

  /** タブ切り替えボタン */
  onClick() :void
  {
    this.showFlg = !this.showFlg;
  }
}

*ngIfの結果

f:id:karoten512:20171201004748g:plain

↑ DOMごと消えてしまっている

この方法だと、

ComponentAが表示されているときはComponentBが破棄されてしまっているので、

Serviceを通じてComponentBの値を操作することが出来ない。悲しい。

次に考えた方法

Componentを削除せずに、非表示にしたい。と思ったので「ngif compnent not destroy」で検索。

angular - Angular2: ngIf without destroying component - Stack Overflow

Q. Is there an angular2 blessed way 
    to hide a component without destroying the component?

A. The [hidden] property is what you are looking for.
    It more or less replaced ng-show / ng-hide in Angular2.

ありがとうstackoverflow。

以下のように[hidden]を使って実験してみる。

<button (click)="onClick()">タブ切り替えボタン</button>
<app-a [hidden]="showFlg"></app-a>
<app-b [hidden]="!showFlg"></app-b>

[hidden]の結果

f:id:karoten512:20171201005146g:plain

↑ hiddenが付くだけで、DOMとしては残っている

うまくComponentが消えずに残ってくれた!

Angular2/4にてjQueryを読み込んで使用する

Angular2/4にてjQueryを使いたい

どうしても使いたいときってありますよね。

0. プロジェクトを立ち上げる

ng new angular-jquery

1. index.htmlファイルにてjQueryを読み込む

<!-- index.html -->
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</head>

2. AppComponentにて「アンビエント宣言」をする

/** app.component.ts */
import { Component } from '@angular/core';

declare var jquery:any;  // 追加
declare var $ :any; // 追加

declareは、アンビエント宣言と言います。

アンビエント宣言は、他のコンポーネント(例えば Web ブラウザや既存の JavaScript ライブラリ)から変数や関数などが提供されることをTypeScript コンパイラに伝えます。これは、既存の JavaScript ライブラリに静的型付けし、TypeScript で利用可能になることを意味します。

docs.solab.jp

3. app.component.tsにjQueryの処理を書き込む

/** app.component.ts */
export class AppComponent {
  title = 'angular 4 with jquery';
  toggleTitle(){
    $('.title').slideToggle();
  }
}

4. app.component.htmlに処理をbind

<h1 class="title" style="display:none">
  {{title}}
</h1>
<button (click)="toggleTitle()"> clickhere</button>

結果

f:id:karoten512:20171127162332g:plain

参考

stackoverflow.com

npm install jquery --save
npm install --save-dev @types/jquery

を行って導入する方法もあります。