使用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来解决。

相关文章
相关标签/搜索