填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

風格指南

Angular coding style guide

如果你正在尋找關於 Angular 語法、約定和應用組織結構的官方指南,那你就來對了。 本風格指南介紹了提倡的約定,更重要的是,解釋了為什麼。

Looking for an opinionated guide to Angular syntax, conventions, and application structure? Step right in! This style guide presents preferred conventions and, as importantly, explains why.

風格指南的用詞

Style vocabulary

每個指導原則都會描述好的或者壞的做法,所有指導原則都用同樣的風格描述。

Each guideline describes either a good or bad practice, and all have a consistent presentation.

指導原則中使用的詞彙用來表明推薦的程度。

The wording of each guideline indicates how strong the recommendation is.

堅持意味著總是應該遵循的約定。 說"總是"可能顯得有點絕對,應該"總是"遵循的指導原則非常少,不過,只有遇到極不尋常的情況才能打破堅持的原則。

Do is one that should always be followed. Always might be a bit too strong of a word. Guidelines that literally should always be followed are extremely rare. On the other hand, you need a really unusual case for breaking a Do guideline.

考慮表示通常應該遵循的指導原則。 如果你能完全理解指導原則背後的含義,並且有很好的理由違反它,那就改吧。但是請保持一致。

Consider guidelines should generally be followed. If you fully understand the meaning behind the guideline and have a good reason to deviate, then do so. Please strive to be consistent.

避免表示你絕對不應該做的事。需要避免的程式碼範例會有明顯的紅色標題。

Avoid indicates something you should almost never do. Code examples to avoid have an unmistakable red header.

為何?會給出隨後的建議的理由。

Why? gives reasons for following the previous recommendations.

檔案結構約定

File structure conventions

在一些程式碼例子中,有的檔案有一個或多個相似名字的配套檔案。(例如 hero.component.ts 和 hero.component.html)。

Some code examples display a file that has one or more similarly named companion files. For example, hero.component.ts and hero.component.html.

本指南將會使用像 hero.component.ts|html|css|spec 的簡寫來表示上面描述的多個檔案,目的是保持本指南的簡潔性,增加描述檔案結構時的可讀性。

The guideline uses the shortcut hero.component.ts|html|css|spec to represent those various files. Using this shortcut makes this guide's file structures easier to read and more terse.

單一職責

Single responsibility

對所有的元件、服務等等應用單一職責原則 (SRP)。這樣可以讓應用更乾淨、更易讀、更易維護、更易測試。

Apply the single responsibility principle (SRP) to all components, services, and other symbols. This helps make the app cleaner, easier to read and maintain, and more testable.

單一規則

Rule of One

風格 01-01

Style 01-01

堅持每個檔案只定義一樣東西(例如服務或元件)。

Do define one thing, such as a service or component, per file.

考慮把檔案大小限制在 400 行程式碼以內。

Consider limiting files to 400 lines of code.

為何?單元件檔案非常容易閱讀、維護,並能防止在版本控制系統裡與團隊衝突。

Why? One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control.

為何?單元件檔案可以防止一些隱蔽的程式缺陷,當把多個元件合寫在同一個檔案中時,可能造成共享變數、建立意外的閉套件,或者與依賴之間產生意外耦合等情況。

Why? One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

為何?單獨的元件通常是該檔案預設的匯出,可以用路由器實現按需載入。

Why? A single component can be the default export for its file which facilitates lazy loading with the router.

最關鍵的是,可以讓程式碼更加可複用、更容易閱讀,減少出錯的可能性。

The key is to make the code more reusable, easier to read, and less mistake prone.

下面的負面例子定義了 AppComponent,它來引導應用程式,定義了 Hero 模型物件,並從伺服器載入了英雄 ... 所有都在同一個檔案。不要這麼做

The following negative example defines the AppComponent, bootstraps the app, defines the Hero model object, and loads heroes from the server all in the same file. Don't do this.

app/heroes/hero.component.ts
      
      /* avoid */
import { Component, NgModule, OnInit } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

interface Hero {
  id: number;
  name: string;
}

@Component({
  selector: 'app-root',
  template: `
      <h1>{{title}}</h1>
      <pre>{{heroes | json}}</pre>
    `,
  styleUrls: ['app/app.component.css']
})
class AppComponent implements OnInit {
  title = 'Tour of Heroes';

  heroes: Hero[] = [];

  ngOnInit() {
    getHeroes().then(heroes => (this.heroes = heroes));
  }
}

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  exports: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);

const HEROES: Hero[] = [
  { id: 1, name: 'Bombasto' },
  { id: 2, name: 'Tornado' },
  { id: 3, name: 'Magneta' }
];

function getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES); // TODO: get hero data from the server;
}
    

最好將元件及其支撐部件重新分配到獨立的檔案。

It is a better practice to redistribute the component and its supporting classes into their own, dedicated files.

      
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
    

隨著應用程式的成長,本法則會變得越來越重要。

As the app grows, this rule becomes even more important.

回到頂部

Back to top

小函式

Small functions

風格 01-02

Style 01-02

堅持定義簡單函式

Do define small functions

考慮限制在 75 行之內。

Consider limiting to no more than 75 lines.

為何?簡單函式更易於測試,特別是當它們只做一件事,只為一個目的服務時。

Why? Small functions are easier to test, especially when they do one thing and serve one purpose.

為何?簡單函式促進程式碼複用。

Why? Small functions promote reuse.

為何?簡單函式更易於閱讀。

Why? Small functions are easier to read.

為何?簡單函式更易於維護。

Why? Small functions are easier to maintain.

為何?小函式可避免易在大函式中產生的隱蔽性錯誤,例如與外界共享變數、建立意外的閉包或與依賴之間產生意外耦合等。

Why? Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

回到頂部

Back to top

命名

Naming

命名約定對可維護性和可讀性非常重要。本指南為檔名和符號名推薦了一套命名約定。

Naming conventions are hugely important to maintainability and readability. This guide recommends naming conventions for the file name and the symbol name.

總體命名原則

General Naming Guidelines

風格 02-01

Style 02-01

堅持所有符號使用一致的命名規則。

Do use consistent names for all symbols.

堅持遵循同一個模式來描述符號的特性和型別。推薦的模式為 feature.type.ts

Do follow a pattern that describes the symbol's feature then its type. The recommended pattern is feature.type.ts.

為何?命名約定提供了一致的方式來查詢內容,讓你一眼就能找到。 專案的一致性是至關重要的。團隊內的一致性也很重要。整個公司的一致性會提供驚人的效率。

Why? Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

為何?命名約定幫助你更快得找到想找的程式碼,也更容易理解它。

Why? The naming conventions should simply help find desired code faster and make it easier to understand.

為何?目錄名和檔名應該清楚的傳遞它們的意圖。 例如,app/heroes/hero-list.component.ts 包含了一個用來管理英雄列表的元件。

Why? Names of folders and files should clearly convey their intent. For example, app/heroes/hero-list.component.ts may contain a component that manages a list of heroes.

回到頂部

Back to top

使用點和橫槓來分隔檔名

Separate file names with dots and dashes

風格 02-02

Style 02-02

堅持 在描述性名字中,用橫槓來分隔單詞。

Do use dashes to separate words in the descriptive name.

堅持使用點來分隔描述性名字和型別。

Do use dots to separate the descriptive name from the type.

堅持遵循先描述元件特性,再描述它的型別的模式,對所有元件使用一致的型別命名規則。推薦的模式為 feature.type.ts

Do use consistent type names for all components following a pattern that describes the component's feature then its type. A recommended pattern is feature.type.ts.

堅持使用慣用的字尾來描述型別,包括 *.service*.component*.pipe.module.directive。 必要時可以建立更多型別名,但必須注意,不要建立太多。

Do use conventional type names including .service, .component, .pipe, .module, and .directive. Invent additional type names if you must but take care not to create too many.

為何?型別名字提供一致的方式來快速的識別檔案中有什麼。

Why? Type names provide a consistent way to quickly identify what is in the file.

為何? 型別名可以讓你輕鬆利用編輯器或者 IDE 的模糊搜尋功能找到特定檔案型別。

Why? Type names make it easy to find a specific file type using an editor or IDE's fuzzy search techniques.

