使用Angular与TypeScript构建Electron应用(五)
这次我们开始关注Angular怎样构建前端路由与逻辑,它与你以前熟悉的方式有一些区别,同时这部分内容非常充实,路由发生变化后原有的文件结构也随之变化,有疑问请参见本次代码变更的Commit。 在进行新的开发之前我们不妨对原有的爬虫代码做一些轻微的更改,在正式显示这些内容时,仅仅有标题与文章详情是远远不够的,可以加入类似于摘要、描述、阅读量、发表人、发表日期等等字段,具体也根据实际爬取的页面与业务需求更改,为此我丰富了 // browser/task/ifeng.js // .... parseContent (html){ if (!html) return; const $ = cheerio.load(html) const title = $('title').text() const description = $('meta[name="description"]').attr('content') const content = $('.yc_con_txt').html() const hot = $('span.js_joinNum').text() return { title: title,content: content,description: description,hot: hot,createdAt: new Date() } } 创建Angular子模块在Angular2中,模块是用来描述各个组件之间关系的文件,就像是树的枝干,所有小的枝干都汇集至此,在模块中填充,模块用一些特有的语法糖来描述它们之间的关系与依赖。在应用复杂时,树的枝干往往不止一根,我们不可能将所有的文件全部挂载在根模块中,这样既不优雅也会导致打包的单个文件过大,影响页面首次加载速度。为此,我们可以在根模块上注册一些子模块,用来描述完全不同且能够得到自治的子模块。
main组件是用户浏览的主体部分,在界面设计上它至少可以分为两个部分,首先是一侧的菜单与用户信息显示,其次是主要显示区域,当然你还可以为它增加一些隐藏、悬浮、弹出菜单。这里至少包含三个组件:菜单、列表、详情,我们先用angular-cli命令生成它们: ng g component main-detail ng g component main-menu ng g component main-list 组件准备就绪,我们在 // src/app/main/main.module.ts 子模块文件 import {CommonModule} from '@angular/common' import {NgModule} from '@angular/core' import {FormsModule} from '@angular/forms' import {MainRoutingModule} from './main.routing' import {MainComponent} from './main.component' import {MainListComponent} from './main-list/main-list.component' import {MainDetailComponent} from './main-detail/main-detail.component'; import {MainMenuComponent} from './main-menu/main-menu.component' @NgModule({ declarations: [ MainComponent,MainListComponent,MainDetailComponent,MainMenuComponent,],imports: [ CommonModule,FormsModule,MainRoutingModule ],exports: [MainComponent],providers: [ SanitizePipe ] }) export class MainModule { } // src/app/main/mian.routing/ts 路由文件 import {NgModule} from '@angular/core' import {Routes,RouterModule} from '@angular/router' import {MainComponent} from './main.component' import {MainListComponent} from './main-list/main-list.component' import {MainDetailComponent} from './main-detail/main-detail.component' export const mainRoutes: Routes = [{ path: '',component: MainComponent,children: [{ path: '',redirectTo:'list',pathMatch:'full' },{ path: 'list',component: MainListComponent },{ path: 'list/:id',component: MainDetailComponent }] }] @NgModule({ imports: [RouterModule.forChild(mainRoutes)],exports: [RouterModule] }) export class MainRoutingModule { } 子模块也需要被根模块检测到才能在编译时被纳入,这里考虑到 从现在开始,每当我们访问/mian路由时Angular会自动为我们加载新的模块,在访问 编写组件与公共服务我为main下的组件写了一些样式,具体可以参考Commit,它看起来有些简陋但并没有关系,在编写应用时不能把注意力过于集中在某一点上,一开始写出非常严谨、不可变的样式会使随后的逻辑重构畏首畏尾,整体式的推进、优化可以大大提升项目进度。等到应用能够运行时我们再回过头来考虑这些问题。 与登录相似,在每个组件下创建一个service,需要记住的是,当前组件下的service仅仅只供给当前组件使用,它被写在组件的providers依赖列表里,如果你真的需要一个共享或状态存储(单次实例)的组件,可以考虑shared文件夹。举个例子来说,现在我们的数据库中文章详情是html富文本格式,这些源数据是不能够被直接解析在dom结构中的,还需要做一些安全化处理,我们以这个功能为例,创建一个公共的pipe解析器。 import {Pipe,PipeTransform} from '@angular/core' import {DomSanitizer,SafeHtml} from '@angular/platform-browser' @Pipe({ name: 'sanitize' }) export class SanitizePipe implements PipeTransform { constructor (private domSanitizer:DomSanitizer){} transform (value: any,args?: any): SafeHtml{ return this.domSanitizer.bypassSecurityTrustHtml(value) } } 前面在创建公共service时我们使用了一种投机取巧的方式,即是将公共service注入在app.component的providers依赖列表中,因为根组件最多只会创建一次,借此机制拿到一个只会被实例化一次的服务。但这不是工程化的做法(显而易见),结合上文所提到Angular的module机制,我们可以为shared建立一个独立的module,用来解决这些问题: // src/app/shared/shared.module.ts import {NgModule,ModuleWithProviders} from '@angular/core' import {CommonModule} from '@angular/common' import {FormsModule} from '@angular/forms' import {IpcRendererService} from './service/ipcRenderer' import {SanitizePipe} from './pipe/sanitize' @NgModule({ imports: [ CommonModule,FormsModule ],declarations: [ SanitizePipe ],exports: [ SanitizePipe ],providers: [ ] }) export class SharedModule { static forRoot(): ModuleWithProviders { return { ngModule: SharedModule,providers: [IpcRendererService] }; } } forRoot静态方法是Angular2的一个公约,具体可以参见官方文档,大家只需要知道的是在app.module的imports依赖中调用 新的通信接口在此之前,我们约定了接口语法为 // 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](event,...args) .then(res => reply(res)) .catch(err => reply({message: '应用出现了错误'})) } }) 现在我们假设路由文件已经是async函数构成的,先将回复方法(reply函数)放在外部,取消之前的对象合并。虽然前面使用对象合并避免对侵入原生对象,但也并不是那么优雅,现在只考虑返回值无疑是最酷的做法! // browser/ipc/api/index.js const screen = require('../../screen') const articleService = require('../../service/article') module.exports = { login: async (e,user) =>{ // todo something screen.setSize(1000,720) return {msg: 'ok'} },list: async (e,page) =>{ try{ const articles = await articleService.findArticlesForPage(page) // todo filter articles return articles } catch (err){ return Promise.reject(err) } },detail: async (e,id) =>{ try{ const article = await articleService.findArticleForID(id) return article } catch (err){ return Promise.reject(err) } } }
现在news-feed已经能够快速显示出数据库里的列表: 点击任何一项进入详情,文章内容都被 最后当然,news-feed还存在很多问题,甚至还不能称之为一个应用,比如不能注销登录、浏览文章时无法返回列表、无法下载文章内容/图片、没有跳转到原文等等。这些细节是真正值得注意的重点,后面几节我们都会一起讨论怎样添加这些逻辑并优化现有的代码。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 如何修复Vim中的Home和End?
- MyEclispe基于JAX-WS的WebService服务端客户端简单实现示例
- Bash中的空白连接
- 基于Node(bootstrap+ejs+express+formidable+fs-extra)制
- 【WebService学习过程记录(二)】Java6+Servlet+tomcat发布H
- 模拟崩溃的bash脚本
- angularjs – 多个指令[myPopup,myDraggable]要求新的/隔离
- shell – 从jq JSON UNIX命令获取密钥名称
- 如何使用Scala更新一个ORC Hive表格
- Symbian学习笔记 8 之 初探WebServices API的使用(下)