Angular 初体验
事情起源当初一个简单的截屏然后推流出去的工具,这个工具当初我用winform简单实现了下,然后因公司业余,添加许多程序包,需要自动管理这些程序包,包含下载更新上传等,以及与后台交互,学生老师提醒,自动开关闭程序,自动推流等等功能。
这些功能都有一些特点,大部分实现不能放在UI线程,但是各个功能对应中间状态都需要在UI上显示,导致相应函数不能分隔,回调也超多,需求几次急改后,导致后续需求引入很麻烦,并且当初这个程序并没考虑给普通用户使用,现在需要考虑放给普通用户,而winform这种事件驱动类型的,界面与上层逻辑深度结合,界面不利于调整。
综合上述考虑,我想选择一种数据驱动的UI界面,避免能在各个事件中维护状态变化与显示,特别是这些事件互相影响状态连动的时候,以及最好能跨平台显示功能,界面易美化,就算我不会设计,让会设计的人方便改动就行,根据这些让我找到了Angular,现在整个项目逻辑已经移植差不多了,也算是对Angular基本了解了点,一句话,太好用的,特别在这项目上,写起来太爽。
先看一下相应界面,我不会设计界面,完全不知界面设计的好与坏,欢迎吐槽。
主界面
程序包更新界面
不会界面设计,放上来是为了后面讲解相应后面相应功能里的对应代码。
先讲一下整体设计,主要分二个部分,一就是angular负责界面展示与后台webapi交互,二是如程序包的安装更新管理功能,截屏推流部分,中途我因为electron框架能访问本地文件,倒是想重构一次TS版程序包管理模块的,但是截屏推流里全是底层C++实现,可能需要用到wasm技术才行,这些可能拉长整个项目时间,综合考虑,还是先利用已经封装好的C#模块完成这些功能,然后angular与这部分模块通过signlar实现的websocket二边通信,故分为二个进程,Angular用于界面展示各种状态信息,显示实时状态更新,而本机后台进程提供程序包的安装与上传下载,根据用户对应时间段自动开关程序,自动截屏推流等功能,这二个进程electron框架里的渲染进程与主进程关系,如下图展示。
当然这种二个进程的方式也增加了些代码量,比如本机后台与angular通讯用的结构体,这些结构C#/TS都要写一份。
在这还是先吹一波Angular这个框架,就我这个项目使用中,我认为一些处理非常好的地方。
- 数据单向绑定,双向绑定的超简洁语法,以及类似ngIf,nfFor语法超方便界面根据需求展示与排版,并且无任何侵入,不需要你为了绑定要在对应方向或是属性上做任何处理,绑定错误定位也非常清晰,这个应该和TS强类型动态语言的特性有关,说实话,我现在都感觉UI上用C#/C++这类语言真是太硬了,如绑定这种一是有侵入以及各种限定,二是界面展示时一般要对数据二次处理才能方便展示,在这完全没这问题,拿到服务里的数据,界面直接使用,不过如果是js又太软了,逻辑一多,又乱又不好排查,TS这种算是刚刚好。
- 注册服务的设计,原来在Winform里用单例来做相关状态的保存,共享这些状态的公用方法,在这使用服务,组件用到就注册,在这我使用二个主要服务,一个是对接webApi里各种调用及相关状态,二是用singlar对接本机后台相关调用与状态,包含互相调用及通知。
- 文档齐全,并且内置各种规则方便易用,官方文件里的组件与模板这块,大部分UI各种处理都涉及到了,按照对应情况选择相应处理方式就行,可能要看要记的多点,不过只要按照规则来,更少的BUG,更容易排错。
- 桌面可以用electron调用本地功能,app可以联合nativescript,内置神器RXJS,web版联合CSS方便设计界面。
举个例子,主界面里,先从后台得到所有程序包的信息,然后发到本机后台,查找本机相应程序现在的版本以及服务器版本,然后再返回给angular统计并显示,这个过程中,所有操作全是异步的,可以看下如何利用RXJS用同步方式来写这些异步实现。
连接后台,通过webapi得到所有程序列表。
// 得到所有程序列表
getProgramList(): Observable<ProgramInfo[] | boolean> {
const xtimestamp = this.getTimeStamp();
const headers = new HttpHeaders()
.set('uid', this.uid)
.set('timestamp', xtimestamp)
.set('signature', this.getSignature())
.set('Accept', 'application/json')
.set('Content-Type', 'application/json;charset=utf-8');
return this.http.post<ProgramListInfo>(this.apiName + '/api/xxxxxxx/', '', { headers })
.pipe(
map(data => {
if (data.code === 200 && data.data !== null) {
this.programList = data.data;
return data.data;
}
return false;
}));
}
连接本机后台,通过signalR得到程序列表具体信息,如本地版本,服务器版本这些然后返回给angular前端。
// 填充所有课件里的本地版本与服务器版本信息
getProgramList(programList: ProgramInfo[]): Observable<AppProgram[]> {
return from(this.hubProxy.invoke('GetProgramList', programList)
.then((programs: AppProgram[]) => {
// 选择一部分可以展示数据出去
const appPrograms = new Array<AppProgram>();
this.allPrograms = [];
programList.forEach(element => {
programs.find((progarm) => {
if (progarm.id === element.app_id) {
if (!progarm.remoteVersion || progarm.remoteVersion.length === 0) {
progarm.remoteVersion = '0.0.0.0';
}
progarm.appName = element.app_name;
// 可以更新/安装/启动的展示
if (progarm.launcherMode !== LauncherMode.inactive) {
appPrograms.push(progarm);
}
this.allPrograms.push(progarm);
}
});
});
this.programs = appPrograms;
return appPrograms;
}));
}
然后在组件里,组合这二个功能,得到当前程序列表里的所有信息以便在界面上展示。
getProgramList(): void {
// 请求HTTP上所有课件列表
this.userService.getProgramList().subscribe(
(programList: ProgramInfo[] | boolean) => {
if (typeof programList === 'boolean') {
return;
} else {
if (!this.signalrService.bHaveConnect) {
return;
}
// 如果数据请求正确,通过signalR请求本机后台进程查找到所有课件信息
this.signalrService.getProgramList(programList)
.subscribe(appProgramArray => {
// 通过本机查找数据后
this.appPrograms = appProgramArray;
this.installCount = this.appPrograms.filter(item => item.launcherMode === 1).length;
this.updateCount = this.appPrograms.filter(item => item.launcherMode === 2).length;
console.log('install count:' + this.installCount);
console.log('update count:' + this.updateCount);
if (this.bHaveLesson) {
this.currentProgram = this.appPrograms?.find((p) => p.id === this.liveLesson.appId);
}
});
}
}, error => this.userService.handleError(error));
}
Observable我觉得,你可以简单理解封装了一个函数回调,毕竟原来没有async/await时,一般想实现类似功能也是传入一个完成后需要做什么的函数,简单来说,上面得到程序包与得到本地程序包详细信息这二个步骤都返回的是一个函数,后面调用subscribe后意思才是执行相应函数,并在执行完这个函数后再执行subscribe里的函数,这种异步方式简单理解成一个函数链,Observable里管理根据函数执行结果选择继续执行挂在它后面回调函数。
至于界面,简单的界面逻辑一般直接写在模板页面里,配合网页的流式布局很适合根据需求显示数据,如上面的更新程序界面,进度条的整个隐藏显示,进度,提示信息简单明了的实现。
<div fxLayout="column" fxLayoutGap="1em">
<div fxLayout="row" fxLayoutGap="1em">
<button mat-raised-button (click)="runPrograme()">{{getRunName()}}</button>
<button *ngIf="program.launcherMode === 2" mat-raised-button (click)="forceRunPrograme()">强制运行</button>
<button mat-raised-button (click)="openProgramPath()">打开目录</button>
<button mat-raised-button (click)="verifyProgram()">验证文件完整</button>
</div>
<div fxLayout="column" fxLayoutGap="1em" *ngIf='bDownUpdate'>
<div>
当前文件: {{fileArgs.Message}}
<mat-divider></mat-divider>
<mat-progress-bar mode="determinate" [value]="fileArgs.Current*100/fileArgs.All"></mat-progress-bar>
</div>
<div>
总进度: {{fileListArgs.Current}}/{{fileListArgs.All}}
<mat-divider></mat-divider>
<mat-progress-bar mode="determinate" [value]="fileListArgs.Current*100/fileListArgs.All">
</mat-progress-bar>
</div>
</div>
</div>
最后说下排版,现在网页布局一般用flax-layout布局,http://flexboxfroggy.com/ 花不到半个小时做完,你就掌握的差不多了。
angular有个包装的模块angular/flex-layout,搞清楚Angular flex-layout,一定要理解css 里 flex-layout概念,国内关于Angular flex-layout的说明是不准确的,特别是对自身生效,对子元素生效这种描述,如主界面中,把设置放右边,按这描述,整体排列我用gdLayoutAlign,设置这个选项用gdFlexAlign,一直没效果,我就把原始CSS里相关flex-layout各种概念理清了下,然后F12对应angular 里的flex-layout各种对象,其实很明显,我就是要个justify-content:flex-end的效果,对应的还是gdLayoutAlign。
上面主界面中,对应不同条件排版,比如没有开启程序打开推流,就从上向下全铺满,有就二二分部排列,如下就能满足,可以说是非常方便。
[gdAreas.gt-sm]="(condition) ? 'header header | cont1 cont2 | cont3 cont4| footer footer':'header | cont1 | cont2 | cont3 | cont4 | footer'"
总的来说,整个项目完成下来,都很顺利,可能需要注意的,signalr有二个版本,一个对应.net CORE,一个对应.net foramework,对应的angular的二个实现,常用的对应的是.net core版的,像这个项目本机后台用.net framewrok加owin搭建的,需要用另一版本,还有开发跨域问题,使用代理是最简单的方法,angular里有集成webpack,在相应文件里配置一下就行。服务里相关事件一般推荐为Subject来实现,不推荐用EventEmitter来实现,这个在关联组件里使用。
当然最大问题还是与现有的C++或是C#链接库交互的问题,现在这种二个进程的方法限制太大,毕竟前面研究的底层渲染,图形处理,多媒体处理大部分是C++所写,如果用C#,写个供C#使用的C++转C导出的链接层非常容易,而与js/ts的交互,现还只找到wasm技术能处理相关问题,以及electron/node.js内部有相关实现,还没开始研究,感觉如果这个问题能很好解决的话,我以后大部分界面都可以用这种方式来实现。
Angular 初体验的更多相关文章
- angular初体验
所有需要ng管理的代码必须被包裹在一个有ng-app指令的元素中ng-app是ng的入口,表示当前元素的所有指令都会被angular管理(对每一个指令进行分析和操作) 利用angular实现双向绑定: ...
- 【Knockout.js 学习体验之旅】(1)ko初体验
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
- AngularJS路由系列(3)-- UI-Router初体验
本系列探寻AngularJS的路由机制,在WebStorm下开发. AngularJS路由系列包括: 1.AngularJS路由系列(1)--基本路由配置2.AngularJS路由系列(2)--刷新. ...
- angularJs初体验,实现双向数据绑定!使用体会:比较爽
使用初体验:ng 双向数据绑定: 最简单的双向数据绑定:(使用默认模块控制) <body ng-app> <input type="text" ng-model= ...
- Handlebars的基本用法 Handlebars.js使用介绍 http://handlebarsjs.com/ Handlebars.js 模板引擎 javascript/jquery模板引擎——Handlebars初体验 handlebars.js 入门(1) 作为一名前端的你,必须掌握的模板引擎:Handlebars 前端数据模板handlebars与jquery整
Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...
- .NET Core 初体验
.NET Core 作为微软的开源项目,neter 们对之的期待还是挺大的. 以前也看过,接触过,摸索建了几个示例项目,今天就罗列下自己的初体验. .NET Core 安装.帮助等 安装的话,直接官网 ...
- day21—AngularJS学习初体验
转行学开发,代码100天——2018-04-06 今天按照学习计划安排,开始AngularJS的学习. 关于AngularJS,在菜鸟教程上这样介绍 好吧,Angular学习起来非常简单,哈哈,现在就 ...
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
随机推荐
- ES6中对数组的扩展
hello,大家好,我又来了. 前面讲了字符串和数值的扩展,今天要讲的是:数组的扩展.不知道大家能否跟得上这个节奏,你们在阅读中对讲解有存在疑惑,记得留言提出来,要真正地理解,否则白白 ...
- [wp]xctf newscenter
手工注入 查询所有数据库名称和表名 ' union select 1,table_schema,table_name from information_schema.tables# 发现就两个数据库i ...
- javascript-数组简单的认识
一起组团(什么是数组) 我们知道变量用来存储数据,一个变量只能存储一个内容.假设你想存储10个人的姓名或者存储20个人的数学成绩,就需要10个或20个变量来存储,如果需要存储更多数据,那就会变的更麻烦 ...
- thinkphp--create()方法
1.create方法可以对POST提交的数据进行处理(通过表中的字段名称与表单提交的名称对应关系自动封装数据实例),例如user表中有一个字段名叫"username",如果表单中有 ...
- css中的宽和高
width width表示宽 height height表示高 max-width.min-width max-width表示最大宽度 min-width表示最小宽度 max-height.min-h ...
- 2019-2020-1 20199310《Linux内核原理与分析》第二周作业
1.问题描述 众所周知,计算机是20世纪最伟大的发明之一,计算机是如何工作的呢?本文主要通过计算机的组成结构和工作原理,以及汇编代码工作过程来进行详细叙述. 2.解决过程 2.1 冯·诺依曼体系结构 ...
- iscsi的工作原理与优化(2)
2.1 iSCSI协议模型,iscsi[会话层协议,即应用协议] iSCSI使用TCP/IP协议在不稳定网络上进行可靠的数据传输.iSCSI层和标准SCSI集在协议栈中的位置如图1所示.iSCSI层包 ...
- Redis持久化存储(二)
redis多实例介绍 接上一篇redis.创建数据存放的目录 vim redis.conf +187 dir /application/data/ 重新启动 mkdir /application/da ...
- python实现逻辑回归
首先得明确逻辑回归与线性回归不同,它是一种分类模型.而且是一种二分类模型. 首先我们需要知道sigmoid函数,其公式表达如下: 其函数曲线如下: sigmoid函数有什么性质呢? 1.关于(0,0. ...
- MyBatis配置项--配置环境(environments)--数据源(dataSource)
数据源(dataSource) dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源. ·许多MyBatis的应用程序会按示例中的例子来配置数据源.虽然是可选的,但为了使用 ...