AngularJS2 笔记

AngularJS2 笔记

元数据

declarations

用于引入视图类(View Class)(指令、组件、管道), 只有可声明的类才能加到模块的declarations列表中。

不要声明:

  • 已经在其它模块中声明过的类。无论它来自应用自己的模块(@NgModule)还是第三方模块。
  • 从其它模块中导入的指令。例如,不要声明来自@angular/forms的FORMS_DIRECTIVES。
  • 模块类
  • 服务类
  • 非Angular的类和对象,比如:字符串、数字、函数、实体模型、配置、业务逻辑和辅助类。

imports

引入该模块依赖的其他模块或路由
- 要从@angular/common中导入CommonModule才能访问Angular的内置通用指令,比如NgIf和NgFor。
- 如果你的组件有[(ngModel)]双向绑定表达式这类表单指令,就要从@angular/forms中导入FormsModule。
- 表单指令模块包括FormsModule(包含NgModel等常用表单指令)、ReactiveFormsModule(包含FormControlDirective、FormGroupDirective等和InternalFormsSharedModule)、InternalFormsSharedModule(包含SelectControlValueAccessor等,无需主动使用,包含RequiredValidator等内置验证指令)
- BrowserModule提供了启动和运行浏览器应用的那些基本的服务提供商。(只能在根模块AppModule中导入BrowserModule。) BrowserModule还从@angular/common中重新导出了CommonModule,这意味着AppModule中的组件也同样可以访问那些每个应用都需要的Angular指令,如NgIf和NgFor。
- Angular不允许模块之间出现循环依赖,所以不要让模块’A’导入模块’B’,而模块’B’又导入模块’A’。

exports

导出那些其它模块希望在自己的模板中引用的可声明类(视图类)。这些也是你的公开类。 如果你不导出某个类,它就是私有的,只对当前模块中声明的其它组件可见。
可以导出任何可声明类(组件、指令和管道),而不用管它是声明在当前模块中还是某个导入的模块中。
可以重新导出整个导入过的模块,这将导致重新导出它们导出的所有类。模块甚至还可以导出它未曾导入过的模块

forRoot()

静态方法forRoot()是一个约定,它可以让开发人员更轻松的配置模块的提供商。
RouterModule.forRoot返回一个ModuleWithProviders对象。 我们把这个结果添加到根模块AppModule的imports列表中。
只能在应用的根模块AppModule中调用并导入.forRoot的结果。 在其它模块中导入它,特别是惰性加载模块中,是违反设计目标的并会导致一个运行时错误。
RouterModule也提供了静态方法forChild,用于配置惰性加载模块的路由。

providers

指定模块依赖的服务
列在引导模块的@NgModule.providers中的服务提供商具有全应用级作用域。 往NgModule.providers中添加服务提供商将导致该服务被发布到整个应用中。
当我们导入一个模块时,Angular就会把该模块的服务提供商(也就是它的providers列表中的内容)加入该应用的根注入器中。
和启动时就加载的模块中的提供商不同,惰性加载模块中的提供商是局限于模块的。

数据绑定

数据绑定方式

  • 属性绑定和插值表达式 组件类-> 模板
<!-- 插值表达式 -->
<img src="{{imgUrl}}" />

<!-- 属性绑定 -->
<img [src]="imgUrl" /> 
  • 事件绑定:模板 -> 组件类
 <a (click)="doClick($event)">点我</a>
  • 双向绑定: 模板 <-> 组件类
<input type="text" [(ngModel)]="name" />
  • 安全导航操作符 ( ?. )
    Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。
    下例中,当currentHero为空时,保护视图渲染器,让它免于失败。否则会报错
    The current hero’s name is {{currentHero?.name}}

原生 hidden 属性绑定数据

通过 [hidden]=”expression” 是用CSS 来控制 DOM 元素的显示状态, 就是简单的把元素设置成 display: none来控制元素显示状态,很容易意外的被其他样式覆盖掉。
我们通常使用 *ngIf 切换元素存在状态来完成相同目标:

<div *ngIf="showGreeting">
   Hello, there!
</div>

和原生 hidden 属性不同, angular2 中的 *ngIf 不受样式约束。无论你写了什么样的CSS,都是安全的。
*ngIf 并不是控制元素的显示状态,而是直接通过从模版中增加/删除元素该元素来达成显示与否这一效果的。

