如何創建和部署 Angular Material 應用程序
已發表: 2022-03-10Angular 是創建新 Web 應用程序時的流行選擇之一。 此外,“材料設計”規格已成為當今創造極簡且引人入勝的體驗的首選。 因此,任何新的“Angular”項目大多使用“Angular Material Design Library”來使用遵循材料設計規範的組件。 從流暢的動畫到適當的交互反饋,所有這些都已作為 Angular 官方材料設計庫的一部分提供。
開發 Web 應用程序後,下一步就是部署它。 這就是“Netlify”出現的地方。 憑藉其非常易於使用的界面、自動部署、用於 A/B 測試的流量拆分和各種其他功能,Netlify 無疑是一個很棒的工具。
本文將介紹使用官方 Angular Material Design 庫創建 Angular 8 Web 應用程序。 我們將在 Netlify 上創建一個完全基於 Angular 的 QR 碼生成器 Web 應用程序。
本教程的文件可以在 GitHub 上找到,這裡部署了一個演示版本。
入門
- 安裝 Angular 8,
- 創建一個 GitHub 帳戶,
- 在您的計算機上安裝 Git,
- 創建一個 Netlify 帳戶。
注意:我將使用 VSCode 和 Microsoft Windows 作為首選的 IDE 和操作系統,儘管對於任何其他操作系統上的任何其他 IDE,這些步驟都是相似的。
完成以上先決條件後,開始吧!
模擬與規劃
在我們開始創建項目之前,提前計劃是有益的:我們希望在我們的應用程序中使用什麼樣的 UI? 會有可重複使用的部件嗎? 應用程序將如何與外部服務交互?
首先,檢查 UI 模擬。
這些是將包含在應用程序中的三個不同頁面。 主頁將是我們應用程序的起點。 創建 QR 頁面應處理新 QR 碼的創建。 歷史頁面將顯示所有保存的二維碼。
模型不僅提供了應用程序的外觀和感覺,而且還分離了每個頁面的職責。
一個觀察結果(來自模擬)是頂部導航欄似乎在所有頁面中都很常見。 因此,導航欄可以創建為可重用組件並重用。
現在我們已經對應用程序的外觀以及可以重用的內容有了一定的了解,讓我們開始吧。
創建一個新的 Angular 項目
啟動 VSCode,然後在 VSCode 中打開一個終端窗口以生成一個新的 Angular 項目。
終端將使用提示中顯示的默認路徑打開。 您可以在繼續之前更改為首選目錄; 在 Windows 的情況下,我將使用cd
命令。
展望未來,angular-cli 有一個命令來生成新項目ng new <project-name>
。 只需使用您喜歡的任何花哨的項目名稱,然後按 Enter,例如ng new qr
。
這將觸發 angular-cli 魔法; 它將提供一些選項來配置項目的某些方面,例如,添加角度路由。 然後,根據選擇的選項,它將生成整個項目骨架,無需任何修改即可運行。
對於本教程,輸入Yes作為路由並選擇CSS作為樣式。 這將生成一個新的 Angular 項目:
我們現在已經有了一個完整的 Angular 項目。 為了確保一切正常,我們可以通過在終端中輸入以下命令來運行項目: ng serve
。 哦,但是等等,這會導致錯誤。 可能發生了什麼?
別擔心。 每當您使用 angular-cli 創建一個新項目時,它都會在以ng new qr
命令中指定的項目名稱命名的文件夾中生成整個骨架。 在這裡,我們必須將當前工作目錄更改為剛剛創建的目錄。 在 Windows 中,使用命令cd qr
更改目錄。
現在,嘗試在ng serve
的幫助下再次運行該項目:
打開 Web 瀏覽器,轉到 URL https://localhost:4200 以查看正在運行的項目。 默認情況下,命令ng serve
在端口 4200 上運行應用程序。
提示:要在不同的端口上運行它,我們使用命令ng serve --port <any-port>
例如, ng serve --port 3000
。
這可以確保我們的基本 Angular 項目啟動並運行。 讓我們繼續前進。
我們需要將項目文件夾添加到 VSCode。 轉到“文件”菜單並選擇“打開文件夾”並選擇項目文件夾。 項目文件夾現在將顯示在左側的 Explorer 視圖中。
添加角度材料庫
要安裝 Angular 材質庫,請在終端窗口中使用以下命令: ng add @angular/material
。 這將(再次)詢問一些問題,例如您想要哪個主題,是否需要默認動畫,是否需要觸摸支持等等。 我們將只選擇默認的Indigo/Pink
主題, Yes
添加HammerJS
庫和瀏覽器動畫。
上述命令還配置了整個項目以啟用對材料組件的支持。
- 它將項目依賴項添加到package.json ,
- 它將 Roboto 字體添加到index.html文件中,
- 它將 Material Design 圖標字體添加到您的index.html ,
- 它還添加了一些全局 CSS 樣式:
- 去除身體的邊緣,
- 設置
height: 100%
進入 HTML 和正文, - 將 Roboto 設置為默認應用程序字體。
為了確保一切正常,此時您可以再次運行該項目,儘管您不會注意到任何新內容。
添加主頁
我們的項目骨架現在已經準備好了。 讓我們從添加主頁開始。
我們想讓我們的主頁保持簡單,就像上圖一樣。 這個主頁使用了一些有角度的材質組件。 讓我們剖析一下。
- 頂部欄是一個簡單的 HTML
nav
元素,其中包含材質樣式按鈕mat-button
,其子元素為圖像和文本。 條形顏色與添加 Angular 材質庫時選擇的原色相同; - 居中的圖像;
- 另一個
mat-button
,只有一個文本作為它的子元素。 這個按鈕將允許用戶導航到歷史頁面; - 一個計數徽章,
matBadge
,附在上面的按鈕上,顯示用戶保存的二維碼數量; - 右下角的浮動操作按鈕
mat-fab
具有所選主題的強調色。
離題一點,讓我們先添加其他必需的組件和服務。
添加標題
正如之前計劃的那樣,導航欄應該被重用,讓我們將它創建為一個單獨的角度組件。 在 VSCode 中打開終端並輸入ng gc header
(ng generate component header 的縮寫),然後按 Enter。 這將創建一個名為“header”的新文件夾,其中包含四個文件:
- header.component.css :用於為該組件提供樣式;
- header.component.html :用於添加 HTML 元素;
- header.component.spec.ts :用於編寫測試用例;
- header.component.ts :添加基於 Typescript 的邏輯。
要使標頭看起來像在模擬中一樣,請在header.component.html中添加以下 HTML:
<nav class="navbar" [class.mat-elevation-z8]=true> <div> <button *ngIf="showBackButton" aria-hidden=false mat-icon-button routerLink="/"> <mat-icon> <i class="material-icons md-32">arrow_back</i> </mat-icon> </button> <span>{{currentTitle}}</span> </div> <button *ngIf="!showBackButton" aria-hidden=false mat-button class="button"> <img src="../../assets/qr-icon-white.png"> <span>QR Generator</span> </button> <button *ngIf="showHistoryNav" aria-hidden=false mat-button class="button" routerLink="/history"> <span>History</span> </button> </nav>
提示:要為任何材質組件添加高程,請使用[class.mat-elevation-z8]=true
,可以通過更改z值來更改高程值,在本例中為z8
。 例如,要將海拔更改為 16,請使用[class.mat-elevation-z16]=true
。
在上面的 HTML 片段中,使用了兩個 Angular 材質元素: mat-icon
和mat-button/mat-icon-button
。 它們的用法非常簡單; 首先,我們需要將這兩個作為模塊添加到我們的app.module.ts中,如下所示:
mat-icon
和mat-button
的模塊導入(大預覽)這將允許我們在任何組件的任何位置使用這兩個 Angular 材質元素。
要添加材質按鈕,使用以下 HTML 片段:
<button mat-button> Material Button </button>
Angular 材質庫中提供了不同類型的材質按鈕元素,例如mat-raised-button
、 mat-flat-button
、 mat-fab
等; 只需將上述代碼片段中的mat-button
替換為任何其他類型即可。
另一個元素是mat-icon
,用於顯示材質圖標庫中可用的圖標。 在開始添加 Angular 材質庫時,還添加了對材質圖標庫的引用,這使我們能夠使用大量圖標中的圖標。
用法很簡單:
<mat-icon> <i class="material-icons md-32">arrow_back</i> </mat-icon>
嵌套的<i>
標籤可用於更改圖標大小(此處為md-32
),這將使圖標大小的高度和寬度為 32px。 該值可以是md-24
、 md-48
等。 嵌套<i>
標記的值是圖標的名稱。 (可以在此處找到任何其他圖標的名稱。)
可訪問性
無論何時使用圖標或圖像,它們都必須為可訪問性目的或屏幕閱讀器用戶提供足夠的信息。 ARIA(Accessible Rich Internet Applications)定義了一種使殘障人士更容易訪問 Web 內容和 Web 應用程序的方法。
需要注意的一點是,具有原生語義的 HTML 元素(例如nav
)不需要 ARIA 屬性。 屏幕閱讀器已經知道nav
是一個導航元素並照此閱讀。
ARIA 規範分為三類:角色、狀態和屬性。 假設一個div
用於在 HTML 代碼中創建一個進度條。 它沒有任何原生語義; ARIA 角色可以將這個小部件描述為一個進度條,ARIA 屬性可以表示它的特性,例如它可以被拖動。 ARIA state 將描述其當前狀態,例如進度條的當前值。 請參閱下面的片段:
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"> </div>
同樣,使用了一個非常常用的 aria 屬性: aria-hidden=true/false
。 值 true 使該元素對屏幕閱讀器不可見。
由於此應用程序中使用的大多數 UI 元素都具有原生語義含義,因此使用的唯一 ARIA 屬性是指定 ARIA 可見性狀態。有關詳細信息,請參閱此。
header.component.html確實包含一些根據當前頁面隱藏和顯示後退按鈕的邏輯。 此外,主頁按鈕還包含應添加到/assets
文件夾的圖像/徽標。 從此處下載圖像並將其保存在/assets
文件夾中。
對於導航欄的樣式,在header.component.css中添加以下 css :
.navbar { position: fixed; top: 0; left: 0; right: 0; z-index: 2; background: #3f51b5; display: flex; flex-wrap: wrap; align-items: center; padding: 12px 16px; } .button { color: white; margin: 0px 10px; }
由於我們希望保持標頭組件在其他組件之間可重用,因此要決定應該顯示什麼,我們將需要這些作為來自其他組件的參數。 這需要使用@Input()
裝飾器,它將綁定到我們在header.component.html中使用的變量。
在header.component.ts文件中添加這些行:
// Add these three lines above the constructor entry. @Input() showBackButton: boolean; @Input() currentTitle: string; @Input() showHistoryNav: boolean; constructor() { }
上述三個綁定將作為參數從頭組件將使用的其他組件傳遞。 一旦我們繼續前進,它的用法將更加清晰。
繼續前進,我們需要創建一個可以由 Angular 組件表示的主頁。 因此,讓我們從創建另一個組件開始; 在終端中鍵入ng gc home
以自動生成 home 組件。 如前所述,將創建一個名為“home”的新文件夾,其中包含四個不同的文件。 在繼續修改這些文件之前,讓我們將一些路由信息添加到角度路由模塊。
添加路由
Angular 提供了一種將 URL 映射到特定組件的方法。 每當發生一些導航時,Angular 框架都會根據app-routing.module.ts文件中的信息監控 URL; 它初始化映射的組件。 這樣不同的組件就不需要承擔初始化其他組件的責任。 在我們的例子中,應用程序具有三個頁面,可通過單擊不同的按鈕進行導航。 我們通過利用 Angular 框架提供的路由支持來實現這一點。
home 組件應該是應用程序的起點。 讓我們將此信息添加到app-routing.module.ts文件中。
path
屬性設置為空字符串; 這使我們能夠將應用程序 URL 映射到主頁組件,例如顯示 Google 主頁的google.com
。
提示:路徑值從不以“ /
”開頭,而是使用空字符串,即使路徑可能類似於search/coffee
。
回到主頁組件,將home.component.html的內容替換為:
<app-header [showBackButton]="false" [currentTitle]=""></app-header> <app-profile></app-profile> <!-- FAB Fixed --> <button mat-fab class="fab-bottom-right" routerLink="/create"> <mat-icon> <i class="material-icons md-48">add</i> </mat-icon> </button>
home 組件包含三個部分:
- 可重用的標頭組件
<app-header>
, - 配置文件組件
<app-profile>
, - 右下角的浮動操作按鈕。
上面的 HTML 片段展示瞭如何在其他組件中使用可重用的 header 組件; 我們只使用組件選擇器並傳入所需的參數。
Profile 組件被創建用作主頁的主體——我們將很快創建它。
帶有+
圖標的浮動操作按鈕是屏幕右下方的一種mat-fab
類型的 Angular 材質按鈕。 它具有routerLink
屬性指令,該指令使用app-routing.module.ts
中提供的路由信息進行導航。 在這種情況下,按鈕的路由值為/create ,它將被映射到創建組件。
要使創建按鈕浮動在右下角,請在home.component.css中添加以下 CSS 代碼:
.fab-bottom-right { position: fixed; left: auto; bottom: 5%; right: 10%; }
由於配置文件組件應該管理主頁正文,我們將保持home.component.ts
不變。
添加配置文件組件
打開終端,輸入ng gc profile
並回車生成配置文件組件。 如前所述,該組件將處理主頁的主體。 打開profile.component.html
並將其內容替換為:
<div class="center profile-child"> <img class="avatar" src="../../assets/avatar.png"> <div class="profile-actions"> <button mat-raised-button matBadge="{{historyCount}}" matBadgeOverlap="true" matBadgeSize="medium" matBadgeColor="accent" color="primary" routerLink="/history"> <span>History</span> </button> </div> </div>
上面的 HTML 片段展示瞭如何使用材質庫的matBadge
元素。 為了能夠在這裡使用它,我們需要按照通常的練習將MatBadgeModule
添加到app.module.ts
文件中。 徽章是 UI 元素(例如按鈕、圖標或文本)的小型圖形狀態描述符。 在這種情況下,它與按鈕一起使用以顯示用戶保存的 QR 計數。 Angular 材質庫徽章具有各種其他屬性,例如使用matBadgeSize
設置徽章的位置,使用matBadgePosition
指定尺寸,使用matBadgeColor
設置徽章顏色。
還需要向 assets 文件夾中添加一項圖片資源:下載。 將其保存到項目的/assets
文件夾中。
打開profile.component.css並添加:
.center { top: 50%; left: 50%; position: absolute; transform: translate(-50%, -50%); } .profile-child { display: flex; flex-direction: column; align-items: center; } .profile-actions { padding-top: 20px; } .avatar { border-radius: 50%; width: 180px; height: 180px; }
上面的 CSS 將按計劃實現 UI。
繼續前進,我們需要某種邏輯來更新歷史計數值,因為它將反映在前面使用的matBadge
中。 打開profile.component.ts並適當地添加以下代碼段:
export class ProfileComponent implements OnInit { historyCount = 0; constructor(private storageUtilService: StorageutilService) { } ngOnInit() { this.updateHistoryCount(); } updateHistoryCount() { this.historyCount = this.storageUtilService.getHistoryCount(); } }
我們添加了StorageutilService ,但直到現在我們還沒有創建這樣的服務。 忽略錯誤,我們已經完成了我們的配置文件組件,該組件也完成了我們的主頁組件。 創建存儲實用程序服務後,我們將重新訪問此配置文件組件。 好的,那麼讓我們這樣做吧。
本地存儲
HTML5 提供了 Web 存儲功能,可用於在本地存儲數據。 與 cookie 相比,這提供了更多的存儲空間——至少 5MB 和 4KB。 有兩種類型的 Web 存儲具有不同的範圍和生命週期:本地和會話。 前者可以永久存儲數據,而後者是臨時的,用於單個會話。 選擇類型的決定可以基於用例,在我們的場景中,我們希望跨會話保存,因此我們將使用本地存儲。
每條數據都存儲在一個鍵/值對中。 我們將使用生成 QR 的文本作為鍵,將編碼為 base64 字符串的 QR 圖像作為值。 創建一個實體文件夾,在該文件夾內創建一個新的qr-object.ts文件並添加代碼片段,如下所示:
課堂內容:
export class QR { text: string; imageBase64: string; constructor(text: string, imageBase64: string) { this.imageBase64 = imageBase64; this.text = text; } }
每當用戶保存生成的 QR 時,我們將創建上述類的對象並使用存儲實用程序服務保存該對象。
創建一個新的服務文件夾,我們將創建許多服務,最好將它們組合在一起。
將當前工作目錄更改為服務cd services
,以使用ng gs <any name>
創建新服務。 這是ng generate service <any name>
的簡寫,鍵入ng gs storageutil
並按 Enter
這將創建兩個文件:
- storageutil.service.ts
- storageutil.service.spec.ts
後者用於編寫單元測試。 打開storageutil.service.ts並添加:
private historyCount: number; constructor() { } saveHistory(key : string, item :string) { localStorage.setItem(key, item) this.historyCount = this.historyCount + 1; } readHistory(key : string) : string { return localStorage.getItem(key) } readAllHistory() : Array<QR> { const qrList = new Array<QR>(); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); if (key && value) { const qr = new QR(key, value); qrList.push(qr); } } this.historyCount = qrList.length; return qrList; } getHistoryCount(): number { if (this.historyCount) { return this.historyCount; } this.readAllHistory(); return this.historyCount; } deleteHistory(key : string) { localStorage.removeItem(key) this.historyCount = this.historyCount - 1; }
導入 qr-object 類以更正任何錯誤。 要使用本地存儲功能,無需導入任何新內容,只需使用關鍵字localStorage
根據鍵保存或獲取值。
現在再次打開profile.component.ts文件並導入StorageutilService
類以正確完成配置文件組件。
運行項目,我們可以看到主頁按計劃啟動。
添加創建二維碼頁面
我們已經準備好了主頁,儘管創建/添加按鈕沒有做任何事情。 不用擔心,實際的邏輯已經寫好了。 我們使用routerLink
指令將 URL 的基本路徑更改為/create
,但app-routing.module.ts文件中沒有添加任何映射。
讓我們創建一個組件來處理新二維碼的創建,輸入ng gc create-qr
並按回車鍵生成一個新組件。
打開app-routing.module.ts文件並將以下條目添加到routes
數組中:
{ path: 'create', component: CreateQrComponent },
這會將CreateQRComponent
映射到 URL /create
。
打開create-qr.components.html並將內容替換為:
<app-header [showBackButton]="showBackButton" [currentTitle]="title" [showHistoryNav]="showHistoryNav"></app-header> <mat-card class="qrCard" [class.mat-elevation-z12]=true> <div class="qrContent"> <!--Close button section--> <div class="closeBtn"> <button mat-icon-button color="accent" routerLink="/" matTooltip="Close"> <mat-icon> <i class="material-icons md-48">close</i> </mat-icon> </button> </div> <!--QR code image section--> <div class="qrImgDiv"> <img *ngIf="!showProgressSpinner" src={{qrCodeImage}} width="200px" height="200px"> <mat-spinner *ngIf="showProgressSpinner"></mat-spinner> <div class="actionButtons" *ngIf="!showProgressSpinner"> <button mat-icon-button color="accent" matTooltip="Share this QR"> <mat-icon> <i class="material-icons md-48">share</i> </mat-icon> </button> <button mat-icon-button color="accent" (click)="saveQR()" matTooltip="Save this QR"> <mat-icon> <i class="material-icons md-48">save</i> </mat-icon> </button> </div> </div> <!--Textarea to write any text or link--> <div class="qrTextAreaDiv"> <mat-form-field> <textarea matInput [(ngModel)]="qrText" cdkTextareaAutosize cdkAutosizeMinRows="4" cdkAutosizeMaxRows="4" placeholder="Enter a website link or any text..."></textarea> </mat-form-field> </div> <!--Create Button--> <div class="createBtnDiv"> <button class="createBtn" mat-raised-button color="accent" matTooltip="Create new QR code" matTooltipPosition="above" (click)="createQrCode()">Create</button> </div> </div> </mat-card>
上面的代碼片段使用了許多 Angular 材質庫元素。 按照計劃,它有一個標頭組件引用,其中傳遞了所需的參數。 接下來是創建頁面的主體; 它由一張 Angular 材質卡或mat-card
組成,使用[class.mat-elevation-z12]=true
時,居中並提升到 12px。
素材卡只是另一種容器,可以用作任何其他div
標籤。 儘管材質庫提供了一些屬性來在mat-card
中佈置明確定義的信息,例如圖像位置、標題、副標題、描述和操作,如下所示。
在上面的 HTML 片段中,我們使用mat-card
就像任何其他容器一樣。 使用的另一個材質庫元素是matTooltip
; 它只是另一個易於使用的工具提示,當用戶懸停或長按元素時顯示。 只需使用下面的代碼片段來顯示工具提示:

