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

接前文。如今咱們完成了了Angular與Electron的交互,在渲染進程進行的任何動做都能及時的發送至主進程分析儲存,再獲得其反饋,渲染進程根據反饋的不一樣的作出合理的應對。javascript

今天咱們須要完成登陸與主進程交互的剩下功能模塊。html

從事件重載窗口

既然咱們須要經過響應事件來更換窗口對象,就至少須要一個窗口對象的函數,固然,這些函數應當被抽離出去做爲一個service。其次,咱們要考慮到窗口對象的句柄存放與回收,在更換對象或合適的時候也要對這些窗口對象的句柄作出更改,鑑於這些,能夠在原代碼的基礎上設計一個共用類。java

暫且把這個操做窗口對象的類叫作Screen(有些不合時宜,但須要與window區分開),它便可以被根目錄下的index.js調用,也能夠在任何的api函數中被使用,也就是說,不管如何Screen都只應有一個實例,這樣窗口對象的句柄就能夠被緩存在內存中供調用者操做。git

結合前面咱們寫的index.js文件再次思考一下這個Screen類,它還須要一些被動的方法,用來響應窗口最大化、最小化、關閉、激活等等操做,這些操做均可以被抽象成固定參數的函數,所以咱們還會給Screen類添加一些靜態方法。具體以下:github

// browser/screen/login.js
const {app, BrowserWindow} = require('electron')

module.exports = new class Login {
    constructor (){
    }

    open (url){
        const win = new BrowserWindow({
            width: 700,
            height: 500,
            show: false,
            frame: false,
            resizable: true
        });
        win.loadURL(url)
        win.webContents.openDevTools()
        return win
    }
}()

建立一個Login類,用於打開login窗口,同理咱們能夠把這份代碼拷貝一次改掉名字成爲console類,負責打開控制面板。它有這樣幾個特色,接受一個參數url,建立一個window對象再加載它,最後它返回這個window對象供外部使用。
須要注意的是你要傳遞loadURL的值,或者你命名一個全局變量來儲存根目錄下的index.js的__dirname,用來簡化路徑,後面咱們還須要它作一些其餘事。web

如今能夠建立Screen類:編程

// browser/screen/index.js
const login = require('./login')
const console = require('./console')
const windowList = {
    login: login,
    console: console
}

module.exports = new class Screen {
    constructor (){
        this.win = null
        this.baseUrl = ''
    }
    static show (win){
        win.show()
        win.focus()
    }

    // 打開一個窗口 默認打開登陸窗口
    open (winName = 'login'){
        if (!windowList[winName]) return ;
        this.win = windowList[winName].open(this.baseUrl)

        this.win.on('closed', () => this.win = null)
        this.win.on('ready-to-show', () => Screen.show(this.win))
    }
    setBaseUrl (baseUrl){
        this.baseUrl = baseUrl
        return this
    }
    
    activate (){
        this.win === null&& this.open()
    }
}()

windowList用於檢測傳入名稱是否有效,這一步看起來有些多餘但不失爲好的編程習慣,在多人協做過程當中你在不斷完善本身的代碼同時也能夠爲他人規避一些錯誤。相似於防守型編程。setBaseUrl就是咱們剛剛提到的儲存__dirname所用函數。看起來screen總體已經完成,咱們再回去對根目錄下的index.js作一些優化:api

// 根目錄下的index.js
const {app, BrowserWindow} = require('electron')
const screen = require('./browser/screen')
require('./browser/ipc/index')
const url = `file://${__dirname}/dist/index.html`

app.on('ready', _ => screen.setBaseUrl(url).open())
app.on('window-all-closed', _ => process.platform !== 'darwin'&& app.quit())
app.on('activate', _ => screen.activate())

怎麼樣?如今看起來像模像樣了,如今只需在ipc/api下的具體文件中使用screen.open('console')便可打開新的窗口,而Angular端在收到通知後也跳轉路由,負責新的頁面。
這是一個例子,幫助你們理解應用的工做方式,在生產環境中你應該首先使用成熟的框架或庫來解決這些問題,如electron-router。緩存

