Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Angular4入門_Vol.19

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/ https://angular.io/docs/ts/latest/api/router/index/RouterLink-directive.html https://angular.io/docs/ts/latest/guide/router.html#!#basics-router-links

Router Link & Improved Navigation

  • Router Linkを使ってみる。video-list.component.htmlに追記してみる。
<p>
  {{ title }}
</p>

<div *ngFor='let item of videoList'>
  <h1><a routerLink="/videos/{{ item.slug }}" routerLinkActive="active" >{{ item.name }}</a></h1>
  <!-- <div [innerHTML]='item.embed'></div>
  <div [innerHTML]='someItem'></div> -->
  <iframe *ngIf='item.embed' width="560" height="315" [src]="getEmbedUrl(item) | safe" frameborder="0" allowfullscreen></iframe>
  <hr>
</div>
  • hrefの代わりに、routerLinkを使うという感じになっているが、hrefとの違いは何なんだろう。。。

  • routerLinkActiveを使うことによって、よってより柔軟にlinkの切り替えができるとかそんな感じなのかな。。。。

  • とりあえず、動画講義では、hrefになっていた箇所を片っ端から、routerLinkに変えていっていた。

Improve Styling

  • interfaceだけを修正するだけなので、割愛

ngClass

  • ngClassを使ってみる。ngClassは、たぶん、classをangularで双方向データバインディングできるようなモジュールだと思われる。
<p>
  {{ title }}
</p>
<div *ngFor='let item of videoList; let i = index' [ngClass]="{'row': (i+1)%3 == 0}">
  <div class="col-sm-4">
    <div class="thumbnail">
        <a routerLink="/videos/{{ item.slug }}" routerLinkActive="active" *ngIf='item.image'><img [src]='item.image' alt="{{ item.name }} image"></a>
        <div class="caption">
          <h3><a routerLink="/videos/{{ item.slug }}" routerLinkActive="active" >{{ item.name }}</a></h3>
          <p>...</p>
          <p><a routerLink="/videos/{{ item.slug }}" routerLinkActive="active" class="btn btn-primary" role="button">View</a></p>
        </div>
      </div>
  </div>
</div>

Angular4入門_Vol.18

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

Video Item Model

  • video classを作成するために、video.tsファイルを作成する。これはdjangoで言うと、modelsを使うようなものか。
export class VideoItem {
  slug: string;
  name: string;
  image: string;
  embed?: string; //optionalな場合は、最後に?をつけるということか?
  featured?: Boolean;
}
  • video-list.component.tsのvideoListの型定義を、VideoItemに設定する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { VideoItem } from '../videos/video'; // VideoItem class をimportする。
import { VideoService } from '../videos/videos.service';

@Component({
  selector: 'video-list',
  templateUrl: './video-list.component.html',
  styleUrls: ['./video-list.component.css'],
  providers: [VideoService]
})
export class VideoListComponent implements OnInit {
  private req:any; // req のアノテーション
  title = "Video List!";
  // someItem = "<h1>HiHi</h1>";
  // todayDate;
  videoList: [VideoItem]; // VideoItemのリスト型を定義する。

  constructor(private _video:VideoService) { }

  ngOnInit() {
    // this.todayDate = new Date();
    this.req = this._video.list().subscribe(data=>{
      this.videoList = data as [VideoItem]; // VideoItemのリスト型を定義する。
    }); //
  }

  ngOnDestroy(){
    this.req.unsubscribe()
  }

  getEmbedUrl(item){
    return 'https://www.youtube.com/embed/' + item.embed;
  }

}
  • video-detail.component.tsも同様にVideoItem classを追記する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { VideoItem } from '../videos/video';
import { VideoService } from '../videos/videos.service';


