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; } }
- これで再度検索してみる。。。できた〜!
まとめ
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という属性を利用することができる。 -->
@Input
については、以下のサイトがわかりやすかったです! http://qiita.com/armorik83/items/a14a4298ead3d18b58a3また、以下のような記述も可能。ただ、これだとtwo way data bindingができないのかな。
<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>