Angular9 組件交互

2020-07-01 13:54 更新

通過輸入型綁定把數(shù)據(jù)從父組件傳到子組件

HeroChildComponent 有兩個輸入型屬性,它們通常帶 @Input 裝飾器。

Path:"component-interaction/src/app/hero-child.component.ts" 。

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


import { Hero } from './hero';


@Component({
  selector: 'app-hero-child',
  template: `
    <h3>{{hero.name}} says:</h3>
    <p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
  `
})
export class HeroChildComponent {
  @Input() hero: Hero;
  @Input('master') masterName: string;
}

第二個 @Input 為子組件的屬性名 masterName 指定一個別名 master(譯者注:不推薦為起別名,請參見風(fēng)格指南).

父組件 HeroParentComponent 把子組件的 HeroChildComponent 放到 *ngFor 循環(huán)器中,把自己的 master 字符串屬性綁定到子組件的 master 別名上,并把每個循環(huán)的 hero 實(shí)例綁定到子組件的 hero 屬性。

Path:"component-interaction/src/app/hero-parent.component.ts" 。

import { Component } from '@angular/core';


import { HEROES } from './hero';


@Component({
  selector: 'app-hero-parent',
  template: `
    <h2>{{master}} controls {{heroes.length}} heroes</h2>
    <app-hero-child *ngFor="let hero of heroes"
      [hero]="hero"
      [master]="master">
    </app-hero-child>
  `
})
export class HeroParentComponent {
  heroes = HEROES;
  master = 'Master';
}

運(yùn)行應(yīng)用程序會顯示三個英雄:

端到端測試,用于確保所有的子組件都如預(yù)期般初始化并顯示出來:

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
let _heroNames = ['Dr IQ', 'Magneta', 'Bombasto'];
let _masterName = 'Master';


it('should pass properties to children properly', function () {
  let parent = element.all(by.tagName('app-hero-parent')).get(0);
  let heroes = parent.all(by.tagName('app-hero-child'));


  for (let i = 0; i < _heroNames.length; i++) {
    let childTitle = heroes.get(i).element(by.tagName('h3')).getText();
    let childDetail = heroes.get(i).element(by.tagName('p')).getText();
    expect(childTitle).toEqual(_heroNames[i] + ' says:');
    expect(childDetail).toContain(_masterName);
  }
});
// ...

通過 setter 截聽輸入屬性值的變化

使用一個輸入屬性的 setter,以攔截父組件中值的變化,并采取行動。

子組件 NameChildComponent 的輸入屬性 name 上的這個 setter,會 trim 掉名字里的空格,并把空值替換成默認(rèn)字符串。

Path:"component-interaction/src/app/name-child.component.ts" 。

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


@Component({
  selector: 'app-name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
  private _name = '';


  @Input()
  set name(name: string) {
    this._name = (name && name.trim()) || '<no name set>';
  }


  get name(): string { return this._name; }
}

下面的 NameParentComponent 展示了各種名字的處理方式,包括一個全是空格的名字。

Path:"component-interaction/src/app/name-parent.component.ts" 。

import { Component } from '@angular/core';


@Component({
  selector: 'app-name-parent',
  template: `
  <h2>Master controls {{names.length}} names</h2>
  <app-name-child *ngFor="let name of names" [name]="name"></app-name-child>
  `
})
export class NameParentComponent {
  // Displays 'Dr IQ', '<no name set>', 'Bombasto'
  names = ['Dr IQ', '   ', '  Bombasto  '];
}

端到端測試:輸入屬性的 setter,分別使用空名字和非空名字。

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
it('should display trimmed, non-empty names', function () {
  let _nonEmptyNameIndex = 0;
  let _nonEmptyName = '"Dr IQ"';
  let parent = element.all(by.tagName('app-name-parent')).get(0);
  let hero = parent.all(by.tagName('app-name-child')).get(_nonEmptyNameIndex);


  let displayName = hero.element(by.tagName('h3')).getText();
  expect(displayName).toEqual(_nonEmptyName);
});


it('should replace empty name with default name', function () {
  let _emptyNameIndex = 1;
  let _defaultName = '"<no name set>"';
  let parent = element.all(by.tagName('app-name-parent')).get(0);
  let hero = parent.all(by.tagName('app-name-child')).get(_emptyNameIndex);


  let displayName = hero.element(by.tagName('h3')).getText();
  expect(displayName).toEqual(_defaultName);
});
// ...

通過ngOnChanges()來截聽輸入屬性值的變化

使用 OnChanges 生命周期鉤子接口的 ngOnChanges() 方法來監(jiān)測輸入屬性值的變化并做出回應(yīng)。

&當(dāng)需要監(jiān)視多個、交互式輸入屬性的時候,本方法比用屬性的 setter 更合適。

這個 VersionChildComponent 會監(jiān)測輸入屬性 majorminor 的變化,并把這些變化編寫成日志以報告這些變化。

Path:"component-interaction/src/app/version-child.component.ts" 。

import { Component, Input, OnChanges, SimpleChange } from '@angular/core';


@Component({
  selector: 'app-version-child',
  template: `
    <h3>Version {{major}}.{{minor}}</h3>
    <h4>Change log:</h4>
    <ul>
      <li *ngFor="let change of changeLog">{{change}}</li>
    </ul>
  `
})
export class VersionChildComponent implements OnChanges {
  @Input() major: number;
  @Input() minor: number;
  changeLog: string[] = [];


  ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
    let log: string[] = [];
    for (let propName in changes) {
      let changedProp = changes[propName];
      let to = JSON.stringify(changedProp.currentValue);
      if (changedProp.isFirstChange()) {
        log.push(`Initial value of ${propName} set to ${to}`);
      } else {
        let from = JSON.stringify(changedProp.previousValue);
        log.push(`${propName} changed from ${from} to ${to}`);
      }
    }
    this.changeLog.push(log.join(', '));
  }
}