@Component({
  selector: 'video-detail',
  templateUrl: './video-detail.component.html',
  styleUrls: ['./video-detail.component.css'],
  providers:[VideoService]
})
export class VideoDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  private req:any;
  video: VideoItem;
  slug: string;
  constructor(private route: ActivatedRoute, private _video:VideoService) { }

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params => {
    this.slug = params['slug']
    this.req = this._video.get(this.slug).subscribe(data=>{
    this.video = data as VideoItem
      })
    })
  }
  ngOnDestroy(){
    this.routeSub.unsubscribe()
    this.req.unsubscribe()
  }

}
  • search-detail.component.ts にも追記する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { VideoItem } from '../videos/video'; // VideoItemをimport する
import { VideoService } from '../videos/videos.service';


@Component({
  selector: 'app-search-detail',
  templateUrl: './search-detail.component.html',
  styleUrls: ['./search-detail.component.css'],
  providers: [VideoService]
})
export class SearchDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  private req:any;
  query: string;
  videoList: [VideoItem]; // VideoItem のリスト型を定義する

  constructor(private route: ActivatedRoute, private _video:VideoService) { }

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{
      console.log(params)
      this.query = params['q']
      this.req = this._video.search(this.query).subscribe(data=>{
        this.videoList = data as [VideoItem]; // VideoItemのリスト型を定義する。
      })
    })
  }

  ngOnDestroy(){
    this.routeSub.unsubscribe()
  }

  getEmbedUrl(item){
    return 'https://www.youtube.com/embed/' + item.embed;
  }

}
  • home.component.tsにも追加する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';

import { VideoItem } from '../videos/video'; // VideoItem import
import { VideoService } from '../videos/videos.service'; // VideoService import

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  providers: [VideoService] //providersに、VideoServiceを定義する
})
export class HomeComponent implements OnInit, OnDestroy {
  private req: any;
  homeImageList: [VideoItem] = [] as [VideoItem] ; //VideoItemのリスト型を定義する。さらに、初期値として、空のリストを渡しておく
  // [] のあとに、as [VideoItem]は必須の模様。代入するときはそのデータのclass定義が必要なのか?よくわからないな。
  //   homeImageList = [] as [VideoItem] ;は通った。
  // あ〜そうか、VideoItemで定義された homeImageListには、何も型定義していない空のリストは、型が不明のため、VideoItem classで定義された空のリストでないと代入できないのか。

  constructor(private http:Http, private router:Router, private _video:VideoService) { } //_video を定義する

  ngOnInit() {
    this.req = this._video.list().subscribe(data=>{ // _video.list()で、呼び出す。
      // console.log(data.json())
      //this.homeImageList = [] as [VideoItem]
      data.filter(item=>{
        if(item.featured){
          this.homeImageList.push(item)
        }
      })
      // this.homeImageList = data.json();
    })
  }

  ngOnDestroy(){
    this.req.unsubscribe()
  }

  preventNormal(event:MouseEvent, image:any){
    if (!image.prevented){
      event.preventDefault()
      // image.link = '/videos'
      // image.prevented = true;
      this.router.navigate(['./videos'])
  }
}

}

まとめ

  • angularのclassは、djangoのmodelのような使い方ができる。
  • 型の定義が厳密になるため、エラーチェックなどに有効

Angular4入門_Vol.17

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

Search Video List

  • search methodを、videos.service.tsに追記する。methodの処理の方法は、get methodと似てるので、中身をコピペして再利用する。
search(query){
  return this.http.get(endpoint) // get methodで、endpointにアクセス
          .map(response=>{ // responseを代入して結果をmappingする
            let data = [] // data に空のリストを代入
            let req = response.json().filter(item=>{ // response.json()のデータをitemのname
                              if (item.name.indexOf(query) >= 0 ){ //indexOf
                                  data.push(item) // itemをpushする。
                              }
                            })
            return data
            })
          .catch(this.handleError)
}
  • indexOfは、indexOf() メソッドは、呼び出す String オブジェクト中で、fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。とのことなので、>=0としてある場合は、文字列を少なくとも1つは含む場合でfilterをかけていることになる。

  • 試しに、video-list.component.tsにsearch methodを記載してみる。

  ngOnInit() {
    this.todayDate = new Date();
    this.req = this._video.search("テスト1").subscribe(data=>{
      this.videoList = data as [any];
    }); //search("テスト1") で、引数として文字列を与えてみる。
  }
  • 無事テスト1だけ表示された!テストは成功したので、searchからlist()methodに戻しておく。

  • search-detailに、search methodを追記する。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { VideoService } from '../videos/videos.service'; //VideoServiceをimportする


