<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                為了更加清晰的認識組件的使用方法,我們暫時停止單元測試的步代,來到教師增加組件,并在該組件中引入教師列表組件。 klass/add/add.component.html ``` <h3>新增班級</h3> <form (ngSubmit)="onSubmit()"> <label for="name">名稱:<input id="name" type="text" [formControl]="name"/></label> <label for="teacherId">教師:<app-teacher-select id="teacherId"></app-teacher-select?></label> <button>保存</button> </form> ``` * ? 對應教師列表組件klass/teacher-select/teacher-select.component.ts定義的`selector: 'app-teacher-select'` 然后我們啟動前后臺,在教師管理中添加兩個測試教師后來到班級新增界面: ![](https://img.kancloud.cn/7c/6d/7c6d38addcd8aaa936188cc700004cfb_399x112.gif) 當前的界面中有兩個基礎組件:班級新增組件及選擇教師組件。 ![](https://img.kancloud.cn/f7/6a/f76ac203e99c67cf715371a357ec3e43_460x202.png) 我們現在面臨的問題是:當用戶選擇了某個教師后,如何將選擇的教師由`教師選擇組件`傳遞給`班級新增組件`,近而在`班級新增組件`中獲取這個選擇上的教師最終完成班級的保存功能。 # 事件彈射器 在前面我們提起到,用戶與瀏覽器的一切交互都可以認為是在觸發一個事件,比如鼠標點擊事件,鍵盤輸入事件。同樣的,用戶選擇select中的某個option也是一個事件。在angular中組件通過定義`EventEmitter 事件彈射器`的方式來向處發送數據。從本質上來講,`EventEmitter 事件彈射器`也是個可被觀察者,它提供的功能是:如果本組件發生了某個事件,就會通過`EventEmitter 事件彈射器`來發送通知,如果你想獲取到這些通知,那么只需要訂閱我即可。 ## 初始化 klass/teacher-select/teacher-select.component.html ``` <select id="teacherSelect" [formControl]="teacherSelect" (change)="onChange()?"> <option *ngFor="let teacher of teachers" [ngValue]="teacher"> {{teacher.name}} </option> </select> ``` * ? 當用戶選擇select中的某個option時,會觸發change事件。 klass/teacher-select/teacher-select.component.ts ``` @Output()? selected① = new EventEmitter<Teacher>();? ... onChange() { console.log(this.teacherSelect.value); ? this.selected.emit(this.teacherSelect.value); ? } ``` * ? 此屬性為組件的輸出屬性 * ① 此處的命名不能與angular自帶的相沖突,比如說我們**不**能使用**change**來命名。 * ? 實例化事件發射器,此發射器發送的內容為`Teacher` * ? 控制臺打印當前select的值(用戶進行選擇時,會實時的綁定到fromControl上),用于測試 * ? 向組件外發(彈)射數據 #### 測試 ![](https://img.kancloud.cn/47/25/47250e780091ba26ac6102c04762a64a_423x174.gif) ## 獲取彈射的值 在班級新增組件中,我們嘗試獲取該組件彈射出的值 klass/add/add.component.html ``` <h3>新增班級</h3> <form (ngSubmit)="onSubmit()"> <label for="name">名稱:<input id="name" type="text" [formControl]="name"/></label> <label for="teacherId">教師:<app-teacher-select id="teacherId" (selected)?="onTeacherSelected($event)①"></app-teacher-select></label> <button>保存</button> </form> ``` * ? 使用我們剛剛在教師組件中定義的`@Output() selected`,這也是為什么我們不能夠命名為`@Output() change`的原因。如果我們命名為:`@Output() change`,則此時在新增班的V層中便應該如下使用:`<app-teacher-select id="teacherId" (change)="onTeacherSelected($event)">`。此時angular會把`change()`解析為內置指令,進而使得該彈射功能失效(**請自行測試**) * ① 方法名需要在C層中定義,而參數名可以隨性起,但我們一般為起名為$event以示此處為該組件的一個彈射器。 klass/add/add.component.ts ``` /** * 當選擇某個教師時觸發 * @param {Teacher} teacher 教師 */ onTeacherSelected(teacher: Teacher) { console.log(teacher); } ``` #### 測試 ![](https://img.kancloud.cn/38/92/389208e447cca9c8042f13dd973ed061_572x249.gif) 如上所示,用戶選擇教師后,我們分別在教師列表組件及班級新增組件中獲取到了該項的值。 # 單元測試 完成了功能以后我們來**補充**單元測試,學習如何在單元測試中來測試組件事件彈射器。由于事件彈射器本質上來講是一個可觀察者,所以我們在測試的代碼中直接來訂閱這個觀察者: klass/teacher-select/teacher-select.component.spec.ts ``` /** * 1. 模擬返回數據給教師列表 * 2. 觀察彈射器 * 3. 模擬點擊第0個option * 4. 斷言觀察到的數據是教師列表的第一個教師 */ fit('測試組件彈射器', () => { const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Teacher'); req.flush(teachers); fixture.detectChanges(); component.selected.subscribe((teacher: Teacher) => { ? console.log('data emit:', teacher); ① expect(teacher.name).toEqual(teachers[0].name); ? }); const htmlSelectElement: HTMLSelectElement = fixture.debugElement.query(By.css('#teacherSelect')).nativeElement; htmlSelectElement.value = htmlSelectElement.options[0].value; ? htmlSelectElement.dispatchEvent(new Event('change')); ? fixture.detectChanges(); ② }); ``` * ? 觀察彈射器,并定義了彈射器彈出的數據類型為Teacher * ① 測試語句,有數據彈出時該執行此語句。若該語句未被執行,說明數據未彈出 * ? 設置select值的值為第一個option * ? 執行change事件來選中該option * ② 多寫幾個fixture.detectChanges(); 不吃虧 測試結果: ``` LOG: Teacher{id: 1, name: '潘杰', username: 'panjie', email: undefined, sex: undefined} Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 0 of 13 SUCCESS (0 secs / 0 secs) LOG: 'data emit', Teacher{id: 1, name: '潘杰', username: 'panjie', email: undefined, sex: undefined} Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 0 of 13 SUCCESS (0 secs / 0 secs) Chrome 78.0.3904 (Mac OS X 10.13.6): Executed 1 of 13 (skipped 12) SUCCESS (0.123 secs / 0.099 secs) TOTAL: 1 SUCCESS TOTAL: 1 SUCCESS ``` ### 重構測試 本代碼使用了與前面代碼段相同的代碼,本著**不造重復的輪子**的原則,對整體的文件進行重構后如下: klass/teacher-select/teacher-select.component.spec.ts ``` import {async, ComponentFixture, flush, TestBed} from '@angular/core/testing'; import {TeacherSelectComponent} from './teacher-select.component'; import {BrowserModule, By} from '@angular/platform-browser'; import {ReactiveFormsModule} from '@angular/forms'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {Teacher} from '../../norm/entity/Teacher'; describe('TeacherSelectComponent', () => { let component: TeacherSelectComponent; let fixture: ComponentFixture<TeacherSelectComponent>; const teachers = new Array(new Teacher(1, 'panjie', '潘杰'), new Teacher(2, 'zhangxishuo', '張喜碩')); beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TeacherSelectComponent], imports: [ BrowserModule, ReactiveFormsModule, HttpClientTestingModule ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TeacherSelectComponent); component = fixture.componentInstance; fixture.detectChanges(); }); /*斷言發請了后臺請求,模擬返回數據后,斷言V層的select個數為2*/ it('獲取教師列表后選擇教師', () => { expectInit(); const htmlSelectElement: HTMLSelectElement = fixture.debugElement.query(By.css('#teacherSelect')).nativeElement; expect(htmlSelectElement.length).toBe(2); testOptionValue(htmlSelectElement); }); /** * 斷言option的值與teacher中name的相同 * 循環teachers數組。斷言與option的值一一相等 * @param htmlSelectElement html元素 */ const testOptionValue = (htmlSelectElement: HTMLSelectElement) => { const htmlOptionElements: HTMLCollectionOf<HTMLOptionElement> = htmlSelectElement.options; for (let i = 0; i < teachers.length; i++) { const htmlOptionElement: HTMLOptionElement = htmlOptionElements.item(i); console.log(htmlOptionElement.text); expect(htmlOptionElement.text).toEqual(teachers[i].name); } }; /** * 1. 模擬返回數據給教師列表 * 2. 觀察彈射器 * 3. 模擬點擊第0個option * 4. 斷言觀察到的數據是教師列表的第一個教師 */ fit('測試組件彈射器', () => { expectInit(); component.selected.subscribe((teacher: Teacher) => { console.log('data emit', teacher); expect(teacher.name).toEqual(teachers[0].name); }); const htmlSelectElement: HTMLSelectElement = fixture.debugElement.query(By.css('#teacherSelect')).nativeElement; htmlSelectElement.value = htmlSelectElement.options[0].value; htmlSelectElement.dispatchEvent(new Event('change')); fixture.detectChanges(); }); /** * 斷言組件進行了初始化 * 訪問了正確的后臺地址 */ const expectInit = () => { const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Teacher'); expect(req.request.method).toEqual('GET'); req.flush(teachers); fixture.detectChanges(); }; }); ``` # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.5.4](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.5.4) | - | | 事件彈射器 | [EventEmitter](https://www.angular.cn/api/core/EventEmitter) | 15 |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看