選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
7 分鐘閱讀時間 (1348 個字)

Angular # 16 - 動手建個 dashboard [1]

shutterstock_198004562
  • 這篇要動手囉!

    • 先去 github 拉一下基底

        git clone -b start https://github.com/angular-in-action/datacenter.git
      • 這是 Angular in Action 這本書的其中一個範例,我們拿來作為基底使用,節省一些手動建立的時間
      • 拿到別人的 Angular 專案第一件要做的事就是 npm install,這個時候會將 package.json 中列到的套件下載到 node_modules
    • 接著來建立一個資料 Component

      • 先建立一個 DashboardComponent

          ng g c dashboard
      • controller 的內容如下:

          import { Component, OnInit, OnDestroy } from '@angular/core';
        
          interface Metric {
            used: number,
            available: number
          };
        
          interface Node {
            name: string,
            cpu: Metric,
            mem: Metric
          };
        
          @Component({
            selector: 'app-dashboard',
            templateUrl: './dashboard.component.html',
            styleUrls: ['./dashboard.component.css']
          })
          export class DashboardComponent implements OnInit, OnDestroy {
        
            cpu: Metric;
            mem: Metric;
            cluster1: Node[];
            cluster2: Node[];
            interval: any;
        
            constructor() {}
        
            ngOnInit(): void {
              this.generateData();
              this.interval = setInterval(() => {
              this.generateData();
              }, 15000);
            }
        
            ngOnDestroy(): void {
              clearInterval(this.interval);
            }
        
            generateData(): void {
              this.cluster1 = [];
              this.cluster2 = [];
              this.cpu = { used: 0, available: 0 };
              this.mem = { used: 0, available: 0 };
              for (let i = 1; i < 4; i++) this.cluster1.push(this.randomNode(i));
              for (let i = 4; i < 7; i++) this.cluster2.push(this.randomNode(i));
            }
        
            private randomNode(i): Node {
              let node = {
                  name: 'node' + i,
                  cpu: { available: 16, used: this.randomInteger(0, 16) },
                  mem: { available: 48, used: this.randomInteger(0, 48) }
              };
              this.cpu.used += node.cpu.used;
              this.cpu.available += node.cpu.available;
              this.mem.used += node.mem.used;
              this.mem.available += node.mem.available;
              return node;
            }
        
            private randomInteger(min: number = 0, max: number = 100): number {
              return Math.floor(Math.random() * max) + 1;
            }
          }
        • 好長好長,不管多長,先 import 並 implement 兩個 Lifecycle Hook,分別是 OnInitOnDestroy
        • 承上,實作介面是要實作方法的,不然會被編譯器抱怨
        • ngOnInit 方法中,每 15 秒會產一筆隨機的資料
        • ngOnDestroy 中,上面的那個 15 秒的 interval 會被移除
        • 這個 component 有 5個屬性, 大部份應該看命名就知道在做什麼的, interval 則是要保留其參考,在最後清理的時候才得到東西,不會因此 Memory Leak
    • 再來我們修改 template dashbaord.component.html 如下:

        <p>{{cluster1 | json}}</p>
      • 這樣做的目的是為了確定我們在 controller 設定的資訊,在 template 有被正確的 render
      • 我們預期上面的 Interpolation 是把 cluster1 透過 JsonPipe 轉成 JSON 格式的字串顯示在頁面上
    • 最後一步就是把我們定義好的 Component 放到 AppComponent 的 template 上, 這樣才能看到畫面長怎樣囉~

        <app-navbar></app-navbar>
        <app-dashboard></app-dashboard>
      • 加上 DashboardComponent 的 selector 後,執行 ng serve 啟動測試 server,再到 localhost:4200,應該就可以看到畫面如下圖:
        • 真的是 JSON 字串
        • 資料的內容也如同 controller 在 OnInit 設定的,每 15 秒會異動一次
  • 我們開發完了資料 Component,接著就是要決定如何呈現資料 Component 所持有的資料

    • 首先,我們再建立一個 MetricComponent

        ng g c metric
    • 然後一如往常,先在 controller 填入以下內容:

        import { Component, Input } from '@angular/core';
      
        @Component({
          selector: 'app-metric',
          templateUrl: './metric.component.html',
          styleUrls: ['./metric.component.css']
        })
        export class MetricComponent {
          @Input() title: string = '';
          @Input() description: string = '';
          @Input('used') value: number = 0;
          @Input('available') max: number = 100;
      
          isDanger() {
            return this.value / this.max > 0.7;
          }
        }
      • 留意我們這次 import 了 Input 這個 Decorator
      • 承上,我們在四個屬性前面加了 @Input(),讓 Angular 知道這個 Component 是能接收這四個資訊的
      • 雖然我們已經是用 Typescript 撰寫,有強型別的保護,但那充其量也是在編譯時期(Compile time)有用,執行時期(Runtime)還是要作一些消毒(Sanitization)來確保資料的正確性
      • 最後兩個 Input 提供了預設值, 同時也提供的 optional 的名稱,讓這個屬性被綁定時的名字為 usedavailable(e.g. [used] = node.used)。即便如此,在 controller 內仍然是用 value 存取其值
      • 承上,雖然這樣可以區隔對外公開的(used)與內部實際的(value), 然而一致性單純化應該是首選,所以不太建議這樣使用
      • 另外,盡量將變數命名的愈清楚愈好,無意義的縮寫並不會節省多少時間
    • 接著定義的是 Template 的部份,我們會在這用到 Angular Bootstrap 的 UI 元件連示範如何使用從 @Input 收到的值

        <div class="card card-block">
          <div class="card-body">
            <nav
              class="navbar navbar-dark bg-primary mb-1"
              [ngClass]="{ 'bg-danger': isDanger(), 'bg-success': !isDanger() }"
            >
              <h1 class="navbar-brand mb-0">{{ title }}</h1>
            </nav>
            <h4 class="card-title">
              {{ value }}/{{ max }} ({{ value / max | percent: "1.0-2" }})
            </h4>
            <p class="card-text">
              {{ description }}
            </p>
            <ngb-progressbar
              [value]="value"
              [max]="max"
              [type]="isDanger() ? 'danger' : 'success'"
            ></ngb-progressbar>
          </div>
        </div>
      • Template 中用到了很多 Bootstrap 定義的 class(e.g. bg-primary, bg-danger),只是為了減輕建立 card 的負擔 ,你也可以使用別的 UI 元件
      • nav 元素是抬頭的部份, 用到了 title 這個屬性,並透過 Directive NgClass 依方法 isDanger() 的結果決定配色
      • 其餘都是 interpolationpipes to 將值以百分比的方式呈現
      • 最後我們用了 ng-bootstrap module 中的 ngb-progressbar,依照其文件的說明,要綁定 value, max, type 三個屬性
    • 定義完 MetricComponent 的 controller 與 template,是時候使用它了

      • 第一步是先調整 DashboardComponent 的 Template:
          <div class="container mt-2">
            <div class="card card-block">
              <nav class="navbar navbar-dark bg-inverse mb-1">
                <h1 class="navbar-brand mb-0">Overall Metrics</h1>
              </nav>
              <div class="row">
                <app-metric
                  class="col-sm-6"
                  [used]="cpu.used"
                  [available]="cpu.available"
                  [title]="'CPU'"
                  [description]="'utilization of CPU cores'"
                >
                </app-metric>
                <app-metric
                  class="col-sm-6"
                  [used]="mem.used"
                  [available]="mem.available"
                  [title]="'Memory'"
                  [description]="'utilization of memory in GB'"
                >
                </app-metric>
              </div>
            </div>
          </div>
        • 記得 DashboardComponent 是資料型的 Component, 因此資料都會先在它這兒,再往下傳遞
        • 傳遞到 MetricComponent 的 @Input 屬性時,就可以正確呈現資料
        • [used]="cpu.used" 為例, 等號左手邊的是 MetricComponent 的 used 屬性, 而右手邊的 cpu.used 則是 DashboardComponent 傳遞下來的資料
        • 資料除了從其他的元件取得之外,也可以使用定數(literals),舉例來說,title 的值就是 "'Memory'"(注意單、雙引號的放置位子)
        • 如果你還沒中止 ng serve 啟動的測試 server,畫面應該會更新如下:
          • 這個 MetricComponent 其實相當通用,只要適用 used/title/available/description 的資料都可以重複使用
  • Dashboard 目前為止建一半了,小結一下:

    • 善用 LifeCycle Hoook
    • 好用的 UI Library 很多,可以回去前幾篇看一下有哪些可選的,首推官方的 Angular Material UI 或是 PrimeNG
    • 資料由 Parent 傳遞下來時:
      • Component 的 @Input 屬性可以拿來接資料
      • 在 Template 上會以 [attribute] 的方式接收
      • MetricComponent 是 DashboardComponent 的 ViewChild
Angular #17 - 動手建個 dashboard [2]
Angular #15 - Components [3]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2024/05/04, 週六

Captcha 圖像