使用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的源码,考虑它有哪些问题是值得优化的。在后面几节中,咱们再来讨论如何优化这些逻辑。

注:原文首次载于维特博客。须要演示项目请看这里

相关文章
相关标签/搜索