@Component({
  selector: 'app-search-detail',
  templateUrl: './search-detail.component.html',
  styleUrls: ['./search-detail.component.css'],
  providers: [VideoService] //VideoServiceをprovidersとして、定義する
})
export class SearchDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  private req:any;
  query: string;
  videoList: [any];

  constructor(private route: ActivatedRoute, private _video:VideoService) { } //_vidoe にVideoServiceを型定義する。

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{
      console.log(params)
      this.query = params['q']
      this.req = this._video.search(this.query).subscribe(data=>{ //searchメソッドを、引数をqueryとして組み込む
        this.videoList = data as [any];
      })
    })
  }

  ngOnDestroy(){
    this.routeSub.unsubscribe()
  }

}
  • search boxに、キーワードを入れて検索してみる。。。。何も表示されない。。。

  • video-list.component.html の、video listを表示する箇所のhtmlのコードをコピペしてみる。

<p *ngIf='query'>
  Searched for <b>{{ query }}</b>
</p>

<div *ngFor='let item of videoList'>
  <h1><a href="videos/{{ item.slug }}" >{{ item.name }}</a></h1>
  <!-- <div [innerHTML]='item.embed'></div>
  <div [innerHTML]='someItem'></div> -->
  <iframe *ngIf='item.embed' width="560" height="315" [src]="getEmbedUrl(item) | safe" frameborder="0" allowfullscreen></iframe>
  <hr>
</div>
  • Error発生! ERROR TypeError: co.getEmbedUrl is not a functionとのこと。なので、getEmbedUrlを定義する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { VideoService } from '../videos/videos.service';


@Component({
  selector: 'app-search-detail',
  templateUrl: './search-detail.component.html',
  styleUrls: ['./search-detail.component.css'],
  providers: [VideoService]
})
export class SearchDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  private req:any;
  query: string;
  videoList: [any];

  constructor(private route: ActivatedRoute, private _video:VideoService) { }

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{
      console.log(params)
      this.query = params['q']
      this.req = this._video.search(this.query).subscribe(data=>{
        this.videoList = data as [any];
      })
    })
  }

  ngOnDestroy(){
    this.routeSub.unsubscribe()
  }

  getEmbedUrl(item){
    return 'https://www.youtube.com/embed/' + item.embed;
  }

}
  • これで再度検索してみる。。。できた〜!

まとめ

  • serviceは、apiからデータを取得>加工>componentに渡す 役割があるぽい。
  • もしかしたら、componentからデータ加工の指示>serviceを通じてapiのデータを変更・削除などもserviceでやるのか?それはまた別なのかな。

Angular4入門_Vol.16

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

Video Service

  • app/videos フォルダを作成する

  • $ ng g service videosで、serviceの雛形を作成する。

  • videos.service.tsと、videos.service.spec.tsを、app/videosフォルダに移動させる。

  • videos.service.tsにコードを追記する。

import { Injectable } from '@angular/core';
import { Http } from '@angular/http'; // Httpをimportする

import 'rxjs/add/operator/map'; // mapをimportする。
import 'rxjs/add/operator/catch'; //catchをimportする。

const endpoint = 'assets/json/videos.json' // yourdomain.com/api/videos
// constは変更不可な変数

@Injectable()
export class VideoService {
  constructor(private http: Http) { } // httpに、Httpclassを定義する。

