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

                與路由參數快照只能獲取一次路由參數不同,可訂閱的路由參數可以在路由參數發生變更時時時的通知我們,路由參數每變化一次我們就會得到一個新的通知。 `ActivatedRoute`提供的`params`屬性具有上述特性,我們可以通過訂閱它來完成:當路由參數發生變化時得到一個最新的通知。 ## `ActivatedRoute.params` 在編輯組件中訂閱`ActivatedRoute.params`看看會發生什么: ```typescript +++ b/first-app/src/app/student/edit/edit.component.ts @@ -30,6 +30,9 @@ export class EditComponent implements OnInit { } ngOnInit(): void { + this.activatedRoute.params.subscribe(params => { + console.log('路由參數發生了變化', params); + }); this.id = +this.activatedRoute.snapshot.params.id; Assert.isNumber(this.id, '接收到的id類型不正確'); this.loadData(this.id); ``` 此時: ? 瀏覽器地址為`http://localhost:4200/student`時,學生列表組件不存在,則初始化學生列表組件。 ? 第1次點擊編輯按鈕時,瀏覽器地址由`http://localhost:4200/student`跳轉至`http://localhost:4200/student/edit/1`,學生編輯組件不存在,則初始化學生編輯組件。該組件中的`ngOnInit`方法被自動調用1次。在該方法中對`ActivatedRoute.params`進行訂閱,通過訂閱操作接收到了路由參數信息: ![image-20210611171405883](https://img.kancloud.cn/87/69/8769797a657b5d0873f82ddc253819ba_1266x140.png) ? 第2次點擊編輯按鈕時,瀏覽器地址由`http://localhost:4200/student/edit/1`變更為`http://localhost:4200/student/edit/2`,學生編輯組件已存在,組件什么也不做。但由于在?中對路由參數進行了訂閱,所以仍然可以在控制臺中打印最新的路由參數信息: ![image-20210611171619260](https://img.kancloud.cn/61/1e/611ea5bd2837fafce87c661a804dcb56_1070x156.png) 如此以來,即使組件沒有重新初始化,我們仍然可以在路由參數發生變化時重新請求后臺,進而達到顯示待更新學生的目的: ```typescript +++ b/first-app/src/app/student/edit/edit.component.ts ngOnInit(): void { this.activatedRoute.params.subscribe(params => { console.log('路由參數發生了變化', params); this.id = +params.id; Assert.isNumber(this.id, '接收到的id類型不正確'); this.loadData(this.id); }); } ``` 此時,無論我們怎么點擊編輯按鈕,學生編輯組件都會根據最新的路由參數重新發起對后臺的數據請求,然后將請求的數據顯示在待學生更新組件上。 ## 萬物歸一 解決完上述BUG后,我們接著利用該思想繼續解決下一個BUG: ? 訪問`http://localhost:4200/student`學生列表。 ? 點擊編輯按鈕,編輯某個學生。 ? 變更學生的基本信息后,點擊保存。 ? 但是:原學生信息并未更新。 ? 然而:刷新當前界面后發生學生信息已更新。 其實這個BUG產生的原因與前面我們剛剛遇到的問題完全一致。都是由于在路由跳轉時Angular檢測到了可以重復利用的組件,進而沒有重新實例化新組件的造成的: ? 訪問`http://localhost:4200/student`學生列表。 - 學生列表組件初始化,執行1次`ngOnInit()`,在此方法中請求后臺數據,使用后臺數據完成渲染,顯示學生列表。 ? 點擊編輯按鈕,編輯某個學生。 - 學生列表組件已存在,復用原組件。 ? 變更學生的基本信息后,點擊保存。 - 學生列表組件已存在,復用原組件。 ? 但是:原學生信息并未更新。 - 學生列表組件并沒有感知到路由發生變化的能力,所以什么也不會做。 ? 然而:刷新當前界面后發生學生信息已更新。 - 重新實例化學生列表組件,執行1次`ngOnInit()`,在此方法中請求后臺數據,使用后臺數據完成渲染,顯示學生列表。 分析完組件的初始化過程后,解決的思路也有了:使學生列表組件感知到路由參數的變化。那么是否可以像`edit`組件一樣直接來訂閱`ActivatedRoute.params`呢?很遺憾,答案是否定的。 這是由于以下路由配置決定的: ```typescript const routes = [ { path: '', component: StudentComponent, ?? children: [ { path: 'add', component: AddComponent }, { path: 'edit/:id', component: EditComponent } ] } ] as Route[]; ``` 這個`children`意味著:無論是由`http://localhost:4200/student`跳轉到`http://localhost:4200/student/edit/1`;還是由`http://localhost:4200/student/edit/1`跳轉到`http://localhost:4200/student`。對于`StudentComponent`中的`ActivatedRoute.params`而言,均未發生變化。 總結:訂閱`ActivatedRoute.params`無法感覺到路由在子路由及當前路由間的跳轉。 這時候就需要請出`Router`這個大佬了。與`ActivatedRoute`不同,`Router`大佬可以感知到任何的路由事件,而不會局限在某一方面。 --------------------------------------**以下內容了解即可**-------------------------------------- 這時候我們可以這樣做: ```typescript +++ b/first-app/src/app/student/student.component.ts @@ -1,25 +1,44 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import {Page} from '../entity/page'; import {Student} from '../entity/student'; import {StudentService} from '../service/student.service'; import {environment} from '../../environments/environment'; import {Confirm, Report} from 'notiflix'; +import {NavigationEnd, Router} from '@angular/router'; +import {filter} from 'rxjs/operators'; +import {Subscription} from 'rxjs'; @Component({ selector: 'app-student', templateUrl: './student.component.html', styleUrls: ['./student.component.css'] }) -export class StudentComponent implements OnInit { +export class StudentComponent implements OnInit, OnDestroy { pageData = {} as Page<Student>; page = 0; size = environment.size; - constructor(private studentService: StudentService) { + /** + * 當前組件所有的訂閱信息 + */ + subscriptions = new Array<Subscription>(); + + + constructor(private studentService: StudentService, + private router: Router) { } ngOnInit(): void { this.loadData(this.page); + this.subscriptions.push(this.router.events + .pipe(filter(e => e instanceof NavigationEnd)) + .subscribe(event => { + event = event as NavigationEnd; + if (event.url === '/student') { + console.log('感知到了路由事件,重新請求數據'); + this.loadData(this.page); + } + })); } /** @@ -74,4 +93,11 @@ export class StudentComponent implements OnInit { }); } } + + /** + * 生產項目中,應該在組件銷毀時,取消所有的訂閱 + */ + ngOnDestroy(): void { + this.subscriptions.forEach(s => s.unsubscribe()); + } } ``` 該部分內容有一定難度,超出了本教程(入門)的教學范圍,具體代碼功能不做解釋。 此時,將編輯完某個學生信息后回跳到學生列表界面時數據便會重新請求1次。 **注意**:由于上述方法直接使用了`url`進行判斷,這將導致其將隨著在生產環境中重新配置路由而失效。在生產環境中,應該通過`service`來發送子組件的更新、新增事件,然后在學生列表組件中進行訂閱。 ^^^^^^^^^^^^^^^^^^^^^^^^^^以上內容了解即可**^^^^^^^^^^^^^^^^^^^^^^^^^^ ## 單元測試 功能實現后,再介紹下類似于這種**可訂閱的數據**應該如何進行單元測試,即在單元測試中如何給出一個**假的**可訂閱數據。 先啟動學生編輯組件的單元測試: ```typescript +++ b/first-app/src/app/student/edit/edit.component.spec.ts @@ -31,7 +31,7 @@ describe('EditComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + fit('should create', () => { expect(component).toBeTruthy(); }); ``` 錯誤如下: ![image-20210611182450891](https://img.kancloud.cn/4b/d8/4bd87684bdb9cc54c67a3626c8be0379_1582x102.png) 報錯的原因是我們在單元測試中沒能提供一個帶有可訂閱屬性`params`的`ActivatedRoute`,那么提供一個好了。 ```typescript +++ b/first-app/src/app/student/edit/edit.component.spec.ts @@ -7,6 +7,7 @@ import {RouterTestingModule} from '@angular/router/testing'; import {ActivatedRoute, RouterModule} from '@angular/router'; import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module'; import {ReactiveFormsModule} from '@angular/forms'; +import {of} from 'rxjs'; describe('EditComponent', () => { @@ -19,7 +20,12 @@ describe('EditComponent', () => { ClazzSelectModule, ReactiveFormsModule], declarations: [EditComponent], providers: [ - {provide: ActivatedRoute, useValue: {snapshot: {params: {id: '123'}}}} + { + provide: ActivatedRoute, + useValue: { + params: of({id: '123'})① + } + } ] }) ``` ① `of`函數可快速的提供一個可被訂閱的數據源。 ① `of`函數接收任意類型,并將該值在被訂閱時發送出去,可以測試以下代碼: ```typescript const a = of('123'); a.subscribe(s => console.log(s)); ``` 上述代碼我們使用`of`將`{id: '123'}`做為數據源發送給了訂閱者。此時,當在編輯組件中訂閱`ActivatedRoute.params`時,則將得到`{id: '123'}`。編輯組件接收到`123`后,則會使用該`id`來請求數據,近而完成待更新學生數據的展示: ![image-20210611183325592](https://img.kancloud.cn/d6/2c/d62c12bab6af48264e55f51ab52a89f9_1454x630.png) 此時當點擊保存按鈕時,則會發生如下異常: ![image-20210611183417365](https://img.kancloud.cn/f4/b8/f4b8ea89ad370dfc79b57ff3ef5a4bb2_1210x132.png) 這是由于此時編輯組件在更新成功后,執行了如下代碼: ```typescript this.router.navigate(['../../'], {relativeTo: this.activatedRoute}); ``` ### Router 由于我們并沒有聲明提供`Router`,所以此時組件中的`this.router`仍然為Angular提供的。此`router` 在進行跳轉時需要依賴于直接的地址,而當前單元測試并沒有這樣的環境,所以報出了上述異常。 解決該問題的方法與解決可訂閱的路由參數的方法異曲同工:手動提供一個`Router`。 ```typescript +++ b/first-app/src/app/student/edit/edit.component.spec.ts @@ -4,7 +4,7 @@ import {EditComponent} from './edit.component'; import {MockApiTestingModule} from '../../mock-api/mock-api-testing.module'; import {getTestScheduler} from 'jasmine-marbles'; import {RouterTestingModule} from '@angular/router/testing'; -import {ActivatedRoute, RouterModule} from '@angular/router'; +import {ActivatedRoute, Router, RouterModule} from '@angular/router'; import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module'; import {ReactiveFormsModule} from '@angular/forms'; import {of} from 'rxjs'; @@ -25,6 +25,14 @@ describe('EditComponent', () => { useValue: { params: of({id: '123'}) } + }, + { + provide: Router, + useValue: { + navigate: (...args: any) => { + console.log('調用了navigate方法', args); + } + } } ] }) ``` 此時在單元測試中再次點擊保存按鈕,則會在控制臺中打印一條信息: ![image-20210611183951122](https://img.kancloud.cn/6e/20/6e2053f9c64d780a1c2926650b429fc6_898x154.png) 學生編輯組件的單元測試完成后,將`fit`恢復為`it`,查看項目整體單元測試情況。 ## ng t 錯誤如下: ![image-20210611184327526](https://img.kancloud.cn/f6/6a/f66a27dad66bc23f8a113d480637e875_1524x148.png) 該錯誤是由于單元測試未能提供`Router`造成的: ```typescript +++ b/first-app/src/app/student/add/add.component.spec.ts @@ -10,6 +10,7 @@ import {map} from 'rxjs/operators'; import {LoadingModule} from '../../directive/loading/loading.module'; import {randomString} from '@yunzhi/ng-mock-api/testing'; import {randomNumber} from '@yunzhi/ng-mock-api'; +import {RouterTestingModule} from '@angular/router/testing'; describe('student -> AddComponent', () => { let component: AddComponent; @@ -19,6 +20,7 @@ describe('student -> AddComponent', () => { await TestBed.configureTestingModule({ declarations: [AddComponent], imports: [ + RouterTestingModule, ReactiveFormsModule, ClazzSelectModule, MockApiTestingModule, ``` 修正后錯誤消失,好了,就到這里。 ## 資源鏈接 | 鏈接 | 名稱 | | ------------------------------------------------------------ | ------------- | | [https://github.com/mengyunzhi/angular11-guild/archive/step7.7.5.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.7.5.zip) | 本節源碼 | | [https://angular.cn/guide/dependency-injection-providers](https://angular.cn/guide/dependency-injection-providers) | DI提供者 | | [https://angular.cn/api/router/NavigationEnd](https://angular.cn/api/router/NavigationEnd) | NavigationEnd | | [https://cn.rx.js.org/class/es6/Observable.js~Observable.html#static-method-of](https://cn.rx.js.org/class/es6/Observable.js~Observable.html#static-method-of) | rxjs - of() |
                  <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>

                              哎呀哎呀视频在线观看