Angular入门基础(第一篇)

350 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天。点击查看活动详情

开发风格用法

单一规则

  • 坚持每个文件只定义一样东西(例如服务或组件)。
  • 考虑把文件大小限制在 400 行代码以内。

小函数

  • 坚持定义简单函数
  • 考虑限制在 75 行之内。

命名

  • 小驼峰形式(camelCase):符号、属性、方法、管道名、非组件指令的选择器、常量。 小驼峰(也叫标准驼峰)形式的第一个字母要使用小写形式。比如 "selectedHero"。
  • 大驼峰形式(UpperCamelCase)或叫帕斯卡形式(PascalCase):类名(包括用来定义组件、接口、NgModule、指令、管道等的类)。 大驼峰形式的第一个字母要使用大写形式。比如 "HeroListComponent"。
  • 中线形式(dash-case)或叫烤串形式(kebab-case):文件名中的描述部分,组件的选择器。比如 "app-hero-list"。
  • 下划线形式(underscore_case)或叫蛇形形式(snake_case):在 Angular 中没有典型用法。蛇形形式使用下划线连接各个单词。 比如 "convert_link_mode"。
  • 大写下划线形式(UPPER_UNDERSCORE_CASE)或叫大写蛇形形式(UPPER_SNAKE_CASE):传统的常量写法(可以接受,但更推荐用小驼峰形式(camelCase) 大蛇形形式使用下划线分隔的全大写单词。比如 "FIX_ME"。

坚持所有符号使用一致的命名规则。

  • 坚持遵循同一个模式来描述符号的特性和类型。推荐的模式为 feature.type.ts
  • 目录名和文件名应该清楚的传递它们的意图。 例如,app/heroes/hero-list.component.ts 包含了一个用来管理英雄列表的组件。

使用点和横杠来分隔文件名

  • 坚持使用惯用的后缀来描述类型,包括 *.service、*.component、*.pipe、.module、.directive。 必要时可以创建更多类型名,但必须注意,不要创建太多。 | 符号名 | 文件名 | | --- | --- | | HeroDetailComponent | hero-detail.component.ts | | HeroesComponent | heroes.component.ts | | @Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { } | init-caps.pipe.ts | | | |

组件选择器

  • 坚持使用中线命名法(dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器。
  • 坚持使用带连字符的小写元素选择器值(例如 admin-users)。
  • 坚持为组件选择器添加自定义前缀。 例如,toh 前缀表示 Tour of Heroes(英雄指南),而前缀 admin 表示管理特性区。
  • 坚持使用前缀来识别特性区或者应用程序本身。
@Component({
  selector: "toh-hero",
})
export class HeroComponent {}

指令选择器

  • 坚持使用小驼峰形式命名指令的选择器。
  • 为何?可以让指令中的属性名与视图中绑定的属性名保持一致。
@Directive({
  selector: "[tohValidate]",
})
export class ValidateDirective {}

管道名 (pipe)

  • 坚持为所有管道使用一致的命名约定,用它们的特性来命名。 管道类名应该使用 UpperCamelCase(类名的通用约定),而相应的 name 字符串应该使用 lowerCamelCasename 字符串中不应该使用中线(“中线格式”或“烤串格式”)。 | 符号名 | 文件名 | | --- | --- | | @Pipe({ name: 'ellipsis' })) export class EllipsisPipe implements PipeTransform { } | ellipsis.pipe.ts | | @Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { } | init-caps.pipe.ts | | | |

单元测试文件名

  • 坚持测试规格文件名与被测试组件文件名相同。
  • 坚持测试规格文件名添加 .spec 后缀。

组件

heroes.component.spec.ts;

hero - list.component.spec.ts;

hero - detail.component.spec.ts;

服务

logger.service.spec.ts;

hero.service.spec.ts;

filter - text.service.spec.ts;

管道

ellipsis.pipe.spec.ts;

init - caps.pipe.spec.ts;

总体结构的指导原则

  • 坚持把所有源代码都放到名为 src 的目录里。
  • 坚持如果组件具有多个伴生文件 (.ts、.html、.css 和 .spec),就为它创建一个文件夹。

共享特性模块

  • 坚持在 shared 目录中创建名叫 SharedModule 的特性模块(例如在 app/shared/shared.module.ts 中定义 SharedModule)。
  • 坚持在共享模块中声明那些可能被特性模块引用的可复用组件、指令和管道。

内联输入和输出属性装饰器

  • 坚持 使用 @Input()@Output(),而非 @Directive@Component 装饰器的 inputsoutputs 属性:
  • 坚持把 @Input() 或者 @Output() 放到所装饰的属性的同一行。
@Component({
  selector: "toh-hero-button",
  template: `
    <button>{{ label }}</button>
  `,
})
export class HeroButtonComponent {
  @Output() heroChange = new EventEmitter<any>();
  @Input() label: string;
}

成员顺序

  • 坚持把属性成员放在前面,方法成员放在后面。
  • 坚持先放公共成员,再放私有成员,并按照字母顺序排列。
export class ToastComponent implements OnInit {
  // public properties
  message: string;
  title: string;

  // 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);
  }
}

