iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
2
Modern Web

Angular 元件庫 NG-ZORRO 基礎入門系列 第 14

[Angular 元件庫 NG-ZORRO 基礎入門] Day 14 - Hacker News: List (Part 2)

前言回顧

昨天我們正式開始了新的專案 hacker news of angular 的開發,在昨天的文章中,我們初步使用了 nz-list 元件來渲染返回的資料,回顧一下昨天我們的單行渲染情況:
https://ithelp.ithome.com.tw/upload/images/20190915/201128294zy8E9tGiF.jpg

對比一下 Hacker News 官網,現在的 UI 完成度與我們想達到的目標還有一定的距離,今天就讓我們繼續完善這個頁面。

開始

同樣的,讓我們先來看看今天要完成的工作:
https://img.alicdn.com/tfs/TB1jKfMf1P2gK0jSZFoXXauIVXa-2478-1356.gif

從頁面上看到,今天我們需要完成:

  • 提取 url 的 domain
  • 時間轉換為 6 months ago 這種相對時間
  • 設計底部操作欄 upvotehidecomment
  • 顯示 list item(非純連結)的內容

讓我們一個一個來解決。

nzActions 自定義操作

我們在上圖發現,我們需要將自定義操作欄放到單行 item 的底部,nz-list-item 已經為我們準備好了屬性:

nzActions:列表操作組,根據 nzItemLayout 的不同, 位置在卡片底部或者最右側,它接受一個 TemplateRef 陣列。

我們來改造一下當前專案的 nz-list 程式碼來適應該屬性:

<nz-list
		 [nzDataSource]="listOfNews"
		 [nzRenderItem]="item"
		 [nzItemLayout]="'vertical'"
		 [nzLoading]="loading"
		 nzSize="small">
  <ng-template #item let-item>
	<nz-list-item
				  nzNoFlex="true"
				  [nzActions]="[upAction, hideAction, commentAction]">
	  <ng-template #upAction><i nz-icon nzType="like" nzTheme="outline"></i> upvote</ng-template>
	  <ng-template #hideAction><i nz-icon nzType="eye-invisible" nzTheme="outline"></i> hide</ng-template>
	  <ng-template #commentAction><i nz-icon nzType="message" nzTheme="outline"></i> comment</ng-template>
	  <nz-list-item-meta
						 [nzTitle]="nzTitle"
						 [nzDescription]="nzDescription">
		...
	  </nz-list-item-meta>
	</nz-list-item>
  </ng-template>
</nz-list>

我們看到,首先我們將 nzItemLayout 切換到 vertical 以支援垂直顯示,nzNoFlex 放棄 flex 佈局(因為開啟會預設在無 extra 內容情況下,nzActions 部分會顯示在右側,可參看 stackblitz 線上示例)。

nzContent

內容項,接受 string 或者 TemplateRef

我們看到返回的資料中,有些並不是單純的新聞外鏈,而是包含內容的新聞或者文章,這時我們就需要將這些內容顯示出來,我們可以使用 nz-list-itemnzContent 屬性來實現:

<nz-list-item
	nzNoFlex="true"
	[nzContent]="item.story_text ? content : ''"
	[nzActions]="[upAction, hideAction, commentAction]">
  <ng-template #content>
	<p [innerHTML]="item.story_text"></p>
  </ng-template>
</nz-list-item>

story_text 有值情況下我們我們用 #content 來渲染內容,否則就顯示空,看一下對比:
https://ithelp.ithome.com.tw/upload/images/20190915/20112829CeJC8K9fMD.jpg

Pipes

好了,現在我們已經完成了頁面功能樣式佈局,那麼時間轉換和 domain 獲取該怎麼做呢?
最好的方案當然是使用 Angular 的 Pipe,這樣我們就可以這樣使用:

<span>
  獲取 domain:
  {{item.url | hostParse}}
</span>
<span>
  獲取時間差:
  {{item.created_at | timeAgo}}
</span>

timeAgo pipe

第一個 pipe 非常容易,大家應該都使用過 moment.js 這個時間庫吧,Moment.js 是一個JavaScript 日期處理類庫,用於解析、檢驗、操作、以及顯示日期.

它給我們提供了非常多簡單方便的時間處理 API,我們需要的 Relative Time 也在其中,即 fromNow(),我們先來看一下 moment.js 是如何使用它的:

moment("20111031", "YYYYMMDD").fromNow();  // 8 years ago
moment("20120620", "YYYYMMDD").fromNow();  // 7 years ago

那我們就很容易實現這個 pipe 了:

$ cd ng-zorro-ironman2020
$ ng g pipe pipes/time-ago --skip-import

修改 time-ago.pipe.ts 內容:

import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'timeAgo'
})
export class TimeAgoPipe implements PipeTransform {
  transform(value: moment.MomentInput, ...args: any[]): any {
    if (value) {
      return moment(value).fromNow();
    }
    return null;
  }
}

是不是很簡單,這樣我們已經實現了相對時間的功能,大家可以自行試一試。

hostParse pipe

