iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
1
Modern Web

高效 Coding 術:Angular Schematics 實戰三十天系列 第 7

[高效 Coding 術:Angular Schematics 實戰三十天] Day06 - 與範本共舞

  • 分享至 

  • xImage
  •  

經過三天樸實無華且枯燥的練習,相信大家一定都迫不及待想要開始學習更進一步的應用了,所以今天筆者就來分享一個比較具有實用性的應用吧!

建立範本

雖然之前練習的時候是直接用程式碼來產生檔案,但身為一個史上最強的前端框架裡的程式碼產生器,絕對不僅止於此。事實上,我們可以透過使用範本來產生我們想要的檔案。

以下是筆者從 StackBlitz 的 Angular 範例裡所擷取的一段程式碼:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent  {
  @Input() name: string;
}

先把這段程式碼存成一個檔案,並把它放到 /src/hello-word/files 底下,接著將它的檔名改成 hello-__name__.component.ts ,像這樣:

Imgur

為什麼檔名這麼奇怪呢?

這是因為 Schematics 在用範本幫我們產生檔案的時候,會將使用者所輸入的參數跟範本結合,然後用 __ 把參數的名稱與固定的名稱區隔開來。

舉例來說,假設使用者輸入 schematics .:hello-world leo 這個指令,屆時所產生出來的檔案,其檔名就會是 hello-leo.component.ts

接著再到 index.ts 裡做以下修改:

import { Rule, SchematicContext, Tree, url, apply, template, mergeWith } from '@angular-devkit/schematics';

import { strings } from '@angular-devkit/core';

export function helloWorld(_options: any): Rule {
  return (_tree: Tree, _context: SchematicContext) => {

    const sourceTemplates = url('./files'); // 使用範本

    const sourceParametrizedTemplates = apply(sourceTemplates, [
      template({
        ..._options, // 使用者所輸入的參數
      })
    ]);

    return mergeWith(sourceParametrizedTemplates);
  };
}

結果:

Imgur

修改完之後記得先存檔,接著輸入 npm run build 編譯程式碼,完成後再執行 schematics .:hello-world leo 才會產出正確結果噢!

如果想要 Schematics 真的產出檔案,記得要加上 --debug=false 的參數。

範本語法

既然檔名可以參數結合,內容當然也可以!打開 hello-__name__.component.ts 做點修改:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello-<%= name %>',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class Hello<%= name %>Component  {
  @Input() name: string;
}

從上方程式碼不難看出, Schematics 是用 <%= 參數名稱 %> 來將參數的值跟範本綁定在一起。

趕快輸入 schematics .:hello-world leo --debug=false 來看看結果:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello-leo',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloleoComponent  {
  @Input() name: string;
}

咦?!怎麼好像怪怪的?!

這是因為雖然現在可以讓使用者輸入的參數跟範本結合起來,但我們無法讓它適用每個命名方式。例如:檔名一般習慣字首小寫,並用 - 來連接多個單字,但如果是用在類別名稱上,就會希望它是字首大寫開頭,且不需要用 - 來連接多個單子。

好在 Schematics 早就知道大家會有這樣的問題,所以非常貼心地準備了以下函式供大家使用:

dasherize

例:

把所有字母都轉成小寫,且用 - 連結多個單字。

 dasherize('innerHTML');         // 'inner-html'
 dasherize('action_name');       // 'action-name'
 dasherize('css-class-name');    // 'css-class-name'
 dasherize('my favorite items'); // 'my-favorite-items'
 dasherize('Leo Chen');          // 'leo-chen'

underscore

把所有字母都轉成小寫,且用 _ 連結多個單字。

例:

underscore('innerHTML');         // 'inner_html'
underscore('action_name');       // 'action_name'
underscore('css-class-name');    // 'css_class_name'
underscore('my favorite items'); // 'my_favorite_items'
dasherize('Leo Chen');           // 'leo-chen'

classify

將每個單字的首個字母轉成大寫,並去掉所有單字之間的 -_空格

例:

classify('innerHTML');         // 'InnerHTML'
classify('action_name');       // 'ActionName'
classify('css-class-name');    // 'CssClassName'
classify('my favorite items'); // 'MyFavoriteItems'

camelize

將首個單字的首個字母轉成小寫,並去掉所有單字之間的 -_空格

例:

camelize('innerHTML');          // 'innerHTML'
camelize('action_name');        // 'actionName'
camelize('css-class-name');     // 'cssClassName'
camelize('my favorite items');  // 'myFavoriteItems'
camelize('Leo Chen');           // 'leoChen'

capitalize

單純將字串的首個字母轉成大寫。

例:

capitalize('innerHTML');          // 'InnerHTML'
capitalize('action_name');        // 'Action_name'
capitalize('css-class-name');     // 'Css-class-name'
capitalize('my favorite items');  // 'My favorite items'

實作細節詳見官方 Github 原始碼

接下來就讓我們用這些函式來完善我們的範本吧!

