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

                本節我們將分頁近一步的剝離為組件的形式,以使其在后期能夠被更多的組件重復使用。 ## 初始化 在clazz模塊中初始化分頁組件: ```bash panjie@panjie-de-Mac-Pro clazz % pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app/clazz panjie@panjie-de-Mac-Pro clazz % ng g c page CREATE src/app/clazz/page/page.component.css (0 bytes) CREATE src/app/clazz/page/page.component.html (19 bytes) CREATE src/app/clazz/page/page.component.spec.ts (612 bytes) CREATE src/app/clazz/page/page.component.ts (267 bytes) UPDATE src/app/clazz/clazz.module.ts (593 bytes) ``` 然后把我們在clazz列表組件中與分頁相關的代碼復制到V層中: ```typescript +++ b/first-app/src/app/clazz/page/page.component.html <nav class="row justify-content-md-center"> <ul class="pagination col-md-auto"> <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一頁</span></li> <li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item"> <span class="page-link" (click)="onPage(p)">{{p + 1}}</span> </li> <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一頁</span></li> </ul> </nav> ``` 根據V層初始化C層的屬性及方法: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts export class PageComponent implements OnInit { pageData: Page<any>; pages: number[]; page: number; constructor() { } ngOnInit(): void { } onPage(page: number): void { } } ``` 最后對屬性進行初始化: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts export class PageComponent implements OnInit { pageData: Page<any> = new Page({ content: [], number: 0, size: 10, numberOfElements: 0 }); pages: number[] = []; page = 0; ``` 代碼完成后,找到對應的單元測試代碼,啟用自動變更檢測: ```typescript +++ b/first-app/src/app/clazz/page/page.component.spec.ts fit('should create', () => { expect(component).toBeTruthy(); fixture.autoDetectChanges(); }); ``` 由于`pages`為空數組,所以對應僅生成了**上一頁**、**下一頁**。又由于當前共0頁、0條數據,所以下一頁、下一頁均為不可點擊狀態: ![image-20210331144515844](https://img.kancloud.cn/99/64/99647ac66c3270db6a2130c362b2bfbf_478x122.png) ## Output() 我們把這種嵌套于其它組件中使用的組件稱為**嵌套組件**,有時也會把**嵌套**該組件的組件稱為父組件,把自己稱為子組件。 在開發嵌套組件的過程中,最重要就是兩項信息:輸入、輸出。所以初始化工作完成后,下一步便是思索該組件的輸入及輸出。 而在輸入與輸出中,應該先站在父組件的角度上思索:當前組件應該輸出什么樣的內容,才能滿足父組件的需求。然后才是站在子組件的角度上思索,預實現輸出需求,需要父組件給自己什么信息。 對于新手而言,弄清父組件需求最簡單的辦法是在開發中將子組件添加到父組件。比如我們將當前page組件應用到clazz列表組件中: ```html +++ b/first-app/src/app/clazz/clazz.component.html @@ -30,6 +30,7 @@ </tbody> </table> +<app-page></app-page> <nav class="row justify-content-md-center"> ``` 然后我們暫時把單元測試的重點移到父組件`clazz`組件上,啟用其對應的單元測試用例: ![image-20210331145536253](https://img.kancloud.cn/04/4e/044e681833d23230336c39b9615f66e1_2112x158.png) 控制臺報錯說不認識`app-page`組件,該問題已然不是第一次出現,請自行解決后繼續。如果解決該錯誤時你并沒有迅速的想到解決方案,則需要復習教程關于模塊組件關系的相關內容;如果看了教程前面的內容還不知道如何解決,則可以參考本節最后的源碼,參考源碼后再與教程前面類似的內容相對照,爭取再以后遇到此類問題時能夠快速的定位錯誤。 接下來我們需要觀察位于clazz列表組件上分頁的相關代碼,觸發C層的哪些方法: ```html <nav class="row justify-content-md-center"> <ul class="pagination col-md-auto"> <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一頁</span></li> <li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item"> <span class="page-link" (click)="onPage(p)??">{{p + 1}}</span> </li> <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一頁</span></li> </ul> </nav> ``` 這個觸發C層的方法,即為父組件對子組件的輸出需求。對于當前分頁組件而言,父組件想要的輸出為:某個被點擊的頁碼。所以我們的分頁組件需要有這樣一個`Output()`: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnInit, Output, EventEmitter} from '@angular/core'; import {Page} from '../../entity/page'; @Component({ @@ -16,6 +16,9 @@ export class PageComponent implements OnInit { pages: number[] = []; page = 0; + @Output() + bePageChange = new EventEmitter<number>(); + constructor() { } ``` `EventEmitter`在使用時,需要指定一個泛型,對于當前需求該泛型的類型為`number`,即點擊的分頁頁碼 。如此便可在clazz列表組件中的page組件上添加對應的方法了: ```html -<app-page></app-page> +<app-page (bePageChange)="onPage($event)"></app-page> ``` - `$event`為Angular的一個關鍵字,表示子組件彈出的內容。 ## Input() 輸出的需求確定后,我們開始思索若要滿足該需求則需要什么樣的支撐數據,該支撐數據是需要由父組件傳入還是可以通過其它的方式獲取,或是可以通過計算得出。 由于我們剛剛已經在教師列表組件實現了分頁功能,所以在此我們清楚的知道如果想生成動態的分頁信息,則需要:當前頁、共幾頁兩項信息,除此以外如果還可以獲取到當前頁是否為首頁、尾頁等信息就更好了。而這些信息,完全可以由父組件集中傳入`Page`類型。 在Angular中可以使用`@Input()`來規定該組件的傳入值,比如我們在`pageData`上使用`@Input()`注解: ```typescript +import {Component, OnInit, Output, EventEmitter, Input} from '@angular/core'; import {Page} from '../../entity/page'; @Component({ @@ -7,6 +7,7 @@ import {Page} from '../../entity/page'; styleUrls: ['./page.component.css'] }) export class PageComponent implements OnInit { + @Input() pageData: Page<any> = new Page({ ``` 如此便可以在使用page組件時,加入`pageData`作為輸入的屬性了: ```html <app-page [pageData]="xxx"></app-page> ``` ### 易懂的代碼 當前使用了`pageData`來表示分頁數據 ,類型為`Page`;使用了`page`來表示當前頁,類型為`number`。這很容易給團隊成員帶來混淆,因為大家往往會想當然的認為`page`的類型應該是`Page`。為了使我們編寫的代碼更易懂,在此將`page`變量名變更為`currentPage`,表示當前頁碼;`pageData`的變量名稱變更為`page`,表示分頁數據: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -8,14 +8,14 @@ import {Page} from '../../entity/page'; }) export class PageComponent implements OnInit { @Input() - pageData: Page<any> = new Page({ + page: Page<any> = new Page({ content: [], number: 0, size: 0, numberOfElements: 0 }); pages: number[] = []; - page = 0; + currentPage = 0; ``` 同步變更V層: ```html +++ b/first-app/src/app/clazz/page/page.component.html - <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一頁</span></li> - <li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item"> + <li [ngClass]="{disabled: page.first}" class="page-item"><span class="page-link">上一頁</span></li> + <li *ngFor="let p of pages" [ngClass]="{active: currentPage === p}" class="page-item"> <span class="page-link" (click)="onPage(p)">{{p + 1}}</span> </li> - <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一頁</span></li> + <li [ngClass]="{disabled: page.last}" class="page-item"><span class="page-link">下一頁</span></li> ``` 最后,在clazz列表組件中為page組件設置`page`數據輸入,同時刪除原分頁信息: ```html +++ b/first-app/src/app/clazz/clazz.component.html @@ -30,13 +30,4 @@ </tbody> </table> -<app-page (bePageChange)="onPage($event)"></app-page> -<nav class="row justify-content-md-center"> - <ul class="pagination col-md-auto"> - <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一頁</span></li> - <li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item"> - <span class="page-link" (click)="onPage(p)">{{p + 1}}</span> - </li> - <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一頁</span></li> - </ul> -</nav> +<app-page [page]="pageData" (bePageChange)="onPage($event)"></app-page> ``` **注意:**當前我們分別在clazz列表組件、page分頁組件中操作,請注意代碼變更的位置。 ## 變更檢測 剛剛我們使用`@Input()`獲取了父組件輸入的分頁信息,該信息中包括了當前頁、總頁數、總條數、每頁大小等。接下來希望能通過當前頁及總頁數來動態的生成頁碼。 為了更清晰的明了父子組間的初始化、傳值過程,我們在分別在`clazz`組件及`page`組件中打幾個斷點: 父組件: ```typescript +++ b/first-app/src/app/clazz/clazz.component.ts ngOnInit(): void { + console.log('clazz組件調用ngOnInit()'); // 使用默認值 page = 0 調用loadByPage()方法 this.loadByPage(); } loadByPage(page = 0): void { + console.log('觸發loadByPage方法'); const httpParams = new HttpParams().append('page', page.toString()) .append('size', this.size.toString()); this.httpClient.get<Page<Clazz>>('/clazz/page', {params: httpParams}) .subscribe(pageData => { // 在請求數據之后設置當前頁 this.page = page; + console.log('clazz組件接收到返回數據,重新設置pageData'); this.pageData = pageData; ``` 子組件: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts ngOnInit(): void { console.log('page組件調用ngOnInit()方法'); console.log('當前頁', this.page.number); console.log('總頁數', this.page.totalPages); } ``` 然后執行查看具體的執行過程: ![image-20210401091049025](https://img.kancloud.cn/26/2a/262a7d72ff4960bdb760d81d52f9fa92_978x304.png) 通過控制臺打印的信息不難得出,當clazz父組件調用page子組件時,執行過程如下: ![image-20210401092004125](https://img.kancloud.cn/25/bb/25bb40711819030f15122d798a1bc01c_854x768.png)分頁組件是根據父組件傳入:共多少頁、當前是第幾頁兩個關鍵信息來生成分頁按鈕的。現在面臨的問題時當父組件接收到后臺返回的數據后,未觸發子組件的`ngOnInit()`方法。 > ? 真實的過程是:實例化子組件、設置子組件的page屬性,最后再調用ngOnInit()方法。 此時如若我們在子組件的`ngOnInit()`方法中根據總頁數、當前頁來生成分頁,則由于總頁數為0,當前頁為0而只能生成一個空分頁。所以現在我們面臨的問題是:當父組件更新分頁數據時,如何去調用子組件中的某個方法。 ![image-20210401092519595](https://img.kancloud.cn/ed/03/ed032e876769a430ab946548080e1497_924x456.png) 在Angular中,`@Input()`除了可以做為組件屬性的注解外,還可以做為`set`方法的注解。當做為屬性的注解時,`@Input()`注解下的值將在組件初始化時被賦值1次;當做為`set`方法的注解時,`@Input()`注解下的方法將在組件初化時被賦值1次,同時父組件中對應的值每變更一次,`@Input()`注解下的方法便執行1次。 新建`set page()`方法,并移除原`page`屬性上的`@Input()`注解: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -7,7 +7,6 @@ import {Page} from '../../entity/page'; styleUrls: ['./page.component.css'] }) export class PageComponent implements OnInit { - @Input() page: Page<any> = new Page({ content: [], number: 0, @@ -17,7 +16,11 @@ export class PageComponent implements OnInit { pages: number[] = []; currentPage = 0; - + @Input() + set page(page: Page<any>) { + this.page = page; + } + ``` 新增`set page()`方法后,使用分頁組件的方法與原來完全相同: ```html <app-page [page]="xxx"></app-page> ``` 不同的是,原來的`page`屬性僅會在子組件`page`初始化賦值一次;而當下父組件中的`xxx`每變化一次,子組件對應的方法便會執行一次。同時TypeScript的語法要求類中的屬性名與方法不能重復: ![image-20210401093738675](https://img.kancloud.cn/c4/9c/c49c7e5563570bda887d825d78fa31bc_783x394.png) 為此我們將原`page`屬性重新命名為`inputPage`: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -7,7 +7,7 @@ import {Page} from '../../entity/page'; styleUrls: ['./page.component.css'] }) export class PageComponent implements OnInit { - page: Page<any> = new Page({ + inputCache: Page<any> = new Page({ content: [], number: 0, size: 0, @@ -17,8 +17,8 @@ export class PageComponent implements OnInit { currentPage = 0; @Input() - set page(page: Page<any>): void { - this.page = page; + set page(page: Page<any>) { + this.inputCache = page; } @Output() @@ -29,8 +29,8 @@ export class PageComponent implements OnInit { ngOnInit(): void { console.log('page組件調用ngOnInit()方法'); - console.log('當前頁', this.page.number); - console.log('總頁數', this.page.totalPages); + console.log('當前頁', this.inputCache.number); + console.log('總頁數', this.inputCache.totalPages); } ``` 最后,在`set page`方法中同樣打印一些輔助信息: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -19,6 +19,9 @@ export class PageComponent implements OnInit { @Input() set page(page: Page<any>) { this.inputPage = page; + console.log('set page被調用'); + console.log('當前頁', this.inputPage.number); + console.log('總頁數', this.inputPage.totalPages); } @Output() ``` 然后查看控制臺的打印信息: ![image-20210401094051279](https://img.kancloud.cn/2b/4b/2b4ba3b8bc6d1782d4dc4d13888539a6_1178x560.png) 加入`set page`方法后整個調用過程如下: ![image-20210401100646484](https://img.kancloud.cn/f3/42/f342db8812907f54591a9379af4ee1cd_1422x756.png) ## 生成分頁 核心的未知問題全部都解決以后,現在可以愉快的在分頁組件中完成其核心功能:根據當前頁、總頁數,生成分頁頁碼了: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -22,6 +22,13 @@ export class PageComponent implements OnInit { console.log('set page被調用'); console.log('當前頁', this.inputPage.number); console.log('總頁數', this.inputPage.totalPages); + // 生成頁數數組 + this.pages = []; + for (let i = 0; i < this.inputPage.totalPages; i++) { + this.pages.push(i); + } + // 設置當前頁 + this.currentPage = this.inputPage.number; } ``` 最終效果: ![image-20210401104133480](https://img.kancloud.cn/f1/61/f161b834b7af0100e269f384b1035f3d_1290x176.png) ### Output() 輸入完成后,開始完成輸出。對本組件而言,輸出相對是比較簡單的,我們僅僅需要在頁碼被點擊時彈出被點擊的頁碼即可: ```typescript +++ b/first-app/src/app/clazz/page/page.component.ts @@ -44,6 +44,7 @@ export class PageComponent implements OnInit { } onPage(page: number): void { - + // 點擊頁碼時彈出該頁碼 + this.bePageChange.emit(page); } } ``` ## 完善 最后,我們刪除父clazz列表組件中關于生成分頁數據的相關代碼: ```typescript +++ b/first-app/src/app/clazz/clazz.component.ts @@ -15,9 +15,6 @@ export class ClazzComponent implements OnInit { // 每頁默認為3條 size = 3; - // 分頁數組 - pages = [] as number[]; - // 初始化一個有0條數據的 pageData = new Page<Clazz>({ content: [], @@ -50,11 +47,6 @@ export class ClazzComponent implements OnInit { console.log('clazz組件接收到返回數據,重新設置pageData'); this.pageData = pageData; console.log(pageData); - // 根據返回的值生成分頁數組 - this.pages = []; - for (let i = 0; i < pageData.totalPages; i++) { - this.pages.push(i); - } }); } } ``` 在生產的項目中,我們還會對`console.log()`方法進行處理,以防止在控制臺打印過多的冗余信息。 ## 總結 本節中我們又學習Angular的又一個重要特性:`@Input()`。`@Input()`可以作用的組件屬性上,也可以作用在組件的`set xxx()`方法上。當作用在屬性上時,父組件將僅在子組件初始化時傳值一次;在作用在`set`方法上時,父組件綁定到子組件上的值每變更一次,都將調用一次對應的方法。 在Angular應用開發中,應該視情況進行組件的拆分。是否拆分的原則一般為:是否重復使用。如果某些功能被重復使用,則應該拆分為組件;如果某些功能不被重復使用,則可以不拆分組件。在有些時間,如果一個界面的邏輯功能比較復雜,我們也會使用組件拆分的方法來降低單個組件的開發難度。 在子組件的首次開發中,我們剛開始往往搞不清組件應該有的輸入及輸出。這時候就需要先在父組件中開發,就像我們在前面兩個小節中直接在clazz列表組件中開發了分頁功能一樣;等開發成功,再建立子組件,進行功能的遷移。當然,等開發的子組件多了,在能力提升的情況下,后期也可以直接開發子組件。 在本節中父子組件交互中,我們使用`console.log()`在控制臺輸出了大量的內容,這在開發時是個應該保持的好習慣。本節中我們便是借助 `console.log()`弄明白父子組件在交互時各個方法的執行順序的。 另外編寫代碼雖然更多是使用鍵盤,但在開發中遇到一些較難解決的問題的時,最有效率的工具卻是紙筆。借助控制臺的信息在筆上寫一寫,畫一畫,圈出當前需要解決的問題,往往可能幫助我們聚集問題所在。 ## 本節作業 1. 請上網查詢typescript的`get`、`set`方法。 2. 開發分頁組件時,啟用了clazz列表的單元測試用例,請嘗試啟用page對應的單元測試用例,模擬輸入值完成分頁組件的測試。 | 名稱 | 鏈接 | | -------------------------------- | ------------------------------------------------------------ | | 把數據發送到子組件 | [https://angular.cn/guide/inputs-outputs#sending-data-to-a-child-component](https://angular.cn/guide/inputs-outputs#sending-data-to-a-child-component) | | 通過 setter 截聽輸入屬性值的變化 | [https://angular.cn/guide/component-interaction#intercept-input-property-changes-with-a-setter](https://angular.cn/guide/component-interaction#intercept-input-property-changes-with-a-setter) | | TypeScript 類 | [https://typescript.bootcss.com/classes.html](https://typescript.bootcss.com/classes.html) | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.3.6.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.3.6.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>

                              哎呀哎呀视频在线观看