  list(){ //list methodを使う(getでも可と言っていた)
    return this.http.get(endpoint) //httpのgetメソッドで、endpointにアクセスする。
            .map(response=>response.json()) //responseをjsonデータに変換して、mappingする。
            .catch(this.handleError) // handleErrorをcatchする。
  }

  private handleError(error:any, caught:any): any{ //handleErrorを定義しておく。
    console.log(error, caught)
  }

}
  • video-list.component.ts に、VideoServiceをimportして使ってみる。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Http } from '@angular/http';

import { VideoService } from '../videos/videos.service'; //importする

@Component({
  selector: 'video-list',
  templateUrl: './video-list.component.html',
  styleUrls: ['./video-list.component.css'],
  providers: [VideoService] // providersとして、VideoServiceを定義しておく。
})
export class VideoListComponent implements OnInit {
  private req:any;
  title = "Video List!";
  someItem = "<h1>HiHi</h1>";
  todayDate;
  videoList: [any];

  constructor(private http:Http, private _video:VideoService) { } // _videoを、VideoService classとして定義する。

  ngOnInit() {
    this.todayDate = new Date();
    this.req = this._video.list().subscribe(data=>{ //
      console.log(data) // data.json() は、すでにservice側で処理済みのため外す
    // this.req = this.http.get('assets/json/videos.json').subscribe(data=>{
    //   console.log(data.json())
      this.videoList = data as [any]; // data.json() は、すでにservice側で処理済みのため外す
    }); //
  }

  ngOnDestroy(){
    this.req.unsubscribe()
  }

  getEmbedUrl(item){
    return 'https://www.youtube.com/embed/' + item.embed;
  }

}
  • 4200/vidoesにアクセスする。。。無事表示された!

  • これで、video-listのhttpメソッドは不要になったので、削除しておく。

import { Component, OnInit, OnDestroy } from '@angular/core'; // OnDestroyをimportする
// httpのimportを削除
import { VideoService } from '../videos/videos.service';

@Component({
  selector: 'video-list',
  templateUrl: './video-list.component.html',
  styleUrls: ['./video-list.component.css'],
  providers: [VideoService]
})
export class VideoListComponent implements OnInit {
  private req:any; // req のアノテーション
  title = "Video List!";
  someItem = "<h1>HiHi</h1>";
  todayDate;
  videoList: [any];

  constructor(private _video:VideoService) { } //Httpを削除

  ngOnInit() {
    this.todayDate = new Date();
    this.req = this._video.list().subscribe(data=>{
      console.log(data)
      this.videoList = data as [any];
    }); //
  }

  ngOnDestroy(){
    this.req.unsubscribe()
  }

  getEmbedUrl(item){
    return 'https://www.youtube.com/embed/' + item.embed;
  }

}
  • video-detailにも同様に、VideoServiceをimportする。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Http } from '@angular/http';
import { VideoService } from '../videos/videos.service'; // VideoServiceをimportする


@Component({
  selector: 'video-detail',
  templateUrl: './video-detail.component.html',
  styleUrls: ['./video-detail.component.css'],
  providers:[VideoService] // VideoServiceをprovidersとして定義する
})
export class VideoDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  private req:any;
  // video = {
  //   name: "Default",
  //   slug: "item-1",
  //   embed: "kzjMI00A1f8"
  // };
  video:any;
  slug: string;
  // _vidoeをVideoServiceとして定義する
  constructor(private route: ActivatedRoute, private http: Http, private _video:VideoService) { }

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params => {
      console.log(params)
      this.slug = params['slug']
      this.req = this._video.list().subscribe(data=>{ //http.getmethodの箇所を_video.list()に修正する
        data.filter(item=>{ // data.json()のjson()変換は不要なので、削除する。
          // console.log(item)
          if (item.slug == this.slug){
            this.video = item
          }
        })
      })
    })
  }
  ngOnDestroy(){
    this.routeSub.unsubscribe()
    this.req.unsubscribe()
  }

}
  • videos.service.tsに、get メソッドを追加する。
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