首先先將範本的檔名改成 hello-__name@dasherize__.component.ts ,並將範本內容做以下調整:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello-<%= dasherize(name) %>', // 這裡使用 dasherize
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class Hello<%= classify(name) %>Component  { // 這裡使用 classify
  @Input() name: string;
}

最後也是最重要的是要將這些字串處理函式加入到我們的 index.ts 之中:

import { Rule, SchematicContext, Tree, url, apply, template, mergeWith } from '@angular-devkit/schematics';

import { strings } from '@angular-devkit/core'; // 引入 strings ,所有的字串處理函式都在裡面

export function helloWorld(_options: any): Rule {
  return (_tree: Tree, _context: SchematicContext) => {

    const sourceTemplates = url('./files');

    const sourceParametrizedTemplates = apply(sourceTemplates, [
      template({
        ..._options,
        ...strings // 將這些函式加到規則裡,範本語法才能正常運作
      })
    ]);

    return mergeWith(sourceParametrizedTemplates);
  };
}

存檔並編譯完之後,輸入 schematics .:hello-world LeoChen --debug=false 來看看實際的產出結果:

Imgur

客製函式給範本使用

雖然官方已經非常貼心地幫大家做了很多處理函式,但總是會有某些情境找不到適合的函式可以使用。這時,我們就得自己撰寫適合情境的函式了。

實作的方式非常簡單,只要先寫好函式,如:

function addExclamation(value: string): string {
  return value + '!';
}

接著將其加到規則內,如:

template({
  ..._options,
  ...strings,
  addExclamation
})

如此一來,就可以在範本裡使用我們自已客製的函式囉!

判斷式

在某些情境下,會需要根據使用者所輸入的某個參數來判斷要不要在範本裡加上某些程式碼。像是每當我們使用 Angular CLI 的 ng new 指令來產生新專案時,它總會問我們這個問題:

Imgur

接著它就會根據我們的回覆來產生相對應的程式碼。

它是怎麼做到的?非常簡單,只要在範本中使用判斷式即可。

例如:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
<% if (routing) { %>
import { AppRoutingModule } from './app-routing.module';<% } %>
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule<% if (routing) { %>,
    AppRoutingModule<% } %>
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

如此一來,範本自然就會根據使用者所輸入的 routing 參數來判斷該不該加入 AppRoutingModule 的相關程式碼。

需要特別留意的是,在我們單純在範本中使用參數時,是使用 <%= 參數名稱 %> 來將該參數的值與範本結合在一起;但在使用判斷式時,則是使用 <% if (參數名稱) { %> 程式碼 <% } else { %> 程式碼 <% } %>,左邊的角括弧會少一個 = ,別搞混囉!

for-of 迴圈

既然有判斷式,迴圈當然也不能少囉!使用方式如下:

<ul>
<% for ( let item of list) { %>
  <li><%= item %></li>
<% } %>
</ul>

不過筆者個人覺得這個語法應該會滿少用到,但還是必須讓大家知道。

本日結語

今天筆者從「如何使用範本來產生程式碼」開始,到「如何在範本裡使用處理函式」來讓我們的範本更加符合實際應用,漸漸地會帶入越來越多的程式碼,筆者的用意是希望能讓大家完全整握每個小細節,如果有讀者覺得筆者的節奏太慢還請多多包涵。

今天的程式碼在這裡,明天筆者將會教大家撰寫用來驗證今天的程式的測試,敬請期待!

參考資料


上一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day05 - JSON Schema
下一篇
[高效 Coding 術:Angular Schematics 實戰三十天] Day07 - 如何測試使用範本的 Schematic
系列文
高效 Coding 術:Angular Schematics 實戰三十天32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
ch_lute
iT邦新手 5 級 ‧ 2020-05-26 11:13:37

從第一篇開始做到這邊的話會出現沒辦法引用@angular/core

Leo iT邦新手 3 級 ‧ 2020-05-26 11:23:27 檢舉

Hi ch_lute,

沒辦法引用是什麼意思?

我們是在練習用範本來讓 Schematics 幫我們產生出檔案噢,而且這裡還沒教怎麼在正常的專案中使用我們做的 Schematics ,所以你看到的錯誤是什麼呢?

ch_lute iT邦新手 5 級 ‧ 2020-05-27 14:24:36 檢舉

Leo
文章最上面

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent  {
  @Input() name: string;
}

新增這段ts的時候,'@angular/core'是紅色底線
'找不到模組 '@angular/core'。ts(2307)'

Leo iT邦新手 3 級 ‧ 2020-05-28 17:10:51 檢舉

Hi ch_lute,

這是當然的,因為這個範本檔是在 Angular Schematics 裡面,不能當做真正的 Angular 檔案來使用

0
紅棗
iT邦新手 4 級 ‧ 2020-09-10 19:11:33

大大好,剛好看到範例有筆誤。
dasherize('Leo Chen'); // 'leo_chen'

Leo iT邦新手 3 級 ‧ 2020-09-11 15:15:16 檢舉

Hi 紅棗,

感謝你的鷹眼!!已更正囉!

我要留言

立即登入留言