繼續筆記002 ,繼續研究下去。這篇會研讀 <aio-search-box>
與 <aio-search-result>
component
aio-search-box
檔案位置
src/app/search/search-box
search-box.component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component ({ selector : 'aio-search-box' , template : `<input #searchBox type="search" aria-label="search" placeholder="Search" (input)="doSearch()" (keyup)="doSearch()" (focus)="doFocus()" (click)="doSearch()">` }) export class SearchBoxComponent implements OnInit { ... }
ngOnInit
如果網址有類似這種情況時 https://angular.io/?search=xxxxxx
, 會預先使用網址的查詢條件並執行搜尋
1 2 3 4 5 6 7 ngOnInit ( ) { const query = this .locationService .search ()['search' ]; if (query) { this .query = query; this .doSearch (); } }
locationService.search() 的程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 search (): { [index : string ]: string ; } { const search = {}; const path = this .location .path (); const q = path.indexOf ('?' ); if (q > -1 ) { try { const params = path.substr (q + 1 ).split ('&' ); params.forEach (p => { const pair = p.split ('=' ); if (pair[0 ]) { search[decodeURIComponent (pair[0 ])] = pair[1 ] && decodeURIComponent (pair[1 ]); } }); } catch (e) { } } return search; }
doSearch()
1 2 3 4 5 6 7 private searchSubject = new Subject <string >();@Output () onSearch = this .searchSubject .distinctUntilChanged ();doSearch ( ) { this .searchSubject .next (this .query ); }
doFocus()
1 2 3 4 5 6 7 8 @Output () onFocus = new EventEmitter <string >();doFocus ( ) { this .onFocus .emit (this .query ); } focus ( ) { this .searchBox .nativeElement .focus (); }
當停駐於這個物件時,也會觸發搜尋功能
focus()
是公開方法,可以讓游標停留在搜尋欄位。
小技巧
1 2 3 4 @ViewChild ('searchBox' ) searchBox : ElementRef ;private get query () { return this .searchBox .nativeElement .value ; }private set query (value : string ) { this .searchBox .nativeElement .value = value; }
使用 getter / setter 的方式,來簡化程式碼的撰寫,這個專案內,這技巧到處都可以看到。
aio-search-result
檔案位置
src/app/search/search-results
search-results.component
樣板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <div class ="search-results" > <div *ngIf ="searchAreas.length; then searchResults; else notFound" > </div > </div > <ng-template #searchResults > <h2 class ="visually-hidden" > Search Results</h2 > <div class ="search-area" *ngFor ="let area of searchAreas" > <h3 > {{area.name}} ({{area.pages.length + area.priorityPages.length}})</h3 > <ul class ="priority-pages" > <li class ="search-page" *ngFor ="let page of area.priorityPages" > <a class ="search-result-item" href ="{{ page.path }}" (click )="onResultSelected(page)" > <span class ="symbol {{page.type}}" *ngIf ="area.name === 'api'" > </span > {{ page.title }} </a > </li > </ul > <ul > <li class ="search-page" *ngFor ="let page of area.pages" > <a class ="search-result-item" href ="{{ page.path }}" (click )="onResultSelected(page)" > <span class ="symbol {{page.type}}" *ngIf ="area.name === 'api'" > </span > {{ page.title }} </a > </li > </ul > </div > </ng-template > <ng-template #notFound > <p > No results found.</p > </ng-template >
利用 ngIf then else
搭配 <ng-template>
加樣版變數來控制要顯示的內容區塊
ngOnInit
1 2 3 4 ngOnInit ( ) { this .resultsSubscription = this .searchService .searchResults .subscribe (search => this .searchAreas = this .processSearchResults (search)); }
註冊 searchService的 searchResults
當有資料產生時經過 processSearchResults
處理後,再將其結果顯示
ngOnDestory
1 2 3 ngOnDestroy ( ) { this .resultsSubscription .unsubscribe (); }
取消searchService.searchResults
的訂閱
processSearchRestuls
1 2 3 4 5 6 7 8 9 10 11 12 export interface SearchResults { query : string ; results : SearchResult []; } export interface SearchResult { path : string ; title : string ; type : string ; titleWords : string ; keywords : string ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private processSearchResults (search : SearchResults ) { const searchAreaMap = {}; search.results .forEach (result => { if (!result.title ) { return ; } const areaName = this .computeAreaName (result) || this .defaultArea ; const area = searchAreaMap[areaName] = searchAreaMap[areaName] || []; area.push (result); }); const keys = Object .keys (searchAreaMap).sort ((l, r ) => l > r ? 1 : -1 ); return keys.map (name => { let pages : SearchResult [] = searchAreaMap[name]; const priorityPages = pages.splice (0 , 5 ); pages = pages.sort (compareResults); return { name, pages, priorityPages }; }); } private computeAreaName (result : SearchResult ) { if (this .topLevelFolders .indexOf (result.path ) !== -1 ) { return result.path ; } const [areaName, rest] = result.path .split ('/' , 2 ); return rest && areaName; } function compareResults (l : {title: string }, r : {title: string } ) { return l.title .toUpperCase () > r.title .toUpperCase () ? 1 : -1 ; }