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

Angular Schematics CLI

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 hay tạo ra service giao tiếp với api có sẵn các phương thức CRUD ...

Trong phần 1, chúng ta đã tìm hiểu về Schematics, cấu trúc của một collection và schematic và cách Schematics làm việc.

Trong phần này, chúng ta sẽ tìm hiểu cách tạo ra collection có thể đóng gói thành npm package, tạo một schematic và sử dụng collection với Angular CLI. Cụ thể chúng ta sẽ đi qua 2 ví dụ: Ví dụ #1. Tạo một collection mang tên bootstrap, tạo một schematic mang tên modal, giúp chúng ta có thể tạo một component có sẵn code modal bootstrap bằng câu lệnh ng g modal dumb-modal --collection my-bootstrap-collection (source code: https://github.com/giathinh910/example-angular-schematic-collections). Ví dụ #2. Kế thừa angular collection, thêm một số thư viện cần thiết cho schematic application có sẵn của angular collection

Cài đặt các công cụ cần thiết

Đầu tiên chúng ta cần cài đặt các gói sau globally

  • @angular-devkit/core: Chứa các phương thức hỗ trợ như classify, dasherize, normalize
  • @angular-devkit/schematics: Công cụ chúng ta nói suốt từ phần 1 đến giờ
  • @schematics/schematics: Một collection dùng cho việc scaffolding nên ... collection :D
  • rxjs

Các bạn có thể dùng câu lệnh: npm i -g @angular-devkit/core @angular-devkit/schematics @schematics/schematics rxjs

Ví dụ số #1:

Tạo một collection mang tên bootstrap, tạo một schematic mang tên modal, giúp chúng ta có thể tạo một component có sẵn code modal bootstrap bằng câu lệnh ng g modal dumb-modal --collection my-bootstrap-collection

Bước 1. Khởi tạo một collection

Chúng ta dùng câu lệnh sau để khởi tạo một collection: schematics @schematics/schematics:schematic --name my-bootstrap Ý nghĩa: @schematics/schematics là collection mà chúng ta đã cài ở câu lệnh trên, :schematic là tên schema và cuối cùng khá dễ hiểu --name my-bootstrap là tên chúng ta đặt cho collection. Lúc này chúng ta có một thư mục collection với cấu trúc sau:

9ba5ca31-2b79-40a5-944a-dc11152807c9.jpg

Để hiểu rõ về cấu trúc collection và schemaic, các bạn có thể đọc lại phần trước.

Bước 2. Cấu hình collection và schematic

Vì chúng ta sẽ chỉ có một schematic là modal, mình sẽ xóa bớt hai thư mục và đổi tên thư mục còn lại thành modal:

f4a869a9-951c-4afc-8560-30cf4803e595.jpg

Để cấu hình collection, chúng ta sẽ sửa file collection.json để trỏ đến schematic modal:

{
  "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "modal": {
      "description": "A schematic to generate a fully working modal module",
      "factory": "./modal",
      "schema": "./modal/schema.json"
    }
  }
}

Tiếp đến chúng ta cấu hình cho schematic. Để cấu hình cho schematic, chúng ta cập nhật file schema.json trong thư mục modal:

{
  "$schema": "http://json-schema.org/schema",
  "id": "Modal Ready Component",
  "title": "Modal ready component",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "styleExt": {
      "type": "string",
      "default": "css"
    },
    "appRoot": {
      "type": "string"
    },
    "path": {
      "type": "string",
      "default": "app"
    },
    "sourceDir": {
      "type": "string",
      "default": "src"
    }
  },
  "required": [
    "name"
  ]
}

Ở đây mình sẽ cần 4 tùy chọn tương ứng với 4 giá trị: tên schematic, kiểu file style (css/scss/...), thư mục gốc của component sau khi tạo, đường dẫn khởi tạo, thư mục src. Trong đó sẽ chỉ có name là bắt buộc.

Tương đương với các tùy chọn bên trên, chúng ta sẽ tạo 1 file tên schema.ts là 1 interface xác định lại các thuộc tính trên, tránh việc bỏ sót:

export interface ModalOptions {
    name: string;
    styleExt: string;
    appRoot: string;
    path: string;
    sourceDir: string;
}

Tiếp theo - một file quan trọng - là file index.ts, tại đây chúng ta sẽ viết Factory function, như mình đã đề cập phần trước, factory function đầu não của một schematic:

import { classify, dasherize, normalize } from '@angular-devkit/core';
import { apply, branchAndMerge, chain, mergeWith, move, Rule, template, url } from '@angular-devkit/schematics';
import { ModalOptions } from './schema';

