透過 Angular CLI 產生的前端程式在 IE11 無法正確執行

Angular CLI 產生的前端程式一般而言無法正確在 IE 瀏覽器執行,主要有幾個原因:

  • polyfill.ts 必須要修改內容:

Polyfill 本身的目的就是提供一個中介API層,讓舊的瀏覽器可以支援新的API,Angular CLI 自帶的 polyfill.ts (在 src/ 目錄下),預設會註解掉 IE 的支援,將其移除即可(以下還多增加 es7/array 的支援):

  /** IE9, IE10 and IE11 requires all of the following polyfills. **/
 import 'core-js/es6/symbol';
 import 'core-js/es6/object';
 import 'core-js/es6/function';
 import 'core-js/es6/parse-int';
 import 'core-js/es6/parse-float';
 import 'core-js/es6/number';
 import 'core-js/es6/math';
 import 'core-js/es6/string';
 import 'core-js/es6/date';
 import 'core-js/es6/array';
 import 'core-js/es6/regexp';
 import 'core-js/es6/map';
 import 'core-js/es6/weak-map';
 import 'core-js/es6/set';
  /** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install --save classlist.js`.
  /** IE10 and IE11 requires the following for the Reflect API. */
import 'core-js/es6/reflect';
  /** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
import 'core-js/es7/array';
  • 不可以使用 angular 的 forEach

forEach() loop 是 Angular 所提供一個很好的服務,他支援 Async 方式解析陣列的元件;但可惜 IE 11 不支援,因此,若程式需要支援 IE 11,必須要將 forEach() 寫法轉成標準的 for loop。

舉例如下,正常而言,透過 angular forEach 可以直接瀏覽陣列元素:

selections.forEach(item =>
    this.get(item.ItemType, item.ParentCode, item.NeedEmpty)
        .subscribe(data => item.Details = data)
);

一個很重要的議題在於處理 subscribe 針對 http async 的回傳元件,forEach 可以確保 subscribe 中的 item 的值會是當下的 item,如果一般的 loop:

for (var i = 0; i < selections.length; i++) {
    var item = selections[i];
    this.get(item.ItemType, item.ParentCode, item.NeedEmpty)
.subscribe(data => item.Details = data)

這樣寫法是會錯誤的,因為 item 會永遠是最後一筆!
需要改為 透過 function 來設定 item 的內容:

for (var i = 0; i < selections.length; i++) {
    var item = selections[i];
    this.get(item.ItemType, item.ParentCode, item.NeedEmpty, item);
}

get(key, parentCode, needEmpty, codeFile: CodeFile): void {
    this.http.get<Code[]>(this.baseUrl)
        .subscribe(response => {
            let items: Code[] = <Code[]>response;
            codeFile.Details = items;
            this.selections.push(codeFile);
        });
    }
  • Cache 問題:IE 預設會自帶 Cache, 會造成 Angular httpClient 的讀取都是回傳 Cache 的值,因此造成明明有變更但頁面卻無法顯示。解決方式是加上 HttpHeaders diable Cache:
private noCacheHeader: HttpHeaders = new HttpHeaders().set('Cache-Control', 'no-cache').set('Pragma', 'no-cache');
  public getDailyReport(id: number): Observable<DailyReportItem> {
    let params = new HttpParams().set('id', id.toString());
    return this.http.get<DailyReportItem>("/dailyreport/getreport", { params: params, headers: this.noCacheHeader });
}

這裡必須要加入 Cache-Control + Pragma 兩個設定。

整合 ng-busy 套件,http 呼叫可顯示資料載入中的效果

client 需要在 server 存取資料時候,往往會有些等待的時間。透過指示讓使用者知道正在下載中,可以增加使用者的操作體驗。

ng-busy 就是這樣的套件,主要組合包含 css:設定 [ngBusy] 顯示等待的畫面,必須要在每一個 會存取 http component 中添加。

做法如下:

 

安裝

npm install --save ng-busy

\node_modules\angular2-busy\build\style\busy.css copy wwwroot/css 下,方便後續引用:

<link href="~/css/busy.css" rel="stylesheet" />

在 app.module 中,加入

  import { NgBusyModule } from 'ng-busy';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
  @NgModule({
      imports: [
      	// ...
          BrowserAnimationsModule,
          BusyModule
      ],
      // ...
  })

使用方式:

建議使用 Subscription 方案(也可以用 Promise,不過處理步驟就會有些許不同,主要看 http 如何處理):

  import { Subscription } from 'rxjs';
  
// 在 Component 中,定義 busy 變數,並且將 subscribe 結果放入
  busy: Subscription;
  ngOnInit() {
    this.busy = this.data.getNurses(this.clinicCode).subscribe(data => this.nurses = data);
}

html 中,就可以直接呼叫:

<div [ngBusy]=”{busy: busy, message: ‘資料載入中…’}”></div>

 

如此就會出現下方資料載入中的內容

參考: https://github.com/victos/ng-busy

其中也包含 onBusyStop() & onBusyStart() 兩個 event,可以透過以下方式關聯:

<div [ngBusy]=“…” (busyStop)=“onBusyStop()” (busyStart)=“onBusyStart()”></div>

Visual Studio 使用 Web Deploy 部署到 IIS 方案

適用於 win 2012 srv

如果是 win 2012 以上版本,加入 IIS 管理服務,再打開IIS 就會出現安裝 Microsoft Web Platform 的訊息:

請注意,windows server 要檢查 IIS 管理服務是否已經安裝:

點選【是】就會進入安裝:

安裝 WPI 後,就要安裝 Web Deploy 3.6 for Hosting Servers(只需要安裝這個即可):

安裝完畢後,下一步就要建立專案,同時允許透過 Web Deploy 部署。首先,先建立網站對應的名稱與放置檔案的路徑:

其次,指定【IIS管理員權限】,這裡的重點只在於指定可以登入 windows 的帳號(用來之後在 Visual Studio deploy 設定使用):

接下來就可以在 Visual Studio 設定部署的方式了:

 

透過 FileSaver 處理檔案下載方案

在 Angular 中,處理檔案下載不是一件容易的事情,主要方法有以下兩種:

1. 透過 anchor 直接將檔案的下載連結放在網頁上:

<a href="/vpn/downloadFile?filename={{item.patientFile}}" class="btn btn-info btn-sm" type="button"
   ng-disabled="!item.patientFile" icon="fa-file-excel-o" target="_self">
    下載 Excel
</a>

其中 /vpn/download 就是用來下載檔案的方式

2. 直接在 angular client code 中處理

最快的方案(而且不需要花費時間)就是透過 HTML 5 FileSaver 套件,引用方式如下:

  • 下載 npm package,這裡同時下載 typescript 定義檔,提供 Intellisense:

npm install file-saver –save

npm install @types/file-saver –save

透過 –save 會直接 update package.json file

  • 透過 http get 下載檔案:
download() {
    this.http.get("/dailyreport/downloadExcel/" + id, { responseType: 'blob' })
        .subscribe(
            data => this.processFile(data),
            error => super.showMessage(AlertType.Danger, error)
        );
}
 
private processFile(data: any) {
    var blob = new Blob([data], { type: 'application/vnd.ms-excel' });
    FileSaver.saveAs(blob, "file.xlsx");
}

注意這裡使用 blob 型態。

  • 在 Server side 將檔案轉成 filestream:
[HttpGet]
public IActionResult DownloadExcel(string filePath)
{
    var fileContents = System.IO.File.ReadAllBytes(filePath);
    return new FileContentResult(fileContents, "application/vnd.ms-excel");
}