【荐】Angular 最佳实践
来源:互联网 发布:s7总决赛现场数据 编辑:程序博客网 时间:2024/05/21 07:55
推荐文章
Armen Vardanyan : Angular: Best Practices
推荐理由
作者根据自身的项目实践,总结出了一些Angular的最佳实践。主要包涵了TypeScript类型的最佳使用,组件的合理使用,通用服务的封装,模版的合理定义。
文章概要
首先作者推荐在阅读原文之间先阅读官方的Angular风格指南,里面包涵了一些常见的设计模式和实用的实践。而文中提到的建议是在《风格指南》中找不到的。最佳实践建议如下:
利用好TypeScript类型
- 利用好类型的并集/交集
interface User { fullname: string; age: number; createdDate: string | Date;}
此处的createdDate
即可以是string类型,也可以是Date类型。
2.限制类型
interface Order { status: 'pending' | 'approved' | 'rejected';}
可以指定status
的数值只能是上述三者之一。
当然也可以通过枚举类型来代替这种方式:
enum Statuses { Pending = 1, Approved = 2, Rejected = 3}interface Order { status: Statuses;}
3.设置“noImplicitAny”: true
在项目的tsconfig.json
文件中,建议设置“noImplicitAny”: true
。这样,所有未明确声明类型的变量都会抛出错误。
组件最佳实践
合理地利用组件,可以有效地减少代码冗余。
- 设置基础组件类
enum Statuses { Unread = 0, Read = 1}@Component({ selector: 'component-with-enum', template: ` <div *ngFor="notification in notifications" [ngClass]="{'unread': notification.status == statuses.Unread}"> {{ notification.text }} </div>`})class NotificationComponent { notifications = [ {text: 'Hello!', status: Statuses.Unread}, {text: 'Angular is awesome!', status: Statuses.Read} ]; statuses = Statuses}
如果有很多组件都需要Statuses
这个枚举接口的话,每次都需要重复声明。把它抽象成基础类,就不需要啦。
enum Statuses { Unread = 0, Read = 1}abstract class AbstractBaseComponent { statuses = Statuses; someOtherEnum = SomeOtherEnum; ... // 其他可复用的}@Component({ selector: 'component-with-enum', template: ` <div *ngFor="notification in notifications" [ngClass]="{'unread': notification.status == statuses.Unread}"> {{ notification.text }} </div>`})class NotificationComponent extends AbstractBaseComponent { notifications = [ {text: 'Hello!', status: Statuses.Unread}, {text: 'Angular is awesome!', status: Statuses.Read} ];}
再进一步,对于Angular表单,不同组件通常有共同的方法。一个常规的表单类如下:
@Component({ selector: 'component-with-form', template: `...omitted for the sake of brevity`})class ComponentWithForm extends AbstractBaseComponent { form: FormGroup; submitted: boolean = false; // 标记用户是否尝试提交表单 resetForm() { this.form.reset(); } onSubmit() { this.submitted = true; if (!this.form.valid) { return; } // 执行具体的提交逻辑 }}
如果有很多地方用到表单,表单类中就会重复很多次上述的代码。
是不是把这些基础的方法抽象一下会更好呢?
abstract class AbstractFormComponent extends AbstractBaseComponent { form: FormGroup; submitted: boolean = false; resetForm() { this.form.reset(); } onSubmit() { this.submitted = true; if (!this.form.valid) { return; } }}@Component({ selector: 'component-with-form', template: `...omitted for the sake of brevity`})class ComponentWithForm extends AbstractFormComponent { onSubmit() { super.onSubmit(); // 继续执行具体的提交逻辑 }}
2.善用容器组件
这点作者觉得可能有点争议,关键在于你要找到合适你的场景。具体是指,建议把顶级组件处理成容器组件,然后再定义一个接受数据的展示组件。这样的好处是,将获取输入数据的逻辑和组件内部业务操作的逻辑分开了,也有利于展示组件的复用。
const routes: Routes = [ {path: 'user', component: UserContainerComponent}];@Component({ selector: 'user-container-component', template: `<app-user-component [user]="user"></app-user-component>`})class UserContainerComponent { constructor(userService: UserService) {} ngOnInit(){ this.userService.getUser().subscribe(res => this.user = user); /* 获取传递到真正的view组件的数据 */ }}@Component({ selector: 'app-user-component', template: `...displays the user info and some controls maybe`})class UserComponent { @Input() user;}
3.组件化循环模板
在使用*ngFor
指令时,建议将待循环的模板处理成组件:
<-- 不推荐 --><div *ngFor="let user of users"> <h3 class="user_wrapper">{{user.name}}</h3> <span class="user_info">{{ user.age }}</span> <span class="user_info">{{ user.dateOfBirth | date : 'YYYY-MM-DD' }}</span></div><-- 推荐 --><user-detail-component *ngFor="let user of users" [user]="user"></user-detail-component>
这样做的好处在于减少父组件的代码,同时也将代码可能存在的业务逻辑移到子组件中。
封装通用的服务
提供合理结构的服务很重要,因为服务可以访问数据,处理数据,或者封装其他重复的逻辑。作者推荐的实践有以下几点:
- 统一封装API的基础服务
将基础的HTTP服务封装成一个基础的服务类:
abstract class RestService { protected baseUrl: 'http://your.api.domain'; constructor(private http: Http, private cookieService: CookieService){} protected get headers(): Headers { /* * for example, add an authorization token to each request, * take it from some CookieService, for example * */ const token: string = this.cookieService.get('token'); return new Headers({token: token}); } protected get(relativeUrl: string): Observable<any> { return this.http.get(this.baseUrl + relativeUrl, new RequestOptions({headers: this.headers})) .map(res => res.json()); // as you see, the simple toJson mapping logic also delegates here } protected post(relativeUrl: string, data: any) { // and so on for every http method that your API supports }}
真正调用服务的代码就会显示很简单清晰:
@Injectable()class UserService extends RestService { private relativeUrl: string = '/users/'; public getAllUsers(): Observable<User[]> { return this.get(this.relativeUrl); } public getUserById(id: number): Observable<User> { return this.get(`${this.relativeUrl}${id.toString()}`); }}
2.封装通用工具服务
项目中总有一些通用的方法,跟展示无关,跟业务逻辑无关,这些方法建议封装成一个通用的工具服务。
3.统一定义API的url
相对于直接在函数中写死,统一定义的方法更加利于处理:
enum UserApiUrls { getAllUsers = 'users/getAll', getActiveUsers = 'users/getActive', deleteUser = 'users/delete'}
4.尽可能缓存请求结果
有些请求结果你可能只需要请求一次,比如地址库,某些字段的枚举值等。这时Rx.js的可订阅对象就发挥作用了。
class CountryService { constructor(private http: Http) {} private countries: Observable<Country[]> = this.http.get('/api/countries') .map(res => res.json()) .publishReplay(1) // this tells Rx to cache the latest emitted value .refCount(); // and this tells Rx to keep the Observable alive as long as there are any Subscribers public getCountries(): Observable<Country[]> { return this.countries; }}
模板
将复杂一点的逻辑移至类中,不推荐直接写在模版中。
比如表单中有个has-error
样式类,当表单控件验证失败才会显示,你可以这么写:
@Component({ selector: 'component-with-form', template: ` <div [formGroup]="form" [ngClass]="{ 'has-error': (form.controls['firstName'].invalid && (submitted || form.controls['firstName'].touched)) }"> <input type="text" formControlName="firstName"/> </div> `})class SomeComponentWithForm { form: FormGroup; submitted: boolean = false; constructor(private formBuilder: FormBuilder) { this.form = formBuilder.group({ firstName: ['', Validators.required], lastName: ['', Validators.required] }); }}
但这里的判断逻辑很复杂,如果有多个控件的话,你就需要重复多次这个冗长的判断。建议处理成:
@Component({ selector: 'component-with-form', template: ` <div [formGroup]="form" [ngClass]="{'has-error': hasFieldError('firstName')}"> <input type="text" formControlName="firstName"/> </div> `})class SomeComponentWithForm { form: FormGroup; submitted: boolean = false; constructor(private formBuilder: FormBuilder) { this.form = formBuilder.group({ firstName: ['', Validators.required], lastName: ['', Validators.required] }); } hasFieldError(fieldName: string): boolean { return this.form.controls[fieldName].invalid && (this.submitted || this.form.controls[fieldName].touched); }}
大概就是这些啦。作者说他还没总结关于指令和管道部分的一些实践经验,他想专门再写一篇文章来说清楚Angular的DOM。我们拭目以待吧!
本文首发知乎野草。如有不当之处,欢迎指正。
- 【荐】Angular 最佳实践
- angular最佳实践
- Angular最佳实践之$http-麻雀虽小 五脏俱全
- angular-数据绑定的最佳实践
- ngCloak 实现 Angular 初始化闪烁最佳实践
- ngCloak 实现 Angular 初始化闪烁最佳实践
- Angular 2 – 提升水平的最佳实践
- 最佳日志实践(荐)
- Atitit. js mvc 总结(2)----angular 跟 Knockout o99 最佳实践
- Atitit.angular.js 使用最佳实践 原理与常见问题解决与列表显示案例 attilax总结
- 升级 AngularJS 1.5:新特性对比与最佳实践(angular.component(),transclusion)
- 最佳实践
- 最佳实践
- 最佳实践
- Angular 2 实践纲要
- ANT十五大最佳实践
- C 编程最佳实践
- J2EE 最佳实践
- 【C++复习】易错的小问题
- Nginx学习问答
- SSH框架的工作原理
- Maven学习总结(七)——eclipse中使用Maven创建Web项目
- 用Python和Pygame写游戏-从入门到精通(2)事件
- 【荐】Angular 最佳实践
- Python获取帮助详解
- C++ 零碎知识点集锦 一
- keepalive+nginx负载均衡
- WrapPanel
- 时间段重复校验-js
- Retrofit源码分析 (四. Retrofit 官网说明,Copy)
- Mysql时区问题
- 一直没搞清楚的数组sort方法传入一个function是怎么运行的