Angular 2 Component Inheritance

Angular 2.3 版本中引入了組件繼承的功能,該功能很是強大,可以大大增長咱們組件的可複用性。javascript

Component Inheritance

組件繼承涉及如下的內容:html

  • Metadata:如 @Input()@Output()@ContentChild/Children@ViewChild/Children 等。在派生類中定義的元數據將覆蓋繼承鏈中的任何先前的元數據,不然將使用基類元數據。java

  • Constructor:若是派生類未聲明構造函數,它將使用基類的構造函數。這意味着在基類構造函數注入的全部服務,子組件都能訪問到。git

  • Lifecycle hooks:若是基類中包含生命週期鉤子,如 ngOnInit、ngOnChanges 等。儘管在派生類沒有定義相應的生命週期鉤子,基類的生命週期鉤子會被自動調用。程序員

須要注意的是,模板是不能被繼承的 ,所以共享的 DOM 結構或行爲須要單獨處理。瞭解詳細信息,請查看 - properly support inheritancegithub

接下來咱們來快速體驗的組件繼承的功能並驗證以上的結論,具體示例以下(本文全部示例基於的 Angular 版本是 - 4.0.1):typescript

exe-base.component.ts編程

import { Component, ElementRef, Input, HostBinding, HostListener, OnInit } from '@angular/core';

@Component({
    selector: 'exe-base',
    // template will not be inherited 
    template: `
    <div>
       exe-base:我是base組件麼? - {{isBase}}
    </div>
  `
})
export class BaseComponent implements OnInit {
    @Input() isBase: boolean = true;

    @HostBinding('style.color') color = 'blue'; // will be inherited 

    @HostListener('click', ['$event']) // will be inherited 
    onClick(event: Event) {
        console.log(`I am BaseComponent`);
    }

    constructor(protected eleRef: ElementRef) { }

    ngOnInit() {
        console.dir('BaseComponent:ngOnInit method has been called');
    }
}

exe-inherited.component.ts瀏覽器

import { Component, HostListener, OnChanges, SimpleChanges } from '@angular/core';
import { BaseComponent } from './exe-base.component';

@Component({
    selector: 'exe-inherited',
    template: `
    <div>
      exe-inherited:我是base組件麼? - {{isBase}}
    </div>
  `
})
export class InheritedComponent extends BaseComponent
    implements OnChanges {

    @HostListener('click', ['$event']) // overridden
    onClick(event: Event) {
        console.log(`I am InheritedComponent`);
    }

    ngOnChanges(changes: SimpleChanges) {
        console.dir(this.eleRef); // this.eleRef.nativeElement:exe-inherited
    }
}

app.component.ts網絡

import { Component, OnInit } from '@angular/core';
import {ManagerService} from "./manager.service";

@Component({
  selector: 'exe-app',
  template: `
    <exe-base></exe-base>
    <hr/>
    <exe-inherited [isBase]="false"></exe-inherited>
  `
})
export class AppComponent {
  currentPage: number = 1;
  totalPage: number = 5;
}

圖片描述

(備註:BaseComponent 中 ngOnInit() 鉤子被調用了兩次哦)

接下來咱們簡要討論一個可能使人困惑的主題,@Component() 中元數據是否容許繼承?答案是否認的,子組件是不能繼承父組件裝飾器中元數據。限制元數據繼承,從根本上說,是有道理的,由於咱們的元數據用是來描述組件類的,不一樣的組件咱們是須要不一樣的元數據,如 selectortemplate 等。Angular 2 組件繼承主要仍是邏輯層的複用,具體能夠先閱讀完下面實戰的部分,再好好體會一下哈。

Component Inheritance In Action

如今咱們先來實現一個簡單的分頁組件,預期的效果以下:

圖片描述