const endpoint = 'assets/json/videos.json' // yourdomain.com/api/videos

@Injectable()
export class VideoService {
  constructor(private http: Http) { }

  list(){
    return this.http.get(endpoint)
            .map(response=>response.json())
            .catch(this.handleError)
  }
  // get methodを定義する
  get(slug){ // slugを引数に取る
    return this.http.get(endpoint) // endpointにアクセスする
            .map(response=>{
              let data = response.json().filter(item=>{
                //responseをjsonデータに変換、item.slug = 引数のslugが一致した場合に、itemを返すfilterを作成する
                                if (item.slug == slug ){
                                  return item
                                }
                              })
              console.log(data) // dataをlogに表示する
              return data // get(slug)の結果としてdataを戻り値として返す
              })
            .catch(this.handleError)
  }

  private handleError(error:any, caught:any): any{
    console.log(error, caught)
  }

}
  • video-detailに, get methodを追記する。
...
ngOnInit() {
  this.routeSub = this.route.params.subscribe(params => {
    // console.log(params)
    this.slug = params['slug']
    this.req = this._video.get(this.slug).subscribe(data=>{ // getメソッドに引数のslugを渡す
      this.video = data // video変数に、dataを渡す (ここのdataは、videos.serviceのlet dataで定義したdata)
      // data.filter(item=>{
      //   // console.log(item)
      //   if (item.slug == this.slug){
      //     this.video = item
      //   }
      // })
    })
  })
}
...
  • video-detailページにアクセスしてみる。。。errorは発生してないが、何も表示されない。これは、data がarrayで渡されており、単体のobjectとして渡されていないことが原因の模様。なので, videos.service.tsのget methodを修正する。
get(slug){
  return this.http.get(endpoint)
          .map(response=>{
            let data = response.json().filter(item=>{
                              if (item.slug == slug ){
                                return item
                              }
                            })
            console.log(data)
            if (data.length == 1){
              return data[0]
            }
            return {}
            })
          .catch(this.handleError)
}
  • video-detailのページにアクセスしてみる。。。無事表示された!(^^)そしてdataに無いitemは表示されないことも確認。

  • 最後に、video-detail.component.ts からHttpを削除しておく。

まとめ

  • angularのserviceは、apiなどにアクセスして、データを取得 と そのデータをcomponentに渡すという役割がある模様
  • componentで、serviceからデータを受け取る場合は、providers:[service名]で指定する。
  • detailpageのようなcomponentにも、serviceで取得してfileterをかけてデータをcomponentに渡す方がよさそう。
  • mapとcatch が基本セットになるのかも

Angular4入門_Vol.15

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

Passing Data to Components

  • search.component.ts にコードを追記する。
import { Component, OnInit, Input } from '@angular/core'; //Input をimportする。
import { Router } from '@angular/router';

@Component({
  selector: 'search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
  searchLocation = 'Newport Beach';
  searchQuery: string; //文字列型のsearchQuery変数を定義する。

  @Input() //Inputデコレータを使う
  passedQuery: string; //passedQueryを文字列型で定義する。

  constructor(private router: Router ) { }
  ngOnInit() {
    console.log(this.passedQuery) //console.logに追記する。
  }

  submitSearch(event, formData){
    console.log(event)
    console.log(formData.value)
    let searchedQuery = formData.value['q']
    if (searchedQuery) {
      this.router.navigate(['/search', {q: searchedQuery}])
    }
  }
  searchQueryChange(){
    this.searchLocation = 'California'
  }

}
  • search-detail.component.htmlのsearch tagに、passedQuery の値を代入してみる。
<search class='text-center search-inline' passedQuery='hello'></search>

<p *ngIf='query'>
  Searched for <b>{{ query }}</b>
</p>
  • Inputは、tagから取ってこれるのか?よくわかんないな。。。

  • 試しに、queryを代入してみる。

<search class='text-center search-inline' [passedQuery]='query'></search>

<!--
* passedQueryを[]で囲む
* queryは、search-detailからのcontextで、それをsearchtagの元のcomponentの、search.component.tsのpassedQueryに代入される。
* query > passedQuery > console に表示されるという流れになっている。
 -->

<p *ngIf='query'>
  Searched for <b>{{ query }}</b>
</p>
  • さらにsearch.component.tsを修正する。
import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
  searchLocation = 'Newport Beach';
  searchQuery: string;

  @Input()
  passedQuery: string;

  constructor(private router: Router ) { }
  ngOnInit() {
    // console.log(this.passedQuery)
    if (this.passedQuery){ //passedQueryがある場合
      this.searchQuery = this.passedQuery // passedQueryをsearchQueryに代入する。
    }
  }

  submitSearch(event, formData){
    console.log(event)
    console.log(formData.value)
    let searchedQuery = formData.value['q']
    if (searchedQuery) {
      this.router.navigate(['/search', {q: searchedQuery}])
    }
  }
  searchQueryChange(){
    this.searchLocation = 'California'
  }

}
  • navbarのsearchboxに、同じような処理を追加するために、app.component.tsにコードを追記する。