為何?.service 這樣的沒有簡寫過的型別名字,描述清楚,毫不含糊。 像 .srv, .svc, 和 .serv 這樣的簡寫可能令人困惑。

Why? Unabbreviated type names such as .service are descriptive and unambiguous. Abbreviations such as .srv, .svc, and .serv can be confusing.

為何?為自動化任務提供模式匹配。

Why? Type names provide pattern matching for any automated tasks.

回到頂部

Back to top

符號名與檔名

Symbols and file names

風格 02-03

Style 02-03

堅持為所有東西使用一致的命名約定,以它們所代表的東西命名。

Do use consistent names for all assets named after what they represent.

堅持使用大寫駝峰命名法來命名類別。

Do use upper camel case for class names.

堅持匹配符號名與它所在的檔名。

Do match the name of the symbol to the name of the file.

堅持在符號名後面追加約定的型別字尾(例如 ComponentDirectiveModulePipeService)。

Do append the symbol name with the conventional suffix (such as Component, Directive, Module, Pipe, or Service) for a thing of that type.

堅持在檔名後面追加約定的型別字尾(例如 .component.ts.directive.ts.module.ts.pipe.ts.service.ts)。

Do give the filename the conventional suffix (such as .component.ts, .directive.ts, .module.ts, .pipe.ts, or .service.ts) for a file of that type.

為何?遵循一致的約定可以快速識別和參考不同型別的資產。

Why? Consistent conventions make it easy to quickly identify and reference assets of different types.

符號名

Symbol Name

檔名

File Name

      
      @Component({ ... })
export class AppComponent { }
    

app.component.ts

      
      @Component({ ... })
export class HeroesComponent { }
    

heroes.component.ts

      
      @Component({ ... })
export class HeroListComponent { }
    

hero-list.component.ts

      
      @Component({ ... })
export class HeroDetailComponent { }
    

hero-detail.component.ts

      
      @Directive({ ... })
export class ValidationDirective { }
    

validation.directive.ts

      
      @NgModule({ ... })
export class AppModule
    

app.module.ts

      
      @Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform { }
    

init-caps.pipe.ts

      
      @Injectable()
export class UserProfileService { }
    

user-profile.service.ts

回到頂部

Back to top

服務名

Service names

風格 02-04

Style 02-04

堅持使用一致的規則命名服務,以它們的特性來命名。

Do use consistent names for all services named after their feature.

堅持為服務的類別名稱加上 Service 字尾。 例如,獲取資料或英雄列表的服務應該命名為 DataServiceHeroService

Do suffix a service class name with Service. For example, something that gets data or heroes should be called a DataService or a HeroService.

有些詞彙顯然就是服務,比如那些以“-er”字尾結尾的。比如把記日誌的服務命名為 Logger 就比 LoggerService 更好些。需要在你的專案中決定這種特例是否可以接受。 但無論如何,都要儘量保持一致。

A few terms are unambiguously services. They typically indicate agency by ending in "-er". You may prefer to name a service that logs messages Logger rather than LoggerService. Decide if this exception is agreeable in your project. As always, strive for consistency.

為何?提供一致的方式來快速識別和參考服務。

Why? Provides a consistent way to quickly identify and reference services.

為何?Logger 這樣的清楚的服務名不需要字尾。

Why? Clear service names such as Logger do not require a suffix.

為何?Credit 這樣的,服務名是名詞,需要一個字尾。當不能明顯分辨它是服務還是其它東西時,應該新增字尾。

Why? Service names such as Credit are nouns and require a suffix and should be named with a suffix when it is not obvious if it is a service or something else.

符號名

Symbol Name

檔名

File Name

      
      @Injectable()
export class HeroDataService { }
    

hero-data.service.ts

      
      @Injectable()
export class CreditService { }
    

credit.service.ts

      
      @Injectable()
export class Logger { }
    

logger.service.ts

回到頂部

Back to top

引導

Bootstrapping

風格 02-05

Style 02-05

堅持把應用的載入程式和平臺相關的邏輯放到名為 main.ts 的檔案裡。

Do put bootstrapping and platform logic for the app in a file named main.ts.

堅持在引導邏輯中包含錯誤處理程式碼。

Do include error handling in the bootstrapping logic.

避免把應用邏輯放在 main.ts 中,而應放在元件或服務裡。

Avoid putting app logic in main.ts. Instead, consider placing it in a component or service.

為何?應用的啟動邏輯遵循一致的約定。

Why? Follows a consistent convention for the startup logic of an app.

為何?這是從其它技術平臺借鑑的常用約定。

Why? Follows a familiar convention from other technology platforms.

main.ts
      
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(success => console.log(`Bootstrap success`))
  .catch(err => console.error(err));
    

回到頂部

Back to top

元件選擇器

Component selectors

風格 05-02

Style 05-02

堅持使用中線命名法(dashed-case)或叫烤串命名法(kebab-case)來命名元件的元素選擇器。

Do use dashed-case or kebab-case for naming the element selectors of components.

為何?讓元素名和自訂元素規範保持一致。

Why? Keeps the element names consistent with the specification for Custom Elements.

app/heroes/shared/hero-button/hero-button.component.ts
      
      /* avoid */

