Một số lời khuyên và một vài tip sử dụng CLI khi bạn làm việc với Angular

Phát triển Angular application với Angular CLI là một trải nghiệm rất thú vị! Nhóm phát triển Angular đã cung cấp cho chúng ta CLI tuyệt vời hỗ trợ hầu hết những thứ cần thiết cho bất kỳ dự án nào. Cấu trúc dự án được chuẩn hóa với các khả năng testing đầy đủ( cả unit và e2e testing),
code scaffolding, production grade build với sự hỗ trợ cho việc sử dụng cấu hình cụ thể của từng môi trường. Đó là một giấc mơ thành hiện thực và tiết kiệm rất nhiều thời gian cho mỗi dự án mới. Mặc dù Angular CLI hoạt động rất tốt ngay từ đầu, nhưng chúng ta cần biết và biết áp dụng và thực thực tiễn thì chúng ta mới có thể sử dụng chúng để làm cho các dự án của chúng ta tốt hơn nữa!

Tôi sẽ nói gì trong bài viết này?

  1. Cấu trúc tốt nhất cho Core, Shared and lazy-loaded Feature modules
  2. Sử dụng aliases cho các thư mục trong ứng dụng và môi trường để support cleaner imports
  3. Tại sao và làm thế nào để sử dụng Sass và Angular Material
  4. Làm thế nào để setup production build tốt
  5. Hãy tạm biệt PhantomJS và sử dụng Headless Chrome (testing)

1. Một chút kiến trúc

OK, vì chúng tôi đã tạo dự án mới của chúng tôi bằng Angular CLI nhưng bây giờ tiếp tục thì sao? Chúng ta có nên tiếp tục tạo các services và component của mình vào một số thư mục ngẫu nhiên. Làm thế nào để chúng tôi cấu trúc dự án của chúng tôi?

Một hướng dẫn tốt để tuân theo là chia ứng dụng của chúng tôi thành ít nhất ba mô-đun khác nhau Core, Shared and Feature (chúng tôi có lẽ sẽ cần nhiều hơn một Feature module)

CoreModule

Tất cả các service phải có một và chỉ một phiên bản cho mỗi ứng dụng (singleton services) nên được triển khai tại đây. Ví dụ điển hình có thể là authentication service hoặc user service. Hãy xem xét một ví dụ về CoreModuleviệc thực hiện.


/* 3rd party libraries */
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

/* our own custom services  */
import { SomeSingletonService } from './some-singleton/some-singleton.service';

@NgModule({
  imports: [
    /* 3rd party libraries */
    CommonModule,
    HttpClientModule,
  ],
  declarations: [],
  providers: [
    /* our own custom services  */
    SomeSingletonService
  ]
})
export class CoreModule {
  /* make sure CoreModule is imported only by one NgModule the AppModule */
  constructor (
    @Optional() @SkipSelf() parentModule: CoreModule
  ) {
    if (parentModule) {
      throw new Error('CoreModule is already loaded. Import only in AppModule');
    }
  }
}

SharedModule

Tất cả các “dumb” components and pipes nên được thực hiện ở đây. Các thành phần này không import và inject services từ core hoặc features trong các constructors của chúng. Chungs sẽ nhận được tất cả dữ liệu mặc dù các thuộc tính trong mẫu của thành phần sử dụng chúng. Tất cả điều này cho thấy thực tế là SharedModulekhông có bất kỳ sự phụ thuộc nào vào phần còn lại của ứng dụng của chúng tôi. Đây cũng là nơi hoàn hảo để import and re-export Angular Material components.

/* 3rd party libraries */
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule  } from '@angular/forms';
import { MdButtonModule } from '@angular/material';

/* our own custom components */
import { SomeCustomComponent } from './some-custom/some-custom.component';

@NgModule({
  imports: [
    /* angular stuff */
    CommonModule,
    FormsModule,

    /* 3rd party components */
    MdButtonModule,
  ],
  declarations: [
    SomeCustomComponent
  ],
  exports: [
    /* angular stuff */
    CommonModule,
    FormsModule,

    /* 3rd party components */
    MdButtonModule,

    /* our own custom components */
    SomeCustomComponent
  ]
})
export class SharedModule { }

Cách chuẩn bị cấu trúc dự án bằng Angular CLI

Chúng tôi có thể tạo các Core và Shared module ngay sau khi tạo dự án mới. Bằng cách đó, chúng tôi sẽ được chuẩn bị để tạo ra các components và service bổ sung ngay từ đầu. Run ng generate module core. Sau đó tạo index.ts file trong core folder và re-export CoreModulechính nó. Chúng tôi sẽ re-export các public service cần có trong toàn bộ ứng dụng trong quá trình phát triển hơn nữa. Điều đó đang được thực hiện, chúng ta có thể làm tương tự cho shared module.

FeatureModule

Chúng tôi sẽ tạo ra nhiều feature modules cho mọi tính năng độc lập của ứng dụng của chúng tôi. Các feature modules chỉ nên import services từ CoreModule. Nếu feature module A cần import service từ feature module B thì nên xem xét chuyển service đó vào core.