import { Component, OnInit } from '@angular/core'; //OnInitを追加する
import { ActivatedRoute } from '@angular/router'; // ActivatedRouteをimport

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  // template: `<h1>{{ title }} is cool!</h1> <p>{{ description }}</p>`, //templateには、直接htmlの記述もできる
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { // OnInitをimplementsする。
  title = 'Hello srvup!!2 ';
  description = '新しいアプリケーションです';
  query: string; // queryを定義
  private routeSub:any; // routeSubを定義する。

  constructor(private route: ActivatedRoute){}  // constructorで、routeを定義する。

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{ //paramsを代入する
      console.log(params)
      this.query = params['q']
    })
  }
}
  • app.component.htmlを修正する。
<search [passedQuery]='query'></search>
<!-- search.component.tsの、passedQuery変数に、app.component.tsのqueryを代入する -->
  • ngOnInitではなく、constructorにコードを記載しても機能する。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  // template: `<h1>{{ title }} is cool!</h1> <p>{{ description }}</p>`, //templateには、直接htmlの記述もできる
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Hello srvup!!2 ';
  description = '新しいアプリケーションです';
  query: string;
  private routeSub:any;

  constructor(private route: ActivatedRoute){
    this.routeSub = route.params.subscribe(params=>{
      console.log(params)
      this.query = params['q']
    })
  }

  ngOnInit() {
  }

  ngOnDestroy(){
    this.routeSub.unsubscribe()
  }
}
 

まとめ

  • Inputデコーレータは、ほぼ予想通り、html内で、独自の属性を持たせることができるデコレータのようだ。
<search [passedQuery]='query'></search>
<!--
上記の場合 search.component.tsに、
@Input()
passedQuery: string;
と定義することで、searchタグ内で、passedQueryという属性を利用することができる。
 -->
<search passedQuery='{{ query }}'></search>
<!--
<search [passedQuery]='query'></search>
と同じような意味だが、微妙に挙動がちがう。。。
 -->

Angular4入門_Vol.14

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

Search Detail

  • search.component.tsにrouterをつける。
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; // import Router