const stringUtils = { dasherize, classify }; // 2 helper method giúp chúng ta xử lý chuỗi tên file và tên class

// factory function bắt đầu
export default function (options: ModalOptions): Rule {
    options.path = options.path ? normalize(options.path) : options.path;

    // hàm apply chỉ định rule này sẽ áp dụng cho các file trong đường dẫn `./files`
    const templateSource = apply(url('./files'), [
    // truyền stringUtils và tùy chọn modal giúp chúng ta có thể sử dụng chúng trong các file template
      template({
        ...stringUtils,
        ...options
      }),
      move(options.sourceDir) // chuyển hết các file vào thư mục trong tùy chọn sourceDir
    ]);

    // cuối cùng chúng ta hợp nhất template rule ở trên và trả về bằng cách sử dụng hàm chain
    return chain([
      branchAndMerge(chain([
        mergeWith(templateSource)
      ])),
    ]);
}

Bước 3: Cấu hình cho các template

Bên trong thư mục files, các bạn tạo thư mục và file theo hình sau:

01a79f0d-7634-4c1a-82c4-e7fab1962155.jpg

Về ý nghĩa của __path__ hay __styleExt__, đó chính là các biến tùy chọn chúng ta sẽ truyền vào lúc khởi tạo component, mình đã đề cập trong phần 1. Có một chút đặc biệt đó là biến [email protected]__, các bạn có thể tưởng tượng @dasherize có nhiệm vụ giống như filter hoặc pipe trong AngularJS hoặc Angular, và nếu bạn để ý hàm này được truyền vào trong factory function. Trong trường hợp này dasherize làm nhiệm vụ xử lý name thành dạng kebab-case. Bạn hoàn toàn có thể chainning nhiều hàm helper này, ví dụ:[email protected]@if-flat__.

Trong các file template, các bạn có thể dùng các options như các biến và các hàm helper đã được truyền vào từ factory fucntion. Các bạn tiến hành update các file template như sau:

[email protected]__.component.__styleExt__ :

<% if(styleExt == 'css') { %>
/* Custom css goes here */
<% } else { %>
/* Custom <%= styleExt %> goes here */
<% } %>

[email protected]__.component.html :

<div class="modal fade" id="{{modalId}}" tabindex="-1" role="dialog"
aria-labelledby="{{modalId}}lLabel" aria-hidden="true">
<div class="modal-dialog modal-{{modal.size}}" role="document">
   <div class="modal-content">
       <div class="modal-header">
           <h5 class="modal-title" id="{{modalId}}lLabel">{{modal.title}}</h5>
           <button (click)="hideModal(true)" type="button" class="close" aria-label="Close">
               <span aria-hidden="true">×</span>
           </button>
       </div>
       <div class="modal-body">
           {{modal.body}}
       </div>
       <div class="modal-footer">
           <button (click)="hideModal(false)" type="button" class="btn btn-secondary">Cancel
           </button>
           <button (click)="hideModal(true)" type="button" class="btn btn-default">Ok
           </button>
       </div>
   </div>
</div>
</div>

[email protected]__.component.ts :

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

declare const $: any;

@Component({
  selector: '<%= dasherize(name) %>',
  templateUrl: './<%= dasherize(name) %>.component.html',
  styleUrls: ['./<%= dasherize(name) %>.component.<%= styleExt %>']
})
export class <%= classify(name) %>Component implements OnInit {
  modalId = 'modal' + Date.now();
  modal: any = {
    size: '',
    title: '',
    body: ''
  };

  constructor() {
  }

  ngOnInit() {
    this.showModal();
  }

  showModal(size = 'md', title = 'Modal Title', body = 'Modal body goes here') {
    this.modal.title = title;
    this.modal.message = body;
    this.modal.size = size;

    $(`#${this.modalId}`).modal({
        show: true,
        backdrop: 'static'
    });
  }

  hideModal() {
    $(`#${this.modalId}`).modal('hide').on('hidden.bs.modal', () => {
        this.modal = {
            size: '',
            title: '',
            body: ''
        };
    });
  }
}

Cấu trúc của template cũng khá dễ đọc nên mình sẽ ko giải thích thêm.

Bước 4: Build collection và dùng thử

Ok rồi. Giờ chúng ta sẽ build collection này và thử dùng collection này với angular app. Đầu tiên chúng ta sẽ khởi tạo một project angular với lệnh ng new example-angular-app. Các bạn có thể đặt ngang cấp với my-bootstrap collection. Chúng ta có 2 thư mục như sau:

| --- example-angular-app
| --- my-bootstrap

