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

                前面的章節中我們都是直接給出了后臺的Api情況。其實如果團隊中都是全棧工程師的話,后臺Api的定義往往是在前臺開發過程中被逐步規定的。后臺的開發會在前臺基本完成,DEMO能夠跑通的情況下進行。我們本節也模擬這個過程,在開發過程中逐步的完成后臺的模擬接口的設定。 ## Service 來到`student.servicer.ts`增加`pageOfCurrentTeacher`方法用于查詢當前登錄教師管理的學生分頁信息: ```typescript +++ b/first-app/src/app/service/student.service.ts @@ -26,4 +27,15 @@ export class StudentService { // 將預請求信息返回 return this.httpClient.post<Student>('/student', student); } + + /** + * 當前登錄用戶的分頁信息 + * @param data 分頁信息 + */ + pageOfCurrentTeacher(①{②page = 0, ②size = 20}③: { page④?: number, size④?: number }): Observable<Page<Student>> { + const httpParams = new HttpParams() + .append('page', page.toString()) + .append('size', size.toString()); + return this.httpClient.get<Page<Student>>('/student/pageOfCurrentTeacher', {params: httpParams}); + } } ``` 定義上述方法時我們使用了幾個小技巧: - ① 不定義參數名,直接在參數中使用`{}` - ② 使用`xxx=yyy`的方法設置默認值 - ③ 使用`:`來指明參數的類型 - ④ 使用`?`標記該參數可選 在以上技巧的支持下,實現了多參數默認值的設置,同時由于該參數的類型為`{}`,所以上述方法支持以下調用形式: ```typescript service.pageOfCurrentTeacher({}); service.pageOfCurrentTeacher({page: 1}); service.pageOfCurrentTeacher({page: 1, size: 2}); service.pageOfCurrentTeacher({size: 2}); service.pageOfCurrentTeacher({size: 2, page: 1}); ``` ### JSON對象與對象 如果你仔細觀察前面我們寫過的MockApi的話,應該會發現一個現象:我們特意的規避了在Mock中使用`new`關鍵字,而是使用了`as`關鍵字。比如我們在返回學生時使用如下代碼: ```typescript result: { name: 'xxx' } as Student; ``` 而不是: ``` result: new Student({name: 'xxx'}) ``` 我們為什么在這么做,上述兩種寫法本質上又有何區別呢? 這是由于真實的后臺返回返回的數據就是`as`的形式,表示:把返回值**看做是**某個類型,這正好是`as`所要表達的意思。而如果在模擬的后臺API中使用`new`,則表示:返回值**是**某個類型。這完全是兩個不同的概念,**看做是**則意味著可能是,也可能不是。 在交流中,我們有時也把這種**看做是**的類型稱為**JSON對象**,而把使用`new`關鍵字生成的對象稱為**對象**,為此隨意找個單元測試簡單演示一下來查看下二者的區別: ```typescript fit('JSON對象與對象', () => { class A { a: string; constructor(a: string) { this.a = a; } } const a1 = new A('123'); console.log('對象;', a1); const a2 = {a: '123'} as A; console.log('JSON對象:', a2); }); ``` 控制臺打印信息如下: ![image-20210416092338084](https://img.kancloud.cn/c0/81/c08169864bf6515882ce8202f7a6fa04_1082x442.png) 可見:對象a1原型鏈對應了類A;而JSON對象雖然通過`as`將類型聲明為`A`,但其本質(原型鏈)仍然是`object`。當前類A僅有一個`a`屬性,并不存在其它的方法。在未為類A定義方法前,上述兩種定義方法并不會有什么不同,但如果類A類中定義一些方法,就不一樣了: ```typescript fit('JSON對象與對象', () => { class A { a: string; constructor(a: string) { this.a = a; } getA(): string { return this.a; } } const a1 = new A('123'); console.log('對象;', a1); console.log(a1.getA()); const a2 = {a: '123'} as A; console.log('JSON對象:', a2); ?? console.log(a2.getA()); }); ``` ?? 由于a2實際的類型并不是A,僅僅是當做A來看待,所以在嘗試調用`getA()`方法時將會觸發一個異常。 ![image-20210416092930971](https://img.kancloud.cn/c0/b3/c0b32e1bb39baba0b796852093a09ca7_1314x110.png) 查看控制臺信息也的確如此,第二個使用`as`方法聲明的JSON對象并不存在`getA()`方法: ![image-20210416101000081](https://img.kancloud.cn/b9/55/b955441ff2a51d37a845d8260c64da46_1880x622.png) 實際上由于JSON對象的本質是個`object`,在javascript中,任意對象的本質都是`object`,這個`object`是`javascript`的最基礎的類。 ![image-20210416101204463](https://img.kancloud.cn/1a/61/1a6183aa7478dc010f34d6d6f4a94204_1006x344.png) 如上圖示對象`a1`由類A實例化而來,而A的父類(原型)同樣也是`object`。 通過上述學習我們發現使用`new`關鍵字來實例化對象與使用`as`關鍵字來將某對象**看做**是某類的實例化,其實是有些本質區別的。而`this.httpClient.get<T>`中的`T`的本質則是一個`as`,也就是說`httpClient`會將請求的數據標識為我們傳入`T`,而不是根據`T`的的類型返回一`T`的實例。 所以在MockApi中我們也應該用`as`關鍵字將JSON對象**標識**為返回值類型,而不是用`new`關鍵字真正返回某個類的實例。 最后我們加入對異常的斷言,以使上述單元測試順利通過: ```typescript +++ b/first-app/src/app/yz-validators.spec.ts @@ -61,6 +61,12 @@ describe('YzValidators', () => { const a2 = {a: '123'} as A; console.log('JSON對象:', a2); - console.log(a2.getA()); + let catchException = false; + try { + console.log(a2.getA()); + } catch (e) { + catchException = true; + } + expect(catchException).toBeTrue(); }); }); ``` 上述代碼展示了測試異常的一種常規方法:在調用異常的代碼前定義個boolean類型的局部變量并設置為fasle,然后在`try`中執行異常代碼,在`catch`中將boolean值設置為true。如果代碼如我們期望的一樣執行,則boolean類型的值必然為true,此時斷言通過。否則斷言不通過。所以斷言是否通過可以做為驗證執行某些代碼是否發生了異常的依據。 > [info] 初次接觸前臺這個即簡單又深奧的領域時,搞不太清JSON對象與對象的異同是很正常的事情。所以看完上面的講解仍然云里霧里的話,這并不是你的錯,而是我們在這里為大家講解的篇幅太少了。在教程中及以后的生產開發中,我們只需要記住:在MocaApi中不能夠使用`new`關鍵字來返回數據便可以了。 ## MockApi 我們繼續使用`as`關鍵字來返回一個`Page`類型,打開`student.mock.api.ts`添加如下API: ```typescript { method: 'GET', url: '/student/pageOfCurrentTeacher', result: (urlMatches: string[], options: RequestOptions) => { const httpParams = options.params as HttpParams; const page = +(httpParams.get('page') as string); const size = +(httpParams.get('size') as string); Assert.isNumber(page, size, 'page size must be number'); const students = [] as Array<Student>; for (let i = 0; i < size; i++) { students.push({ id: i + 1, name: randomString('姓名'), number: randomNumber(10000).toString(), phone: '13900001111', clazz: { name: randomString('班級名稱'), teacher: { name: randomString('教師名稱') } as Teacher } as Clazz } as Student); } return { content: students, number: page, size, totalPages: (page + 1 + randomNumber(10)) * size } as Page<Student>; } } ``` ## 測試 確保某一段代碼是否正常運行的最簡單的方法是寫個單元測試,這比用鼠標點一點,鍵盤輸入一下要可靠、穩定的多。而且從長久來看也省時的多。當Service以及MockApi都準備好以后,我們啟用單元測試來測試一下,以保證`Service`發起請求后MockApi可以做出響應: ```typescript +++ b/first-app/src/app/service/student.service.spec.ts @@ -3,6 +3,7 @@ import {TestBed} from '@angular/core/testing'; import {StudentService} from './student.service'; import {MockApiTestingModule} from '../mock-api/mock-api-testing.module'; import {HttpClient} from '@angular/common/http'; +import {getTestScheduler} from 'jasmine-marbles'; describe('StudentService', () => { let service: StudentService; @@ -24,4 +25,19 @@ describe('StudentService', () => { .subscribe(success => console.log('success', success), error => console.log('error', error)); }); + + fit('pageOfCurrentTeacher', () => { + let called = false; + service.pageOfCurrentTeacher({page: 1, size: 2}) + .subscribe(data => { + // 當called為true時,說明接收到了數據 + called = true; + expect(data.number).toBe(1); + expect(data.size).toBe(2); + }); + + // 手動發送數據并斷言已成功接收 + getTestScheduler().flush(); + expect(called).toBeTrue(); + }); }); ``` 測試通過。 ![image-20210416161337997](https://img.kancloud.cn/00/ad/00ad814bec1a6898a457cb581ef30551_790x102.png) 有了測試保證的Service,接下來完成組件C層初始化的對接。 ## C層 在C層的`ngOnInit()`方法中增加后臺請求的代碼。雖然我們可以將組件初始化的代碼直接寫在構造函數中,但Angular官方并不建議我們這樣做,之所以這樣Angular給出的答案是:這樣會更高效。 ```typescript +++ b/first-app/src/app/student/student.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {Page} from '../entity/page'; import {Student} from '../entity/student'; +import {StudentService} from '../service/student.service'; @Component({ selector: 'app-student', @@ -9,11 +10,17 @@ import {Student} from '../entity/student'; }) export class StudentComponent implements OnInit { pageData = {} as Page<Student>; + page = 0; + size = 20; - constructor() { + constructor(private studentService: StudentService) { } ngOnInit(): void { + this.studentService.pageOfCurrentTeacher({ + page: this.page, + size: this.size + }).subscribe(data => this.pageData = data); } onDelete(index: number, id: number): void { ``` ## 測試 測試大家已經輕車熟路了: ```typescript +++ b/first-app/src/app/student/student.component.spec.ts @@ -2,6 +2,8 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {StudentComponent} from './student.component'; import {RouterTestingModule} from '@angular/router/testing'; +import {getTestScheduler} from 'jasmine-marbles'; +import {MockApiTestingModule} from '../mock-api/mock-api-testing.module'; describe('StudentComponent', () => { let component: StudentComponent; @@ -11,7 +13,8 @@ describe('StudentComponent', () => { await TestBed.configureTestingModule({ declarations: [StudentComponent], imports: [ - RouterTestingModule + RouterTestingModule, + MockApiTestingModule ] }) .compileComponents(); @@ -26,4 +29,9 @@ describe('StudentComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + fit('onInit', () => { + getTestScheduler().flush(); + fixture.autoDetectChanges(); + }); }); ``` ![image-20210416162431130](https://img.kancloud.cn/33/7c/337cf663142a2b91550e1c8fd2292d7c_1626x438.png) | 名稱 | 鏈接 | | -------- | ------------------------------------------------------------ | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step7.4.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.4.1.zip) |
                  <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>

                              哎呀哎呀视频在线观看