VersionParentComponent 提供 minormajor 值,把修改它們值的方法綁定到按鈕上。

Path:"component-interaction/src/app/version-parent.component.ts" 。

import { Component } from '@angular/core';


@Component({
  selector: 'app-version-parent',
  template: `
    <h2>Source code version</h2>
    <button (click)="newMinor()">New minor version</button>
    <button (click)="newMajor()">New major version</button>
    <app-version-child [major]="major" [minor]="minor"></app-version-child>
  `
})
export class VersionParentComponent {
  major = 1;
  minor = 23;


  newMinor() {
    this.minor++;
  }


  newMajor() {
    this.major++;
    this.minor = 0;
  }
}

下面是點(diǎn)擊按鈕的結(jié)果。

測試確保這兩個輸入屬性值都被初始化了,當(dāng)點(diǎn)擊按鈕后,ngOnChanges 應(yīng)該被調(diào)用,屬性的值也符合預(yù)期。

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
// Test must all execute in this exact order
it('should set expected initial values', function () {
  let actual = getActual();


  let initialLabel = 'Version 1.23';
  let initialLog = 'Initial value of major set to 1, Initial value of minor set to 23';


  expect(actual.label).toBe(initialLabel);
  expect(actual.count).toBe(1);
  expect(actual.logs.get(0).getText()).toBe(initialLog);
});


it('should set expected values after clicking \'Minor\' twice', function () {
  let repoTag = element(by.tagName('app-version-parent'));
  let newMinorButton = repoTag.all(by.tagName('button')).get(0);


  newMinorButton.click().then(function() {
    newMinorButton.click().then(function() {
      let actual = getActual();


      let labelAfter2Minor = 'Version 1.25';
      let logAfter2Minor = 'minor changed from 24 to 25';


      expect(actual.label).toBe(labelAfter2Minor);
      expect(actual.count).toBe(3);
      expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
    });
  });
});


it('should set expected values after clicking \'Major\' once', function () {
  let repoTag = element(by.tagName('app-version-parent'));
  let newMajorButton = repoTag.all(by.tagName('button')).get(1);


  newMajorButton.click().then(function() {
    let actual = getActual();


    let labelAfterMajor = 'Version 2.0';
    let logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0';


    expect(actual.label).toBe(labelAfterMajor);
    expect(actual.count).toBe(4);
    expect(actual.logs.get(3).getText()).toBe(logAfterMajor);
  });
});


function getActual() {
  let versionTag = element(by.tagName('app-version-child'));
  let label = versionTag.element(by.tagName('h3')).getText();
  let ul = versionTag.element((by.tagName('ul')));
  let logs = ul.all(by.tagName('li'));


  return {
    label: label,
    logs: logs,
    count: logs.count()
  };
}
// ...

父組件監(jiān)聽子組件的事件