Trong một số trường hợp, nhu cầu về các service chỉ được chia sẻ bởi một số tính năng và sẽ không có ý nghĩa gì khi chuyển chúng vào core. Trong trường hợp đó, chúng ta có thể tạo các shared feature modules như được mô tả sau trong bài viết này.

Nguyên tắc chung là cố gắng tạo các tính năng không phụ thuộc vào bất kỳ tính năng nào khác chỉ trên các service được cung cấp bởi CoreModule và các component được cung cấp bởi SharedModule. Điều này sẽ giữ cho code của chúng ta luôn sạch sẽ, dễ bảo trì và mở rộng với các tính năng mới. Nó cũng làm giảm nỗ lực cần thiết cho tái cấu trúc. Nếu được tuân thủ đúng, chúng tôi sẽ tự tin rằng các thay đổi đối với một tính năng không thể ảnh hưởng hoặc phá vỡ phần còn lại của ứng dụng của chúng tôi.

LazyLoading

Chúng ta nên lazy load các feature module của chúng tôi bất cứ khi nào có thể. Về mặt lý thuyết chỉ có một feature module nên được tải đồng bộ trong quá trình khởi động ứng dụng để hiển thị nội dung ban đầu. Mỗi feature module khác nên được lazy loading sau khi người dùng triggered navigation.

2. Aliases cho app và environments

Bí danh folder ứng dụng và môi trường của chúng tôi sẽ cho phép chúng tôi thực hiện import clear và nhất quán trong toàn bộ ứng dụng của chúng tôi. Xem xét giả thuyết, nhưng tình hình thông thường. Chúng tôi đang làm việc trên một component nằm sâu ba thư mục trong một tính năng A và chúng tôi muốn import service từ lõi nằm sâu hai thư mục. Điều này sẽ dẫn import statement trông giống như import { SomeService } from '../../../core/subpackage1/subpackage2/some.service'.

Definitely not the cleanest import ever…

Và điều tồi tệ hơn nữa, bất cứ khi nào chúng tôi muốn thay đổi vị trí của bất kỳ hai tệp nào trong số đó, import statement của chúng tôi sẽ bị hỏng. So sánh điều đó với ngắn hơn nhiều import { SomeService } from "@app/core". Có vẻ tốt hơn phải không?

Để có thể sử dụng các alias, chúng tôi phải thêm baseUrlvà pathscác thuộc tính vào tsconfig.jsontệp của chúng tôi như thế này.

{
  "compilerOptions": {
    "...": "reduced for brevity",
    
    "baseUrl": "src",
    "paths": {
      "@app/*": ["app/*"],
      "@env/*": ["environments/*"]
    }
  }
}

Chúng tôi cũng đang thêm alias @env để có thể dễ dàng truy cập các biến môi trường từ bất kỳ đâu trong ứng dụng của chúng tôi bằng cách sử dụng cùng một import statement { environment } from "@env/environment". Nó sẽ hoạt động cho tất cả các môi trường được chỉ định bởi vì nó sẽ tự động giải quyết file môi trường chính xác dựa trên cờ --env được truyền cho lệnh ng build . Với các đường dẫn của chúng tôi, giờ đây chúng tôi có thể import env và service như thế này:


/* 3rd party libraries */
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

/* globally accessible app code (in every feature module) */
import { SomeSingletonService } from '@app/core';
import { environment } from '@env/environment';

/* localy accessible feature module code, always use relative path */
import { ExampleService } from './example.service';

@Component({
  /* ... */
})
export class ExampleComponent implements OnInit {
  constructor(
    private someSingletonService: SomeSingletonService,
    private exampleService: ExampleService
  ) {}
}

Bạn có thể nhận thấy rằng chúng tôi đang nhập các thực thể (như SomeSingletonServicetrong ví dụ trên) trực tiếp từ @app/core thay vì @app/core/some-package/some-singleton.service. Điều này có thể là nhờ re-exporting mọi public entity trong main index.ts file . Chúng tôi tạo một file index.ts cho package (folder) và chúng trông giống như thế này:

export * from './core.module';
export * from './auth/auth.service';
export * from './user/user.service';
export * from './some-singleton-service/some-singleton.service';

Trong hầu hết các ứng dụng, các component và service của feature module cụ thể thường sẽ chỉ phải có quyền truy cập vào các service từ CoreModule và các thành phần từ SharedModule. Đôi khi điều này có thể không đủ để giải quyết các business cụ thể và chúng tôi cũng sẽ cần một số loại “shared feature module” , mà chúng cung cấp chức năng cho một tập hợp con giới hạn của các feature modules khác.

Trong trường hợp này, chúng tôi sẽ kết thúc với một cái gì đó như import { SomeService } from '@app/shared-feature'; Tương tự core, shared-feature cũng được truy cập bằng @app alias.

3. Sử dụng Sass