ngFor指令&&trackBy

<!--可获取index索引变量-->
<div *ngFor="let contact of contacts; let i= index">{{i+1}} - {{contact.name}}</div>

<!--根据id检查属性变化,若变化则更新DOM,未变化则留下DOM,避免重复渲染-->
<div *ngFor="let contact of contacts; trackBy: trackByContacts">{{contact.name}}</div>

//trackByContacts(index:number, contact:Contact){
    return contact.id;
}

父组件、子组件通讯

@input装饰器

@input装饰器定义 输入型属性,把数据从父组件传到子组件

父组件:

@Component({
  selector: 'parent',
  template: `
    <child [major]="major"></child>
  `
})
export class HeroParentComponent {
  major: number = 1;
}

子组件:

@Component({
  selector: 'child',
  template: `
    <h3>Version {{major}}.{{minor}}</h3>
  `
})
export class ChildComponent implements OnChanges {
  @Input() major: number;
  }

父组件监听子组件的事件

子组件暴露一个EventEmitter属性,当事件发生时,子组件利用该属性emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。子组件的EventEmitter属性是一个输出属性。
子组件

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'child',
  template: `
    <h4>{{name}}</h4>
    <button (click)="vote(true)">Agree</button>
    <button (click)="vote(false)">Disagree</button>
  `
})
export class ChildComponent {
  @Input()  name: string;
  @Output() onVoted = new EventEmitter<boolean>();
  vote(agreed: boolean) {
    this.onVoted.emit(agreed);
  }
}

父组件

