目录
把所有应用程序的源代码都放到名叫 app 的目录里。所有内容都遵循每个文件单个特性的原则。每个组件、服务和管道都在自己的文件里。所有第三方程序包都被保存到其它目录里而不在 app 目录里,我们不会修改它们,所以不希望它们弄乱我们的应用程序。使用本指南介绍的文件命名约定。
尽可能保持平面化的目录结构
当有 7个或更多文件时才新建目录;
为每个组件新建一个目录,保存它的 .ts,.html,.css,.spec 等文件;
在单个特性范围内,把所有共享的文件放到 shared 目录,在一个特性范围内,分离出组件共享的文件;
把目录的名字命名为它包含的特性名字;
布局组件
把定义总体布局的组件放到 shared 目录;
把共享的布局组件放到 shared 目录下它自己的独立目录里;
创建和导入封装桶
新建一个文件,用来导入、归集和重新导出项目。该技巧被称作 封装桶 。把该封装桶文件命名为 index.ts ;
为惰性加载目录名字加前缀 +,使用组件路由器来惰性加载可以路由的特性。
命名文件到这个程度:可以从名字立刻知道它包含了什么,代表了什么;
避免 创建包含很多组件、服务或者混合体的文件;
当组件名字为 [component-name] 的时候,命名它的模板为 [component-name].component.html
文件结构如下图所示:
每个文件只定义一样东西 (比如服务或者组件),把文件大小限制在 400行代码以内;
定义小函数,限制在 75 行之内;
遵循同一个模式来描述符号的特性和类型。推荐的模式为 feature.type.ts,使用惯用的后缀来描述类型,比如 *.service 、 *.component 、 *.pipe、*.model;
在描述性名字里面,使用横杠来分隔单词;
启动
把应用的引导程序和平台相关的逻辑放到名为main.ts
的文件里;
避免把应用逻辑放到main.ts
里。考虑把它放到组件或服务里面;
组件与指令命名
以它们所代表的东西命名;
使用大写驼峰命名法来命名所有符号 ( 类 ) 。保持符号的名字与它所在的文件名字相同;
把符号的类型 ( 比如组件、服务、指令等 ) 附加到符号名的后面;
如下所示:
符号名 |
文件名 |
export class HeroesComponent |
heroes.component.ts |
export class HeroListComponent |
hero-list.component.ts |
export class ValidationDirective |
validation.directive.ts |
服务
使用大写驼峰命名法来命名服务;
当不能从它们的名字里清楚的看出它们是什么的时候 ( 比如它们的名字是名词时 ) ,添加 Service 后缀;
如下所示:
符号名 |
文件名 |
export class HeroDataService {} |
hero-data.service.ts |
export class CreditService {} |
credit.service.ts |
指令的选择器
使用小驼峰命名法来命名指令的选择器,Angular 2 HTML 解析器是大小写敏感的,它识别小写驼峰写法;
保持指令里定义的属性名字与它们绑定的视图 HTML 属性名字一致;
为组件的选择器使用自定义前缀。比如,前缀 tod 是从 Tour of Heros 来的,前缀 admin 代表了 admin 的特性区域;
使用前缀来识别特性区域或者应用程序本身;
如下所示
@Component({
selector: 'admin-users'
})
export class UsersComponent {}
@Directive({
selector: '[tohValidate]'
})
export class ValidateDirective {}
管道名
为所有管道使用前后一致的命名约定,用它们的特性来命名;
如下:
@Pipe({ name: 'ellipsis' })
export class EllipsisPipe implementsPipeTransform { }
编程约定
类
使用大写驼峰命名法来命名类;
export class ExceptionService {
constructor() { }
}
常量
用 const 声明变量,除非它们的值在应用的生命周期内会发生变化;
把常量名拼写为小驼峰格式;
export const mockHeroes = ['Sam','Jill'];
接口
使用大写驼峰命名法来命名接口;
不要在接口名字前面加 I 前缀;
import { Injectable } from '@angular/core';
@Injectable()
export class ToastService {
message: string;
privatetoastCount: number;
hide() {
this.toastCount--;
this.log();
}
show() {
this.toastCount++;
this.log();
}
private log() {
console.log(this.message);
}
}
内联 Input 和 Output 属性装饰器
使用 @Input 和 @Output,而非 @Directive 和 @Component 装饰器里面的 inputs 和 outputs 属性;
把 @Input() 或者 @Output() 放到它们装饰的属性的同一行;
成员顺序
先放公共成员,再放私有成员,并按照字母顺序排列;
export class ToastComponent implementsOnInit {
//public properties
message: string;
title: string;
//private fields
privatedefaults = {
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);
}
}
把逻辑放到服务里
把组件类中的逻辑限制到只有视图需要的逻辑。所有其它逻辑都应该被放到服务;
把可以重复使用的逻辑放到服务里,保持组件简单并聚焦于它们预期目的;
错误的方式
/* avoid */
import { OnInit } from '@angular/core';
import { Http,Response } from'@angular/http';
import { Observable } from'rxjs/Observable';
import { Hero } from'../shared/hero.model';
const heroesUrl = 'http://angular.io';
export class HeroListComponent implementsOnInit {
heroes: Hero[];
constructor(private http: Http) {}
getHeroes() {
this.heroes = [];
this.http.get(heroesUrl)
.map((response: Response) => <Hero[]>response.json().data)
.catch(this.catchBadResponse)
.finally(() => 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
}
}
正确的方式
import { Component,OnInit } from'@angular/core';
import { Hero,HeroService } from'../shared';
@Component({
selector: 'toh-hero-list',
template: `...`
})
export class HeroListComponent implementsOnInit {
heroes: Hero[];
constructor(private heroService: HeroService) {}
getHeroes() {
this.heroes = [];
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
ngOnInit() {
this.getHeroes();
}
}
命名事件时,不要带前缀 on;
命名事件处理方法时,带前缀 on ,紧跟事件名字;
export class HeroComponent {
@Output() savedTheDay = new EventEmitter<boolean>();
}
使用指令来加强已有元素
当你需要有无模板的展示逻辑时,使用属性型指令。
@Directive({
selector: '[tohHighlight]'
})
export class HighlightDirective {
@HostListener('mouSEOver') onMouseEnter() {
// do highlight work
}
}
<divtohHighlight>Bombasta</div>
使用 HostListener 和 HostBinding 类装饰器
使用 @HostListener 和 @HostBinding ,而非 @Directive 和 @Component 装饰器的宿主属性。与属性或方法名字相关联的@HostBinding 或者 @HostListener 应该只在一个地方被修改:在指令的类里。反过来,如果我们使用宿主属性,我们需要在控制器内修改属性声明,然后在指令相关的元数据里修改
@Directive({
selector: '[tohValidator]'
})
export class ValidatorDirective {
@HostBinding('attr.role') role = 'button';
@HostListener('mouseenter') onMouseEnter() {
// do work
}
}
服务
在同一个注入器内,服务是单例
在同一个注入器内,把服务当做单例使用。使用它们来共享数据和功能。
export class HeroService {
constructor(private http: Http) { }
getHeroes() {
return this.http.get('api/heroes')
.map((response: Response) => <Hero[]>response.json().data);
}
}
单一职责
新建单一职责的服务,把它封装在自己的环境内;
当服务成长到超出单一用途时,新建一个服务;
提供一个服务
在被共享范围内的顶级组件里,将服务提供到 Angular 2 的注入器里;
Angular 2 注入器是层次性的。在顶层组件提供服务时,该服务实例在所有该顶级组件的子级组件中可见并共享。
当不同的两个组件需要一个服务的不同的实例时,上面的方法这就不理想了。在这种情况下,我们最好在需要崭新和单独服务实例的组件里提供服务
使用 @Injectable() 类装饰器
当使用类型作为令牌来注入服务的依赖时,使用 @Injectable 类装饰器,而非 @Inject 参数装饰器。
Angular 的依赖注入机制,是根据在服务的构造函数里面的类型的声明,来解析所有服务的依赖的。
数据服务
分离数据调用
把数据操作和数据互动重构到服务里;
让数据服务来负责 XHR 调用、本地储存、内存储存或者其他数据操作;
生命周期钩子
实现生命周期钩子接口
实现生命周期钩子接口,避免在方法名字拼写错误时,造成无意间没有调用钩子的可能。
@Component({
selector: 'toh-hero-button',
template: `<button>OK</button>`
})
export class HeroButtonComponent implementsOnInit {
ngOnInit() {
console.log('The component is initialized');
}
}