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前綴之後緊跟着事件名

  • 堅持把表現層邏輯放進組件類中,而不要放在模板裏。堅持當你需要有表現層邏輯,但沒有模板時,使用屬性型指令。

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