把逻辑放到服务里

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();
  }
}

不要给输出属性加前缀

/* avoid */

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

<!-- avoid -->

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

<toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero>

把表现层逻辑放到组件类里

/* avoid */

@Component({
  selector: "toh-hero-list",
  template: `
    <section>
      Our list of heroes:
      <hero-profile *ngFor="let hero of heroes" [hero]="hero"> </hero-profile>
      Total powers: {{ totalPowers }}

      Average power: {{ totalPowers / heroes.length }}
    </section>
  `,
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
}

<!-- good -->
@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: number;

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

提供一个服务

  • 坚持在服务的 @Injectable 装饰器上指定通过应用的根注入器提供服务。坚持当使用类型作为令牌来注入服务的依赖时,使用 @Injectable() 类装饰器,而非 @Inject() 参数装饰器。
/* avoid */

export class HeroArena {
  constructor(
    @Inject(HeroService) private heroService: HeroService,
    @Inject(HttpClient) private http: HttpClient
  ) {}
}
/* good */
@Injectable()
export class HeroArena {
  constructor(private heroService: HeroService, private http: HttpClient) {}
}

管道 (Pipe)

public today=new Date();
<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>

大小写转换

<p>{{str | uppercase}}</p>
//转换成大写
<p>{{str | lowercase}}</p>
//转换成小写

日期格式转换

<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>

小数位数

<p>{{p | number:'1.2-4'}}</p>
JavaScript 对象序列化

JavaScript 对象序列化

<p>{{ { name: 'semlinker' } | json }}</p>
<!-- Output: { "name": "semlinker" } -->

slice

<p>{{ 'semlinker' | slice:0:3 | uppercase }}</p>
<!-- Output: SEM -->

自定义管道

  • WelcomePipe 定义
import { Pipe, PipeTransform } from "@angular/core";

@Pipe({ name: "welcome" })
export class WelcomePipe implements PipeTransform {
  transform(value: string): string {
    if (!value) return value;
    if (typeof value !== "string") {
      throw new Error("Invalid pipe argument for WelcomePipe");
    }
    return "Welcome to " + value;
  }
}
  • WelcomePipe 使用
<div>
  <p ngNonBindable>{{ 'semlinker' | welcome }}</p>
  <p>{{ 'semlinker' | welcome }}</p>
  <!-- Output: Welcome to semlinker -->
</div>
  • RepeatPipe 定义
import { Pipe, PipeTransform } from "@angular/core";

@Pipe({ name: "repeat" })
export class RepeatPipe implements PipeTransform {
  transform(value: any, times: number) {
    return value.repeat(times);
  }
}
  • RepeatPipe 使用
<div>
  <p ngNonBindable>{{ 'lo' | repeat:3 }}</p>
  <p>{{ 'lo' | repeat:3 }}</p>
  <!-- Output: lololo -->
</div>