matTooltip="Any text you want to show"
它可以與圖標按鈕或任何其他 UI 元素一起使用,以傳達額外的信息。 在應用程序上下文中,它顯示有關關閉圖標按鈕的信息。 要更改工具提示的位置,使用matTooltipPosition
:
matTooltip="Any text you want to show" matTooltipPosition="above"
除了matTooltip
之外, mat-spinner
用於顯示加載進度。 當用戶單擊“創建”按鈕時,會進行網絡調用。 這是顯示進度微調器的時候。 當網絡調用返回結果時,我們只是隱藏微調器。 它可以像這樣簡單地使用:
<mat-spinner *ngIf="showProgressSpinner"></mat-spinner>
showProgressSpinner
是一個布爾變量,用於顯示/隱藏進度微調器。 該庫還提供了一些其他參數,例如[color]='accent'
來更改顏色, [mode]='indeterminate'
來更改進度微調器類型。 不確定的進度微調器不會顯示任務的進度,而確定的進度微調器可以具有不同的值來反映任務進度。 這裡使用了一個不確定的微調器,因為我們不知道網絡調用需要多長時間。
材質庫提供了一個符合材質指南的 textarea 變體,但它只能用作mat-form-field
的後代。 材質 textarea 的使用與默認的 HTML 一樣簡單,如下所示:
<mat-form-field> <textarea matInput placeholder="Hint text"></textarea> </mat-form-field>
matInput
是一個指令,它允許本機input
標籤與mat-form-field
一起使用。 placeholder
屬性允許為用戶添加任何提示文本。
提示:使用cdkTextareaAutosize
textarea 屬性使其可自動調整大小。 使用cdkAutosizeMinRows
和cdkAutosizeMaxRows
設置行和列以及所有三個一起使 textarea 自動調整大小,直到達到最大行和列限制設置。
要使用所有這些材質庫元素,我們需要將它們添加到app.module.ts文件中。
HTML 中使用了佔位符圖像。 下載並保存到/assets
文件夾。
上面的 HTML 還需要 CSS 樣式,所以打開create-qr.component.ts文件並添加以下內容:
.qrCard { display: flex; flex-direction: column; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 20%; height: 65%; padding: 50px 20px; } .qrContent { display: flex; flex-direction: column; align-items: center; width: 100%; } .qrTextAreaDiv { width: 100%; display: flex; flex-direction: row; justify-content: center; padding: 0px 0px; position: absolute; bottom: 10%; } .createBtn { left: 50%; transform: translate(-50%, 0px); width: 80%; } .createBtnDiv { position: absolute; bottom: 5%; width: 100%; } .closeBtn { display: flex; flex-direction: row-reverse; align-items: flex-end; width: 100%; margin-bottom: 20px; } .closeBtnFont { font-size: 32px; color: rgba(0,0,0,0.75); } .qrImgDiv { top: 20%; position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; } .actionButtons { display: flex; flex-direction: row; padding-top: 20px; }
讓我們用邏輯連接 UI。 打開create-qr.component.ts文件並添加以下代碼,保留那些已經存在的行:
export class CreateQrComponent implements OnInit { qrCodeImage = '../../../assets/download.png'; showProgressSpinner = false; qrText: string; currentQR; showBackButton = true; title = 'Generate New QR Code'; showHistoryNav = true; constructor(private snackBar: MatSnackBar, private restutil: RestutilService, private storageService: StorageutilService) { } ngOnInit() { } createQrCode() { //Check if any value is given for the qr code text if (!!this.qrText) { //Make the http call to load qr code this.loadQRCodeImage(this.qrText); } else { //Show snackbar this.showSnackbar('Enter some text first') } } public loadQRCodeImage(text: string) { // Show progress spinner as the request is being made this.showProgressSpinner = true; // Trigger the API call this.restutil.getQRCode(text).subscribe(image =>{ // Received the result - as an image blob - require parsing this.createImageBlob(image); }, error => { console.log('Cannot fetch QR code from the url', error) // Hide the spinner - show a proper error message this.showProgressSpinner = false; }); } private createImageBlob(image: Blob) { // Create a file reader to read the image blob const reader = new FileReader(); // Add event listener for "load" - invoked once the blob reading is complete reader.addEventListener('load', () => { this.qrCodeImage = reader.result.toString(); //Hide the progress spinner this.showProgressSpinner = false; this.currentQR = reader.result.toString(); }, false); // Read image blob if it is not null or undefined if (image) { reader.readAsDataURL(image); } } saveQR() { if (!!this.qrText) { this.storageService.saveHistory(this.qrText, this.currentQR); this.showSnackbar('QR saved') } else { //Show snackbar this.showSnackbar('Enter some text first') } } showSnackbar(msg: string) { //Show snackbar this.snackBar.open(msg, '', { duration: 2000, }); } }
為了向用戶提供上下文信息,我們還使用了材料設計庫中的MatSnackBar
。 這顯示為屏幕下方的彈出窗口,並在消失前停留幾秒鐘。 這不是一個元素,而是一個可以從 Typescript 代碼調用的服務。
上面的方法名稱為showSnackbar
的代碼片段展示瞭如何打開一個snackbar,但是在它可以使用之前,我們需要在app.module.ts文件中添加MatSnackBar
條目,就像我們為其他材質庫元素所做的那樣。
提示:在最近的 Angular 材質庫版本中,沒有直接的方法可以更改快餐欄樣式。 相反,必須對代碼進行兩次添加。
首先,使用下面的 CSS 來改變背景和前景色:
::ng-deep snack-bar-container.snackbarColor { background-color: rgba(63, 81, 181, 1); } ::ng-deep .snackbarColor .mat-simple-snackbar { color: white; }
其次,使用一個名為panelClass
的屬性將樣式設置為上述 CSS 類:
this.snackBar.open(msg, '', { duration: 2000, panelClass: ['snackbarColor'] });
上述兩種組合將允許對材料設計庫快餐欄組件進行自定義樣式。
這樣就完成瞭如何創建 QR 頁面的步驟,但還缺少一個。 檢查create-qr.component.ts文件,它將顯示有關缺失部分的錯誤。 這個難題缺少的部分是RestutilService
,它負責從第三方 API 獲取 QR 碼圖像。
在終端中,通過輸入ng gs restutil
並按 Enter 將當前目錄更改為 services。 這將創建 RestUtilService 文件。 打開restutil.service.ts文件並添加以下代碼段:
private edgeSize = '300'; private BASE_URL = 'https://api.qrserver.com/v1/create-qr-code/?data={data}!&size={edge}x{edge}'; constructor(private httpClient: HttpClient) { } public getQRCode(text: string): Observable { // Create the url with the provided data and other options let url = this.BASE_URL; url = url.replace("{data}", text).replace(/{edge}/g, this.edgeSize); // Make the http api call to the url return this.httpClient.get(url, { responseType: 'blob' }); }
private edgeSize = '300'; private BASE_URL = 'https://api.qrserver.com/v1/create-qr-code/?data={data}!&size={edge}x{edge}'; constructor(private httpClient: HttpClient) { } public getQRCode(text: string): Observable { // Create the url with the provided data and other options let url = this.BASE_URL; url = url.replace("{data}", text).replace(/{edge}/g, this.edgeSize); // Make the http api call to the url return this.httpClient.get(url, { responseType: 'blob' }); }
上述服務從第三方 API 獲取 QR 圖像,由於響應不是 JSON 類型,而是圖像,因此我們在上述代碼段中將responseType
指定為'blob'
。
Angular 提供了HttpClient
類來與任何支持 HTTP 的服務器進行通信。 它提供了許多功能,例如在觸發請求之前過濾請求、獲取響應、通過回調和其他方式處理響應。 要使用它,請在app.module.ts文件中為HttpClientModule添加一個條目。
最後將該服務導入create-qr.component.ts文件,完成二維碼的創建。
可是等等! 上面的創建二維碼邏輯有問題。 如果用戶一次又一次地使用相同的文本生成二維碼,就會導致網絡調用。 解決此問題的一種方法是緩存基於請求,因此如果請求文本相同,則從緩存中提供響應。
緩存請求
Angular 提供了一種簡化的 HTTP 調用方式 HttpClient 以及 HttpInterceptor 來檢查和轉換與服務器之間的 HTTP 請求或響應。 它可用於身份驗證或緩存以及許多此類事情,可以添加多個攔截器並將其鏈接起來以進行進一步處理。 在這種情況下,如果 QR 文本相同,我們將攔截請求並從緩存中提供響應。
創建一個攔截器文件夾,然後創建一個文件cache-interceptor.ts :
將以下代碼片段添加到文件中:
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpResponse, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { tap } from 'rxjs/operators'; import { of, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class RequestCachingService implements HttpInterceptor { private cacheMap = new Map<string, HttpResponse<any>>(); constructor() { } intercept(req: HttpRequest , next: HttpHandler): Observable<HttpEvent<any>> { const cachedResponse = this.cacheMap.get(req.urlWithParams); if (cachedResponse) { return of(cachedResponse); } return next.handle(req).pipe(tap(event => { if (event instanceof HttpResponse) { this.cacheMap.set(req.urlWithParams, event); } })) } }
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpResponse, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { tap } from 'rxjs/operators'; import { of, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class RequestCachingService implements HttpInterceptor { private cacheMap = new Map<string, HttpResponse<any>>(); constructor() { } intercept(req: HttpRequest , next: HttpHandler): Observable<HttpEvent<any>> { const cachedResponse = this.cacheMap.get(req.urlWithParams); if (cachedResponse) { return of(cachedResponse); } return next.handle(req).pipe(tap(event => { if (event instanceof HttpResponse) { this.cacheMap.set(req.urlWithParams, event); } })) } }
在上面的代碼片段中,我們有一個映射,鍵是請求 URL,響應是值。 我們檢查當前 URL 是否存在於地圖中; 如果是,則返迴響應(其餘部分自動處理)。 If the URL is not in the map, we add it.
We are not done yet. An entry to the app.module.ts is required for its proper functioning. Add the below snippet:
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { CacheInterceptor } from './interceptor/cache-interceptor'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true } ],
This adds the caching feature to our application. Let's move on to the third page, the History page.
Adding The History Page
All the saved QR codes will be visible here. To create another component, open terminal type ng gc history
and press Enter.
Open history.component.css and add the below code:
.main-content { padding: 5% 10%; } .truncate { width: 90%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .center-img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; }
Open history.component.html and replace the content with this:
<app-header [showBackButton]="showBackButton" [currentTitle]="title" [showHistoryNav]="showHistoryNav"></app-header> <div class="main-content"> <mat-grid-list cols="4" rowHeight="500px" *ngIf="historyList.length > 0"> <mat-grid-tile *ngFor="let qr of historyList"> <mat-card> <img mat-card-image src="{{qr.imageBase64}}"> <mat-card-content> <div class="truncate"> {{qr.text}} </div> </mat-card-content> <mat-card-actions> <button mat-button (click)="share(qr.text)">SHARE</button> <button mat-button color="accent" (click)="delete(qr.text)">DELETE</button> </mat-card-actions> </mat-card> </mat-grid-tile> </mat-grid-list> <div class="center-img" *ngIf="historyList.length == 0"> <img src="../../assets/no-see.png" width="256" height="256"> <span>Nothing to see here</span> </div> </div>
As usual, we have the header component at the top. Then, the rest of the body is a grid list that will show all the saved QR codes as individual mat-card
. For the grid view, we are using mat-grid-list
from the Angular material library. As per the drill, before we can use it, we have to first add it to the app.module.ts file.
Mat 網格列表充當一個容器,其中包含多個名為mat-grid-tile
子圖塊。 在上面的 HTML 片段中,每個 tile 都是使用mat-card
創建的,它使用它的一些屬性來通用放置其他 UI 元素。 我們可以提供number of columns
和rowHeight
,用於自動計算寬度。 在上面的代碼片段中,我們提供了列數和rowHeight
值。
當歷史記錄為空時,我們使用佔位符圖像,下載並添加到資產文件夾。
要實現填充所有這些信息的邏輯,請打開history.component.ts文件並將以下代碼段添加到HistoryComponent
類中:
showBackButton = true; title = 'History'; showHistoryNav = false; historyList; constructor(private storageService: StorageutilService, private snackbar: MatSnackBar ) { } ngOnInit() { this.populateHistory(); } private populateHistory() { this.historyList = this.storageService.readAllHistory(); } delete(text: string) { this.storageService.deleteHistory(text); this.populateHistory(); } share(text: string) { this.snackbar.open(text, '', {duration: 2000,}) }
上面的邏輯只是獲取所有保存的 QR 並用它填充頁面。 用戶可以刪除保存的 QR,這將從本地存儲中刪除該條目。
所以這完成了我們的歷史組件......還是這樣? 我們仍然需要為這個組件添加路由映射。 打開app-routing.module.ts並為歷史頁面添加一個映射:
{ path: 'history', component: HistoryComponent },
整個路由數組現在應該是這樣的:
const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'create', component: CreateQrComponent }, { path: 'history', component: HistoryComponent }, ];
現在是運行應用程序以檢查完整流程的好時機,因此打開終端並鍵入ng serve
並按 Enter。 然後,轉到localhost:4200
以驗證應用程序的工作。
添加到 GitHub
在繼續部署步驟之前,最好將項目添加到 GitHub 存儲庫。
- 打開 GitHub。
- 創建一個新的存儲庫。
- 在 VS Code 中,使用終端並按照快速入門指南中提到的第一組命令推送所有項目文件。
只需刷新頁面以檢查所有文件是否可見。 從此時起,任何 git 更改(例如提交、拉/推)都將反映在這個新創建的存儲庫中。
Netlify 和部署
我們的應用程序運行在我們的本地機器上,但是為了讓其他人可以訪問它,我們應該將它部署在雲平台上並註冊到一個域名。 這就是 Netlify 發揮作用的地方。 它提供了持續部署服務、與 GitHub 的集成以及更多可以從中受益的功能。 現在,我們希望啟用對我們應用程序的全局訪問。 讓我們開始吧。
- 在 Netlify 上註冊。
- 在儀表板中,單擊“從 Git 新建站點”按鈕。
- 在下一個屏幕中單擊 GitHub。
- 授權 Netlify 能夠訪問您的 GitHub 存儲庫。
- 搜索並選擇新創建的
qr
存儲庫。 - 在下一步中,Netlify 允許我們選擇 GitHub 存儲庫分支進行部署。 通常使用
master
分支,但也可以有一個單獨的release
分支,其中僅包含與發布相關的穩定功能。

由於這是一個 Angular Web 應用程序,因此添加ng build --prod
作為構建命令。 如angular.json
文件中所述,發布的目錄將是dist/qr
。
現在單擊Deploy site
按鈕,該按鈕將使用命令ng build --prod
觸發項目構建,並將文件輸出到dist/qr
。
由於我們向 Netlify 提供了路徑信息,它會自動選擇正確的文件來為 Web 應用程序提供服務。 默認情況下,Netlify 會向我們的應用程序添加一個隨機域。
您現在可以單擊上述頁面中提供的鏈接,以便從任何地方訪問該應用程序。 最後,應用程序已部署完畢。
自定義域
在上圖中,顯示了我們應用程序的 URL,而子域是隨機生成的。 讓我們改變它。
單擊Domain settings
按鈕,然後在自定義域部分中單擊三點菜單並選擇Edit site name
。
這將打開一個彈出窗口,可以在其中輸入新的站點名稱; 此名稱在 Netlify 域中應該是唯一的。 輸入任何可用的站點名稱,然後單擊保存。
現在,指向我們應用程序的鏈接將更新為新的站點名稱。
拆分測試
Netlify 提供的另一個很酷的功能是拆分測試。 它支持流量拆分,以便不同的用戶集與不同的應用程序部署進行交互。 我們可以將新功能添加到不同的分支,並將流量拆分到此分支部署,分析流量,然後將功能分支與主部署分支合併。 讓我們配置它。
啟用拆分測試的先決條件是擁有至少兩個分支的 GitHub 存儲庫。 前往之前在 GitHub 中創建的應用程序存儲庫,然後創建一個新分支a
.
存儲庫現在將有一個master
分支和a
分支。 Netlify 需要配置為進行分支部署,因此打開 Netlify 儀表板並單擊Settings
。 在左側,單擊Build & Deploy
,然後單擊Continuous Deployment
,然後在右側的Deploy contexts
部分中單擊Edit settings
。
在Branch deploys
子部分中,選擇“讓我添加單個分支”選項,然後輸入分支名稱並保存。
部署分支是 Netlify 提供的另一個有用的功能; 我們可以選擇要部署的 GitHub 存儲庫分支,我們還可以在合併之前對master
分支的每個拉取請求啟用預覽。 這是一個簡潔的功能,使開發人員能夠在將代碼更改添加到主部署分支之前實際測試他們的更改。
現在,單擊頁面頂部的Split Testing
選項卡選項。 此處將介紹拆分測試配置。
我們可以選擇分支(生產分支除外)——在本例中a
。 我們也可以玩轉流量分割的設置。 根據每個分支分配的流量百分比,Netlify 會將一些用戶重新路由到使用a
分支部署的應用程序,而將其他用戶重新路由到master
分支。 配置完成後,點擊Start test
按鈕啟用流量拆分。
提示: Netlify 可能無法識別連接的 GitHub 存儲庫有多個分支,並且可能會出現以下錯誤:
要解決此問題,只需從Build & Deploy
選項重新連接到存儲庫。
Netlify 還提供了許多其他功能。 我們剛剛介紹了它的一些有用功能,以演示如何輕鬆配置 Netlify 的不同方面。
這將我們帶到了旅程的終點。 我們已經成功地創建了一個基於 Web 應用程序的 Angular Material 設計,並將其部署在 Netlify 上。
結論
Angular 是一個偉大且流行的 Web 應用程序開發框架。 使用官方的 Angular 材料設計庫,可以更輕鬆地創建符合材料設計規範的應用程序,以便與用戶進行非常自然的交互。 而且,用一個好的框架開發的應用程序應該使用一個好的平台進行部署,而Netlify就是這樣。 隨著不斷的發展、強大的支持和眾多的功能,它無疑是一個將 Web 應用程序或靜態站點帶給大眾的絕佳平台。 希望這篇文章能夠幫助您開始一個新的 Angular 項目,從構思到部署。
延伸閱讀
- 角度架構
- 更多 Angular 材質組件
- 有關 Netlify 功能的更多信息