import { Component } from '@angular/core';
@Component({
  selector: 'parent',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <child (onVoted)="onVoted($event)">
    </child>
  `
})
export class ParentComponent {
  agreed = 0;
  disagreed = 0;
  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}

点击按钮会触发true或false(布尔型有效载荷)的事件。
父组件绑定了一个事件处理器(onVoted()),用来响应子组件的事件($event)并更新一个计数器。

本地变量通讯

在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法,如下例所示:

import { Component } from '@angular/core';
import { ChildComponent }  from './child.component';
@Component({
  selector: 'parent',
  template: `
  <button (click)="child.start()">Start</button>
  <button (click)="child.stop()">Stop</button>
  <div class="seconds">{{child.seconds}}</div>
  <child #child></child>
  `
})
export class ParentComponent { }

本地变量#child,这个本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
如果父组件的需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。
当父组件需要这种访问时,可以把子组件作为ViewChild,注入到父组件里面:

import { AfterViewInit, ViewChild } from '@angular/core';
import { Component }                from '@angular/core';
import { ChildComponent }  from './child.component';
@Component({
  selector: 'parent',
  template: `
  <button (click)="start()">Start</button>
  <button (click)="stop()">Stop</button>
  <div class="seconds">{{ seconds() }}</div>
  <child></child>
  `,
  styleUrls: ['demo.css']
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent)
  private childComponent: ChildComponent;
  seconds() { return 0; }
  ngAfterViewInit() {
    setTimeout(() => this.seconds = () => this.childComponent.seconds, 0);
  }
  start() { this.childComponent.start(); }
  stop() { this.childComponent.stop(); }
}

子组件获取父组件实例

找到已知类型的父组件

//父组件
@Component({
  selector: 'parent',
  template: `
    <div class="a">
      <h3>{{name}}</h3>
      <child></child>
    </div>`,
})
export class ParentComponent
{
  name= 'Alex';
}

//子组件
import {ParentComponent} from './parent.component';
@Component({
  selector: 'child',
  template: `
  <div class="c">
    <h3>Cathy</h3>
    {{parent ? 'Found' : 'Did not find'}} Alex via the component class.<br>
  </div>`
})
export class ChildComponent {
  constructor( @Optional() public parent: ParentComponent ) { }
}

未知父组件类型

通过类-接口找到父组件

//创建Parent抽象类
export abstract class Parent {
    name:string;
}

//父组件
//用useExisting注入ParentComponent
//ParentComponent引用了自身,造成循环引用,使用forwardRef打破了该循环
@Component({
  selector: 'parent',
  template: `
    <div class="a">
      <h3>{{name}}</h3>
      <child></child>
    </div>`,
    providers:[{provide: Parent, useExisting: forwardRef(() => ParentComponent)}]
})
export class ParentComponent implements Parent
{
  name= 'Alex';
}

//子组件
//通过Parent标识找到父组件实例
export class ChildComponent {
  name= 'Child';
  constructor( @Optional() public parent: Parent ) { }
}

Angular的forwardRef()函数建立一个间接地引用,Angular可以随后解析。

管道

自定义管道

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myNewPipe'
})
export class MyNewPipePipe implements PipeTransform {
//必须实现接口的transform方法
  transform(value: string, args?: any): any {
  switch(value){
    case 'male':return '男';
    case 'female':return '女';
    default:return '';
  }
 }
}

纯管道&&非纯管道

  • 纯管道
@Component({
  selector:'pure-pipe-demo',
  template:`
    <div>
      <p>{{dateObj|date}}</p>
      <p>{{dateStr|date}}</p>
    </div>
  `
})
export class PurePipeDemoComponent{
  dateObj:date=new Date('2017-07-17 20:00:00');
  dateStr:string='2017-07-17 20:00:00';
  constructor(){
    setTimeout(()=>{
      this.dateObj.setMonth(11);
      this.dateStr='2017-12-17 20:00:00';
    },2000);
  }
}
//页面显示结果:
最初:2017-07-17 20:00:00 Monday
2017-07-17 20:00:00 Monday

两秒后:2017-07-17 20:00:00 Monday
2017-12-17 20:00:00 Sunday

以上使用内置管道date,模板中日期都改为12月,确只有dateStr在页面上发生了变化。
因为,date管道默认为纯管道,只有当输入值发生纯改变(基本数据类型值变化或对象引用改变)时才会调用管道更新变化的值。
应用纯管道可以优化性能,忽略对象内部属性变化,不会频繁变化检测;但不一定适用所有的场景。

  • 非纯管道
//管道元数据的pure属性定义为false(默认为true),则是非纯管道
@Pipe({
  name: 'myNewPipe',
  pure:false
})

Angular组件的每个变化检测周期都会调用非纯管道,每个周期都会去检测并执行transform方法

指令

内置指令包括通用指令、路由指令、表单指令。
表单指令模块包括FormsModule(包含NgModel等常用表单指令)、ReactiveFormsModule(包含FormControlDirective、FormGroupDirective等和InternalFormsSharedModule)、InternalFormsSharedModule(包含SelectControlValueAccessor等,无需主动使用,包含RequiredValidator等内置验证指令)。

结构指令

结构指令指可以改变DOM树结构的指令,如内置的NgIf。
自定义结构指令,类似NgIf:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

/**
 * Add the template content to the DOM unless the condition is true.
 */
@Directive({ selector: '[myUnless]'})
export class UnlessDirective {
  private hasView = false;

    //TemplateRef用来访问组件的模板
    //ViewContainerRef作为视图内容的渲染器,可将模板内容插入DOM
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }
//根据绑定结果增加或者删除模板内容
  @Input() set myUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}
//模板中使用
<p *myUnless="true">
xxxx
</p>

使得自定义指令可以响应用户操作,在事件处理函数上添加@HostListener装饰器:

import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
  selector: '[myHighlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) { }
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight('yellow');
  }
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }

依赖注入

模块中注入的服务作用域是应用程序级的,在@NgModule里注册的服务默认可在整个应用程序中可用;在根组件注入的服务是组件级的,在根组件和其子组件中共享一个服务实例。
延迟加载的模块是一个特例,延迟加载使得应用程序在启动时没有载入模块,而是结合路由配置,在需要时才动态加载相应模块,创建新的注入器,注入的依赖只在模块内部可见。
在根组件还是子组件中进行服务注入呢? 取决于想让注入的依赖服务具有全局性还是局部性,每个注入器总是将它提供的服务维持单例。

注入到派生类

当编写一个继承自另一个组件的组件时,要格外小心。如果基础组件有依赖注入,必须要在派生类中重新提供和重新注入它们,并将它们通过super()构造函数传给基类。因为派生类不能继承父类组件的注入器

//父类组件
@Component({
  selector: 'unsorted-heroes',
  template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`,
  providers: [HeroService]
})
export class HeroesBaseComponent implements OnInit {
  constructor(private heroService: HeroService) { }
  heroes: Array<Hero>;
  ngOnInit() {
    this.heroes = this.heroService.getAllHeroes();
    this.afterGetHeroes();
  }
  // Post-process heroes in derived class override.
  protected afterGetHeroes() {}
}

