当前位置: 首页 > 后端技术 > Node.js

使用Angular和TypeScript构建Electron应用(二)

时间:2023-04-03 20:47:21 Node.js

评论请到第一节本文所有代码都可以在github上找到。你可以通过提交历史看到代码是如何一步一步构建的。如果大家有什么问题,也可以在github上的issue上提出来。接上一篇文章,现在我们已经设置了一系列环境并创建了一些初始代码,是时候开始工作了。在本文中,我们主要负责创建登录界面和主界面。在空间上,我们不再使用远程服务器进行交互,而是创建一些模拟登录请求。当然,与服务端的交互方式可以在本系列文章的几篇文章中找到。OK,这里我们希望前端能够像QQ或者微信一样,展示一个登录界面,引导我们登录成功后打开一个长留的主界面。先梳理一下需要做的几件事:在Angular中创建路由,包括登录界面和主界面。创建浏览器相关代码,为登录和跳转提供通信反馈。登录成功后,我们关闭登录界面,跳转到主界面。Angular创建一个前端页面。由于我们安装了angular-cli,所以每次都可以使用cli的方式来解决各种文件。这样非常方便,也降低了Angular的学习成本。如果你不明白这一点,你可以阅读这里的文档。1、创建组件和路由首先在src/app路径下创建两个组件:login和main。嗯,你需要输入nggcomponentlogin来创建这个组件,但是我们稍后不会讨论这些细节,我只会告诉你如何做一件事。其次,我们在src/app的路径下创建一个路由app.routing.ts。我们希望它能做两件事,根据URL导航页面,在没有权限的时候保护对应的导航。具体代码可以参考Angular的官方文档,不过我估计你懒得看。代码如下:import{NgModule}from'@angular/core'import{Routes,RouterModule}from'@angular/router'import{LoginComponent}from'./login/login.component'import{MainComponent}from'./main/main.component'exportconstappRoutes:Routes=[{path:'',component:LoginComponent},{path:'login',component:LoginComponent},{path:'main',component:MainComponent}]@NgModule({imports:[RouterModule.forRoot(appRoutes)],exports:[RouterModule]})exportclassAppRoutingModule{}ok,这个很简单,我们就是熟悉Angular1.x或react-route也没有太大区别。但是要使路由正常工作,需要做两件事。第一种是在app.module.ts中注册路由,将文件挂载到module上,Angular在编译时会导入该文件,第二种是添加到app中。在.component.html中添加路由套接字。####2、创建样式和逻辑现在,我们在前端页面添加了一些样式和逻辑,这里记录了这个的commit,现在我们需要在登录界面添加逻辑和路由保护。登录可以提交用户名和密码进行验证。这时候可以使用Angular模板语法来快速完成它们:

login我们希望所有严格的逻辑或与数据库相关的问题都得到解决在主进程中,所以确认登录需要和electron主进程交互,这样主进程才能切换窗口。当然在实际业务中,你可以选择将服务端交互放在Angular中,也可以在Electron中发起请求。现在我们按照以下步骤操作:在登录组件文件夹下创建login.service.ts,同时不要忘记将服务添加到组件的providers依赖中!在src/index.html文件中添加varelectron=require('electron'),不要忘记script标签。在src/app下添加一个共享文件夹,用来存放一些共享的组件和逻辑。在这里创建一个名为ipc-renderer的服务,并在app.component.ts中注册它。具体代码如下:import{Injectable}from'@angular/coredeclareletelectron:any;@Injectable()导出类IpcRendererService{constructor(){}privateipcRenderer=electron.ipcRendereron(message:string,done){returnthis.ipcRenderer.on(message,done);}send(message:string,...args){this.ipcRenderer.send(message,args);}api(action:string,...args){this.ipcRenderer.send('api',action,...args);returnnewPromise((resolve,reject)=>{this.ipcRenderer.once(`${action}reply`,(e,reply,status)=>{if(!reply){returnreject(status)}returnresolve(reply)})})}dialog(action:string,...args){this.ipcRenderer.send('dialog',action,...args);}}sendSync(message:string,...args){returnthis.ipcRenderer.sendSync(message,arguments);这里我们通过ipcRenderer与electron进行交互。ipc-renderer是Angular中用于通信的公共服务。这个服务模块理论上是共享的,而我们只希望它被实例化一次,所以把它注入到app.component.ts中,这样子组件每次需要服务的时候,就不用在providers中标明了,而是直接注入到构造函数中。这一点非常重要,尤其是当你想在一个类中保存一些即时数据信息,并且希望只有一个实例进行共享时。可以看出api方法是我们添加的一个有趣的方法。这里我们可以在参数上做一些约定,方便监听事件时更好的反馈。####3.监听与反馈此时约定api的第一个参数为action,用于描述本次API事件的目的。每个API事件都会发起一个apiName+reply事件进行回复。在Angular的公共服务中,我们不妨先将其转化为我们熟悉的Promise,再返回给各个具体的组件服务。当然,你也可以直接把它当做一个ObservableforfromEvent,但是在这里,我们希望它看起来像一个http服务,让大家更好的理解它们是如何工作的。其实,你可以选择一些成熟的电子数据通信库或框架来解决这些复杂的问题,但请不要第一次这样做,这就像使用Rails入门一样,虽然很快,但无济于事你有多少好处。这里有一些复杂的地方,如果你想从当时的代码中学习,你可以看到这个commit。好了,你可以想象,我们现在需要做的就是在electron中新建一个事件接收器,处理一些逻辑并返回,在根文件夹下新建一个browser/ipc/index.js,填写基本代码:const{ipcMain}=require('electron')constapi=require('./api')ipcMain.on('api',(event,actionName,...args)=>{constreply=(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,监听事件,合并一个名为reply的方法对于事件,返回数据。据此,我们创建了这个虚拟浏览器/ipc/api文件:module.exports={login:(e,user)=>{//todosomethinge.reply({msg:'ok'})}}howSample?现在看起来一切都完成了!我们在loginService中每次调用this.ipcRendererService.api,都会将相应的数据传递给相应的事件(看起来更像一个路由),我们在nodejs环境下做一些操作,比如存储session,更新数据库,fetch新闻、向远程服务器发送消息等。最重要的是,我们还可以轻松获取想要的数据,回复数据也足够简单,e.reply({msg:'ok'})就像res.xxx({});快递;同样,整个项目变得分层。当有一天我们需要下载、上传、显示系统原生提示框、读取一个文件等时,我们只需要更换Action名称,在api文件夹下添加几段逻辑即可。文章的这一部分有点琐碎和复杂。登录跳转成功的逻辑可能会在下一节讨论。可以尝试去阅读github的源码,考虑一下它有哪些值得优化的问题。在接下来的几节中,我们将讨论如何优化这些逻辑。注:原文首发于维特的博客。如果您需要演示项目,请看这里