子組件暴露一個 EventEmitter 屬性,當(dāng)事件發(fā)生時,子組件利用該屬性 emits(向上彈射)事件。父組件綁定到這個事件屬性,并在事件發(fā)生時作出回應(yīng)。

子組件的 EventEmitter 屬性是一個輸出屬性,通常帶有 @Output 裝飾器,就像在 VoterComponent 中看到的。

Path:"component-interaction/src/app/voter.component.ts" 。

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


@Component({
  selector: 'app-voter',
  template: `
    <h4>{{name}}</h4>
    <button (click)="vote(true)"  [disabled]="didVote">Agree</button>
    <button (click)="vote(false)" [disabled]="didVote">Disagree</button>
  `
})
export class VoterComponent {
  @Input()  name: string;
  @Output() voted = new EventEmitter<boolean>();
  didVote = false;


  vote(agreed: boolean) {
    this.voted.emit(agreed);
    this.didVote = true;
  }
}

點(diǎn)擊按鈕會觸發(fā) truefalse (布爾型有效載荷)的事件。

父組件 VoteTakerComponent 綁定了一個事件處理器(onVoted()),用來響應(yīng)子組件的事件($event)并更新一個計數(shù)器。

Path:"component-interaction/src/app/votetaker.component.ts" 。

import { Component }      from '@angular/core';


