此次咱們開始關注Angular怎樣構建前端路由與邏輯,它與你之前熟悉的方式有一些區別,同時這部份內容很是充實,路由發生變化後原有的文件結構也隨之變化,有疑問請參見本次代碼變動的Commit。javascript
在進行新的開發以前咱們不妨對原有的爬蟲代碼作一些輕微的更改,在正式顯示這些內容時,僅僅有標題與文章詳情是遠遠不夠的,能夠加入相似於摘要、描述、閱讀量、發表人、發表日期等等字段,具體也根據實際爬取的頁面與業務需求更改,爲此我豐富了browser/task/ifeng.js
中parseContent函數的代碼:html
// browser/task/ifeng.js // .... parseContent (html){ if (!html) return; const $ = cheerio.load(html) const title = $('title').text() const description = $('meta[name="description"]').attr('content') const content = $('.yc_con_txt').html() const hot = $('span.js_joinNum').text() return { title: title, content: content, description: description, hot: hot, createdAt: new Date() } }
在Angular2中,模塊是用來描述各個組件之間關係的文件,就像是樹的枝幹,全部小的枝幹都聚集至此,在模塊中填充,模塊用一些特有的語法糖來描述它們之間的關係與依賴。在應用複雜時,樹的枝幹每每不止一根,咱們不可能將全部的文件所有掛載在根模塊中,這樣既不優雅也會致使打包的單個文件過大,影響頁面首次加載速度。爲此,咱們能夠在根模塊上註冊一些子模塊,用來描述徹底不一樣且可以獲得自治的子模塊。前端
「自治」是很是關鍵的一點,這很像Angular1.x中的概念。咱們知道在Angular1.x中module也是能夠互相依賴的,每個模塊/指令/服務都應當可以不受任何狀態影響完成基礎邏輯。想象一下,咱們加入指令前須要考慮爲指令新建一個模板,新建幾個變量放在模板的某個位置等等,這確定會使總體耦合性過強。在Angular2中pipe便有『純』與『非純』的概念,非純的管道在變動時就須要考慮更多的外部環境變化,固然效率也會大大降低。咱們但願大部分的函數、代碼段、集合都能達到自治的標準,這也是你們常說的高內聚低耦合。java
main組件是用戶瀏覽的主體部分,在界面設計上它至少能夠分爲兩個部分,首先是一側的菜單與用戶信息顯示,其次是主要顯示區域,固然你還能夠爲它增長一些隱藏、懸浮、彈出菜單。這裏至少包含三個組件:菜單、列表、詳情,咱們先用angular-cli命令生成它們:git
ng g component main-detail ng g component main-menu ng g component main-list
組件準備就緒,咱們在src/app/main
文件夾下新增模塊與路由文件,並把原有的組件改造爲路由插座:github
// src/app/main/main.module.ts 子模塊文件 import {CommonModule} from '@angular/common' import {NgModule} from '@angular/core' import {FormsModule} from '@angular/forms' import {MainRoutingModule} from './main.routing' import {MainComponent} from './main.component' import {MainListComponent} from './main-list/main-list.component' import {MainDetailComponent} from './main-detail/main-detail.component'; import {MainMenuComponent} from './main-menu/main-menu.component' @NgModule({ declarations: [ MainComponent, MainListComponent, MainDetailComponent, MainMenuComponent, ], imports: [ CommonModule, FormsModule, MainRoutingModule ], exports: [MainComponent], providers: [ SanitizePipe ] }) export class MainModule { }
// src/app/main/mian.routing/ts 路由文件 import {NgModule} from '@angular/core' import {Routes, RouterModule} from '@angular/router' import {MainComponent} from './main.component' import {MainListComponent} from './main-list/main-list.component' import {MainDetailComponent} from './main-detail/main-detail.component' export const mainRoutes: Routes = [{ path: '', component: MainComponent, children: [{ path: '', redirectTo:'list',pathMatch:'full' },{ path: 'list', component: MainListComponent },{ path: 'list/:id', component: MainDetailComponent }] }] @NgModule({ imports: [RouterModule.forChild(mainRoutes)], exports: [RouterModule] }) export class MainRoutingModule { }
子模塊也須要被根模塊檢測到才能在編譯時被歸入,這裏考慮到main.module
是一個子路由產生的懶模塊,咱們能夠考慮在路由轉向它時纔開始加載。這時app.routing須要改寫一條路由規則:{path: 'main', loadChildren: './main/main.module#MainModule', data: {preload: true}}
。數據庫
從如今開始,每當咱們訪問/mian路由時Angular會自動爲咱們加載新的模塊,在訪問/mian/*
時,main.routing.ts
文件會開始檢測路由地址並切換到相應的頁面組件上。後面全部的業務都將專一於main路由中,爲了項目的可讀性,每一個子路由工做的子頁面組件,都應當寫在main文件夾下。api
我爲main下的組件寫了一些樣式,具體能夠參考Commit,它看起來有些簡陋但並無關係,在編寫應用時不能把注意力過於集中在某一點上,一開始寫出很是嚴謹、不可變的樣式會使隨後的邏輯重構畏首畏尾,總體式的推動、優化能夠大大提高項目進度。等到應用可以運行時咱們再回過頭來考慮這些問題。安全
與登陸類似,在每一個組件下建立一個service,須要記住的是,當前組件下的service僅僅只供給當前組件使用,它被寫在組件的providers依賴列表裏,若是你真的須要一個共享或狀態存儲(單次實例)的組件,能夠考慮shared文件夾。舉個例子來講,如今咱們的數據庫中文章詳情是html富文本格式,這些源數據是不可以被直接解析在dom結構中的,還須要作一些安全化處理,咱們以這個功能爲例,建立一個公共的pipe解析器。
在shared/pipe/sanitize
下建立一個pipe:bash
import {Pipe, PipeTransform} from '@angular/core' import {DomSanitizer, SafeHtml} from '@angular/platform-browser' @Pipe({ name: 'sanitize' }) export class SanitizePipe implements PipeTransform { constructor (private domSanitizer:DomSanitizer){} transform (value: any, args?: any): SafeHtml{ return this.domSanitizer.bypassSecurityTrustHtml(value) } }
前面在建立公共service時咱們使用了一種投機取巧的方式,便是將公共service注入在app.component的providers依賴列表中,由於根組件最多隻會建立一次,藉此機制拿到一個只會被實例化一次的服務。但這不是工程化的作法(顯而易見),結合上文所提到Angular的module機制,咱們能夠爲shared創建一個獨立的module,用來解決這些問題:
// src/app/shared/shared.module.ts import {NgModule, ModuleWithProviders} from '@angular/core' import {CommonModule} from '@angular/common' import {FormsModule} from '@angular/forms' import {IpcRendererService} from './service/ipcRenderer' import {SanitizePipe} from './pipe/sanitize' @NgModule({ imports: [ CommonModule, FormsModule ], declarations: [ SanitizePipe ], exports: [ SanitizePipe ], providers: [ ] }) export class SharedModule { static forRoot(): ModuleWithProviders { return { ngModule: SharedModule, providers: [IpcRendererService] }; } }
forRoot靜態方法是Angular2的一個公約,具體能夠參見官方文檔,你們只須要知道的是在app.module的imports依賴中調用SharedModule.forRoot(),
而其餘地方僅僅依賴SharedModule
便可。看它們不一樣的使用方法不少人應該已經猜出module是怎樣工做的了,先無論這些,讓咱們回到mian.module裏注入依賴項試試效果。
在此以前,咱們約定了接口語法爲ipcRendererService.api('接口名', '參數')
,新增的組件裏也參考此方式發起請求便可,這裏咱們可能至少須要兩個接口:this.ipcRendererService.api('list', page)
,this.ipcRendererService.api('detail', id)
。想象一下,在列表組件初始化時調用list接口傳入一個頁碼得到一些列表數據,而後使用Angular的路由方法this.router.navigate(['/main/list', id])
把列表中某一項的id傳至詳情頁面,詳情頁面在初始化時從url上取得頁面id,再次經過detail接口獲取本身須要的文章詳情數據。一次正常的瀏覽就完成了。
在給Electron中的api增長方法時先等等,上一篇文章咱們聊到Async函數,如今咱們可使用async函數來時路由更簡單易懂一些:
// browser/ipc/index.js const {ipcMain} = require('electron') const api = require('./api') ipcMain.on('api', (event, actionName, ...args) =>{ const reply = (replayObj, status = 'success') =>{ event.sender.send(`${actionName}reply`, replayObj, status); } if (api[actionName]){ api[actionName](event, ...args) .then(res => reply(res)) .catch(err => reply({message: '應用出現了錯誤'})) } })
如今咱們假設路由文件已是async函數構成的,先將回複方法(reply函數)放在外部,取消以前的對象合併。雖然前面使用對象合併避免對侵入原生對象,但也並非那麼優雅,如今只考慮返回值無疑是最酷的作法!
// browser/ipc/api/index.js const screen = require('../../screen') const articleService = require('../../service/article') module.exports = { login: async (e, user) =>{ // todo something screen.setSize(1000, 720) return {msg: 'ok'} }, list: async (e, page) =>{ try{ const articles = await articleService.findArticlesForPage(page) // todo filter articles return articles } catch (err){ return Promise.reject(err) } }, detail: async (e, id) =>{ try{ const article = await articleService.findArticleForID(id) return article } catch (err){ return Promise.reject(err) } } }
articleService
是原生數據庫查詢的封裝,相比於每次寫find/update方法與大量參數,我更建議你們把這些垃圾代碼統一封裝成更富有語義性的函數,不管過去多久,你再次讀到這段代碼時總能很清楚的知道本身作了什麼,這很關鍵。
另外,我給你們展現的是代碼框架如何搭建,單個await帶來的便利性沒有想象的大,但你在實際業務中會涉及屢次查詢、更新、篩選、遍歷操做,async語法糖會給你帶來極高的可讀性!
如今news-feed已經可以快速顯示出數據庫裏的列表:
點擊任何一項進入詳情,文章內容都被sanitize.pipe
過濾解析在dom裏:
固然,news-feed還存在不少問題,甚至還不能稱之爲一個應用,好比不能註銷登陸、瀏覽文章時沒法返回列表、沒法下載文章內容/圖片、沒有跳轉到原文等等。這些細節是真正值得注意的重點,後面幾節咱們都會一塊兒討論怎樣添加這些邏輯並優化現有的代碼。