//派生类组件
@Component({
  selector: 'sorted-heroes',
  template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`,
  providers: [HeroService]
})
export class SortedHeroesComponent extends HeroesBaseComponent {
  constructor(heroService: HeroService) {
    super(heroService);
  }
  protected afterGetHeroes() {
    this.heroes = this.heroes.sort((h1, h2) => {
      return h1.name < h2.name ? -1 :
            (h1.name > h2.name ? 1 : 0);
    });
  }
}

ps: Angular官方推荐最佳实践是让构造函数保持简单。它们应该只用来初始化变量,业务逻辑放到ngOnInit中处理。这个规则会帮助我们在测试环境中放心的构造组件,以免在构造它们时,无意做了一些非常戏剧化的动作(比如连接服务)。

限定方式的依赖注入

当组件注入一个依赖时,Angular从该组件本身的注入器开始,沿着依赖注入器的树往上找(从当前组件向父级组件),直到找到第一个符合要求的提供商。如果Angular不能在这个过程中找到合适的依赖,它就会抛出一个错误。

@Optional可以兼容依赖不存在的情况;@Host可以限定查找规则,明确实例初始化的位置。
当Angular找不到依赖时,@Optional装饰器会告诉Angular继续执行。Angular把此注入参数设置为null(而不用默认的抛出错误的行为)。
@Host装饰器将把往上搜索的行为截止在宿主组件(如果一个组件注入了依赖,那么这个组件就是这个依赖项的宿主组件。但当这个组件被嵌入进一个父组件后,这个父组件就变成了该依赖项的宿主组件。)
例,父组件为宿主组件的情况:

//父类组件
@Component({
  selector: 'contact-list',
  template: `
<h1>Contact List</h1>
<contact-a></contact-a>
`,
  providers: [LoggerService]
})
export class ContactListComponent {
  constructor(logger: LoggerService) {
    logger.logInfo('Creating ContactListComponent');
  }
}


//子类组件
@Component({
  selector: 'contact-a',
  template: `
<div>ContactA</div>
`
})
export class ContactAComponent {
  constructor(
  @Host
  @Optional
  logger: LoggerService
  ) {
    logger.logInfo('Creating ContactAComponent');
  }
}

Provider注册方式

类 Provider

providers:[
{ provide: HeroService,   useClass:    HeroService },
{ provide: LoggerService, useClass:    DateLoggerService }
]

//第一个提供商是展开了语法糖的完整形式,是一个典型情况的展开。
//缩写形式:providers:[HeroService]

//第二种提供商使用DateLoggerService来满足LoggerService。该LoggerService在AppComponent级别已经被注册。当这个组件要求LoggerService的时候,它得到的却是DateLoggerService服务。DateLoggerService从LoggerService继承;它把当前的日期/时间附加到每条信息上。

@Injectable()
export class DateLoggerService extends LoggerService
{
  logInfo(msg: any)  { super.logInfo(stamp(msg)); }
  logDebug(msg: any) { super.logInfo(stamp(msg)); }
  logError(msg: any) { super.logError(stamp(msg)); }
}

function stamp(msg: any) { return msg + ' at ' + new Date(); }

值Provider

在实际项目中,依赖的对象不一定是类,也可以是常量、字符串、对象等其他数据类型,可以方便用在全局变量、系统相关参数配置等场景中。

const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');
//值Provider依赖的值(useValue)必须在当前或providers元数据配置之前定义。
......

providers:[
  { provide: Hero, useValue: someHero },//对象
  { provide: TITLE, useValue: 'Hero of the Month' }//字符串
]

别名Provider

别名Provider可以在一个Provider中配置多个标识,其对应的对象指向同一个实例。
假如应用已有一个日志服务OldLoggerService,现在已经开发了有相同接口的新版服务NewLoggerService,考虑到重构的代价,并不想替换OldLoggerService服务被使用的地方。此时让新旧服务标识同时使用:

providers:[
  { provide: NewLoggerService, useClass: NewLoggerService },
  { provide: OldLoggerService, useClass: NewLoggerService }
]

// 但上述两个NewLoggerService 对应不同的实例
// 可以使用useExisting使得多个标识指向同一个实例

providers:[
  { provide: NewLoggerService, useClass: NewLoggerService },
  { provide: OldLoggerService, useExisting: NewLoggerService }
]

工厂Provider

有时候依赖对象是不明确且动态变化的,可能需要根据运行环境、执行权限来生成。工厂Provider通过暴露一个工厂方法,返回最终依赖的对象。
假设这样一个场景:有些联系人的信息是保密的,只有特定权限的人才能看到,所以需要对每个登录用户进行鉴权。可以在构造函数中通过一个布尔值来判断是否有权限并返回对应的服务,在返回的服务中根据这个布尔值来判断是否显示联系人信息,实例代码如下:

let contactServiceFactory =(_logger:LoggerService, _userService:UserService)=>{
  return new contactService(_logger,_userService.user.isAuthorized);
}
export let contactServiceProvider=
{ provide: ContactService, useFactory:  contactServiceFactory,  deps: [LoggerService, UserService] };

//useFactory声明Provider是一个工厂方法;deps是一个数组属性,指定所需要的依赖,可注入到工厂方法中

Angular官方建议风格标准

  • 坚持用const声明变量,除非它们的值在应用的生命周期内会发生变化。告诉读者这个值是不可变的。

  • 考虑 把常量名拼写为小驼峰格式。小驼峰变量名 (heroRoutes) 比传统的大写蛇形命名法 (HERO_ROUTES) 更容易阅读和理解。

  • 坚持使用小写驼峰命名法来命名属性和方法。避免为私有属性和方法添加下划线前缀。

  • 坚持在第三方导入和应用导入之间留一个空行。

  • 坚持命名文件到这个程度:看到名字立刻知道它包含了什么,代表了什么。坚持文件名要具有说明性,确保文件中只包含一个组件。

  • 坚持尽可能保持扁平的目录结构。

  • 坚持为应用中每个明显的特性创建一个 Angular 模块。坚持把特性模块放在与特性区同名的目录中(例如app/heroes)。

  • 坚持在shared目录中创建名叫SharedModule的特性模块,坚持在共享模块中声明那些可能被特性模块引用的可复用组件、指令和管道

  • 避免 在共享模块中提供服务。服务通常是单例的,应该在整个应用或一个特定的特性模块中只有一份

  • 坚持在SharedModule中导入所有模块都需要的资产(例如CommonModule和FormsModule)。坚持从SharedModule中导出其它特性模块所需的全部符号

  • 坚持在core目录下创建一个名叫CoreModule的特性模块,坚持把要共享给整个应用的单例服务放进CoreModule中(例如ExceptionService和LoggerService)。只在应用启动时从AppModule中导入它一次,以后再也不要导入它,避免在AppModule之外的任何地方导入CoreModule

  • 坚持把惰性加载特性下的内容放进惰性加载目录中。 典型的惰性加载目录包含路由组件及其子组件以及与它们有关的那些资产和模块。避免让兄弟模块和父模块直接导入惰性加载特性中的模块。

  • 坚持当超过 3 行时,把模板和样式提取到一个单独的文件。

  • 坚持指定相对于模块的 URL ,给它加上./前缀。./前缀是相对URL的标准语法,不必依赖Angular的特殊处理,如果没有前缀则不行。

  • 坚持把@Input()或者@Output()放到所装饰的属性的同一行。避免除非有重要目的,否则不要为输入和输出指定别名。

  • 坚持把属性成员放在前面,方法成员放在后面。坚持先放公共成员,再放私有成员,并按照字母顺序排列。

  • 坚持在组件中只包含与视图相关的逻辑。所有其它逻辑都应该放到服务中。坚持把可重用的逻辑放到服务中,保持组件简单,聚焦于它们预期目的。

  • 坚持命名事件时,不要带前缀on。坚持把事件处理器方法命名为on前缀之后紧跟着事件名

  • 坚持把表现层逻辑放进组件类中,而不要放在模板里。坚持当你需要有表现层逻辑,但没有模板时,使用属性型指令。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章