使用Angular與TypeScript構建Electron應用(二)

回顧請前往第一節
本文全部代碼均可以在github找到。你能夠經過commit歷史來查看這些代碼是如何一步一步構建的。若是有任何問題,也能夠在github的issue上提出。javascript

接前文,如今咱們搭建好了一系列的環境,建立了一些初始的代碼,是時候開始工做了。
在這篇文章中咱們主要負責建立登陸界面與主界面,涉及篇幅關係咱們再也不使用遠程服務端來交互,而是建立一些模擬的登陸請求,固然,與服務端的交互方法能夠在此係列文章後面幾篇找到。OK,這裏咱們但願前端可以像QQ或微信同樣,先展現一個登陸界面,在登陸成功後帶領咱們打開一個長時間停留的主界面,咱們先理清須要作那幾件事:html

  1. 在Angular中建立路由,包括登陸界面與主界面。前端

  2. 建立browser相關代碼,給登陸與跳轉提供通訊反饋。java

  3. 在登陸成功後咱們關閉登陸界面跳轉至主界面。node

Angular建立前端頁面

因爲咱們安裝了angular-cli,因此每次建立各種文件時均可以經過cli的方式來解決,這很方便,也下降了Angular的學習成本,若是對此不明白,能夠看這裏的文檔react

1, 建立組件與路由

首先在src/app的路徑下建立2個組件: login與main。好吧,你須要輸入ng g component login來建立這個組件,但在以後咱們就再也不討論這些細節,我只會告訴該怎麼作一件事。git

其次咱們在src/app的路徑下建立一個路由app.routing.ts,咱們但願它能夠作好兩件事,根據URL進行頁面的導航,在沒有權限時對相應的導航進行保護。具體代碼能夠參照Angular的官方文檔,但我猜大家懶得看,代碼以下:github

import {NgModule} from '@angular/core'
import {Routes, RouterModule} from '@angular/router'

import {LoginComponent} from './login/login.component'
import {MainComponent} from './main/main.component'

export const appRoutes: Routes = [
   {path: '', component: LoginComponent},
   {path: 'login', component: LoginComponent},
   {path: 'main', component: MainComponent}
]

@NgModule({
   imports: [RouterModule.forRoot(appRoutes)],
   exports: [RouterModule]
})
export class AppRoutingModule {
}

ok,這很簡單,和咱們熟悉的Angular1.x或react-route也沒有太大區別。可是要讓路由運行起來還要作兩件事,第一是將路由在app.module.ts中註冊,在module上掛載文件,Angular在編譯時纔會將文件引入進來,第二是在app.component.html中增長路由插座。數據庫

#### 2, 建立樣式與邏輯 express

如今,咱們爲前端頁面添加一些樣式與邏輯,這此的commit記錄在這裏,如今咱們須要爲登陸界面添加邏輯與路由保護。

登陸頁面樣式

登陸能夠提交用戶名與密碼用做驗證,這時候能夠藉助Angular的模板語法來快速的完成它們:

<div class="input-box">
           <input type="text" #username>
   </div>
   <div class="input-box">
           <input type="text" #password>
   </div>
   <button (click)="login(username.value, password.value)">登陸</button>

咱們但願全部嚴格的邏輯或涉及數據庫的問題都放在主進程解決,那麼確認登陸須要與electron主進程進行交互,以便於主進程來切換窗口。固然,在實際業務中你能夠選擇把服務器的交互放在Angular中來作,也能夠在electron發起一個request。如今咱們按下面幾步來操做:

  1. 在login組件文件夾下建立login.service.ts,別忘了將服務添加到組件的providers依賴項中!

  2. src/index.html文件中添加var electron = require('electron'),別忘了script標籤。

  3. src/app下添加shared文件夾,用來存放一些共用的組件與邏輯。在這裏建立一個名爲ipc-renderer的服務,並將它註冊到app.component.ts中。具體代碼以下:

