这次我们开始关注Angular怎样构建前端路由与逻辑,它与你以前熟悉的方式有一些区别,同时这部分内容非常充实,路由发生变化后原有的文件结构也随之变化,有疑问请参见本次代码变更的Commit。

在进行新的开发之前我们不妨对原有的爬虫代码做一些轻微的更改,在正式显示这些内容时,仅仅有标题与文章详情是远远不够的,可以加入类似于摘要、描述、阅读量、发表人、发表日期等等字段,具体也根据实际爬取的页面与业务需求更改,为此我丰富了browser/task/ifeng.js中parseContent函数的代码:

// 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中,模块是用来描述各个组件之间关系的文件,就像是树的枝干,所有小的枝干都汇集至此,在模块中填充,模块用一些特有的语法糖来描述它们之间的关系与依赖。在应用复杂时,树的枝干往往不止一根,我们不可能将所有的文件全部挂载在根模块中,这样既不优雅也会导致打包的单个文件过大,影响页面首次加载速度。为此,我们可以在根模块上注册一些子模块,用来描述完全不同且能够得到自治的子模块。

「自治」是非常关键的一点,这很像Angular1.x中的概念。我们知道在Angular1.x中module也是可以互相依赖的,每一个模块/指令/服务都应当能够不受任何状态影响完成基础逻辑。想象一下,我们加入指令前需要考虑为指令新建一个模板,新建几个变量放在模板的某个位置等等,这肯定会使整体耦合性过强。在Angular2中pipe便有『纯』与『非纯』的概念,非纯的管道在变更时就需要考虑更多的外部环境变化,当然效率也会大大下降。我们希望大部分的函数、代码段、集合都能达到自治的标准,这也是大家常说的高内聚低耦合。

main组件是用户浏览的主体部分,在界面设计上它至少可以分为两个部分,首先是一侧的菜单与用户信息显示,其次是主要显示区域,当然你还可以为它增加一些隐藏、悬浮、弹出菜单。这里至少包含三个组件:菜单、列表、详情,我们先用angular-cli命令生成它们:

ng g component main-detail
ng g component main-menu
ng g component main-list

组件准备就绪,我们在src/app/main文件夹下新增模块与路由文件,并把原有的组件改造为路由插座:

// 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 {
}

子模块也需要被根模块检测到才能在编译时被纳入,这里考虑到main.module是一个子路由产生的懒模块,我们可以考虑在路由转向它时才开始加载。这时app.routing需要改写一条路由规则:{path: 'main', loadChildren: './main/main.module#MainModule', data: {preload: true}}