Sass là một bộ tiền xử lý kiểu mang đến sự hỗ trợ cho những thứ ưa thích như các biến (mặc dù css cũng sẽ sớm nhận được các biến), các function, mixins,... Sass cũng được yêu cầu sử dụng hiệu quả thư viện Angular Material Components chính thức với extensive theming capabilities. Sẽ an toàn khi cho rằng sử dụng Sass, đó là lựa chọn mặc định cho hầu hết các dự án. Để sử dụng Sass, chúng ta phải tạo dự án của mình bằng lệnh ng new tron Angular CLI với flag --style scss . Điều này thiết lập hầu hết các cấu hình cần thiết. Một điều không được thêm vào theo mặc định là stylePreprocessorOptions với includePaths và chúng ta có thể tự thiết lập nó với các giá trị gốc "./" và tùy chọn không bắt buộc "./themes".

{
  "apps": [
    {
      "...": "reduced for brevity",
      
      "stylePreprocessorOptions": {
        "includePaths": ["./", "./themes"]
      }
    }
  ]
}

Điều này editor của chúng tôi tìm các imported symbol và nâng cao trải nghiệm của developer với việc completion code các biến và function của Angular Material.

4. “PROD” build

Dự án được tạo bởi Angular CLI chỉ đi kèm với một ng build script rất đơn giản . Để tạo ra các production grade artifacts , chúng tôi phải tự thực hiện một chút tùy chỉnh. Chúng tôi thêm "build:prod": "ng build --target production --build-optimizer --vendor-chunk" vào script package.json của chúng tôi.

Target Production

Đây là một flag cho phép thu nhỏ code của bạn. Cùng với nó sẽ có rất nhiều cờ khác đi cùng, nhiều useful build flags thường đã đi kèm khi với giá trị mặc định khi ta chạy command. Ví dụ như:

  • --environment prod - sử dụng environment.prod.ts file cho các biến môi trường .
  • --aot - cho phép Ahead-of-Time compilation. Điều này sẽ trở thành một thiết lập mặc định trong các phiên bản tương lai của Angular CLI nhưng ở phiên bản Angular 6 chúng tôi phải kích hoạt thủ công tùy chọn này.
  • --output-hashing all - hash contents của tệp được tạo và nối hash vào tên tập để tạo điều kiện cho bộ đệm ẩn của trình duyệt(mọi thay đổi đối với nội dung tệp sẽ dẫn đến hàm băm khác nhau và do đó trình duyệt buộc phải tải phiên bản mới của tệp)
  • --extract-css true trích xuất tất cả các css vào style-sheet file riêng
  • --sourcemaps false vô hiệu hóa việc tạo source maps
  • --named-chunks false vô hiệu hóa sử dụng human readable names cho chunk và sử dụng số để thay thế

Các flag hữu ích khác

  • --build-optimizer tính năng mới dẫn đến các bundle nhỏ hơn nhưng thời gian xây dựng lâu hơn nhiều vì vậy hãy thận trọng khi sử dụng! (cũng nên được bật theo mặc định trong tương lai)
  • --vendor-chunk - trích xuất tất cả endor (library) code thành chunk riêng Đồng thời kiểm official docs cho các configuration flags có sẵn khác có thể hữu ích trong dự án cá nhân của bạn.

5. Phantom JS đã lỗi thời! Headless Chrome sẽ là lựa chọn thay thế!

PhantomJS là một headless browser để chạy thử nghiệm frontend trên máy chủ CI và nhiều máy dev. Mặc dù vẫn ổn, nhưng sự hỗ trợ cho các tính năng ECMAScript hiện đại đã bị chậm trễ. Hơn nữa, hành vi không chuẩn của nó gây ra đau đầu trong nhiều trường hợp, khi các các được thực hiện tại local mà không gặp vấn đề gì nhưng chúng vẫn lỗi trên môi trường CI. May mắn thay, chúng ta không phải đối phó với nó nữa! Như tài liệu chính thức nói:

Headless Chrome is shipping in Chrome 59. It’s a way to run the Chrome browser in a headless environment. Essentially, running Chrome without chrome! It brings all modern web platform features provided by Chromium and the Blink rendering engine to the command line.

Tuyệt quá! Vậy làm thế nào chúng ta có thể sử dụng nó trong dự án Angular CLI của chúng tôi? Chúng tôi thêm flag --browser ChromeHeadless vài lên ng test để sử dụng: ng test --browser ChromeHeadless --single-runng test --browser ChromeHeadless trong file package.json trong dự án của chúng tôi, khá đơn giản. Phải không?

Thêm nữa

Với ai sử dụng (Intellij IDEA, Webstorm) Intellij IDEA sẽ không luôn luôn tìm thấy tất cả các đường dẫn theo mặc định, điều này sẽ dẫn đến nhiều dấu lỗi màu đỏ và crippled code completion bị tê liệt. May mắn thay, giải pháp rất đơn giản. Chỉ cần chọn thư mục src và đánh dấu nó là Sources Root.

Tổng kết

Bài viết này tôi đã nêu lên một số điều bạn nên thực hiện nó trong một dự án angular, cũng như tìm hiểu thêm một flag hữu ích khi sử dụng angular CLI, mong rằng các bạn có thể áp dụng được nó cho dự án của mình. Cảm ơn bạn đã đọc bài viết này.

Nguồn tham khảo: https://medium.com/@tomastrajan/6-best-practices-pro-tips-for-angular-cli-better-developer-experience-7b328bc9db81