當然還有一個提取 domainpipe,和上述建立流程一致,我們看一下 host-parse.pipe.ts 是如何實現的:

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  name: 'hostParse'
})
export class HostParsePipe implements PipeTransform {
  // 參考了 https://github.com/websanova/js-url
  regxDomain() {
    return new RegExp(/(.*?)\.?([^\.]*?)\.(gl|com|net|org|biz|ws|in|me|co\.uk|co|org\.uk|ltd\.uk|plc\.uk|me\.uk|edu|mil|br\.com|cn\.com|eu\.com|hu\.com|no\.com|qc\.com|sa\.com|se\.com|se\.net|us\.com|uy\.com|ac|co\.ac|gv\.ac|or\.ac|ac\.ac|af|am|as|at|ac\.at|co\.at|gv\.at|or\.at|asn\.au|com\.au|edu\.au|org\.au|net\.au|id\.au|be|ac\.be|adm\.br|adv\.br|am\.br|arq\.br|art\.br|bio\.br|cng\.br|cnt\.br|com\.br|ecn\.br|eng\.br|esp\.br|etc\.br|eti\.br|fm\.br|fot\.br|fst\.br|g12\.br|gov\.br|ind\.br|inf\.br|jor\.br|lel\.br|med\.br|mil\.br|net\.br|nom\.br|ntr\.br|odo\.br|org\.br|ppg\.br|pro\.br|psc\.br|psi\.br|rec\.br|slg\.br|tmp\.br|tur\.br|tv\.br|vet\.br|zlg\.br|br|ab\.ca|bc\.ca|mb\.ca|nb\.ca|nf\.ca|ns\.ca|nt\.ca|on\.ca|pe\.ca|qc\.ca|sk\.ca|yk\.ca|ca|cc|ac\.cn|com\.cn|edu\.cn|gov\.cn|org\.cn|bj\.cn|sh\.cn|tj\.cn|cq\.cn|he\.cn|nm\.cn|ln\.cn|jl\.cn|hl\.cn|js\.cn|zj\.cn|ah\.cn|gd\.cn|gx\.cn|hi\.cn|sc\.cn|gz\.cn|yn\.cn|xz\.cn|sn\.cn|gs\.cn|qh\.cn|nx\.cn|xj\.cn|tw\.cn|hk\.cn|mo\.cn|cn|cx|cz|de|dk|fo|com\.ec|tm\.fr|com\.fr|asso\.fr|presse\.fr|fr|gf|gs|co\.il|net\.il|ac\.il|k12\.il|gov\.il|muni\.il|ac\.in|co\.in|org\.in|ernet\.in|gov\.in|net\.in|res\.in|is|it|ac\.jp|co\.jp|go\.jp|or\.jp|ne\.jp|ac\.kr|co\.kr|go\.kr|ne\.kr|nm\.kr|or\.kr|li|lt|lu|asso\.mc|tm\.mc|com\.mm|org\.mm|net\.mm|edu\.mm|gov\.mm|ms|nl|no|nu|pl|ro|org\.ro|store\.ro|tm\.ro|firm\.ro|www\.ro|arts\.ro|rec\.ro|info\.ro|nom\.ro|nt\.ro|se|si|com\.sg|org\.sg|net\.sg|gov\.sg|sk|st|tf|ac\.th|co\.th|go\.th|mi\.th|net\.th|or\.th|tm|to|com\.tr|edu\.tr|gov\.tr|k12\.tr|net\.tr|org\.tr|com\.tw|org\.tw|net\.tw|ac\.uk|uk\.com|uk\.net|gb\.com|gb\.net|vg|sh|kz|ch|info|ua|gov|name|pro|ie|hk|com\.hk|org\.hk|net\.hk|edu\.hk|us|tk|cd|by|ad|lv|eu\.lv|bz|es|jp|cl|ag|mobi|eu|co\.nz|org\.nz|net\.nz|maori\.nz|iwi\.nz|io|la|md|sc|sg|vc|tw|travel|my|se|tv|pt|com\.pt|edu\.pt|asia|fi|com\.ve|net\.ve|fi|org\.ve|web\.ve|info\.ve|co\.ve|tel|im|gr|ru|net\.ru|org\.ru|hr|com\.hr|ly|xyz)$/);
  }

  parseDomain(url: string) {
    if (!url) {
      return '';
    }
    let hostname = '';
    let domain;
	// to get hostname
    const match = url.match(/:\/\/(www[0-9]?\.)?(.[^/:]+)/i);
    if (match != null && match.length > 2 && typeof match[ 2 ] === 'string' && match[ 2 ].length > 0) {
      hostname = match[ 2 ];
    }

	// to match domain
    const domainArray = hostname.match(this.regxDomain());
    if (domainArray) {
      domain = domainArray[ 2 ] ? domainArray[ 2 ] + '.' + domainArray[ 3 ] : '';
    }
    return domain;
  }

  transform(value: string, ...args: any[]): any {
    return `${this.parseDomain(value)}`;
  }
}

新的頁面

這樣其實我們已經完成了 pipe 設計以及 nz-list-item 的改造,感興趣的同學可以下載 github 專案檢視。

總結 & 預告

今天我們對 nz-list 元件和樣式做了簡單的優化,現在頁面上的元素已經包含了源網站包含的所有內容,我們當前並沒有對一些功能去實現(如 hide、upvote等),這種業務邏輯不是我們需要關注的重點。

我們在後面的章節會結合 NG-ZORRO 元件逐步完善分頁功能和comment頁面設計等功能。

Tips:還記得我們之前說過,NG-ZORRO 的一些元件可以和 Material CDK 結合使用嗎?參考 官方無限滾動示例,可以自行寫一寫體驗一下把。

相關資料

  • Github 今日程式碼:https://github.com/simplejason/ng-zorro-ironman2020/tree/day-13-hacker-news-list
  • nz-list:https://ng.ant.design/components/list/zh
  • url 解析參考程式碼:https://github.com/websanova/js-url

上一篇
[Angular 元件庫 NG-ZORRO 基礎入門] Day 13 - Hacker News: List
下一篇
[Angular 組件庫 NG-ZORRO 基礎入門] Day 15 - Hacker News: Pagination
系列文
Angular 元件庫 NG-ZORRO 基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言