从现在开始,每当我们访问/mian路由时Angular会自动为我们加载新的模块,在访问/mian/*时,main.routing.ts文件会开始检测路由地址并切换到相应的页面组件上。后面所有的业务都将专注于main路由中,为了项目的可读性,每个子路由工作的子页面组件,都应当写在main文件夹下。

编写组件与公共服务

我为main下的组件写了一些样式,具体可以参考Commit,它看起来有些简陋但并没有关系,在编写应用时不能把注意力过于集中在某一点上,一开始写出非常严谨、不可变的样式会使随后的逻辑重构畏首畏尾,整体式的推进、优化可以大大提升项目进度。等到应用能够运行时我们再回过头来考虑这些问题。

与登录相似,在每个组件下创建一个service,需要记住的是,当前组件下的service仅仅只供给当前组件使用,它被写在组件的providers依赖列表里,如果你真的需要一个共享或状态存储(单次实例)的组件,可以考虑shared文件夹。举个例子来说,现在我们的数据库中文章详情是html富文本格式,这些源数据是不能够被直接解析在dom结构中的,还需要做一些安全化处理,我们以这个功能为例,创建一个公共的pipe解析器。
shared/pipe/sanitize下创建一个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.tsimport {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依赖中调用SharedModule.forRoot(),而其他地方仅仅依赖SharedModule即可。看它们不同的使用方法很多人应该已经猜出module是怎样工作的了,先不管这些,让我们回到mian.module里注入依赖项试试效果。

新的通信接口

在此之前,我们约定了接口语法为ipcRendererService.api('接口名', '参数'),新增的组件里也参考此方式发起请求即可,这里我们可能至少需要两个接口:this.ipcRendererService.api('list', page)this.ipcRendererService.api('detail', id)。想象一下,在列表组件初始化时调用list接口传入一个页码获得一些列表数据,然后使用Angular的路由方法this.router.navigate(['/main/list', id])把列表中某一项的id传至详情页面,详情页面在初始化时从url上取得页面id,再次通过detail接口获取自己需要的文章详情数据。一次正常的浏览就完成了。
在给Electron中的api增加方法时先等等,上一篇文章我们聊到Async函数,现在我们可以使用async函数来时路由更简单易懂一些:

// browser/ipc/index.jsconst {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 somethingscreen.setSize(1000, 720)return {msg: 'ok'}},list: async (e, page) =>{try{const articles = await articleService.findArticlesForPage(page)// todo filter articlesreturn 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)}}
}

articleService是原生数据库查询的封装,相比于每次写find/update方法与大量参数,我更建议大家把这些垃圾代码统一封装成更富有语义性的函数,无论过去多久,你再次读到这段代码时总能很清楚的知道自己做了什么,这很关键。
另外,我给大家展示的是代码框架如何搭建,单个await带来的便利性没有想象的大,但你在实际业务中会涉及多次查询、更新、筛选、遍历操作,async语法糖会给你带来极高的可读性!

现在news-feed已经能够快速显示出数据库里的列表:

点击任何一项进入详情,文章内容都被sanitize.pipe过滤解析在dom里:

最后

当然,news-feed还存在很多问题,甚至还不能称之为一个应用,比如不能注销登录、浏览文章时无法返回列表、无法下载文章内容/图片、没有跳转到原文等等。这些细节是真正值得注意的重点,后面几节我们都会一起讨论怎样添加这些逻辑并优化现有的代码。

使用Angular与TypeScript构建Electron应用(五)相关推荐

  1. 使用Angular与TypeScript构建Electron应用(二)

    回顾请前往第一节 本文所有代码都可以在github找到.你可以通过commit历史来查看这些代码是如何一步一步构建的.如果有任何问题,也可以在github的issue上提出. 接前文,现在我们搭建好了 ...

  2. angular2创建应用_如何使用Angular和SQLite3创建Electron应用程序。

    angular2创建应用 by William Boxx 威廉·博克斯(William Boxx) 如何使用Angular和SQLite3创建Electron应用程序. (How to create ...

  3. 构建Electron的常见问题(Mac)

    背景 起因是产品的需求,需要更换Electron为底层平台,但因为会有不少定制化的功能要实现,必须自己实现此类内容,所以也就导致必须自己编译Electron的源代码. 整个构建过程,看Electron ...

  4. typescript工程_使用TypeScript构建游戏。 工程图网格2/5

    typescript工程 Chapter III in the series of tutorials on how to build a game from scratch with TypeScr ...

  5. 《深入理解 Spring Cloud 与微服务构建》第五章 Kubernetes

    <深入理解 Spring Cloud 与微服务构建>第五章 Kubernetes 文章目录 <深入理解 Spring Cloud 与微服务构建>第五章 Kubernetes 一 ...

  6. 【Angular】—— TypeScript问号的奇特用法

    更新日志: [2019-09-19] 文章发布 说明: 本文地址 <[Angular]-- TypeScript问号的奇特用法>https://blog.csdn.net/maixiaoc ...

  7. Angular Component TypeScript代码和最后转换生成的JavaScript代码比较

    TypeScript代码使用@Component定义一个Component: @Component({selector: 'app-shipping',templateUrl: './shipping ...

  8. typescript get方法_使用 Typescript 构建类型安全的 Websocket 应用

    本文会通过一个简单的聊天室例子分享如何使用 Typescript 实现一个类型安全 Websocket 应用,在文章最后有 Github 项目地址.例子中的前端是使用 Angular 不过本文不会涉及 ...

  9. Github Action 快速构建 Electron 应用

    前言 在开发 Electron 应用时,比较耗时的部分应该是构建打包的过程,像用 electron-builder 这种打包工具来说,它会根据你要打包的系统来下载应用的系统镜像打包工具,由于这些镜像的 ...

最新文章

  1. 复旦大学邱锡鹏教授等「Transformers全面综述」论文
  2. MongoDB3.4安装及卸载
  3. Upwork 发布最赚钱的编程语言 Top 15
  4. bad src image pointers
  5. java 奖xls转成csv_java - 在Java中将.csv转换为.xls - 堆栈内存溢出
  6. 审计 Linux 系统的操作行为的 5 种方案对比
  7. 关闭word_Word教程第2讲:文档的基本操作(含视频)
  8. python面试题汇总(1)
  9. jquery 加载中_在 vue 中使用 ztree
  10. 【Spring】Feign客户端发送HTTPS请求绕过认证
  11. Linux 文件特殊权限 SUID、SGID 与 SBIT
  12. From Apprentice To Artisan 翻译 17
  13. 运行在一个完全隔离环境中的完整计算机系统,凤凰系统完全释放PC性能 模拟器是指通过软件模拟具有完整硬件系统功能、运行在一个完全隔离环境中的计算机系统...
  14. Python3之excel操作--xlsxwriter模块
  15. 概率的意义:随机世界与大数法则
  16. iOS开发之打包上传到App Store——(一)各种证书的理解
  17. adb 详细使用文档(ADB命令使用大全)
  18. ftp服务器怎样批量删除文件,ftp地址不能从快速访问中删除,其他的文件夹可以...
  19. 微信公众号新上线“快捷私信”功能 微信留言功能没有的号迎来转机
  20. 物联网python教程慕课_物联网技术基础,中国大学MOOC(慕课)答案公众号搜题

热门文章

  1. 发那科机器人编程软件fanuc roboguide授权补丁_发那科Robot | Line Tracking功能
  2. python语言源程序文件类型_浅谈Python的文件类型
  3. java怎么实现日程提醒_如何用java和xml实现日程提醒
  4. Balancing Act(树的重心入门)
  5. 全局变量定义的时候左侧加了static_c语言中static 用法
  6. Python中报错Invalid return character or leading space in header: Cookie
  7. 三维模型转换html,一种三维网格模型视图转换方法与流程
  8. c语言空白字符的aci,c语言的保留字符有32个是那些啊???代表什么于是啊??...
  9. mysql数据库连接空闲超时设置不生效,未区分全局变量及interactive_timeout设置
  10. Oracle日期和时间总结