about 2 years ago

本章重點:

  1. 當Component複雜後如何剝離重組Component
  2. 母子Comp溝通方式:Property Binding和Event Binding
  3. content概念與用法

chap3 from yuanchieh on Vimeo.

當程式碼越寫越多,Component邏輯更加複雜後,為了程式整潔與維護就必須剝離Component並重組,剝離Component主要依據業務的劃分,這裡我參考Structuring Applications with Components,裡頭提及Component應當分成兩類,一類是
Smart:充當Container Component,不涉及複雜業務邏輯,主要做Dumb Component的組合
Dumb:涉及UI繪製與邏輯操作等特定實體(entity)所結合,例如Form表單、表格List等

從現在開始要一步步打造Hero Project,主要就是一個英雄工作媒合平台,可以登入英雄,分配工作給英雄賺外快等
目前先實作英雄清單與查看英雄資料,以下是結構圖


首先專案結構為

接著再app.module.ts加入

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HeroDetailComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

app.component.ts

@Component({
  selector: 'my-app',
  templateUrl: './app/app.component.html',
  styleUrls:['./app/app.component.css']
})
export class AppComponent {
    private selectedHero:Hero;
    private heros:Array<Hero> = new Array();
    constructor(){..初始化heros和seletedHero..}
    handlerHelloFromDetail(ev){..處理HeroDetailComp觸發的事件..}
}

部分app.component.html

<my-hero-detail [selectedHero]="selectedHero" (onHelloFromDetail)="handlerHelloFromDetail($event)">
    <footer>Today is good day</footer>
    <div class="bad">Today is bad day</div>
</my-hero-detail>
<div class="hero-list">
    <h2>Hero list</h2>
    <ul>
        <li *ngFor="let h of heros">
            <span (click)="selectedHero = h"> {{h.name}} {{h.age}}</span>
        </li>
    </ul>
</div>

先忽略上半部的my-hero-detail,下半部則是Hero清單,運用上一篇的*ngFor創建一個個可以點擊的英雄item,點擊後改變選擇的英雄

Hero-Detail

當母Comp在清單點選英雄後,我希望上方的子Comp(Hero-Detail)可以跟著顯示相對應的英雄資料
hero-detail.component.ts

@Component({
  selector: 'my-hero-detail',
  templateUrl: './app/hero-detail/hero-detail.html',
  styleUrls:['./app/hero-detail/hero-detail.css']
})
export class HeroDetailComponent {
    @Input() selectedHero:Hero;
    @Output() onHelloFromDetail:EventEmitter<any> = new EventEmitter();
    constructor(){
    }

    onClick(ev){
        this.onHelloFromDetail.emit(ev);
    }
}

看到Input有沒有熟悉的感覺呢?這和上一章的Directive做法相同,可以從母Comp使用Property Binding方式讀值進來!
至於下一行Output則是讓子Comp可以將值透過觸發事件(EventEmitter)方式回報母Comp
使用方式如上述:先宣告一個 EventEmitter,接著在觸發事件的地方加入 EventEmitter.emit(value)
母Comp接收方式如下,就是把它當作一般的事件觸發就可以囉~
注意命名一樣可以在@Output('alias name')取別名

<my-hero-detail [selectedHero]="selectedHero" (onHelloFromDetail)="handlerHelloFromDetail($event)">

hero-detail.html

<div class="hero-detail">
    <h2>Hero list</h2>
    <div class="hero-detail-body">
        <img [style.background-image]="'url('+ (selectedHero.imgUrl || 'http://demo.sc.chinaz.com/Files/pic/icons/5285/260.png')+')'">
        <div class="hero-info">
            <p>Hero Name: {{selectedHero.name || 'unkown'}}</p>
            <p>Hero Age: {{selectedHero.age || 'unkown'}}</p>
            <button (click)="onClick(selectedHero.name)">Say Hello</button>
        </div>
    </div>
    <div class="footer">
        <ng-content select="footer"></ng-content>
    </div>
</div>

a.
在img部分,為了不要讓圖片有大有小,我改用background-image顯示,注意單引號括著的地方為單純字串,其餘為expression
b.
其餘資料都使用字串內插顯示

Content概念

在AppComp中,我們看到<my-hero-detail> Tag之間存在一些HTML元素,這在NG2中稱為Content,母Comp可以透過這種方式將定義好的HTML元素傳給子Comp,而子Comp則於<ng-content>之中顯示

<my-hero-detail ...>
    <footer>Today is good day</footer>
    <div class="bad">Today is bad day</div>
</my-hero-detail>
<ng-content select="footer"></ng-content>

有趣的是ng-content支援CSS selector,也就是說可以透過選擇class name或是HTML元素名稱來獨家顯示某些符合區塊
例如說上例就單純顯示 <footer>,所以圖片中Hero-Detail下方有Today is good day顯示,如果要顯示bad Day則改為

<ng-content select=".bad"></ng-content>

都顯示則去除select即可


可以看到 ng-content直接變成footer

總結

Component理當小而美,NG2也有提出Style Guide說 單個Comp盡量不要超過400行程式碼、函式不要超過70行
剝離Comp後如果剛好是母子Comp則可以透過Property Binding、Event Binding和content傳遞資料,但如果跨多層則需要其他方法,這就下集待續了
- 組件Component的基本組成: selector/styles/template
- 分離組件後的Property Binding(@Input $name <= [$name])、Event Binding( @Output() $eventEmitter => ($eventEmitter)="handleIt()" )
- 加入content

← Angular2系列文二-Directive Angular2系列文四-Form →
 
comments powered by Disqus