@Component({
  selector: 'tohHeroButton',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}
    
      
      @Component({
  selector: 'toh-hero-button',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}
    

回到頂部

Back to top

為元件新增自訂字首

Component custom prefix

風格 02-07

Style 02-07

堅持使用帶連字元的小寫元素選擇器值(例如 admin-users)。

Do use a hyphenated, lowercase element selector value; for example, admin-users.

堅持為元件選擇器新增自訂字首。 例如,toh 字首表示 Tour of Heroes(英雄之旅),而字首 `admin 表示管理特性區。

Do use a custom prefix for a component selector. For example, the prefix toh represents Tour of Heroes and the prefix admin represents an admin feature area.

堅持使用字首來識別特性區或者應用程式本身。

Do use a prefix that identifies the feature area or the app itself.

為何?防止與其它應用中的元件和原生 HTML 元素髮生命名衝突。

Why? Prevents element name collisions with components in other apps and with native HTML elements.

為何?更容易在其它應用中推廣和共享元件。

Why? Makes it easier to promote and share the component in other apps.

為何?元件在 DOM 中更容易被區分出來。

Why? Components are easy to identify in the DOM.

app/heroes/hero.component.ts
      
      /* avoid */

// HeroComponent is in the Tour of Heroes feature
@Component({
  selector: 'hero'
})
export class HeroComponent {}
    
app/users/users.component.ts
      
      /* avoid */

// UsersComponent is in an Admin feature
@Component({
  selector: 'users'
})
export class UsersComponent {}
    
app/heroes/hero.component.ts
      
      @Component({
  selector: 'toh-hero'
})
export class HeroComponent {}
    
app/users/users.component.ts
      
      @Component({
  selector: 'admin-users'
})
export class UsersComponent {}
    

回到頂部

Back to top

指令選擇器

Directive selectors

風格 02-06

Style 02-06

堅持使用小駝峰形式命名指令的選擇器。

Do Use lower camel case for naming the selectors of directives.

為何?可以讓指令中的屬性名與檢視中繫結的屬性名保持一致。

Why? Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.

為何? Angular 的 HTML 解析器是大小寫敏感的,可以識別小駝峰形式。

Why? The Angular HTML parser is case sensitive and recognizes lower camel case.

回到頂部

Back to top

為指令新增自訂字首

Directive custom prefix

風格 02-08

Style 02-08

堅持為指令的選擇器新增自訂字首(例如字首 toh 來自 Tour of Heroes)。

Do use a custom prefix for the selector of directives (e.g, the prefix toh from Tour of Heroes).

堅持用小駝峰形式拼寫非元素選擇器,除非該選擇器用於匹配原生 HTML 屬性。

Do spell non-element selectors in lower camel case unless the selector is meant to match a native HTML attribute.

Don't prefix a directive name with ng because that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose.

為何?防止名字衝突。

Why? Prevents name collisions.

為何?指令更加容易被識別。

Why? Directives are easily identified.

app/shared/validate.directive.ts
      
      /* avoid */

@Directive({
  selector: '[validate]'
})
export class ValidateDirective {}
    
app/shared/validate.directive.ts
      
      @Directive({
  selector: '[tohValidate]'
})
export class ValidateDirective {}
    

回到頂部

Back to top

管道名

Pipe names

風格 02-09

Style 02-09

堅持為所有管道使用一致的命名約定,用它們的特性來命名。 管道類別名稱應該使用 UpperCamelCase(類別名稱的通用約定),而相應的 name 字串應該使用 lowerCamelCasename 字串中不應該使用中線(“中線格式”或“烤串格式”)。

Do use consistent names for all pipes, named after their feature. The pipe class name should use UpperCamelCase (the general convention for class names), and the corresponding name string should use lowerCamelCase. The name string cannot use hyphens ("dash-case" or "kebab-case").

為何?提供一致的方式快速識別和參考管道。

Why? Provides a consistent way to quickly identify and reference pipes.

符號名

Symbol Name

檔名

File Name

      
      @Pipe({ name: 'ellipsis' })
export class EllipsisPipe implements PipeTransform { }
    

ellipsis.pipe.ts

      
      @Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform { }
    

init-caps.pipe.ts

回到頂部

Back to top

單元測試檔名

Unit test file names

風格 02-10

Style 02-10

堅持測試規格檔名與被測試元件檔名相同。

Do name test specification files the same as the component they test.

堅持測試規格檔名新增 .spec 字尾。

Do name test specification files with a suffix of .spec.

為何?提供一致的方式來快速識別測試。

Why? Provides a consistent way to quickly identify tests.

為何?提供一個與 karma 或者其它測試執行器相配的命名模式。

Why? Provides pattern matching for karma or other test runners.

測試型別

Test Type

檔名

File Names

元件

Components

heroes.component.spec.ts

hero-list.component.spec.ts

hero-detail.component.spec.ts

服務

Services

logger.service.spec.ts

hero.service.spec.ts

filter-text.service.spec.ts

管道

Pipes

ellipsis.pipe.spec.ts

init-caps.pipe.spec.ts

回到頂部

Back to top

端到端(E2E)測試的檔名

End-to-End (E2E) test file names

風格 02-11

Style 02-11

堅持端到端測試規格檔案和它們所測試的特性同名,新增 .e2e-spec 字尾。

Do name end-to-end test specification files after the feature they test with a suffix of .e2e-spec.

為何?提供一致的方式快速識別端到端測試檔案。

Why? Provides a consistent way to quickly identify end-to-end tests.

為何?提供一個與測試執行器和建構自動化匹配的模式。

Why? Provides pattern matching for test runners and build automation.

測試型別

Test Type

檔名

File Names

端到端測試

End-to-End Tests

app.e2e-spec.ts

heroes.e2e-spec.ts

回到頂部

Back to top

Angular NgModule 命名

Angular NgModule names

風格 02-12

Style 02-12

堅持為符號名新增 Module 字尾

Do append the symbol name with the suffix Module.

堅持為檔名新增 .module.ts 副檔名。

Do give the file name the .module.ts extension.

堅持用特性名和所在目錄命名模組。

Do name the module after the feature and folder it resides in.

為何?提供一致的方式來快速標識和參考模組。

Why? Provides a consistent way to quickly identify and reference modules.

為何?大駝峰命名法是一種命名約定,用來標識可用建構函式實例化的物件。

Why? Upper camel case is conventional for identifying objects that can be instantiated using a constructor.

為何?很容易就能看出這個模組是同名特性的根模組。

Why? Easily identifies the module as the root of the same named feature.

堅持RoutingModule 類別名稱新增 RoutingModule 字尾。

Do suffix a RoutingModule class name with RoutingModule.

堅持RoutingModule 的檔名新增 -routing.module.ts 字尾。

Do end the filename of a RoutingModule with -routing.module.ts.

為何?RoutingModule 是一種專門用來配置 Angular 路由器的模組。 “類別名稱和檔名保持一致”的約定使這些模組易於發現和驗證。

Why? A RoutingModule is a module dedicated exclusively to configuring the Angular router. A consistent class and file name convention make these modules easy to spot and verify.

符號名

Symbol Name

檔名

File Name

      
      @NgModule({ ... })
export class AppModule { }
    

app.module.ts

      
      @NgModule({ ... })
export class HeroesModule { }
    

heroes.module.ts

      
      @NgModule({ ... })
export class VillainsModule { }
    

villains.module.ts

      
      @NgModule({ ... })
export class AppRoutingModule { }
    

app-routing.module.ts

      
      @NgModule({ ... })
export class HeroesRoutingModule { }
    

heroes-routing.module.ts

回到頂部

Back to top

應用程式結構與 NgModule

Application structure and NgModules

準備一個近期實施方案和一個長期的願景。從零開始,但要考慮應用程式接下來的路往哪兒走。

Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the app is heading down the road.

所有應用程式的原始碼都放到名叫 src 的目錄裡。 所有特性區都在自己的資料夾中,帶有它們自己的 NgModule。

All of the app's code goes in a folder named src. All feature areas are in their own folder, with their own NgModule.

所有內容都遵循每個檔案一個特性的原則。每個元件、服務和管道都在自己的檔案裡。 所有第三方程式包儲存到其它目錄裡,而不是 src 目錄。 你不會修改它們,所以不希望它們弄亂你的應用程式。 使用本指南介紹的檔案命名約定。

All content is one asset per file. Each component, service, and pipe is in its own file. All third party vendor scripts are stored in another folder and not in the src folder. You didn't write them and you don't want them cluttering src. Use the naming conventions for files in this guide.

回到頂部

Back to top

LIFT

風格 04-01

Style 04-01

堅持組織應用的結構,力求:快速定位 (Locate) 程式碼、一眼識別 (Identify) 程式碼、 儘量保持扁平結構 (Flattest) 和嘗試 (Try) 遵循 DRY (Do Not Repeat Yourself, 不重複自己) 原則。

Do structure the app such that you can Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY.

堅持四項基本原則定義檔案結構,上面的原則是按重要順序排列的。

Do define the structure to follow these four basic guidelines, listed in order of importance.

為何?LIFT 提供了一致的結構,它具有擴充套件性強、模組化的特性。因為容易快速鎖定程式碼,提高了開發者的效率。 另外,檢查應用結構是否合理的方法是問問自己:我能快速開啟與此特性有關的所有檔案並開始工作嗎?

Why? LIFT provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. To confirm your intuition about a particular structure, ask: can I quickly open and start work in all of the related files for this feature?

回到頂部

Back to top

定位

Locate

風格 04-02

Style 04-02

堅持直觀、簡單和快速地定位程式碼。

Do make locating code intuitive, simple, and fast.

為何? 要想高效的工作,就必須能迅速找到檔案,特別是當不知道(或不記得)檔案時。 把相關的檔案一起放在一個直觀的位置可以節省時間。 富有描述性的目錄結構會讓你和後面的維護者眼前一亮。

Why? To work efficiently you must be able to find files quickly, especially when you do not know (or do not remember) the file names. Keeping related files near each other in an intuitive location saves time. A descriptive folder structure makes a world of difference to you and the people who come after you.

回到頂部

Back to top

識別

Identify

風格 04-03

Style 04-03

堅持命名檔案到這個程度:看到名字立刻知道它包含了什麼,代表了什麼。

Do name the file such that you instantly know what it contains and represents.

堅持檔名要具有說明性,確保檔案中只包含一個元件。

Do be descriptive with file names and keep the contents of the file to exactly one component.

避免建立包含多個元件、服務或者混合體的檔案。

Avoid files with multiple components, multiple services, or a mixture.

為何?花費更少的時間來查詢和琢磨程式碼,就會變得更有效率。 較長的檔名遠勝於較短卻容易混淆的縮寫名。

Why? Spend less time hunting and pecking for code, and become more efficient. Longer file names are far better than short-but-obscure abbreviated names.

當你有一組小型、緊密相關的特性時,違反一物一檔案的規則可能會更好, 這種情況下單一檔案可能會比多個檔案更容易發現和理解。注意這個例外。

It may be advantageous to deviate from the one-thing-per-file rule when you have a set of small, closely-related features that are better discovered and understood in a single file than as multiple files. Be wary of this loophole.

回到頂部

Back to top

扁平

Flat

風格 04-04

Style 04-04

堅持儘可能保持扁平的目錄結構。

Do keep a flat folder structure as long as possible.

考慮當同一目錄下達到 7 個或更多個檔案時建立子目錄。

Consider creating sub-folders when a folder reaches seven or more files.

考慮配置 IDE,以隱藏無關的檔案,例如產生出來的 .js 檔案和 .js.map 檔案等。

Consider configuring the IDE to hide distracting, irrelevant files such as generated .js and .js.map files.

為何?沒人想要在超過七層的目錄中查詢檔案。扁平的結構有利於搜尋。

Why? No one wants to search for a file through seven levels of folders. A flat structure is easy to scan.

另一方面,心理學家們相信, 當關注的事物超過 9 個時,人類就會開始感到吃力。 所以,當一個資料夾中的檔案有 10 個或更多個檔案時,可能就是建立子目錄的時候了。

On the other hand, psychologists believe that humans start to struggle when the number of adjacent interesting things exceeds nine. So when a folder has ten or more files, it may be time to create subfolders.

還是根據你自己的舒適度而定吧。 除非建立新資料夾能有顯著的價值,否則儘量使用扁平結構。

Base your decision on your comfort level. Use a flatter structure until there is an obvious value to creating a new folder.

回到頂部

Back to top

T-DRY(儘量不重複自己)

T-DRY (Try to be DRY)

風格 04-05

Style 04-05

堅持 DRY(Don't Repeat Yourself,不重複自己)。

Do be DRY (Don't Repeat Yourself).

避免過度 DRY,以致犧牲了閱讀性。

Avoid being so DRY that you sacrifice readability.

為何?雖然 DRY 很重要,但如果要以犧牲 LIFT 的其它原則為代價,那就不值得了。 這也就是為什麼它被稱為 T-DRY。 例如,把元件命名為 hero-view.component.html 是多餘的,因為帶有 .html 副檔名的檔案顯然就是一個檢視 (view)。 但如果它不那麼顯著,或不符合常規,就把它寫出來。

Why? Being DRY is important, but not crucial if it sacrifices the other elements of LIFT. That's why it's called T-DRY. For example, it's redundant to name a template hero-view.component.html because with the .html extension, it is obviously a view. But if something is not obvious or departs from a convention, then spell it out.

回到頂部

Back to top

總體結構的指導原則

Overall structural guidelines

風格 04-06

Style 04-06

堅持從零開始,但要考慮應用程式接下來的路往哪兒走。

Do start small but keep in mind where the app is heading down the road.

堅持有一個近期實施方案和一個長期的願景。

Do have a near term view of implementation and a long term vision.

堅持把所有原始碼都放到名為 src 的目錄裡。

Do put all of the app's code in a folder named src.

堅持如果元件具有多個伴生檔案 (.ts.html.css.spec),就為它建立一個資料夾。

Consider creating a folder for a component when it has multiple accompanying files (.ts, .html, .css and .spec).

為何? 在早期階段能夠幫助保持應用的結構小巧且易於維護,這樣當應用增長時就容易進化了。

Why? Helps keep the app structure small and easy to maintain in the early stages, while being easy to evolve as the app grows.

為何? 元件通常有四個檔案 (*.html*.css*.ts*.spec.ts),它們很容易把一個目錄弄亂。

Why? Components often have four files (e.g. *.html, *.css, *.ts, and *.spec.ts) and can clutter a folder quickly.

下面是符合規範的目錄和檔案結構

Here is a compliant folder and file structure:

<project root>

src

app

core

exception.service.ts|spec.ts

user-profile.service.ts|spec.ts

heroes

hero

hero.component.ts|html|css|spec.ts

hero-list

hero-list.component.ts|html|css|spec.ts

shared

hero-button.component.ts|html|css|spec.ts

hero.model.ts

hero.service.ts|spec.ts

heroes.component.ts|html|css|spec.ts

heroes.module.ts

heroes-routing.module.ts

shared

shared.module.ts

init-caps.pipe.ts|spec.ts

filter-text.component.ts|spec.ts

filter-text.service.ts|spec.ts

villains

villain

...

villain-list

...

shared

...

villains.component.ts|html|css|spec.ts

villains.module.ts

villains-routing.module.ts

app.component.ts|html|css|spec.ts

app.module.ts

app-routing.module.ts

main.ts

index.html

...

node_modules/...

...

把元件放在專用目錄中的方式廣受歡迎,對於小型應用,還可以保持元件扁平化(而不是放在專用目錄中)。 這樣會把四個檔案放在現有目錄中,也會減少目錄的巢狀。無論你如何選擇,請保持一致。

While components in dedicated folders are widely preferred, another option for small apps is to keep components flat (not in a dedicated folder). This adds up to four files to the existing folder, but also reduces the folder nesting. Whatever you choose, be consistent.

回到頂部

Back to top

按特性組織的目錄結構

Folders-by-feature structure

風格 04-07

Style 04-07

堅持根據特性區命名目錄。

Do create folders named for the feature area they represent.

為何?開發人員可以快速定位程式碼,掃一眼就能知道每個檔案代表什麼,目錄儘可能保持扁平,既沒有重複也沒有多餘的名字。

Why? A developer can locate the code and identify what each file represents at a glance. The structure is as flat as it can be and there are no repetitive or redundant names.

為何? LIFT 原則中包含了所有這些。

Why? The LIFT guidelines are all covered.

為何?遵循 LIFT 原則精心組織內容,避免應用變得雜亂無章。

Why? Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

為何?當有很多檔案時(例如 10 個以上),在專用目錄型結構中定位它們會比在扁平結構中更容易。

Why? When there are a lot of files, for example 10+, locating them is easier with a consistent folder structure and more difficult in a flat structure.

堅持為每個特性區建立一個 NgModule。

Do create an NgModule for each feature area.

為何? NgModule 使延遲載入可路由的特性變得更容易。

Why? NgModules make it easy to lazy load routable features.

為何? NgModule 隔離、測試和複用特性更容易。

Why? NgModules make it easier to isolate, test, and reuse features.

欲知詳情,參閱目錄和檔案結構的範例

For more information, refer to this folder and file structure example.

回到頂部

Back to top

應用的根模組

App root module

風格 04-08

Style 04-08

堅持在應用的根目錄建立一個 NgModule(例如 /src/app)。

Do create an NgModule in the app's root folder, for example, in /src/app.

為何?每個應用都至少需要一個根 NgModule。

Why? Every app requires at least one root NgModule.

考慮把根模組命名為 app.module.ts

Consider naming the root module app.module.ts.

為何?能讓定位和識別根模組變得更容易。

Why? Makes it easier to locate and identify the root module.

app/app.module.ts
      
      import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';

@NgModule({
  imports: [
    BrowserModule,
  ],
  declarations: [
    AppComponent,
    HeroesComponent
  ],
  exports: [ AppComponent ],
  entryComponents: [ AppComponent ]
})
export class AppModule {}
    

回到頂部

Back to top

特性模組

Feature modules

風格 04-09

Style 04-09

堅持為應用中每個明顯的特性建立一個 NgModule。

Do create an NgModule for all distinct features in an application; for example, a Heroes feature.

堅持把特性模組放在與特性區同名的目錄中(例如 app/heroes)。

Do place the feature module in the same named folder as the feature area; for example, in app/heroes.

堅持特性模組的檔名應該能反映出特性區的名字和目錄(例如 app/heroes/heroes.module.ts)。

Do name the feature module file reflecting the name of the feature area and folder; for example, app/heroes/heroes.module.ts.

堅持特性模組的符號名應該能反映出特性區、目錄和檔名(例如在 app/heroes/heroes.module.ts 中定義 HeroesModule)。

Do name the feature module symbol reflecting the name of the feature area, folder, and file; for example, app/heroes/heroes.module.ts defines HeroesModule.

為何?特性模組可以對其它模組暴露或隱藏自己的實現。

Why? A feature module can expose or hide its implementation from other modules.

為何?特性模組標記出組成該特性分區的相關元件集合。

Why? A feature module identifies distinct sets of related components that comprise the feature area.

為何?方便路由到特性模組 —— 無論是用主動載入還是延遲載入的方式。

Why? A feature module can easily be routed to both eagerly and lazily.

為何?特性模組在特定的功能和其它應用特性之間定義了清晰的邊界。

Why? A feature module defines clear boundaries between specific functionality and other application features.

為何?特性模組幫助澄清開發職責,以便於把這些職責指派給不同的專案組。

Why? A feature module helps clarify and make it easier to assign development responsibilities to different teams.

為何?特性模組易於隔離,以便測試。

Why? A feature module can easily be isolated for testing.

回到頂部

Back to top

共享特性模組

Shared feature module

風格 04-10

Style 04-10

堅持shared 目錄中建立名叫 SharedModule 的特性模組(例如在 app/shared/shared.module.ts 中定義 SharedModule)。

Do create a feature module named SharedModule in a shared folder; for example, app/shared/shared.module.ts defines SharedModule.

堅持在共享模組中宣告那些可能被特性模組參考的可複用元件、指令和管道。

Do declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules.

考慮把可能在整個應用中到處參考的模組命名為 SharedModule

Consider using the name SharedModule when the contents of a shared module are referenced across the entire application.

考慮 不要在共享模組中提供服務。服務通常是單例的,應該在整個應用或一個特定的特性模組中只有一份。 不過也有例外,比如,在下面的範例程式碼中,注意 SharedModule 提供了 FilterTextService。這裡可以這麼做,因為該服務是無狀態的,也就是說,該服務的消費者不會受到這些新實例的影響。

Consider not providing services in shared modules. Services are usually singletons that are provided once for the entire application or in a particular feature module. There are exceptions, however. For example, in the sample code that follows, notice that the SharedModule provides FilterTextService. This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances.

堅持SharedModule 中匯入所有模組都需要的資產(例如 CommonModuleFormsModule)。

Do import all modules required by the assets in the SharedModule; for example, CommonModule and FormsModule.

為何? SharedModule 中包含的元件、指令和管道可能需要來自其它公共模組的特性(例如來自 CommonModule 中的 ngFor 指令)。

Why? SharedModule will contain components, directives and pipes that may need features from another common module; for example, ngFor in CommonModule.

堅持SharedModule 中宣告所有元件、指令和管道。

Do declare all components, directives, and pipes in the SharedModule.

堅持SharedModule 中匯出其它特性模組所需的全部符號。

Do export all symbols from the SharedModule that other feature modules need to use.

為何? SharedModule 的存在,能讓常用的元件、指令和管道在很多其它模組的元件範本中都自動可用。

Why? SharedModule exists to make commonly used components, directives and pipes available for use in the templates of components in many other modules.

避免SharedModule 中指定應用級的單例服務提供者。如果是刻意要得到多個服務單例也行,不過還是要小心。

Avoid specifying app-wide singleton providers in a SharedModule. Intentional singletons are OK. Take care.

為何?延遲載入的特性模組如果匯入了這個共享模組,會建立一份自己的服務副本,這可能會導致意料之外的後果。

Why? A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesirable results.

為何?對於單例服務,你不希望每個模組都有自己的實例。 而如果 SharedModule 提供了一個服務,那就有可能發生這種情況。

Why? You don't want each module to have its own separate instance of singleton services. Yet there is a real danger of that happening if the SharedModule provides a service.

src

app

shared

shared.module.ts

init-caps.pipe.ts|spec.ts

filter-text.component.ts|spec.ts

filter-text.service.ts|spec.ts

app.component.ts|html|css|spec.ts

app.module.ts

app-routing.module.ts

main.ts

index.html

...

      
      import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { FilterTextComponent } from './filter-text/filter-text.component';
import { FilterTextService } from './filter-text/filter-text.service';
import { InitCapsPipe } from './init-caps.pipe';

@NgModule({
  imports: [CommonModule, FormsModule],
  declarations: [
    FilterTextComponent,
    InitCapsPipe
  ],
  providers: [FilterTextService],
  exports: [
    CommonModule,
    FormsModule,
    FilterTextComponent,
    InitCapsPipe
  ]
})
export class SharedModule { }
    

回到頂部

Back to top

延遲載入資料夾

Lazy Loaded folders

風格 04-11

Style 04-11

某些邊界清晰的應用特性或工作流可以做成延遲載入按需載入的,而不用總是隨著應用啟動。

A distinct application feature or workflow may be lazy loaded or loaded on demand rather than when the application starts.

堅持把延遲載入特性下的內容放進延遲載入目錄中。 典型的延遲載入目錄包含路由元件及其子元件以及與它們有關的那些資產和模組。

Do put the contents of lazy loaded features in a lazy loaded folder. A typical lazy loaded folder contains a routing component, its child components, and their related assets and modules.

為何?這種目錄讓標識和隔離這些特性內容變得更輕鬆。

Why? The folder makes it easy to identify and isolate the feature content.

回到頂部

Back to top

永遠不要直接匯入延遲載入的目錄

Never directly import lazy loaded folders

樣式 04-14

Style 04-12

避免讓兄弟模組和父模組直接匯入延遲載入特性中的模組。

Avoid allowing modules in sibling and parent folders to directly import a module in a lazy loaded feature.

為何?直接匯入並使用此模組會立即載入它,而原本的設計意圖是按需載入它。

Why? Directly importing and using a module will load it immediately when the intention is to load it on demand.

回到頂部

Back to top

元件

Components

把元件當做元素

Components as elements

風格 05-03

Style 05-03

考慮給元件一個元素選擇器,而不是屬性類別選擇器。

Consider giving components an element selector, as opposed to attribute or class selectors.

為何?元件有很多包含 HTML 以及可選 Angular 範本語法的範本。 它們顯示內容。開發人員會把元件像原生 HTML 元素和 WebComponents 一樣放進頁面中。

Why? Components have templates containing HTML and optional Angular template syntax. They display content. Developers place components on the page as they would native HTML elements and web components.

為何?檢視元件範本的 HTML 時,更容易識別一個符號是元件還是指令。

Why? It is easier to recognize that a symbol is a component by looking at the template's html.

少數情況下,你要為元件使用屬性選擇器,比如你要加強某個內建元素時。 比如,Material Design 元件庫就會對 <button mat-button> 使用這項技術。不過,你不應該在自訂元件上使用這項技術。

There are a few cases where you give a component an attribute, such as when you want to augment a built-in element. For example, Material Design uses this technique with <button mat-button>. However, you wouldn't use this technique on a custom element.

app/heroes/hero-button/hero-button.component.ts
      
      /* avoid */

@Component({
  selector: '[tohHeroButton]',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}
    
app/app.component.html
      
      <!-- avoid -->

<div tohHeroButton></div>
    
      
      @Component({
  selector: 'toh-hero-button',
  templateUrl: './hero-button.component.html'
})
export class HeroButtonComponent {}
    

回到頂部

Back to top

把範本和樣式提取到它們自己的檔案

Extract templates and styles to their own files

風格 05-04

Style 05-04

堅持當超過 3 行時,把範本和樣式提取到一個單獨的檔案。

Do extract templates and styles into a separate file, when more than 3 lines.

堅持把範本檔案命名為 [component-name].component.html,其中,[component-name] 是元件名。

Do name the template file [component-name].component.html, where [component-name] is the component name.

堅持把樣式檔案命名為 [component-name].component.css,其中,[component-name] 是元件名。

Do name the style file [component-name].component.css, where [component-name] is the component name.

堅持指定相對於模組的 URL,給它加上 ./ 字首。

Do specify component-relative URLs, prefixed with ./.

為何?巨大的、內聯的範本和樣式表會遮蓋元件的意圖和實現方式,削弱可讀性和可維護性。

Why? Large, inline templates and styles obscure the component's purpose and implementation, reducing readability and maintainability.

為何?在多數編輯器中,編寫內聯的範本和樣式表時都無法使用語法提示和程式碼片段功能。 Angular 的 TypeScript 語言服務(即將到來)可以幫助那些編輯器在編寫 HTML 範本時克服這一缺陷,但對 CSS 樣式沒有幫助。

Why? In most editors, syntax hints and code snippets aren't available when developing inline templates and styles. The Angular TypeScript Language Service (forthcoming) promises to overcome this deficiency for HTML templates in those editors that support it; it won't help with CSS styles.

為何?當你移動元件檔案時,相對於元件的 URL 不需要修改,因為這些檔案始終會在一起。

Why? A component relative URL requires no change when you move the component files, as long as the files stay together.

為何?./ 字首是相對 URL 的標準語法,不必依賴 Angular 的特殊處理,如果沒有字首則不行。

Why? The ./ prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix.

app/heroes/heroes.component.ts
      
      /* avoid */

@Component({
  selector: 'toh-heroes',
  template: `
    <div>
      <h2>My Heroes</h2>
      <ul class="heroes">
        <li *ngFor="let hero of heroes | async" (click)="selectedHero=hero">
          <span class="badge">{{hero.id}}</span> {{hero.name}}
        </li>
      </ul>
      <div *ngIf="selectedHero">
        <h2>{{selectedHero.name | uppercase}} is my hero</h2>
      </div>
    </div>
  `,
  styles: [`
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class HeroesComponent {
  heroes: Observable<Hero[]>;
  selectedHero!: Hero;

  constructor(private heroService: HeroService) {
    this.heroes = this.heroService.getHeroes();
  }
}
    
      
      @Component({
  selector: 'toh-heroes',
  templateUrl: './heroes.component.html',
  styleUrls:  ['./heroes.component.css']
})
export class HeroesComponent {
  heroes: Observable<Hero[]>;
  selectedHero!: Hero;

  constructor(private heroService: HeroService) {
    this.heroes = this.heroService.getHeroes();
  }
}
    

回到頂部

Back to top

內聯輸入和輸出屬性裝飾器

Decorate input and output properties

風格 05-12

Style 05-12

堅持 使用 @Input()@Output(),而非 @Directive@Component 裝飾器的 inputsoutputs 屬性:

Do use the @Input() and @Output() class decorators instead of the inputs and outputs properties of the @Directive and @Component metadata:

堅持@Input() 或者 @Output() 放到所裝飾的屬性的同一行。

Consider placing @Input() or @Output() on the same line as the property it decorates.

為何?易於在類別裡面識別哪些屬性是輸入屬性或輸出屬性。

Why? It is easier and more readable to identify which properties in a class are inputs or outputs.

為何? 如果需要重新命名與 @Input() 或者 @Output() 關聯的屬性或事件名,你可以在一個位置修改。

Why? If you ever need to rename the property or event name associated with @Input() or @Output(), you can modify it in a single place.

為何?依附到指令的元資料宣告會比較簡短,更易於閱讀。

Why? The metadata declaration attached to the directive is shorter and thus more readable.

為何?把裝飾器放到同一行可以精簡程式碼,同時更易於識別輸入或輸出屬性。

Why? Placing the decorator on the same line usually makes for shorter code and still easily identifies the property as an input or output. Put it on the line above when doing so is clearly more readable.

app/heroes/shared/hero-button/hero-button.component.ts
      
      /* avoid */

@Component({
  selector: 'toh-hero-button',
  template: `<button></button>`,
  inputs: [
    'label'
  ],
  outputs: [
    'heroChange'
  ]
})
export class HeroButtonComponent {
  heroChange = new EventEmitter<any>();
  label: string;
}
    
app/heroes/shared/hero-button/hero-button.component.ts
      
      @Component({
  selector: 'toh-hero-button',
  template: `<button>{{label}}</button>`
})
export class HeroButtonComponent {
  @Output() heroChange = new EventEmitter<any>();
  @Input() label = '';
}
    

回到頂部

Back to top

避免為輸入和輸出屬性指定別名

Avoid aliasing inputs and outputs

風格 05-13

Style 05-13

避免除非有重要目的,否則不要為輸入和輸出指定別名。

Avoid input and output aliases except when it serves an important purpose.

為何?同一個屬性有兩個名字(一個對內一個對外)很容易導致混淆。

Why? Two names for the same property (one private, one public) is inherently confusing.

為何?如果指令名也同時用作輸入屬性,而且指令名無法準確描述這個屬性的用途時,應該使用別名。

Why? You should use an alias when the directive name is also an input property, and the directive name doesn't describe the property.

app/heroes/shared/hero-button/hero-button.component.ts
      
      /* avoid pointless aliasing */

@Component({
  selector: 'toh-hero-button',
  template: `<button>{{label}}</button>`
})
export class HeroButtonComponent {
  // Pointless aliases
  @Output('heroChangeEvent') heroChange = new EventEmitter<any>();
  @Input('labelAttribute') label: string;
}
    
app/app.component.html
      
      <!-- avoid -->

<toh-hero-button labelAttribute="OK" (changeEvent)="doSomething()">
</toh-hero-button>
    
      
      @Component({
  selector: 'toh-hero-button',
  template: `<button>{{label}}</button>`
})
export class HeroButtonComponent {
  // No aliases
  @Output() heroChange = new EventEmitter<any>();
  @Input() label = '';
}
    

回到頂部

Back to top

成員順序

Member sequence

風格 05-14

Style 05-14

堅持把屬性成員放在前面,方法成員放在後面。

Do place properties up top followed by methods.

堅持先放公共成員,再放私有成員,並按照字母順序排列。

Do place private members after public members, alphabetized.

為何?把類別的成員按照統一的順序排列,易於閱讀,能立即識別出元件的哪個成員服務於何種目的。

Why? Placing members in a consistent sequence makes it easy to read and helps instantly identify which members of the component serve which purpose.

app/shared/toast/toast.component.ts
      
      /* avoid */

export class ToastComponent implements OnInit {

  private defaults = {
    title: '',
    message: 'May the Force be with you'
  };
  message: string;
  title: string;
  private toastElement: any;

  ngOnInit() {
    this.toastElement = document.getElementById('toh-toast');
  }

  // private methods
  private hide() {
    this.toastElement.style.opacity = 0;
    window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
  }

  activate(message = this.defaults.message, title = this.defaults.title) {
    this.title = title;
    this.message = message;
    this.show();
  }

  private show() {
    console.log(this.message);
    this.toastElement.style.opacity = 1;
    this.toastElement.style.zIndex = 9999;

    window.setTimeout(() => this.hide(), 2500);
  }
}
    
app/shared/toast/toast.component.ts
      
      export class ToastComponent implements OnInit {
  // public properties
  message = '';
  title = '';

  // private fields
  private defaults = {
    title: '',
    message: 'May the Force be with you'
  };
  private toastElement: any;

  // public methods
  activate(message = this.defaults.message, title = this.defaults.title) {
    this.title = title;
    this.message = message;
    this.show();
  }

  ngOnInit() {
    this.toastElement = document.getElementById('toh-toast');
  }

  // private methods
  private hide() {
    this.toastElement.style.opacity = 0;
    window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
  }

  private show() {
    console.log(this.message);
    this.toastElement.style.opacity = 1;
    this.toastElement.style.zIndex = 9999;
    window.setTimeout(() => this.hide(), 2500);
  }
}
    

回到頂部

Back to top

把邏輯放到服務裡

Delegate complex component logic to services

風格 05-15

Style 05-15

堅持在元件中只包含與檢視相關的邏輯。所有其它邏輯都應該放到服務中。

Do limit logic in a component to only that required for the view. All other logic should be delegated to services.

堅持把可複用的邏輯放到服務中,保持元件簡單,聚焦於它們預期目的。

Do move reusable logic to services and keep components simple and focused on their intended purpose.

為何?當邏輯被放置到服務裡,並以函式的形式暴露時,可以被多個元件重複使用。

Why? Logic may be reused by multiple components when placed within a service and exposed via a function.

為何?在單元測試時,服務裡的邏輯更容易被隔離。當元件中呼叫邏輯時,也很容易被模擬。

Why? Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

為何?從元件移除依賴並隱藏實現細節。

Why? Removes dependencies and hides implementation details from the component.

為何?保持元件苗條、精簡和聚焦。

Why? Keeps the component slim, trim, and focused.

app/heroes/hero-list/hero-list.component.ts
      
      /* avoid */

import { OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';

import { Hero } from '../shared/hero.model';

const heroesUrl = 'http://angular.io';

export class HeroListComponent implements OnInit {
  heroes: Hero[];
  constructor(private http: HttpClient) {}
  getHeroes() {
    this.heroes = [];
    this.http.get(heroesUrl).pipe(
      catchError(this.catchBadResponse),
      finalize(() => this.hideSpinner())
    ).subscribe((heroes: Hero[]) => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }

  private catchBadResponse(err: any, source: Observable<any>) {
    // log and handle the exception
    return new Observable();
  }

  private hideSpinner() {
    // hide the spinner
  }
}
    
app/heroes/hero-list/hero-list.component.ts
      
      import { Component, OnInit } from '@angular/core';

import { Hero, HeroService } from '../shared';

@Component({
  selector: 'toh-hero-list',
  template: `...`
})
export class HeroListComponent implements OnInit {
  heroes: Hero[] = [];
  constructor(private heroService: HeroService) {}
  getHeroes() {
    this.heroes = [];
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }
  ngOnInit() {
    this.getHeroes();
  }
}
    

回到頂部

Back to top

不要給輸出屬性加字首

Don't prefix output properties

風格 05-16

Style 05-16

堅持命名事件時,不要帶字首 on

Do name events without the prefix on.

堅持把事件處理器方法命名為 on 字首之後緊跟著事件名。

Do name event handler methods with the prefix on followed by the event name.

為何?與內建事件命名一致,例如按鈕點選。

Why? This is consistent with built-in events such as button clicks.

為何?Angular 允許另一種備選語法 on-*。如果事件的名字本身帶有字首 on,那麼繫結的表示式可能是 on-onEvent

Why? Angular allows for an alternative syntax on-*. If the event itself was prefixed with on this would result in an on-onEvent binding expression.

app/heroes/hero.component.ts
      
      /* avoid */

@Component({
  selector: 'toh-hero',
  template: `...`
})
export class HeroComponent {
  @Output() onSavedTheDay = new EventEmitter<boolean>();
}
    
app/app.component.html
      
      <!-- avoid -->

<toh-hero (onSavedTheDay)="onSavedTheDay($event)"></toh-hero>
    
      
      export class HeroComponent {
  @Output() savedTheDay = new EventEmitter<boolean>();
}
    

回到頂部

Back to top

把表現層邏輯放到元件類別裡

Put presentation logic in the component class

風格 05-17

Style 05-17

堅持把表現層邏輯放進元件類別中,而不要放在範本裡。

Do put presentation logic in the component class, and not in the template.

為何?邏輯應該只出現在一個地方(元件類別裡)而不應分散在兩個地方。

Why? Logic will be contained in one place (the component class) instead of being spread in two places.

為何?將元件的表現層邏輯放到元件類別而非範本裡,可以增強測試性、維護性和重複使用性。

Why? Keeping the component's presentation logic in the class instead of the template improves testability, maintainability, and reusability.

app/heroes/hero-list/hero-list.component.ts
      
      /* avoid */

@Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <toh-hero *ngFor="let hero of heroes" [hero]="hero">
      </toh-hero>
      Total powers: {{totalPowers}}<br>
      Average power: {{totalPowers / heroes.length}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
}
    
app/heroes/hero-list/hero-list.component.ts
      
      @Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <toh-hero *ngFor="let hero of heroes" [hero]="hero">
      </toh-hero>
      Total powers: {{totalPowers}}<br>
      Average power: {{avgPower}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers = 0;

  get avgPower() {
    return this.totalPowers / this.heroes.length;
  }
}
    

回到頂部

Back to top

初始化輸入屬性

Initialize inputs

Style 05-18

TypeScript 的編譯器選項 --strictPropertyInitialization,會確保某個類別在建構函式中初始化其屬性。當啟用時,如果該類別沒有對任何未顯式標為可選值的屬性提供初始值,TypeScript 編譯器就會報錯。

TypeScript's --strictPropertyInitialization compiler option ensures that a class initializes its properties during construction. When enabled, this option causes the TypeScript compiler to report an error if the class does not set a value to any property that is not explicitly marked as optional.

按照設計,Angular 把所有 @Input 都視為可選值。只要有可能,你就應該透過提供預設值來滿足 --strictPropertyInitialization 的要求。

By design, Angular treats all @Input properties as optional. When possible, you should satisfy --strictPropertyInitialization by providing a default value.

app/heroes/hero/hero.component.ts
      
      @Component({
  selector: 'toh-hero',
  template: `...`
})
export class HeroComponent {
  @Input() id = 'default_id';
}
    

如果該屬性很難構造出預設值,請使用 ? 來把該屬性顯式標記為可選的。

If the property is hard to construct a default value for, use ? to explicitly mark the property as optional.

app/heroes/hero/hero.component.ts
      
      @Component({
  selector: 'toh-hero',
  template: `...`
})
export class HeroComponent {
  @Input() id?: string;

  process() {
    if (this.id) {
      // ...
    }
  }
}
    

你可能希望某個 @Input 欄位是必填的,也就是說此元件的所有使用者都必須傳入該屬性。這種情況下,請使用預設值。僅僅使用 ! 來抑制 TypeScript 報錯是不夠的,應該避免它,因為這樣做會阻止型別檢查器來確保必須提供此輸入值。

You may want to have a required @Input field, meaning all your component users are required to pass that attribute. In such cases, use a default value. Just suppressing the TypeScript error with ! is insufficient and should be avoided because it will prevent the type checker ensure the input value is provided.

app/heroes/hero/hero.component.ts
      
      @Component({
  selector: 'toh-hero',
  template: `...`
})
export class HeroComponent {
  // The exclamation mark suppresses errors that a property is
  // not initialized.
  // Ignoring this enforcement can prevent the type checker
  // from finding potential issues.
  @Input() id!: string;
}
    

指令

Directives

使用指令來增強已有元素

Use directives to enhance an element

風格 06-01

Style 06-01

堅持當你需要有表現層邏輯,但沒有範本時,使用屬性型指令。

Do use attribute directives when you have presentation logic without a template.

為何?屬性型指令沒有範本。

Why? Attribute directives don't have an associated template.

為何?一個元素可以使用多個屬性型指令。

Why? An element may have more than one attribute directive applied.

app/shared/highlight.directive.ts
      
      @Directive({
  selector: '[tohHighlight]'
})
export class HighlightDirective {
  @HostListener('mouseover') onMouseEnter() {
    // do highlight work
  }
}
    
app/app.component.html
      
      <div tohHighlight>Bombasta</div>
    

回到頂部

Back to top

HostListenerHostBinding 裝飾器 vs. 元件元資料 host

HostListener/HostBinding decorators versus host metadata

風格 06-03

Style 06-03

考慮優先使用 @HostListener@HostBinding,而不是 @Directive@Component 裝飾器的 host 屬性。

Consider preferring the @HostListener and @HostBinding to the host property of the @Directive and @Component decorators.

堅持讓你的選擇保持一致。

Do be consistent in your choice.

為何?對於關聯到 @HostBinding 的屬性或關聯到 @HostListener 的方法,要修改時,只需在指令類別中的一個地方修改。 如果使用元資料屬性 host,你就得在元件類別中修改屬性宣告的同時修改相關的元資料。

Why? The property associated with @HostBinding or the method associated with @HostListener can be modified only in a single place—in the directive's class. If you use the host metadata property, you must modify both the property/method declaration in the directive's class and the metadata in the decorator associated with the directive.

app/shared/validator.directive.ts
      
      import { Directive, HostBinding, HostListener } from '@angular/core';

@Directive({
  selector: '[tohValidator]'
})
export class ValidatorDirective {
  @HostBinding('attr.role') role = 'button';
  @HostListener('mouseenter') onMouseEnter() {
    // do work
  }
}
    

與不推薦的方式(host 元資料)比較一下。

Compare with the less preferred host metadata alternative.

為何?host 元資料只是一個便於記憶的名字而已,並不需要額外的 ES 匯入。

Why? The host metadata is only one term to remember and doesn't require extra ES imports.

app/shared/validator2.directive.ts
      
      import { Directive } from '@angular/core';

@Directive({
  selector: '[tohValidator2]',
  host: {
    '[attr.role]': 'role',
    '(mouseenter)': 'onMouseEnter()'
  }
})
export class Validator2Directive {
  role = 'button';
  onMouseEnter() {
    // do work
  }
}
    

回到頂部

Back to top

服務

Services

服務總是單例的

Services are singletons

風格 07-01

Style 07-01

堅持在同一個注入器內,把服務當做單例使用。用它們來共享資料和功能。

Do use services as singletons within the same injector. Use them for sharing data and functionality.

為何?服務是在特性範圍或應用內共享方法的理想載體。

Why? Services are ideal for sharing methods across a feature area or an app.

為何?服務是共享狀態性記憶體資料的理想載體。

Why? Services are ideal for sharing stateful in-memory data.

app/heroes/shared/hero.service.ts
      
      export class HeroService {
  constructor(private http: HttpClient) { }

  getHeroes() {
    return this.http.get<Hero[]>('api/heroes');
  }
}
    

回到頂部

Back to top

單一職責

Single responsibility

風格 07-02

Style 07-02

堅持建立封裝在上下文中的單一職責的服務。

Do create services with a single responsibility that is encapsulated by its context.

堅持當服務成長到超出單一用途時,建立一個新服務。

Do create a new service once the service begins to exceed that singular purpose.

為何?當服務有多個職責時,它很難被測試。

Why? When a service has multiple responsibilities, it becomes difficult to test.

為何?當某個服務有多個職責時,每個注入它的元件或服務都會承擔這些職責的全部開銷。

Why? When a service has multiple responsibilities, every component or service that injects it now carries the weight of them all.

回到頂部

Back to top

提供一個服務

Providing a service

風格 07-03

Style 07-03

堅持在服務的 @Injectable 裝飾器上指定透過應用的根注入器提供服務。

Do provide a service with the app root injector in the @Injectable decorator of the service.

為何? Angular 注入器是層次化的。

Why? The Angular injector is hierarchical.

為何?當你在根注入器上提供該服務時,該服務實例在每個需要該服務的類別中是共享的。當服務要共享方法或狀態時,這是最理想的選擇。

Why? When you provide the service to a root injector, that instance of the service is shared and available in every class that needs the service. This is ideal when a service is sharing methods or state.

為何?當你在服務的 @Injectable 中註冊服務時,Angular CLI 生產環境建構時使用的優化工具可以進行搖樹優化,從而移除那些你的應用中從未用過的服務。

Why? When you register a service in the @Injectable decorator of the service, optimization tools such as those used by the Angular CLI's production builds can perform tree shaking and remove services that aren't used by your app.

為何?當不同的兩個元件需要一個服務的不同的實例時,上面的方法這就不理想了。在這種情況下,對於需要嶄新和單獨服務實例的元件,最好在元件級提供服務。

Why? This is not ideal when two different components need different instances of a service. In this scenario it would be better to provide the service at the component level that needs the new and separate instance.

src/app/treeshaking/service.ts
      
      @Injectable({
  providedIn: 'root',
})
export class Service {
}
    

回到頂部

Back to top

使用 @Injectable() 類別裝飾器

Use the @Injectable() class decorator

風格 07-04

Style 07-04

堅持當使用型別作為令牌來注入服務的依賴時,使用 @Injectable() 類別裝飾器,而非 @Inject() 引數裝飾器。

Do use the @Injectable() class decorator instead of the @Inject parameter decorator when using types as tokens for the dependencies of a service.

為何? Angular 的 DI 機制會根據服務的建構函式引數的宣告型別來解析服務的所有依賴。

Why? The Angular Dependency Injection (DI) mechanism resolves a service's own dependencies based on the declared types of that service's constructor parameters.

為何?當服務只接受型別令牌相關的依賴時,比起在每個建構函式引數上使用 @Inject()@Injectable() 的語法簡潔多了。

Why? When a service accepts only dependencies associated with type tokens, the @Injectable() syntax is much less verbose compared to using @Inject() on each individual constructor parameter.

app/heroes/shared/hero-arena.service.ts
      
      /* avoid */

export class HeroArena {
  constructor(
      @Inject(HeroService) private heroService: HeroService,
      @Inject(HttpClient) private http: HttpClient) {}
}
    
app/heroes/shared/hero-arena.service.ts
      
      @Injectable()
export class HeroArena {
  constructor(
    private heroService: HeroService,
    private http: HttpClient) {}
}
    

回到頂部

Back to top

資料服務

Data Services

透過服務與 Web 伺服器通訊

Talk to the server through a service

風格 08-01

Style 08-01

堅持把資料操作和與資料互動的邏輯重構到服務裡。

Do refactor logic for making data operations and interacting with data to a service.

堅持讓資料服務來負責 XHR 呼叫、本地儲存、記憶體儲存或者其它資料操作。

Do make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations.

為何?元件的職責是為檢視展示或收集資訊。它不應該關心如何獲取資料,它只需要知道向誰請求資料。把如何獲取資料的邏輯移動到資料服務裡,簡化了元件,讓其聚焦於檢視。

Why? The component's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the component be simpler and more focused on the view.

為何?在測試使用資料服務的元件時,可以讓資料呼叫更容易被測試(模擬或者真實)。

Why? This makes it easier to test (mock or real) the data calls when testing a component that uses a data service.

為何?資料管理的詳情,比如頭資訊、方法、快取、錯誤處理和重試邏輯,不是元件和其它的資料消費者應該關心的事情。

Why? The details of data management, such as headers, HTTP methods, caching, error handling, and retry logic, are irrelevant to components and other data consumers.

資料服務應該封裝這些細節。這樣,在服務內部修改細節,就不會影響到它的消費者。並且更容易透過實現一個模擬服務來對消費者進行測試。

A data service encapsulates these details. It's easier to evolve these details inside the service without affecting its consumers. And it's easier to test the consumers with mock service implementations.

回到頂部

Back to top

生命週期鉤子

Lifecycle hooks

使用生命週期鉤子來介入到 Angular 暴露的重要事件裡。

Use Lifecycle hooks to tap into important events exposed by Angular.

回到頂部

Back to top

實現生命週期鉤子介面

Implement lifecycle hook interfaces

風格 09-01

Style 09-01

堅持實現生命週期鉤子介面。

Do implement the lifecycle hook interfaces.

為何?如果使用強型別的方法簽名,編譯器和編輯器可以幫你揪出拼寫錯誤。

Why? Lifecycle interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.

app/heroes/shared/hero-button/hero-button.component.ts
      
      /* avoid */

@Component({
  selector: 'toh-hero-button',
  template: `<button>OK<button>`
})
export class HeroButtonComponent {
  onInit() { // misspelled
    console.log('The component is initialized');
  }
}
    
app/heroes/shared/hero-button/hero-button.component.ts
      
      @Component({
  selector: 'toh-hero-button',
  template: `<button>OK</button>`
})
export class HeroButtonComponent implements OnInit {
  ngOnInit() {
    console.log('The component is initialized');
  }
}
    

回到頂部

Back to top

附錄

Appendix

有用的 Angular 工具和小提示

Useful tools and tips for Angular.

回到頂部

Back to top

文件範本和程式碼片段

File templates and snippets

風格 A-02

Style A-02

堅持使用檔案範本或程式碼片段來幫助實現一致的風格和模式。下面是為一些網路開發編輯器和 IDE 準備的範本和/或程式碼片段:

Do use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs.

考慮使用 Visual Studio Code程式碼片段 來實施本風格指南。

Consider using snippets for Visual Studio Code that follow these styles and guidelines.

Use Extension

考慮使用 Atom程式碼片斷來實施本風格指南。

Consider using snippets for Atom that follow these styles and guidelines.

考慮使用 Sublime Text程式碼片斷 來實施本風格指南。

Consider using snippets for Sublime Text that follow these styles and guidelines.

考慮使用 Vim程式碼片斷來實施本風格指南。

Consider using snippets for Vim that follow these styles and guidelines.

回到頂部

Back to top