(圖片來源 - https://scotch.io/tutorials/c...

具體實現代碼以下:

simple-pagination.component.ts

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

@Component({
    selector: 'simple-pagination',
    template: `
       <button (click)="previousPage()" [disabled]="!hasPrevious()">Previous</button> 
       <button (click)="nextPage()" [disabled]="!hasNext()">Next</button>
       <p>page {{ page }} of {{ pageCount }} </p>
    `
})

export class SimplePaginationComponent {
    @Input() pageCount: number;

    @Input() page: number;

    @Output() pageChanged = new EventEmitter<number>();

    nextPage() {
        this.pageChanged.emit(++this.page);
    }

    previousPage() {
        this.pageChanged.emit(--this.page);
    }

    hasPrevious(): boolean {
        return this.page > 1;
    }

    hasNext(): boolean {
        return this.page < this.pageCount;
    }
}

app.component.ts

import { Component, OnInit } from '@angular/core';
import {ManagerService} from "./manager.service";

@Component({
  selector: 'exe-app',
  template: `
   <simple-pagination [page]="currentPage" [pageCount]="totalPage"></simple-pagination>
  `
})
export class AppComponent {
  currentPage: number = 2;
  totalPage: number = 10;
}

假設咱們如今想更換分頁組件的風格,以下圖所示:

圖片描述

(圖片來源 - https://scotch.io/tutorials/c...

咱們發現 UI 界面風格已經徹底不同了,但仔細想一下組件分頁的控制邏輯仍能夠繼續使用。Angular 團隊也考慮到了這種場景,所以爲咱們引入組件繼承的特性,這對咱們開發者來講,能夠大大地提升組件的複用性。接下來咱們來一步步實現新的分頁組件,首先先更新 UI 界面,具體代碼以下:

exe-pagination.component.ts

import { Component } from '@angular/core';
import { SimplePaginationComponent } from './simple-pagination.component';

@Component({
    selector: 'exe-pagination',
    template: `
    <a (click)="previousPage()" [class.disabled]="!hasPrevious()" 
      href="javascript:void(0)">
      ««
    </a> 
    <span>{{ page }} / {{ pageCount }}</span>
    <a (click)="nextPage()" [class.disabled]="!hasNext()"
      href="javascript:void(0)" >
      »»
    </a>
  `
})
export class ExePaginationComponent extends SimplePaginationComponent {
    
}

上面代碼中,有幾個注意點:

  • 首先咱們先導入已開發完的 SimplePaginationComponent 組件類

  • 而後讓咱們新定義的 ExePaginationComponent 類繼承於 SimplePaginationComponent 類

  • 接着咱們更新頁面的視圖模板,把按鈕替換爲 <<>>

  • 咱們看到更新的視圖模板,咱們仍然可使用基類 (SimplePaginationComponent) 中定義的全部輸入、輸出屬性

再繼續開發 ExePaginationComponent 組件前,咱們先來更新一下 SimplePaginationComponent 組件:

@Component({
  selector: 'simple-pagination',
  template: `
    <button (click)="previousPage()" [disabled]="!hasPrevious()">{{ previousText }}</button> 
    <button (click)="nextPage()" [disabled]="!hasNext()">{{ nextText }}</button>

    <p>page {{ page }} of {{ pageCount }}</p>
  `
})
export class SimplePaginationComponent {
  ...

  @Input()
  previousText = 'Previous';

  @Input()
  nextText = 'Next';

  ...

}

注意:

  • 當用戶沒有設置 previousText 輸入屬性值時,咱們使用的默認值是 'Previous'

  • 當用戶沒有設置 nextText 輸入屬性值時,咱們使用的默認值是 'Next'

對於 ExePaginationComponent 組件,咱們也但願讓用戶自定義 previousText 和 nextText 的值,但它們對應的默認值是:'<<' 和 '>>',這時咱們能夠覆蓋 SimplePaginationComponent 組件的輸入屬性,具體示例以下:

import { Component , Input, Output} from '@angular/core';
import { SimplePaginationComponent } from './simple-pagination.component';

@Component({
    selector: 'exe-pagination',
    template: `
    <a (click)="previousPage()" [class.disabled]="!hasPrevious()" 
      href="javascript:void(0)">
      ««
    </a> 
    <span>{{ page }} / {{ pageCount }}</span>
    <a (click)="nextPage()" [class.disabled]="!hasNext()"
      href="javascript:void(0)" >
      »»
    </a>
  `
})
export class ExePaginationComponent extends SimplePaginationComponent {
    @Input() previousText = '<<'; // override default text
    @Input() nextText = '>>'; // override default text
}

以上代碼成功運行後,瀏覽器的輸出結果以下:

圖片描述

功能已經實現了,但有時候咱們想在分頁中顯示一個標題,且支持用戶自定義該標題,那就得在現有組件的基礎上,再新增一個 title 輸入屬性,調整後的代碼以下:

import { Component , Input, Output} from '@angular/core';
import { SimplePaginationComponent } from './simple-pagination.component';

@Component({
    selector: 'exe-pagination',
    template: `
     <h2>{{ title }}</h2>
    <a (click)="previousPage()" [class.disabled]="!hasPrevious()" 
      href="javascript:void(0)">
      ««
    </a> 
    <span>{{ page }} / {{ pageCount }}</span>
    <a (click)="nextPage()" [class.disabled]="!hasNext()"
      href="javascript:void(0)" >
      »»
    </a>
  `
})
export class ExePaginationComponent extends SimplePaginationComponent {
    @Input() previousText = '<<'; // override default text
    @Input() nextText = '>>'; // override default text
    @Input() title: string; // title input for child component only
}

我有話說

1.面向對象編程中類的概念?

傳統的 JavaScript 程序使用函數和基於原型的繼承來建立可重用的組件,但對於熟悉使用面向對象方式的程序員來說就有些棘手,由於他們用的是基於類的繼承而且對象是由類構建出來的。 從 ECMAScript 2015,也就是ECMAScript 6 開始,JavaScript 程序員將可以使用基於類的面向對象的方式。 使用 TypeScript,咱們容許開發者如今就使用這些特性,而且編譯後的 JavaScript 能夠在全部主流瀏覽器和平臺上運行,而不須要等到下個JavaScript 版本。

類的概念

雖然 JavaScript 中有類的概念,可是可能大多數 JavaScript 程序員並非很是熟悉類,這裏對類相關的概念作一個簡單的介紹。

  • 類 (Class):一種面向對象計算機編程語言的構造,是建立對象的藍圖,描述了所建立的對象共同的屬性和方法。

  • 對象 (Object):類的實例,經過 new 建立

  • 面向對象 (OOP) 的三大特性:封裝、繼承、多態

  • 封裝 (Encapsulation):將對數據的操做細節隱藏起來,只暴露對外的接口。外界調用端不須要知道細節,就能經過對外提供的接口來訪問該對象,同時也保證了外界沒法任意更改對象內部的數據

  • 繼承 (Inheritance):子類繼承父類,子類除了擁有父類的全部特性外,還能夠擴展自有的功能特性

  • 多態 (Polymorphism):由繼承而產生了相關的不一樣的類,對同一個方法能夠有不一樣的響應。好比 CatDog 都繼承自 Animal,可是分別實現了本身的 eat() 方法。此時針對某一個實例,咱們無需瞭解它是 Cat 仍是 Dog,就能夠直接調用 eat() 方法,程序會自動判斷出來應該如何執行 eat()

  • 存取器(getter & setter):用於屬性的讀取和賦值

  • 修飾符(Modifiers):修飾符是一些關鍵字,用於限定成員或類型的性質。好比 public 表示公有屬性或方法

  • 抽象類(Abstract Class):抽象類是供其餘類繼承的基類,抽象類不容許被實例化。抽象類中的抽象方法必須在子類中被實現

  • 接口(Interfaces):不一樣類之間公有的屬性或方法,能夠抽象成一個接口。接口能夠被類實現(implements)。一個類只能繼承自另外一個類,可是能夠實現多個接口。

TypeScript 類示例

class Greeter {
    private greeting: string; // 定義私有屬性,訪問修飾符:public、protected、private
    constructor(message: string) { // 構造函數,通常執行用於進行數據初始化操做
        this.greeting = message;
    }
    greet() { // 定義方法
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

2.面向對象編程中繼承的概念是什麼?

繼承(英語:inheritance)是面向對象軟件技術當中的一個概念。若是一個類別A 「繼承自」 另外一個類別B,就把這個A稱爲 「B的子類別」,而把B稱爲「A的父類別」也能夠稱 「B是A的超類」。繼承可使得子類別具備父類別的各類屬性和方法,而不須要再次編寫相同的代碼。在令子類別繼承父類別的同時,能夠從新定義某些屬性,並重寫某些方法,即覆蓋父類別的原有屬性和方法,使其得到與父類別不一樣的功能。另外,爲子類別追加新的屬性和方法也是常見的作法。 通常靜態的面向對象編程語言,繼承屬於靜態的,意即在子類別的行爲在編譯期就已經決定,沒法在執行期擴充。 —— 維基百科

繼承 (Inheritance) 是一種聯結類與類的層次模型。指的是一個類 (稱爲子類、子接口) 繼承另外的一個類 (稱爲父類、父接口) 的功能,並能夠增長它本身的新功能的能力,繼承是類與類或者接口與接口之間最多見的關係;繼承是一種 is-a 關係。

圖片描述

(圖片來源網絡)

TypeScript 類繼承示例

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
sam.move();

參考資源

相關文章
相關標籤/搜索