import {Injectable} from '@angular/core
        declare let electron:any;
        @Injectable()
        export class IpcRendererService {
                constructor (){}
                
                private ipcRenderer = electron.ipcRenderer
                on (message: string, done){
                        return this.ipcRenderer.on(message, done);
                }
                send (message: string, ...args){
                        this.ipcRenderer.send(message, args);
                }
                api (action: string, ...args) {
                        this.ipcRenderer.send('api', action, ...args);
                        return new Promise((resolve, reject) => {
                                    this.ipcRenderer.once(`${action}reply`, (e, reply, status) =>{
                                            if (!reply){
                                                    return reject(status)
                                            }
                                            return resolve(reply)
                                    })
                            })
                }
                dialog (action: string, ...args) {
                        this.ipcRenderer.send('dialog', action, ...args);
                }
                sendSync (message: string, ...args){
                        return this.ipcRenderer.sendSync(message, arguments);
                }
        }

這裏咱們經過ipcRenderer與electron交互,ipc-renderer就是Angular中用來通訊的公共服務,這個服務模塊理論上共享的,並且咱們也只但願它被實例化一次,因此將它注入在app.component.ts中。這樣每次子組件須要服務時沒必要在providers中標明它,而是直接在constructor中注入便可。這很重要,特別是你想要在一個類中保存一些即時的數據信息,但願只存在一個實例用來共享時頗有用。
能夠看出來,api這個方法是咱們增長的一個有意思的方法,這裏咱們能夠做出一些參數上的約定,便於監聽事件時作出更好的反饋。

#### 3, 監聽與反饋

這時,api的第一個參數被約定爲action,用於描述這個API事件的用途,每個API事件都會發起一次apiName+reply的事件用於回覆。在Angular的公共服務中,咱們不妨先把它轉化爲咱們熟悉的Promise,再返回給每個具體的組件服務,固然你也能夠直接把它用做作fromEvent的Observable,但在這裏,咱們但願它看起來像是一個http服務,便於你們更好的理解它們工做的方式。
實際上,你能夠選擇一些成熟electron數據通訊庫或框架來解決這些複雜的問題,但在第一次請不要這樣作,這就像上手使用Rails同樣,雖然作的很快,但對你並無多少益處。

這裏有一些複雜,若是你但願對照當時的代碼來學習,能夠看這一次的commit

ok,你們也能夠想象的到,如今要作的是在electron中新建一個事件接收器,處理一些邏輯而且將它們返回,在根文件夾下新建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](Object.assign({reply: reply}, event), ...args)
    }
})

假設如今有一個browser/ipc/api文件做爲處理器,以上代碼作的事情便是肯定一個Action,而且監聽事件,爲event合併一個名爲reply的方法,用於返回數據。根據此,咱們再建立這個虛擬的browser/ipc/api文件:

module.exports = {
    login: (e, user) =>{
        // todo something

        e.reply({msg: 'ok'})
    }
}

怎麼樣?如今看起來一切都完成了!每次當咱們在loginService中調用this.ipcRendererService.api時,相應的數據就會被傳達至對應的事件(看起來它更像一個路由)上,咱們在nodejs環境中作一些操做,好比儲存session,更新數據庫,抓取新聞,向遠程服務器發送一條信息等等。

最關鍵的是咱們也能用垂手可得的方式來獲得想要的數據,回覆數據也足夠簡單,e.reply({msg: 'ok'})就像是express中的res.xxx({});同樣,整個項目也變得井井有條。等到有一天咱們須要下載、上傳、顯示系統原生提示框、讀取一個文件等等之類的功能時,只須要將Action名替換一下,在api文件夾下新增幾段邏輯便可。

這一小節文章有些瑣碎和複雜,登陸成功與跳轉等等邏輯不妨放在下一節中再講。你們能夠嘗試閱讀github的源碼,考慮它有哪些問題是值得優化的。在後面幾節中,咱們再來討論如何優化這些邏輯。

注:原文首次載於維特博客。須要演示項目請看這裏

相關文章
相關標籤/搜索