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

                基本的功能完成了但還有一些小問題,比如:如果添加了重名的課程,則需要到控制臺中查看錯誤信息。 本節將采用異步驗證器的方式判斷添加的課程名是否重復,這樣一來如果要添加的課程名已經存在于數據庫中則直接在前臺提示用戶。 # 異步驗證 在前面的章節中已經接觸了required、minLength、maxLength三個**同步**驗證器,只所以稱為同步驗證器是由于其驗證過程直接發生在前臺。而要添加的課程名稱是否與數據庫的課程名稱發生沖突,則需要借助于后臺進行判斷。JS中有兩種情況下執行異步操作,第一種情況是執行setTimeout方法時,第二種情況是發生資源請求時(與后臺通訊)時。所以借助于后臺才能驗證成功的驗證器被稱為異步驗證器。 新的知識點我們按由后到前的順序逐點進行開發。 # 后臺 對課程名稱的驗重需要接收課程名稱,返回值的類型定義為boolean,當傳入的名稱已存在于數據庫中的話返回true,當傳入的名稱在數據庫中不存在話返回false。 接口規范如下: ``` GET /Course/existsByName?name=xxx true: 名稱已存在 false: 名稱不存在 ``` ## 倉庫層 repository/CourseRepository.java ```java public interface CourseRepository extends CrudRepository<Course, Long> { /** * 課程名稱是否存在 * @param name 課程名稱 * @return true 存在 */ boolean existsByName(String name); } ``` ### 單元測試 新建對應的單元測試并初始化如下: repository/CourseRepositoryTest.java ```java @SpringBootTest @RunWith(SpringRunner.class) public class CourseRepositoryTest { @Autowired CourseRepository courseRepository; @Test public void existsByName() { // 生成隨機字符串的課程名 // 調用existsByName方法,斷言返回false // 新建課程,課程名用上面生成的隨機字符串,保存課程 // 再次調用existsByName方法,斷言返回true } } ``` 補充測試代碼如下: repository/CourseRepositoryTest.java ```java @Test public void existsByName() { // 生成隨機字符串的課程名 String name = RandomString.make(10); // 調用existsByName方法,斷言返回false Assert.assertFalse(this.courseRepository.existsByName(name)); // 新建課程,課程名用上面生成的隨機字符串,保存課程 Course course = new Course(); course.setName(name); this.courseRepository.save(course); // 再次調用existsByName方法,斷言返回true Assert.assertTrue(this.courseRepository.existsByName(name)); } ``` 單元測試通過。 ## M層 在M層中僅僅做數據轉發即可。 service/CourseService.java ```java /** * 名稱是否存在 * @param name 課程名稱 * @return true 存在 */ boolean existsByName(String name); ``` 實現類: service/CourseServiceImpl.java ```java @Override public boolean existsByName(String name) { return this.courseRepository.existsByName(name); } ``` ### 單元測試 service/CourseServiceImplTest.java ```java @Test public void existsByName() { String name = RandomString.make(10); Mockito.when(this.courseRepository.existsByName(name)).thenReturn(false); boolean result = this.courseService.existsByName(name); Assert.assertFalse(result); } ``` ## C層 controller/CourseController.java ```java @GetMapping("existsByName") public boolean existsByName(@RequestParam String name) { return this.courseService.existsByName(name); } ``` ### 單元測試 controller/CourseControllerTest.java ```java @Test public void existsByName() throws Exception { String name = RandomString.make(4); String url = "/Course/existsByName"; Mockito.when(this.courseService.existsByName(Mockito.eq(name))).thenReturn(false); this.mockMvc.perform(MockMvcRequestBuilders.get(url) .param("name", name)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("false")) ; } ``` 后臺的接口準備完畢后,開始進行前臺表單的異步驗證。 # 前臺 在正式的書寫異步驗證器前,在進行一些準備工作:在M層中添加對應的existsByName方法。 ## M層 service/course.service.ts ```typescript /** * 課程名稱是否存在 * @param name 課程名稱 */ existsByName(name: string): Observable<boolean> { const url = this.url + '/existsByName'; return this.httpClient.get<boolean>(url, {params: {name}}); } ``` ### 單元測試 service/course.service.spec.ts ```typescript fit('existsByName', () => { const service: CourseService = TestBed.get(CourseService); const name = 'test'; let result; service.existsByName(name).subscribe((data) => { result = data; }); const testController = TestBed.get(HttpTestingController) as HttpTestingController; const request = testController.expectOne(req => req.url === 'http://localhost:8080/Course/existsByName'); expect(request.request.params.get('name')).toEqual('test'); expect(request.request.method).toEqual('GET'); }); ``` ## 異步驗證器 來到src/app/course文件夾下建立validator文件夾,建立UniqueNameValidator驗證器: ``` panjiedeMac-Pro:validator panjie$ ng g class UniqueNameValidator CREATE src/app/course/validator/unique-name-validator.spec.ts (208 bytes) CREATE src/app/course/validator/unique-name-validator.ts (37 bytes) ``` 異步驗證器需要實現AsyncValidator接口中的validate方法,初始化如下: course/validator/unique-name-validator.ts ```typescript import {AbstractControl, AsyncValidator, ValidationErrors} from '@angular/forms'; import {Observable, of} from 'rxjs'; import {Injectable} from '@angular/core'; import {CourseModule} from '../course.module'; /** * 課程名稱唯一性異步驗證器 */ @Injectable({ providedIn: 'root' }) export class UniqueNameValidator implements AsyncValidator? { validate?(control: AbstractControl?): Promise<ValidationErrors? | null> | Observable<ValidationErrors? | null> { console.log(control); ? return of({uniqueName: true}); ? } } ``` * ? 實現AsyncValidator的validate方法。 * ? 驗證器對應驗證的表單內容。 * ? 返回值可以是promise(promise是Observable的簡化版,有了Observable以后使用promise的頻率較低) * ? 返回值也可以是Observable * ? 如驗證通過則返回null。如果未通過則返回字符串格式的鍵值對(ValidationErrors) ### 測試 啟動前后臺,將此驗證器添加到表單中的name字段上。 course/add/add.component.ts ```typescript constructor(private formBuilder: FormBuilder, private courseService: CourseService, private uniqueNameValidator: UniqueNameValidator ?) { } ngOnInit() { this.formGroup = this.formBuilder.group({ name: ['', [Validators.minLength(2), Validators.required], this.uniqueNameValidator.validate?] }); this.course = new Course(); } ``` * ? 注入異步驗證器 * ? 添加到name字段的異步驗證器中 課程名稱輸入`1`,同步驗證器驗證失敗,未調用異步驗證器: ![](https://img.kancloud.cn/19/92/19928ac39b43d117a98cd28c0ed6b8ee_650x324.png) 課程名稱輸入`1`,同步驗證器驗證通過,調用異步驗證器中的validate方法,觸發語句`console.log(control);`在控制臺中輸出了AbstractControl信息: ![](https://img.kancloud.cn/ba/2b/ba2bde2ecc9d222a4615f5cc5fb7ccb4_689x386.png) 查看詳情: ![](https://img.kancloud.cn/13/a1/13a1e9294ab598f35c26dafe046e3bbb_473x606.png) 可見在AbstractControl中可以得到當前表單項的輸入值,異步驗證器的返回信息被添加到`errors`中。由此定義前臺V層的提示信息如下: course/add/add.component.html ```html <small id="nameMinLength" *ngIf="formGroup.get('name').errors && formGroup.get('name').errors.minlength" class="form-text text-danger">課程名稱不得少于2個字符</small> <small id="nameUnique" *ngIf="formGroup.get('name').errors && formGroup.get('name').errors.uniqueName" class="form-text text-danger">當前課程名已被占用</small> <div class="form-group"> ``` ![](.9_images/84.gif) ### 對接M層 完成課程是否存在的邏輯功能是由CourseService中的existsByName方法實現的,在驗證器中注入CourseService以調用該功能 course/validator/unique-name-validator.ts ```typescript export class UniqueNameValidator implements AsyncValidator { static courseService: CourseService; ?★ constructor(courseService: CourseService) { UniqueNameValidator.courseService = courseService; } validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors? | null> { return UniqueNameValidator.courseService?.existsByName(control.value); ? return of({uniqueName: true}) ? ? } ``` * ? 由于this作用域的問題,需要將注入的courseService對UniqueNameValidator的靜態變量courseService賦值。 * ? 異步驗證器要求返回的數據流中的數據類型是字符串形式的`鍵值對` * ? CourseService中返回的數據流中的數據類型是boolean >[info] ★這個問題相對比較復雜,需要對ts中的this作用域有較深的理解,在此不進行深入講解。在此不能使用this.courseService的原因是由于在name的異步驗證器中使用了`this.uniqueNameValidator.validate`,這意味著將`validate`函數脫離了`uniqueNameValidator`對象單獨使用。在調用`validate`函數時`this`將取決于被調用時的上下文【選學】。在生產環境訓,還有另外一種更有效的定義異步驗證器的方法,請在google中搜索`AsyncValidatorFn`以獲取更多知識。教程中為了更貼近于angular的官方文檔,使用了官方文檔中的示例方法。 將exists返回的boolean類型的數據流變成`鍵值對`形式的數據流轉發下卻便需要借助RxJS的map操作符了。 # RxJS實踐 在前面的章節中學習過:位于數據流中的轉發者是可以通過操作符來對過境的數據進行轉變的。 ![](.5_images/4620bf8a.png) ## map實踐一 在RxJS中使用map操作符來完成數據格式的轉換。 course/validator/unique-name-validator.ts ```typescript validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> { return this.courseService.existsByName(control.value) .pipe(map()); } ``` 比如將courseService.existsByName方法傳輸過來的boolean類型的數據,轉換為`{uniqueName: true}`,則可以使用以下代碼完成: course/validator/unique-name-validator.ts ```typescript validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> { return this.courseService.existsByName(control.value) .pipe(map?((input?) => { console.log(input); const output = {uniqueName: true}; return output; ? })); } ``` * ? map 操作符接收的參數類型為:回調函數 * ? 將源數據流做為input輸入至回調函數中 * ? 將回調函數中的返回數據做為新的數據流向后進行轉發 ![](https://img.kancloud.cn/76/cd/76cd9f4f9e9ed8331b0d02c652a81de5_879x444.png) 控制臺中打印了input的值為false,說明input接收正確。在V層中顯示了`當前課程名已被占用`的提示語句說明返回的數據流的確為`{uniqueName: true}` 。 查看數據流后,完成邏輯:若existsByName返回的值為true,說明該名稱已被占用,則返回`{uniqueName: true}`;若若existsByName返回的值為false,說明該名稱未被占用,則返回`null`。 course/validator/unique-name-validator.ts ```typescript .pipe(map((input) => { if (input) { return {uniqueName: true}; } else { return null; } })); ``` 重構如下: course/validator/unique-name-validator.ts ```typescript .pipe(map((input) => { return input ? {uniqueName: true} : null; })); ``` 在箭頭函數中,如果函數體中僅有一行代碼且以return打頭,則還可以省略`{}`以及`return`進行如下縮寫: ```typescript .pipe(map((input) => input ? {uniqueName: true} : null)); } ``` 同時若輸入的參數個數為1,且無指定數據類型的需求時,還可以省略`()`: ```typescript .pipe(map(input => input ? {uniqueName: true} : null)); } ``` 刪除回車符后變更為: ```typescript .pipe(map(input => input ? {uniqueName: true} : null)); ``` ### 測試 首先添加教師及班級基本數據,然后添加一個名稱為test的班級。接著刷新頁面,重新輸入班級名稱test ![](https://img.kancloud.cn/c2/e6/c2e6bae8a1caeae9c93f63ff041c586d_491x187.png) 測試通過。 # 單元測試 最后執行`ng test`對全局進行測試。 ``` ERROR in src/app/course/validator/unique-name-validator.spec.ts:5:12 - error TS2554: Expected 1 arguments, but got 0. 5 expect(new UniqueNameValidator()).toBeTruthy(); ~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/course/validator/unique-name-validator.ts:16:15 16 constructor(courseService: CourseService) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An argument for 'courseService' was not provided. ``` 在unique-name-validator.spec.ts中發生了語法錯誤: course/validator/unique-name-validator.spec.ts ```typescript describe('course -> validator -> niqueNameValidator', () => { it('should create an instance', () => { const courseService = new CourseStubService() as CourseService; expect(new UniqueNameValidator(courseService)).toBeTruthy(); }); }); ``` 錯誤: ![](https://img.kancloud.cn/62/0c/620c953e487482627aeab4fc8ec96834_653x147.png) 這是由于更新CourseService卻沒有對應更新其測試替身的原因造成的。 service/course-stub.service.ts ```typescript existsByName(name: string): Observable<boolean> { return of(false★); } ``` 再次測試全部通過。 >[success] ★如果將此處的返回值修改為true,則會觸發其它2個單元測試的錯誤,你知道這是為什么嗎? # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.9](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.9) | - | | 異步驗證器 | [https://www.angular.cn/guide/form-validation#async-validation](https://www.angular.cn/guide/form-validation#async-validation) | 10 | | map操作符 | [https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-map](https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-map) | 5 |
                  <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>

                              哎呀哎呀视频在线观看