Tạo template riêng cho Angular CLI bằng Schematics (phần 1)

Angular CLI là một công cụ generator tuyệt vời giúp rút ngắn thời gian scaffold ứng dụng và các thành phần bên trong một ứng dụng Angular. Tuy nhiên, sẽ còn tuyệt vời hơn nữa nếu chúng ta có thể tự tạo cho mình một khung có đầy đủ các thư viện hay dùng, có thể tạo ra các component có sẵn html và css, tạo ra service giao tiếp với api có sẵn các phương thức CRUD .... Trong series bài viết này, mình sẽ hướng dẫn các bạn sử dụng một công cụ tuyệt vời trong bộ angular-devkit có tên Schematics giúp bạn làm được điều đó.

Ở phần 1 này sẽ giúp các bạn nắm được nguyên lý hoạt động của Schematics. Phần 2: https://viblo.asia/p/tao-template-rieng-cho-angular-cli-bang-schematics-phan-2-XL6lAPAgZek

Schematics

Từ version 1.4, team phát triển Angular đã bổ sung tùy chọn --collection cho các câu lệnh như ng new, ng generate của CLI cho phép bạn tùy chọn custom collection thay vì sử dụng các collection mặc định.

Ẩn sau tùy chọn này là việc CLI bắt đầu sử dụng một thư viện có tên Schematics, một thư viện generator giúp tạo ra skeleton, component, service ... nhưng điểm đáng giá là:

  • công cụ này cho phép bạn tạo ra generator của riêng bạn, bắt CLI generate code theo custom collection thay vì sử dụng template có sẵn với các tùy chọn hạn chế
  • collection có thể đóng gói thành NPM package tiện lợi cho việc chia sẻ

Cơ chế làm việc của Schematics

Schematics sẽ dựa trên một collection để generate ra code (như application, component, service, ...). Một collection gồm tập hợp nhiểu schema, mỗi schematic sẽ hướng dẫn cho Schematics thực hiện việc generate ra các template riêng. Nếu các bạn đã cài đặt Angular CLI bản 1.4.2 trở lên, các bạn có thể mở đường dẫn sau để xem collection mặc định có tên angular: Trên Mac và Linux: /usr/local/lib/node_modules/@angular/cli/node_modules/@schematics/angular Trên Windows 10: %AppData%\Roaming\npm\node_modules\@angular\cli\node_modules\@schematics\angular Nếu không tìm thấy các bạn có thể dùng câu lệnh: npm root -g để tìm ra đường dẫn thư mục node_modules, sau đó trỏ tiếp đến /@angular/cli/node_modules/@schematics/angular.

Thư mục cha angular được gọi là một collection. Mỗi thư mục như application, class, component ... như trong hình được gọi là một schematic.

Cấu trúc collection và schematic

Collection

Collection cần ít nhất một file collection.json giúp định nghĩa các schematic có mặt trong collection này, ví dụ file collection.json của collection angular:

{
  "schematics": {
    "class": {
      "aliases": [ "cl" ],
      "factory": "./class",
      "description": "Create a class.",
      "schema": "./class/schema.json"
    }
    ...
  }
}
  • factory: trỏ đến đến file chứa factory function (sẽ nói chi tiết ở phần schema). Trong trường hợp này đường dẫn là thư mục vì vậy chúng ta có thể ngầm hiểu factory function nằm trong file index.ts bên trong thư mục đó.
  • schema: trỏ đến file schema.json
  • aliases: tên thay thế cho tên mặc định, ví dụ ng generate class a-class-name có thể rút ngắn thành ng generate cl a-class-name.

Schematic

schema.json

Giống collection, schematic cũng cần một file định nghĩa tên schema.json:

{
  "$schema": "http://json-schema.org/schema",
  "id": "SchematicsAngularClass",
  "title": "Angular Class Options Schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "appRoot": {
      "type": "string"
    },
    "path": {
      "type": "string",
      "default": "app"
    },
    "sourceDir": {
      "type": "string",
      "default": "src"
    },
    "spec": {
      "type": "boolean",
      "description": "Specifies if a spec file is generated.",
      "default": false
    },
    "type": {
      "type": "string",
      "description": "Specifies the type of class.",
      "default": ""
    }
  },
  "required": [
    "name"
  ]
}

Nhìn vào tên các thuộc tính các bạn cũng có thể đoán ra nhiệm vụ của chúng rồi. Đáng chú ý nhất là thuộc tính properties. Tại đây chúng ta sẽ định nghĩa các giá trị đầu vào khi generate.

Factory function:

Đây là bộ não của một schematic. Tại đây bạn sẽ viết logic việc tạo ra các đầu file cũng như định nghĩa các phương thức hoặc sử dụng các phương thức hỗ trợ trong bộ devkit:

import { Rule } from '@angular-devkit/schematics';
import { Schema as ClassOptions } from './schema';
export default function (options: ClassOptions): Rule;

Trong ví dụ này, việc generate ra class sẽ dựa theo rule chung trong bộ schematics của angular devkit. Chúng ta sẽ đi sâu hơn vào factory function này trong phần 2.

  • schema.ts
export interface Schema {
    path?: string;
    appRoot?: string;
    sourceDir?: string;
    name: string;
    /**
     * Specifies if the style will be in the ts file.
     */
    inlineStyle?: boolean;
    ...
}

Đây là interface dùng để check kiểu cho các tùy chọn khi khởi tạo Class schema.

Template

Các bạn có thể nhìn thấy thư mục files trong cấu trúc một schematic, thư mục này sẽ chứa schematic template. Dưới đây là ví dụ về schematic template của Angular Component:

  • Tên directory/file Các bạn có thể nhận thấy ngay cả tên file và tên thư mục cũng ở dạng template. Ví dụ __path__ sẽ được thay bằng giá trị bạn truyền vào khi generate component bằng tùy chọn --path đã được định nghĩa trong schema.json.

Trong tên file template các bạn có thể để ý chuỗi @dasherize, đây chính là tên một hàm helper trong @angular-devkit/core giúp biến đổi parametter truyền vào thành dạng kebab-case. Ví dụ khi bạn khởi tạo component ng g c UserList, UserList sẽ được convert thành user-list và tên file sẽ là user-list.component.ts.

  • Nội dung một file template: Ở đây mình sẽ lấy ví dụ template tạo ra file .component.ts
import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';

@Component({
  selector: '<%= selector %>',<% if(inlineTemplate) { %>
  template: `
    <p>
      <%= dasherize(name) %> works!
    </p>
  `,<% } else { %>
  templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
  styles: []<% } else { %>
  styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
  encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
  changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= classify(name) %>Component implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

Các bạn có thể để ý cú pháp của template khá giống với các template phổ thông như EJS hay Blade với cặp <% %> và các câu lệnh điểu khiển như if else.

Tạm kết

Đến đây các bạn đã có thể nắm được cấu trúc của Schematics collection phục vụ cho việc tạo custom template Angular. Ở phần tiếp theo mình sẽ hướng dẫn các bạn tạo một collection từ đầu, đóng gói và sử dụng với Angular CLI.