@Component({
  selector: 'search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
  searchLocation = 'Newport Beach'
  constructor(private router: Router ) { } // constructorに、routerを定義する。

  ngOnInit() {
  }

  submitSearch(event, formData){
    console.log(event)
    console.log(formData.value)
    let query = formData.value['q'] // formDataから、qの値を取り出し、queryという変数に代入する。
    if (query) {
      this.router.navigate(['/search', {q: query}]) // navigateで、/search;q= というurlにあくせすするように設定する。
    }
  }
  searchQueryChange(){
    this.searchLocation = 'California'
  }

}
  • これで、searchを実行してみる。。。Error発生!
Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'search;q=ddd'
Error: Cannot match any routes. URL Segment: 'search;q=ddd'
  • urlを設定する必要あり。app.routing.tsに追記する。
const appRoutes: Routes = [
...
  {
    path:"search", // /search の場合のcomponentをセット
    component: VideoListComponent,
  }
  ...
]
  • これで、searchをクリックすると、video-listのpageにアクセスされていればOK!

  • search-detail componentを作成する。ng g component search-detailを実行する。

  • app.routing.tsのpath: “search"の箇所のcomponentを、SearchDetailComponentに変更する。

import { SearchDetailComponent } from './search-detail/search-detail.component';
...
  {
    path:"search",
    component: SearchDetailComponent,
  },
  • searchボタンをクリックして、search-detail works!が表示されていればOK!

  • search-detail.component.tsを編集する。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; // ActivatedRouteをimportする。Routerとの違いは何か?

@Component({
  selector: 'app-search-detail',
  templateUrl: './search-detail.component.html',
  styleUrls: ['./search-detail.component.css']
})
export class SearchDetailComponent implements OnInit, OnDestroy {
  private routeSub:any; // routeSubを定義する。
  constructor(private route: ActivatedRoute) { } // route: ActivatedRouteを定義する

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{
      console.log(params) //route.paramsをsbuscribeして、成功した場合、paramsをconsoleに表示する。
    })
  }

  ngOnDestroy(){ //OnDestroyのときに、unsubscribeする。
    this.routeSub.unsubscribe()
  }

}
  • search-detail.component.tsをさらに修正する。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-search-detail',
  templateUrl: './search-detail.component.html',
  styleUrls: ['./search-detail.component.css']
})
export class SearchDetailComponent implements OnInit, OnDestroy {
  private routeSub:any;
  query: string; // queryを定義する。
  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.routeSub = this.route.params.subscribe(params=>{
      console.log(params)
      this.query = params['q'] // paramsから、qのvalueを取り出して、queryに代入する。
    })
  }

  ngOnDestroy(){
    this.routeSub.unsubscribe()
  }

}
  • queryを、search-detail.component.htmlに表示させる。
<p>
  Searched for {{ query }}
</p>
  • searchボタンをクリックしたら、q= 以降のvalueが、queryに代入されて、表示される。

  • search boxの値が空の場合と、そうでない場合で分ける。

<p *ngIf='query'>
  Searched for <b>{{ query }}</b>
</p>

<div class="text-center" *ngIf='!query'>
  <h1>Search Something</h1>
  <search></search>
</div>
  • サーチボックスの体裁を整える。
<search class='text-center search-inline'></search>

<p *ngIf='query'>
  Searched for <b>{{ query }}</b>
</p>
  • style.cssに、search-inlineを追記する。
/* You can add global styles to this file, and also import other style files */
.main-container{
  min-height: 800px;
  margin-bottom: 20px;
}

.img-main-carousel{
  max-height: 500px;
  margin: 0 auto;
}

.carousel-control.right, .carousel-control.left{
  cursor: pointer;
}

.search-inline .navbar-left, .search-inline .navbar-right{
  float: none !important;
}

まとめ

  • RouterとActivatedRouteの違いは、Routerは、別のurlにアクセスするためのモジュールで、ActivatedRouteは、現在のurlからparamsなどのvalueを取得するためのモジュール。

  • search componentのRouterで、path:search にアクセス。path:searchにアクセスしたあとは、search-detail.componentが実行されるという二段構えになっている。

Angular4入門_Vol.13

参考サイト

https://www.codingforentrepreneurs.com/projects/try-angular-v4/

ngForm Basics

  • navbarのformにngFormを使う準備をする。まず通常のformで実装するときのコードを確認する。search.component.htmlに追記する。
 <!-- methodとactionを追加 -->
<form class="navbar-form navbar-left" method="get" action="/search/">
  <div class="form-group">
    <!-- name="q"に変更 -->
    <input type="text" class="form-control" placeholder="Search" name='q' [(ngModel)]='searchQuery' >
  </div>
  <button type="submit" class="btn btn-default"><span *ngIf='!searchQuery'>Search</span><span *ngIf='searchQuery'>Search for... {{ searchQuery }}</span></button>