重載窗口的重構

如今還有一些小問題,在登陸成功後咱們讓Electron打開新窗口,但不管如何這都是不優雅的解決方案,彈出一個新窗口意味着原來的窗口須要瞬間消失,在退出登陸時還要再次開啓一個新的登陸窗口。咱們能夠對現有的業務邏輯進行更新,讓路由的控制迴歸到Angular本身手中,同時,Electron在合適的時候對窗口大小與位置進行合理的變化。如今讓咱們爲Screen類再添加一個方法:session

// browser/screen/index.js
// ......
setSize (w, h){
        if (this.win){
            const bounds = this.win.getBounds();
            const newBounds = {
                x: bounds.x - (w - bounds.width)/2,
                y: bounds.y - (h - bounds.height)/2
            }
            this.win.setBounds({
                x: newBounds.x,
                y: newBounds.y,
                width: w,
                height: h
            }, true)
        }
    }

雖然名爲setSize方法,但實際上咱們對window的bounds進行了更改,這是合理的,咱們始終對外暴露一個簡單的方法,即使這裏作了一些事情,但這是不受參數影響的變化。在每次窗口變化時,它老是可以找到合理的位置,對於調用者來講,它就至關於一個setSize。不要急於優化這個函數,後面咱們還要討論到如何解決配置文件與緩存的問題,屆時再將用戶的習慣設定導入到函數中,讓主界面每次打開位置與上次關閉位置保持一致便可。甚至咱們須要爲Angular添加一些session識別路由跳轉的功能。

如今,/browser/ipc/api/index.js被咱們又改動一次,像這樣:

const screen = require('../../screen')

module.exports = {
    login: (e, user) =>{
        // todo something
        screen.setSize(1000, 720)
        e.reply({msg: 'ok'})
    }
}

一切都瓜熟蒂落,在MAC上窗口的變化還帶有一些動畫效果,是否是很酷?並且它總能找到最合理的位置,看起來更像一個成熟的應用。如今,咱們爲Angular應用作一些改變。

Angular事件訂閱

雖然咱們用Promise能夠很快的搞定這些活,但既然開始學了不妨瞭解一些新技術。Rx.js就是很是有意思的一個。可能不少朋友都聽過其餘語言的Reactive模式,那麼理解起來也不難,若是你是第一次聽到這個名詞,不妨先去看一下這幾個文檔:官方文檔翻譯 另外一個不錯的翻譯

把一個Promise轉化爲Observable是很是簡單的,你能夠簡單的將Rx.js理解爲一個用函數式編程操做Event的庫:

// src/app/login/login.service.ts
import {Injectable} from '@angular/core'
import {IpcRendererService} from '../shared/service/ipcRenderer'
import {Observable} from 'rxjs/Observable'
import 'rxjs/Rx'

@Injectable()
export class LoginService {

    constructor (
        private ipcRendererService: IpcRendererService
    ){
    }

    login (user: any): Observable<any> {
        return Observable.fromPromise(this.ipcRendererService.api('login', user))
    }

}

這裏僅僅須要fromPromise就能快速的將Promise轉化爲Observable,在component中,你仍是和之前同樣用subscribe去訂閱這個流便可。看到fromPromise你會想到可能會有fromEvent,fromCallback之類,其實這些都屬於Rx的靜態操做符,簡單的來講,都是Observable類下的static方法而已。當你使用map/filter/first時,也只是調用了Observable類下的實例方法,這些實例方法都會返回this,因此才能不斷的鏈式調用。只要你喜歡,你能夠爲它添加各種方法,甚至能將本身的實例方法掛載在Observable類上。具體你們能夠看一看Rx的源碼研究一下。

如今咱們幾乎完成了最難以理解的部分,後面幾節開始構建一些爬蟲代碼與界面展現邏輯。若是你也在同步的構建代碼,對這一小節有任何疑問,均可以參見此次的commit來解決。

相關文章
相關標籤/搜索