Tại thư mục my-modal, các bạn nhớ cài các npm packages trước bằng lệnh npm i. Tiếp theo các bạn chạy lệnh npm run build. Sau đó copy thư mục my-modal vào ./example-angular-app/node_modules/:

cp -R example-bootstrap-schematic-collections/ example-angular-app/node_modules/my-bootstrap

Thư mục node_modules trong ./example-angular-app/node_modules/my-modal sau khi copy cũng cần xóa bỏ.

rm -rf example-angular-app/node_modules/my-bootstrap/node_modules/

Giờ là đến bước chúng ta mong đợi nhất, dùng thử collection này. Các bạn di chuyển vào trong ./example-angular-app và chạy thử lệnh:

ng g modal default-modal --collection my-bootstrap

Và đây là thành quả:

197b50fd-2042-4fe2-a956-22c42bf9c388.jpg

Chúng ta gen ra đc một modal component có sẵn 2 phương thức showModal() và hideModal() sử dụng css mặc định. Chúng ta sẽ thử sử dụng tùy chọn --styleExt để gen ra một modal khác nhưng sử dụng scss nhé. Các bạn chạy lệnh:

ng g modal modal-using-scss --styleExt scss --collection my-bootstrap

d6e7428c-c1c9-4619-91bf-f37a16db4a2d.jpg

Chúng ta đã có thêm một modal component khác nhưng sử dụng scss.

Ví dụ số #2:

Kế thừa angular collection, thêm một số thư viện cần thiết cho schematic application có sẵn của angular collection

Như các bạn thấy chúng ta hoàn toàn có thể cấu hình một collection từ đầu nhưng rõ ràng sẽ tốn rất nhiều công sức. Thay vì thế, chúng ta có thể mở rộng chính angular collection để đáp ứng nhu cầu cá nhân.

Chỉnh sửa application schematic

Đầu tiên các bạn lấy angular collection tại địa chỉ https://github.com/angular/devkit/tree/master/packages/schematics/angular hoặc có thể copy collection này trong máy bạn (tham khảo phần 1).

Cụ thể trong ví dụ này, mục tiêu của chúng ta là sử dụng câu lệnh ng new để tạo ra một project xương sống hỗ trợ sẵn Bootstrap 4 và một số thư viện thường dùng. Vì thế trong collection angular chúng ta sẽ tiến hành sửa đổi schematic có tên application.

120d0ab6-3d14-4926-90b4-2d5b259dc713.jpg

Trong application/files/package.json, chúng ta sẽ thêm các packages cần thiết cho Bootstrap 4 vào dependencies:

"dependencies": {
    ...
    "bootstrap": "^4.0.0-beta.2",
    "jquery": "^3.2.1",
    "popper.js": "^1.12.6",
    "tether": "^1.4.0",
  }

Do thói quen của từng đội phát triển, mọi người chắc cũng luôn bỏ túi một bộ styling tủ. Mình cũng sẽ hỗ trợ sẵn scss cho application. Chúng ta sẽ thêm thư mục scss vào đường dẫn ./files/__path__/ để chứa bộ style mặc định:

63ab185d-d1da-41dc-9780-8cbc12d2618f.jpg

Link các tài nguyên chúng ta vừa thêm thông qua .angular-cli.json:

...
"styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.css",
    "styles.<%= style %>"
],
"scripts": [
    "../node_modules/jquery/dist/jquery.js",
    "../node_modules/popper.js/dist/umd/popper.js",
    "../node_modules/tether/dist/js/tether.js",
    "../node_modules/bootstrap/dist/js/bootstrap.js"
],
...

Và cuối cùng cập nhật field _location trong file package.json của angular collection thành:

"_location": "/@angular/cli/@custom/my-angular",

Chạy thử với ng new

Copy thư mục angular vào cạnh collection @schematic mặc định trong /usr/local/lib/node_modules/@angular/cli/node_modules/ và đặt tên là my-angular:

cp -R angular/ /usr/local/lib/node_modules/@angular/cli/node_modules/@custom/my-angular

Ok rồi, giờ các bạn có thể chạy thử lệnh:

ng new my-custom-angular-app [email protected]/my-angular

Các bạn có thể tự mình kiểm tra app vừa đc tạo.

Lời kết

Mặc dù Schematics vẫn đang ở giai đoạn sơ khai, chưa có documentation và rất ít bài viết đề cập sâu đến nó. Tuy nhiên, qua 2 bài viết, mình hy vọng các bạn có thể nắm được các kiến thức cần thiết về schematics sẵn sàng ứng dụng Schematics trong tương lai gần.

Previous Post Next Post