@Component({
  selector: 'app-vote-taker',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <app-voter *ngFor="let voter of voters"
      [name]="voter"
      (voted)="onVoted($event)">
    </app-voter>
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = ['Narco', 'Celeritas', 'Bombasto'];


  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}

本框架把事件參數(shù)(用 $event 表示)傳給事件處理方法,該方法會處理它:

測試確保點(diǎn)擊 AgreeDisagree 按鈕時,計數(shù)器被正確更新。

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
it('should not emit the event initially', function () {
  let voteLabel = element(by.tagName('app-vote-taker'))
    .element(by.tagName('h3')).getText();
  expect(voteLabel).toBe('Agree: 0, Disagree: 0');
});


it('should process Agree vote', function () {
  let agreeButton1 = element.all(by.tagName('app-voter')).get(0)
    .all(by.tagName('button')).get(0);
  agreeButton1.click().then(function() {
    let voteLabel = element(by.tagName('app-vote-taker'))
      .element(by.tagName('h3')).getText();
    expect(voteLabel).toBe('Agree: 1, Disagree: 0');
  });
});


it('should process Disagree vote', function () {
  let agreeButton1 = element.all(by.tagName('app-voter')).get(1)
    .all(by.tagName('button')).get(1);
  agreeButton1.click().then(function() {
    let voteLabel = element(by.tagName('app-vote-taker'))
      .element(by.tagName('h3')).getText();
    expect(voteLabel).toBe('Agree: 1, Disagree: 1');
  });
});
// ...

父組件與子組件通過本地變量互動

父組件不能使用數(shù)據(jù)綁定來讀取子組件的屬性或調(diào)用子組件的方法。但可以在父組件模板里,新建一個本地變量來代表子組件,然后利用這個變量來讀取子組件的屬性和調(diào)用子組件的方法,如下例所示。

子組件 CountdownTimerComponent 進(jìn)行倒計時,歸零時發(fā)射一個導(dǎo)彈。startstop 方法負(fù)責(zé)控制時鐘并在模板里顯示倒計時的狀態(tài)信息。

Path:"component-interaction/src/app/countdown-timer.component.ts" 。

import { Component, OnDestroy, OnInit } from '@angular/core';


@Component({
  selector: 'app-countdown-timer',
  template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnInit, OnDestroy {


  intervalId = 0;
  message = '';
  seconds = 11;


  clearTimer() { clearInterval(this.intervalId); }


  ngOnInit()    { this.start(); }
  ngOnDestroy() { this.clearTimer(); }


  start() { this.countDown(); }
  stop()  {
    this.clearTimer();
    this.message = `Holding at T-${this.seconds} seconds`;
  }


  private countDown() {
    this.clearTimer();
    this.intervalId = window.setInterval(() => {
      this.seconds -= 1;
      if (this.seconds === 0) {
        this.message = 'Blast off!';
      } else {
        if (this.seconds < 0) { this.seconds = 10; } // reset
        this.message = `T-${this.seconds} seconds and counting`;
      }
    }, 1000);
  }
}

計時器組件的宿主組件 CountdownLocalVarParentComponent 如下:

Path:"component-interaction/src/app/countdown-parent.component.ts" 。

import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';


@Component({
  selector: 'app-countdown-parent-lv',
  template: `
  <h3>Countdown to Liftoff (via local variable)</h3>
  <button (click)="timer.start()">Start</button>
  <button (click)="timer.stop()">Stop</button>
  <div class="seconds">{{timer.seconds}}</div>
  <app-countdown-timer #timer></app-countdown-timer>
  `,
  styleUrls: ['../assets/demo.css']
})
export class CountdownLocalVarParentComponent { }

父組件不能通過數(shù)據(jù)綁定使用子組件的 start 和 stop 方法,也不能訪問子組件的 seconds 屬性。

把本地變量(#timer)放到(<countdown-timer>)標(biāo)簽中,用來代表子組件。這樣父組件的模板就得到了子組件的引用,于是可以在父組件的模板中訪問子組件的所有屬性和方法。

這個例子把父組件的按鈕綁定到子組件的 start 和 stop 方法,并用插值來顯示子組件的 seconds 屬性。

下面是父組件和子組件一起工作時的效果。

測試確保在父組件模板中顯示的秒數(shù)和子組件狀態(tài)信息里的秒數(shù)同步。它還會點(diǎn)擊 Stop 按鈕來停止倒計時:

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
it('timer and parent seconds should match', function () {
  let parent = element(by.tagName(parentTag));
  let message = parent.element(by.tagName('app-countdown-timer')).getText();
  browser.sleep(10); // give `seconds` a chance to catchup with `message`
  let seconds = parent.element(by.className('seconds')).getText();
  expect(message).toContain(seconds);
});


it('should stop the countdown', function () {
  let parent = element(by.tagName(parentTag));
  let stopButton = parent.all(by.tagName('button')).get(1);


  stopButton.click().then(function() {
    let message = parent.element(by.tagName('app-countdown-timer')).getText();
    expect(message).toContain('Holding');
  });
});
// ...

父組件調(diào)用@ViewChild()

這個本地變量方法是個簡單便利的方法。但是它也有局限性,因?yàn)楦附M件-子組件的連接必須全部在父組件的模板中進(jìn)行。父組件本身的代碼對子組件沒有訪問權(quán)。

如果父組件的類需要讀取子組件的屬性值或調(diào)用子組件的方法,就不能使用本地變量方法。

當(dāng)父組件類需要這種訪問時,可以把子組件作為 ViewChild ,注入到父組件里面。

下面的例子用與倒計時相同的范例來解釋這種技術(shù)。 它的外觀或行為沒有變化。子組件 CountdownTimerComponent 也和原來一樣。

注:
- 由本地變量切換到 ViewChild 技術(shù)的唯一目的就是做示范。

下面是父組件 CountdownViewChildParentComponent:

Path:"component-interaction/src/app/countdown-parent.component.ts" 。

import { AfterViewInit, ViewChild } from '@angular/core';
import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';


@Component({
  selector: 'app-countdown-parent-vc',
  template: `
  <h3>Countdown to Liftoff (via ViewChild)</h3>
  <button (click)="start()">Start</button>
  <button (click)="stop()">Stop</button>
  <div class="seconds">{{ seconds() }}</div>
  <app-countdown-timer></app-countdown-timer>
  `,
  styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {


  @ViewChild(CountdownTimerComponent)
  private timerComponent: CountdownTimerComponent;


  seconds() { return 0; }


  ngAfterViewInit() {
    // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
    // but wait a tick first to avoid one-time devMode
    // unidirectional-data-flow-violation error
    setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
  }


  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}

把子組件的視圖插入到父組件類需要做一點(diǎn)額外的工作。

首先,你必須導(dǎo)入對裝飾器 ViewChild 以及生命周期鉤子 AfterViewInit 的引用。

接著,通過 @ViewChild 屬性裝飾器,將子組件 CountdownTimerComponent 注入到私有屬性 timerComponent 里面。

組件元數(shù)據(jù)里就不再需要 #timer 本地變量了。而是把按鈕綁定到父組件自己的 startstop 方法,使用父組件的 seconds 方法的插值來展示秒數(shù)變化。

這些方法可以直接訪問被注入的計時器組件。

ngAfterViewInit() 生命周期鉤子是非常重要的一步。被注入的計時器組件只有在 Angular 顯示了父組件視圖之后才能訪問,所以它先把秒數(shù)顯示為 0.

然后 Angular 會調(diào)用 ngAfterViewInit 生命周期鉤子,但這時候再更新父組件視圖的倒計時就已經(jīng)太晚了。Angular 的單向數(shù)據(jù)流規(guī)則會阻止在同一個周期內(nèi)更新父組件視圖。應(yīng)用在顯示秒數(shù)之前會被迫再等一輪。

使用 setTimeout() 來等下一輪,然后改寫 seconds() 方法,這樣它接下來就會從注入的這個計時器組件里獲取秒數(shù)的值。

注:
- 可以使用和之前一樣的倒計時測試,此處不再重復(fù)操作。

父組件和子組件通過服務(wù)來通訊

父組件和它的子組件共享同一個服務(wù),利用該服務(wù)在組件家族內(nèi)部實(shí)現(xiàn)雙向通訊。

該服務(wù)實(shí)例的作用域被限制在父組件和其子組件內(nèi)。這個組件子樹之外的組件將無法訪問該服務(wù)或者與它們通訊。

這個 MissionServiceMissionControlComponent 和多個 AstronautComponent 子組件連接起來。

Path:"component-interaction/src/app/mission.service.ts" 。

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs';


@Injectable()
export class MissionService {


  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();


  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();


  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }


  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}

MissionControlComponent 提供服務(wù)的實(shí)例,并將其共享給它的子組件(通過 providers 元數(shù)據(jù)數(shù)組),子組件可以通過構(gòu)造函數(shù)將該實(shí)例注入到自身。

Path:"component-interaction/src/app/missioncontrol.component.ts" 。

import { Component }          from '@angular/core';


import { MissionService }     from './mission.service';


@Component({
  selector: 'app-mission-control',
  template: `
    <h2>Mission Control</h2>
    <button (click)="announce()">Announce mission</button>
    <app-astronaut *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </app-astronaut>
    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;


  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }


  announce() {
    let mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}

AstronautComponent 也通過自己的構(gòu)造函數(shù)注入該服務(wù)。由于每個 AstronautComponent 都是 MissionControlComponent 的子組件,所以它們獲取到的也是父組件的這個服務(wù)實(shí)例。

Path:"component-interaction/src/app/astronaut.component.ts" 。

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


import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs';


@Component({
  selector: 'app-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;


  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }


  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }


  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

注:
- 這個例子保存了 subscription 變量,并在 AstronautComponent 被銷毀時調(diào)用 unsubscribe() 退訂。 這是一個用于防止內(nèi)存泄漏的保護(hù)措施。實(shí)際上,在這個應(yīng)用程序中并沒有這個風(fēng)險,因?yàn)?AstronautComponent 的生命期和應(yīng)用程序的生命期一樣長。但在更復(fù)雜的應(yīng)用程序環(huán)境中就不一定了。

  • 不需要在 MissionControlComponent 中添加這個保護(hù)措施,因?yàn)樗鳛楦附M件,控制著 MissionService 的生命期。

History 日志證明了:在父組件 MissionControlComponent 和子組件 AstronautComponent 之間,信息通過該服務(wù)實(shí)現(xiàn)了雙向傳遞。

測試確保點(diǎn)擊父組件 MissionControlComponent 和子組件 AstronautComponent 兩個的組件的按鈕時,History 日志和預(yù)期的一樣。

Path:"component-interaction/e2e/src/app.e2e-spec.ts" 。

// ...
it('should announce a mission', function () {
  let missionControl = element(by.tagName('app-mission-control'));
  let announceButton = missionControl.all(by.tagName('button')).get(0);
  announceButton.click().then(function () {
    let history = missionControl.all(by.tagName('li'));
    expect(history.count()).toBe(1);
    expect(history.get(0).getText()).toMatch(/Mission.* announced/);
  });
});


it('should confirm the mission by Lovell', function () {
  testConfirmMission(1, 2, 'Lovell');
});


it('should confirm the mission by Haise', function () {
  testConfirmMission(3, 3, 'Haise');
});


it('should confirm the mission by Swigert', function () {
  testConfirmMission(2, 4, 'Swigert');
});


function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
  let _confirmedLog = ' confirmed the mission';
  let missionControl = element(by.tagName('app-mission-control'));
  let confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
  confirmButton.click().then(function () {
    let history = missionControl.all(by.tagName('li'));
    expect(history.count()).toBe(expectedLogCount);
    expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + _confirmedLog);
  });
}
// ...
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號