<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>

                按由后到前的順序初始化如下: ## service service/course.service.ts ```typescript /** * 分頁 * @param params name課程名稱 klassId 班級 teacherId 教師 */ page(params?: {name?: string, klassId?: number, teacherId?: number}): Observable<Page?> { return null; } ``` * ? 分頁信息屬于常用數據結構,自定Page類以便復用。 ## page Page接口 ``` panjiedeMac-Pro:norm panjie$ ng g class Page CREATE src/app/norm/page.spec.ts (146 bytes) CREATE src/app/norm/page.ts (22 bytes) panjiedeMac-Pro:norm panjie$ ``` 參考學生管理中后臺返回的分頁數據初始化Page類如下: norm/page.ts ```typescript /** * 分頁數據 */ export class Page<T?> { /* 內容 */ content: Array<T>?; /* 總頁數 */ totalPages: number; constructor(params?: { content?: T, totalPages?: number }) { if (params) { if (params.content) { this.content = params.content; } if (params.totalPages) { this.totalPages = params.totalPages; } } } } ``` * ? 聲明Page具有容器的性質,可以裝入不同的對象 * ? 內部容器,該類型與Page類型的泛型相同。 新建Page類后在service/course.service.ts中引用Page并設置泛型。 service/course.service.ts ```typescript import {Page} from '../norm/page'; ? page(params?: {name?: string, klassId?: number, teacherId?: number}): Observable<Page<Course?>> { return null; } ``` * ? 引入Page類型 * ? 聲明Page里裝入的對象類型為Course ## ServiceStub 對應在service的替身中增加page方法 service/course-stub.service.ts ```typescript page(params?: {name?: string, klassId?: number, teacherId?: number}): Observable<Page<Course>> { return null; } ``` ## 組件 在course中新建index組件: ``` panjiedeMac-Pro:course panjie$ ng g c index CREATE src/app/course/index/index.component.sass (0 bytes) CREATE src/app/course/index/index.component.html (20 bytes) CREATE src/app/course/index/index.component.spec.ts (621 bytes) CREATE src/app/course/index/index.component.ts (266 bytes) UPDATE src/app/course/course.module.ts (761 bytes) panjiedeMac-Pro:course panjie$ ``` ### V層初始化 course/index/index.component.html ```html <form (ngSubmit)="onQuery()"> <label>課程名稱:<input name="name" [formControl]="params.name" type="text"/></label> <label>教師: <app-teacher-select (selected)="onSelectTeacher($event)"></app-teacher-select> </label> <label>班級: <app-klass-select (selected)="onSelectKlass($event)"></app-klass-select> </label> <button type="submit">查詢</button> </form> <div class="row"> <div class="col text-right"> <a class="btn btn-primary" routerLink="./add"><span class="oi oi-plus"></span>新增課程</a> </div> </div> <table class="table"> <tr> <th>序號</th> <th>名稱</th> <th>任課教師</th> <th>班級</th> <th>操作</th> </tr> <tr *ngFor="let course of coursePage.content; index as index"> <td>{{index + 1}}</td> <td>{{course.name}}</td> <td>{{course.teacher.name}}</td> <td>{{course.klass.name}}</td> <td> <a routerLink="./edit/{{course.id}}" class="btn btn-sm btn-info"><span class="oi oi-pencil"></span>編輯</a> <button (click)="onDelete(course)" class="btn btn-sm btn-danger"><span class="oi oi-trash"></span>刪除</button> </td> </tr> </table> ``` 以下是分頁信息,暫時省略 ### C層初始化 course/index/index.component.ts ```typescript import {Component, OnInit} from '@angular/core'; import {Course} from '../../norm/entity/course'; import {Page} from '../../norm/page'; import {FormControl} from '@angular/forms'; import {Klass} from '../../norm/entity/Klass'; import {Teacher} from '../../norm/entity/Teacher'; @Component({ selector: 'app-index', templateUrl: './index.component.html', styleUrls: ['./index.component.sass'] }) export class IndexComponent implements OnInit { params = { name: new FormControl('') }; coursePage: Page<Course>; constructor() { } ngOnInit() { } onQuery() { } onSelectTeacher($event: Teacher) { } onSelectKlass($event: Klass) { } onDelete(course: Course) { } } ``` ### 單元測試初始化 course/index/index.component.spec.ts ```typescript beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ IndexComponent ], imports: [ ReactiveFormsModule, ? FormsModule,? TestModule ? ] }) .compileComponents(); })); ``` * ? formControl指令 * ? (ngSubmit)方法 * ? app-teacher-select ![](https://img.kancloud.cn/31/0e/310edc660f97b23a7250816bff616e63_672x116.png) 單元測試提示不能解析app-klass-select。app-klass-select位于student模塊中的KlassSelect組件中。但由于歷史原因尚未生成KlassSelect組件對應的測試替身。 ## 遷移公共組件 我們之所以要將項目分成多個模塊,有一個重要的原因是為了:惰性加載。將項目分離成多個模塊可以將一個大型的項目分成多次被用戶加載。這樣可以使得一個較大的項目能夠有著比較良好的使用體驗。而這一切的前提是:各個模塊互相獨立。如若在當前Course模塊中使用位于Student模塊中的KlassSelect組件,則Course模塊產生了對Student模塊的依賴,也就無法獨立于Student模塊存在了。在實際的生產項目中往往把一些公用的組件放到一個單獨的模塊中。為此將 Student模塊中的KlassSelect組件遷移到Core模塊中。 剪切 ![](https://img.kancloud.cn/fd/a8/fda8eb7c0e28c391eb3852e989b636c0_591x349.png) 粘貼 ![](https://img.kancloud.cn/76/e3/76e3578a0ff3b750af32a28c9845cc85_608x229.png) 然后將此組件添加到Core模塊的declarations中以及exports中,同時刪除原Student模塊declarations中對此組件的聲明。 core/core.modult.ts ```typescript @NgModule({ declarations: [SelectComponent, MultipleSelectComponent, KlassSelectComponent?], ... exports: [ SelectComponent, MultipleSelectComponent, KlassSelectComponent? ] ``` student/student.module.ts ```typescript import { KlassSelectComponent } from '../core/klass-select/klass-select.component'; ? @NgModule({ declarations: [AddComponent, KlassSelectComponent?, IndexComponent, EditComponent], export class StudentModule { } ``` ### 新建組件測試替身 遷移完成后為其建立測試替身以便其被其它模塊使用時能夠優雅的進行單元測試。 ``` panjiedeMac-Pro:core-testing panjie$ ng g c klassSelect -s -t --skip-tests CREATE src/app/core/core-testing/klass-select/klass-select.component.ts (273 bytes) UPDATE src/app/core/core-testing/core-testing.module.ts (562 bytes) panjiedeMac-Pro:core-testing panjie$ ``` 初始化輸入、輸出并加入到測試控制器中: ```typescript import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Klass} from '../../../norm/entity/Klass'; import {CoreTestingController} from '../core-testing-controller'; @Component({ selector: 'app-klass-select', template: ` <p> klass-select works! </p> `, styles: [] }) export class KlassSelectComponent implements OnInit { @Output() selected = new EventEmitter<Klass>(); ? @Input() klass: Klass; ? constructor(private controller: CoreTestingController) { this.controller.addUnit(this); ? } ngOnInit() { } } ``` * ? 對應原組件的輸出 * ? 對應原組件的輸入 * ? 添加到測試控制器中以便在單元測試中由測試控制器中獲取該組件 在測試模塊中將此組件替身輸出: core/core-testing/core-testing.module.ts ```typescript exports: [ MultipleSelectComponent, KlassSelectComponent ? ], ... export class CoreTestingModule { } ``` ## 單元測試 準備好測試組件替身后將CoreTestingModule開入到課程列表組件測試文件中: course/index/index.component.spec.ts ```typescript imports: [ ReactiveFormsModule, TestModule, CoreTestingModule ? ] ``` ![](https://img.kancloud.cn/bd/50/bd50b0a562e978978e1c64b8071eac47_541x73.png) ```typescript TestModule, CoreTestingModule, RouterTestingModule? ] ``` ## V層中的? 引用了多個模塊后再次執行單元測試,錯誤如下: ![](https://img.kancloud.cn/80/45/8045007178a23a3fdc29a17c0ab3bb4c_414x72.png) 此錯誤在說:在undefined上讀取content時發生了錯誤。此錯誤產生的原因是由于在組件中使用了`xxxx.content`嘗試獲取數據,但在執行該語句時`xxxx`的值為undefined。 在組件的C層中并沒有讀取content屬性,所以錯誤并不是在C層發生了。在V層中以content進行搜索發現有以下語句:`<tr *ngFor="let course of coursePage.content; index as index">`。該錯誤便是由此發生了。 這是由于:C層在初始化時只是聲明了coursePage的類型,并未對其進行初始化,所以coursePage當前的值為undefined。在V層中嘗試執行`coursePage.content`時便發生了`TypeError: Cannot read property 'content' of undefined`的錯誤。解決該問題的方法最少有兩種:由于V層的渲染操作發生在C層的ngOnInit方法以后,所以第一種方法可以在ngOnInit中對coursePage進行初始化,此后在進行V層的渲染時coursePage的值并不為undefined,當然也就不會發生此錯誤了;除此以外angular也充分地考慮到了此問題,可以在V層中使用`?`來標注某個變量以表示:當此值為undefined時暫停渲染,當此值不為undefined時繼續完成渲染。 course/index/index.component.html ```html <tr *ngFor="let course of coursePage??.content; index as index"> ``` * ? 在可能為undefined的變量后添加?以防止undefined錯誤。 在渲染V層時,除要規避在undefined上讀取某個屬性發生錯誤以外,還要規避類似的在null上讀取某個屬性發生錯誤。而以下代碼則可能引發在null讀取某個屬性的錯誤: ```html <tr *ngFor="let course of coursePage.content; index as index"> <td>{{index + 1}}</td> <td>{{course.name}}</td> <td>{{course.teacher.name}}</td> ? <td>{{course.klass.name}}</td> <td> ``` * ? 在進行數據表的設計時,課程是可以不設置任課教師的,所以course.teacher的值可能為null。當獲取的某個course沒有設置任課教師時,則會發生在null讀取name屬性的錯誤。 修正如下: ```html <td>{{course.teacher?.name}}</td> ``` 最終單元測試通過: ![](https://img.kancloud.cn/ed/85/ed858e36b48fb9a46e2635bde87eead1_359x126.png) # 修正其它測試 由于變更了KlassSelectComponent的位置以及所屬模塊所以必然會引起其它的單元測試錯誤。逐個解決如下: ## 某組件同時存在于多個模塊中 ``` student/AddComponent > should create Failed: Type KlassSelectComponent is part of the declarations of 2 modules: CoreModule and DynamicTestModule! Please consider moving KlassSelectComponent to a higher module that imports CoreModule and DynamicTestModule. You can also create a new NgModule that exports and includes KlassSelectComponent then import that NgModule in CoreModule and DynamicTestModule. ``` 在angular中某個組件不能同時存在于兩個模塊中(除非這兩個模塊不知道對方的存在)。這時由于在測試模塊(文件)中將KlassSelectComponent聲明到了declarations中。修正如下: student/add/add.component.spec.ts ```typescript declarations: [AddComponent, KlassSelectComponent?], ``` student/index/index.component.spec.ts ```typescript declarations: [AddComponent, KlassSelectComponent?], ``` core/klass-select/klass-select.component.spec.ts ```typescript beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [KlassSelectComponent, SelectComponent??], imports: [CoreModule??, HttpClientTestingModule] }) ``` * ? KlassSelectComponent依賴于原CoreModule的SelectComponent,此時兩個組件位于同一個模塊下,直接引用即可 * ? KlassSelectComponent當前已經屬于CoreModule,不能再進行引用,否則將發生組件位于多模塊的錯誤 最后加入其它依賴: ```typescript imports: [HttpClientTestingModule, ReactiveFormsModule?] }) ``` 單元測試整體通過。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.2.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.2.1) | - | | 安全導航運算符( ? )和空屬性路徑 | [https://www.angular.cn/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths](https://www.angular.cn/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths) | 10 |
                  <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>

                              哎呀哎呀视频在线观看