</form>
  • ngSubmitを使ってみる。search.component.htmlに追記する。
<form class="navbar-form navbar-left" (ngSubmit)='submitSearch($event)'>
  <div class="form-group">
    <input type="text" class="form-control" placeholder="Search" name='q' [(ngModel)]='searchQuery' >
  </div>
  <button type="submit" class="btn btn-default"><span *ngIf='!searchQuery'>Search</span><span *ngIf='searchQuery'>Search for... {{ searchQuery }}</span></button>
</form>
  • submitSearch をsearch.component.tsに定義する。
submitSearch(event){
  console.log(event)
}
  • submitをクリックしたときに、consoleにeventの内容が表示されれば成功!

  • ngFormをhtmlタグ内で、挿入して、submitSearchメソッドの引数として設定する。

<form #searchForm=(ngForm) class="navbar-form navbar-left" (ngSubmit)='submitSearch($event, searchForm)'>
  • search.component.ts にngFormの内容を、formDataという引数で受けるように、追記する。
submitSearch(event, formData){
  console.log(event)
  console.log(formData)
}
  • 1点errorが発生!#searchForm='ngForm'に修正(間違えて、(ngform)と設定してしまっていた。)

  • consoleに表示する内容を、formData.valueに設定してみる。

submitSearch(event, formData){
  console.log(event)
  console.log(formData.value)
}
  • Object {q: "ddd"} q:"ddd" と、formData(ngForm)のObjectが表示される。input tagのnameはここででてくるのか!

  • 例えば、<input type='hidden' name='loc' value='Newport Beach' [(ngModel)]='searchLocation'/>というように別のnameの ngModelを挿入してみる。

  • 認識はしたが、loc: undefinedになってしまう。。。searchLocation のdefault valueは、search.component.tsで設定する必要あり。

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
  searchLocation = 'Newport Beach'
  searchQuery = "New Search"
  constructor() { }

  ngOnInit() {
  }

  submitSearch(event, formData){
    console.log(event)
    console.log(formData.value)
  }

}
  • これで無事、searchLocationがconsoleに表示されるようになる。Object {loc: "Newport Beach", q: "Search"}といった具合。

  • changeメソッドを使ってみる。

<input type="text" class="form-control" placeholder="Search" name='q' [(ngModel)]='searchQuery'
(change)='searchQueryChange()'>
  • changeメソッドに代入するsearchQueryChangeを定義する。
searchQueryChange(){
  this.searchLocation = 'California' // searchLocationの内容を変更するだけ
}
  • これを実行すると、Object {loc: "California", q: "ddd"}という感じで、two way bindingで、locの内容を書き換えできる!

まとめ

  • ngSubmit, ngModel, ngForm, change のangularのmethodを使うと、two way bindingな formが作れる!
<!-- ngFormで、formの内容を取得 > ngSubmitで、submitしたときに、formの内容を引数としたfunctionを実行できる -->
<form #searchForm='ngForm' class="navbar-form navbar-left" (ngSubmit)='submitSearch($event, searchForm)'>
  <!-- ngModelで、dataの双方向のやりとりが可能になる -->
  <!-- changeで、変更があったときに、実行するメソッドを指定できる。 -->
    <input type='hidden' name='loc' [(ngModel)]='searchLocation'/>
  <div class="form-group">
    <input type="text" class="form-control" placeholder="Search" name='q' [(ngModel)]='searchQuery'
    (change)='searchQueryChange()'>
  </div>
  <!-- {{}}で、ngModelで定義した変数の中身を表示できる。 -->
  <button type="submit" class="btn btn-default"><span *ngIf='!searchQuery'>Search</span><span *ngIf='searchQuery'>Search for... {{ searchLocation }}{{ searchQuery }}</span></button>
</form>