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

                一切由實踐中來,一切又要到實踐中去。在沒有進行正式的對接前還不能說當前的思想及實現是一定沒有問題的。本節將利用上一小節中的/Teacher/me接口來實現簡單的個人中心功能。 # 初始化 打開前臺并來到app文件夾,使用`ng g c PersonalCenter`初始化個人中心 ``` panjiedeMac-Pro:app panjie$ ng g c PersonalCenter CREATE src/app/personal-center/personal-center.component.sass (0 bytes) CREATE src/app/personal-center/personal-center.component.html (30 bytes) CREATE src/app/personal-center/personal-center.component.spec.ts (685 bytes) CREATE src/app/personal-center/personal-center.component.ts (305 bytes) UPDATE src/app/app.module.ts (1356 bytes) ``` 將personal-center.component.spec.ts中的測試用例由`it`暫時修改為`fit`后啟動單元測試。 ## C層 personal-center/personal-center.component.ts ```typescript import { Component, OnInit } from '@angular/core'; import {Teacher} from '../norm/entity/Teacher'; @Component({ selector: 'app-personal-center', templateUrl: './personal-center.component.html', styleUrls: ['./personal-center.component.sass'] }) export class PersonalCenterComponent implements OnInit { /** 綁定到V層 */ public teacher: Teacher; constructor() { } ngOnInit() { // 調用M層的相關方法 } } ``` ## V層 在V層顯示當前登錄用戶的基本信息:用戶名、姓名、性別、email四項信息。 personal-center.component.html ```html <div class="row"> <div class="col-sm-2 text-right">姓名</div> <div class="col-sm-10"> {{teacher.name}} </div> </div> <div class="row"> <div class="col-sm-2 text-right">用戶名</div> <div class="col-sm-10"> {{teacher.username}} </div> </div> <div class="row"> <div class="col-sm-2 text-right">性別</div> <div class="col-sm-10"> {{teacher.sex}} </div> </div> <div class="row"> <div class="col-sm-2 text-right">email</div> <div class="col-sm-10"> {{teacher.email}} </div> </div> ``` ## 單元測試 在CV層的基礎上測試CV層的數據綁定是否成功。測試步驟為:1.在C層設置teacher的值。2.V層獲取顯示的值并進行斷言 測試的重點在于如何通過V層的class值獲取到輸出的問題,具體的過程略。 # 功能開發 像這種直接由后臺獲取數據后顯示在V層的邏輯非常的簡單。C層中直接調用M層的相關方法獲取返回值即可。 ## M層 打開TeacherService新建me方法 service/teacher.service.ts ```typescript /** * 獲取當前登錄的教師 */ me(): Observable<Teacher> { const url = 'http://localhost:8080/Teacher/me'; return this.httpClient.get<Teacher>(url); } ``` ### 單元測試 service/teacher.service.spec.ts ```typescript fit('me', () => { // 獲取service實例 const service: TeacherService = TestBed.get(TeacherService); // 調用測試方法 let result; service.me().subscribe((teacher) => { result = teacher; }); // 斷言發起了特定的http請求 const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Teacher/me'); expect(req.request.method).toEqual('GET'); // 模擬返回數據,請斷言在訂閱的方法中成功的接收到了數據 const mockReturnTeacher = new Teacher(null, null, null, null); req.flush(mockReturnTeacher); expect(result).toBe(mockReturnTeacher); }); ``` ## C層代碼對接 personal-center/personal-center.component.ts ```typescript constructor(private teacherService: TeacherService) { } ngOnInit() { // 調用M層的相關方法 this.teacherService.me().subscribe((teacher) => { this.teacher = teacher; }); } ``` ### 單元測試 在單元測試中,沿用引用TestModule的方式。該方式能夠自動注入TeacherService的替身TeacherStubService personal-center/personal-center.component.spec.ts ```typescript beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ PersonalCenterComponent ], imports: [ TestModule ? ] }) .compileComponents(); })); fit('ngOnInit', () => { }); ``` * ? 引入TestModule 初充功能如下: personal-center/personal-center.component.spec.ts ```typescript fit('ngOnInit', () => { const teacherService = TestBed.get(TeacherService) as TeacherService; const mockReturnTeacher = new Teacher(null, null, null); spyOn(teacherService, 'me').and.returnValue(of(mockReturnTeacher)); component.ngOnInit(); expect(component.teacher).toBe(mockReturnTeacher); }); ``` 測試結果: ``` TypeError: this.teacherService.me is not a function ``` 此錯誤前面碰到過,原因是由于做為替身出現的TeacherStubService中并沒有me方法,所以在調用`spyOn(teacherService, 'me')`出現了該錯誤。找到此替身被加入`me`方法 test/service/teacher-stub.service.ts ```typescript me(): Observable<Teacher> { return null; } ``` 出現錯誤如下: ``` TypeError: Cannot read property 'subscribe' of null ... at PersonalCenterComponent.ngOnInit (http://localhost:9876/_karma_webpack_/src/app/personal-center/personal-center.component.ts:17:29) ``` 錯誤顯示在以下代碼的執行過程中發出了null錯誤: personal-center/personal-center.component.ts ```typescript ngOnInit() { // 調用M層的相關方法 this.teacherService.me()★.subscribe((teacher) => { this.teacher = teacher; }); } ``` * ★ 此方法的返回值為null 這個此錯誤的原因如下: 在單元測試文件中的測試用例執行以前首先會構建一個測試組件,語句如下: ``` beforeEach(() => { fixture = TestBed.createComponent(PersonalCenterComponent); component = fixture.componentInstance; fixture.detectChanges(); ★ }); ``` * ★ 此語句首次被執行時,將調用`ngOnInit`方法一次。 該測試組件構造完成后,才會執行測試用例中的測試代碼。而前面的錯誤就在于執行`fixture.detectChanges()`時自動調用的`ngOnInit`上。此時執行`ngOnInit`時,組件中的teacherService為替身TeacherStubService,所以在執行teacherService.me()方法時相當于調用的是TeacherStubService中的me()方法,而該方法的返回值被設置為null。便發生了在null上調用subscribe方法的錯誤。 >[success] 在構建組件時,組件中的ngOnInit方法會被自動調用一次。測試代碼中的fixture.detectChanges()模似了該過程。 修正方法為設置TeacherService.me()方法的返回值: test/service/teacher-stub.service.ts ```typescript me(): Observable<Teacher> { return of(new Teacher(1, 'username', 'name')); } ``` >[info] 在生產環境中,會為此方法設置與后臺返回字段一一對應的示例返回值。 再次運行單元測試通過。 # 集成測試 為個人中心設置路由及菜單項如下 app-routing.module.ts ```typescript const routes: Routes = [ { path: '', component: WelcomeComponent }, { path: 'personalCenter', component: PersonalCenterComponent }, { path: 'teacher', component: TeacherIndexComponent }, ``` nav/nav.component.ts ```typescript this.menus.push({url: 'student', name: '學生管理'}); this.menus.push({url: 'personalCenter', name: '個人中心'}); } ``` 啟動前后臺,并在數據表中添加登錄用戶后進行測試: ![](https://img.kancloud.cn/5f/70/5f70736737853e9889802ab296c7ba18_1248x366.gif) 測試成功,當訪問個人中心時,顯示了當前的登錄用戶信息。但看似一切正常的程序其實尚有bug:打開控制臺會發現控制臺中尚有錯誤: ![](https://img.kancloud.cn/d8/25/d82580f5bb43bdae1d459b2f157678e1_1384x131.png) 提示說在V層的第3行有在undefined上讀取name屬性的錯誤。 如果在個人中心處進行頁面的刷新,還會產生如下錯誤: ![](https://img.kancloud.cn/04/f3/04f3fa8696d1eda7f2ac3ee60682973d_1248x366.gif) 刷新頁面后個人中心中的內容不見了。。。 ## undefined上讀取name屬性 該錯誤的產生是由于向后臺進行數據請求是異步產生了,在前面的章節中已經介紹過這個問題。在此重復介紹一遍。個人中心的C層代碼如下: ```typescript /** 綁定到V層 */ public teacher: Teacher; constructor(private teacherService: TeacherService) { // ? 構造此類,此時this.teacher的類型為undefined } ngOnInit() ? { // 調用M層的相關方法 this.teacherService.me().subscribe?((teacher) => { this.teacher = teacher; ? // ? 與V層綁定的數據this.teacher發生變化,再次進行渲染。此時this.teacher非undefiend }); // ? ngOnInit方法執行結束后,渲染V層(多次渲染)。此時this.teacher的類型為undefined發生錯誤 } ``` 執行方法?時,由于該方法最終調用的是httpClient.get向后臺發起請求,js無法判斷此請求的時間,所以不會等待其返回以后再繼續執行,而是選擇直接執行?。?執行完畢后,請求后臺有了結果,此時再執行?。所以最終的執行順序為 ? -> ? -> ? -> ? -> ? -> ? 為了更加清晰的看到上述的執行過程,使用以下代碼改造該文件: ```typescript export class PersonalCenterComponent implements OnInit { /** 綁定到V層 */ public teacher: Teacher; constructor(private teacherService: TeacherService) { console.log('construct'); } ngOnInit() { console.log('ngInit'); // 調用M層的相關方法 this.teacherService.me().subscribe((teacher) => { console.log('return teacher'); this.teacher = teacher; console.log('set return teacher'); }); console.log('done'); } } ``` 然后再次測試(不要在個人中心頁面刷新,應該先登錄然后再點個人中心),控制臺信息如下: ``` personal-center.component.ts:14 construct personal-center.component.ts:18 ngInit personal-center.component.ts:25 go PersonalCenterComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined PersonalCenterComponent.html:3 ERROR CONTEXT PersonalCenterComponent.html:3 ERROR TypeError: Cannot read property 'name' of undefined PersonalCenterComponent.html:3 ERROR CONTEXT personal-center.component.ts:21 return teacher personal-center.component.ts:23 set return teacher ``` 以上信息足以印證了剛剛對執行過程的猜測,也進一步的驗證了在單元測試中`detectChanges()`方法的作用。在單元測試中需要手動的調用`detectChanges()`來完成V層的渲染,在非單元測試中angular會自動調用組件的`detectChanges`方法來完成組件的渲染,且會渲染多次,當連續兩次渲染的結果相同時認為渲染成功終止當次渲染。 angular在V層提供了`?`來避免異步請求渲染出現在undefined上讀取屬性錯誤的問題。在進行渲染時angular遇到`?`關鍵字,則首先會查看用`?`標識的變量的類型是否為undefined。如果為undefined,則停止此字段的渲染,否則繼續此字段的渲染。所以對V層進行如下的修正: ``` {{teacher?.name}} ``` 其它的字段請自行修正,將`teacher.name`修改為`teacher?.name`后,渲染到該字段時會首先對teacher是否為undefined進行判斷。再次測試錯誤消失。 ## 刷新個人中心異常 刷新個人中心后未顯示當前登錄用戶的信息。這是由于前臺、后臺的設計思想都有些問題造成的。 ### 前臺 對前臺而言,每次進行頁面刷新后的首次請求會使用空的auth-token請求后臺,這導致了后臺為刷新的請求分配了新的auth-token,而此auth-token并未與任何的用戶進行綁定。所以當用戶使用最新分發的未與任何用戶綁定的auth-token請求me接口時,接收到了空值,近而導致個人中心的內容為空。 ### 后臺 對后臺而言,用戶請求me接口的前提應該是:用戶使用了已綁定了用戶的auth-token,因為只有在這個前提下,才可能給用戶返回預期的值。而當使用了未綁定用戶的auth-token時,后臺應該給前臺發送**請先完成系統登錄**的前置條件,而非返回了空值。 在一下個小節中,將使用后臺的攔截器完成接口的認證校驗問題。每次請求后臺均要進行攔截,如果當前請求訪問的是用戶登錄接口則放行,如果是其它接口則對當前auth-token是否已綁定了用戶,未綁定則返回相應的錯誤信息以提示前臺。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.7](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.7) | - |
                  <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>

                              哎呀哎呀视频在线观看