前言
接到一个pc端后台项目,还会加入两个安卓同事一起学习和做这个项目,需要带一下他们。 既ng1之后,我就没怎么有过其它后台框架的实际项目经验了,期间用的移动端框架也并非vue、angular系列、react,包括es6、webpack等也都并不熟悉。 公司一个其它后台项目使用了vue,偶尔我会维护一下但是总体来说对体系不熟悉。 一直比较喜欢angular,应该是有过ng1框架搭建的经验的原因(前面也有写过博客),学习过ng2和ng4的官方demo,总的来说照着抄写一遍意义不大,必须要用于实际项目才能真正吸收。 目前ng1肯定不要用了,我要重新熟悉和搭建一个架子,从自我喜好和前期一些基础来讲,ng4是最好的选择,刚好typescript的语法对安卓同事也比较友好。 向领导请示了之后,也得到了允许。
入坑之初,问题一般比较多,使用的是官方angular-cli创建项目。 中途完善cli的功能,理解整个框架是比较费精力的事情。 总的来说这次就花了两三天时间,梳理好了框架,配置好了一些基本功能和依赖,比如环境部署,路由嵌套,主页面布局(侧边栏,导航栏,内容,底部),公共服务(loading,http请求封装,全局服务title,用户信息存取)。 而后就直接和同事一起开发了。 最开始还有点没底,不知道搭架子要花多久时间,现在来看这个进度和时间还是很满意的,当然过程中参考了一些网上的同行分享的资源和代码。 非常感谢两位同事的积极支持,他们学习的也很快。
问题细节点
angular-cli.json 设置 styleExt为scss之后,在组件里写Styles,并不会编译把scss编译成css,必须要写在独立的scss,然后通过styleUrls引入。
routes path前面不能加 / 否则会报错
-
forRoot
creates a module that contains all the directives,the given routes,and the router service itself. -
forChild
creates a module that contains all the directives and the given routes,but does not include the router service.
总的来说forRoot是定义一级路由,forChild是定义二级路由或者说是子路由。比如我们的引导模块(命名一般是app.module.ts),也就是根模块,里面注册路由假设是,就需要使用forRoot。根模块通过forRoot注册的路由/main,需要嵌套路由成为/home ,那home的module就需要用forChild去注册路由。
ng4的提供了路由懒加载,如下,我们有个一级路由,main有个二级路由对应home组件,我们可以通过,下面的方法来定义子模块的路由和组件,但是这种写法就需要把路由写在一起,而我们希望组件的路由是一个单独的文件存在于文件夹中,通过在类似父模块中引入子模块的方式注册路由
所以就要使用loadChildren,通过它来注册子模块和子路由,代码如下。 loadChildren会找到路径文件app/home/home.module,#隔开,后面是exports的模块名。 下面的,定义了的路由和组件关系,因为是二级路由,所以这里用的是RouterModule.forChild,最后通过框架的处理,就达到了上面代码里的 children 属性的效果。 原本是访问main/home 就会载入和组件,但是发现直接访问路径 /home 能直接载入组件,似乎也注册了一个根域名。 原来是使用了loadChildren之后,HomeModule模块就已经被注册了到模块下了,而我在AppModule里又通过imports注册了一次HomeModule,这样就重复注入了,而且还是一次和父模块同级的注册,这一点要小心。
const routes: Routes =<span style="color: #000000;"> [
{ path: ''<span style="color: #000000;">,component: HomeCompontent}
]
@NgModule({
declarations: [
HomeCompontent
],imports: [
RouterModule.forChild(routes)
],providers: []
})
export class HomeModule { }
假设一个场景,根模块注册两个路径,一个是/login,一个是/main。这个路由访问就是单纯的一个登陆页面,main下面的路由都将是对应核心页面和业务,因为在组件里包含了公用的侧边栏、导航栏、内容容器和底部栏,所以 /main路由加载的组件的内容容器里需要嵌套子模块。 举个实例,当我访问main/home的时候组件会加载组件到content容器中,当我访问main/manager,组件又会替换content中的。这样我们的公共页面部分就是不变的组件,根据子路由的变化,去加载不同的组件到content里。
以下是main组件的html大致代码和实际页面截图:
这里也有一个知识点是
那么打开调试器,我们就能从dom节点上看清楚,router-outlet的嵌套关系:
再看下src的目录结构,component文件夹是存放一些公共的组件,login和main组件是注册的一级路由,home和另一个马赛克组件是注册为main的二级路由,实际后面会注册很多组件到main下,但是他们的文件夹划分都是同级。
引入了platform-browser的Title,使用它的setTitle方法改标题
在非hash路由情况下,有时候环境的原因布置静态资源路径的时候可能不是根域名,同时还要删除index.html的
import { APP_BASE_HREF } from '@angular/common';
在app.module里注册providers: [{provide: APP_BASE_HREF,useValue: environment.publicBase}],
使用http相关API,需要注入HttpModule,否则会报错: No provider for Http
import { HttpModule } from '@angular/http';
declare
规范
文件命名service,component,route,module,主要类型的文件种类不多,每次新建文件命名太长,引入的时候也麻烦,所以除了根目录命名保持xx.component.ts这种格式,其他文件统一为xx.c.ts,xx.s.ts。
bootstrap4
考虑引入boostrap4来作为css库布局。
关于rem,我们一般用rem作为单位的时候,是更希望利用它的特性改变font-size达到自适应效果,会先定义一个font-size的范围和对应的屏幕宽度范围,根据设计稿的宽度得到一个基数,再用设计稿中元素的实际像素去除以基数得到rem,最后根据屏幕宽度动态设置font-size的相应值达到自适应效果。bootstrap4以浏览器字体默认大小16px,直接定义了元素的rem值,它的源码里没有任何计算,我想他们是参照16px来设置的元素大小,然后求出的rem值,当页面根font-size的值是16px的时候,所有的bootstrap4的元素大小就是标准大小,如果我们想让页面的元素整体放大或者缩小,我们只需要去改变font-size的大小,font-size设置为多少,需要我们自己计算和定义规则。因为是三方库,所以这块的实现方法和我们自己实际项目使用rem的时候,会有些反差。
如果项目中引入了它,我们给自定义元素直接设置px值的话,就会出现问题,如果我们需要改变font-size的大小,就必须统一使用rem,否则font-size改变的时候,自定义的px元素并不会改变。那么自定义元素需要设置为rem值。
NG-ZORRO
想了想,需要快速开发,还是需要一个UI插件库,自己去造轮子开发成本太高,经撸哥介绍,知道了蚂蚁金服的ng库ng-zorro,支持ng4,。 看了下很全,还提供了栅格布局和按钮样式,转眼一想,如果用bootstrap4,相互之间可能有冲突,比如boostrap的reset相关的,而且用boot的按钮样式和蚂蚁的样式可能看起来不搭调。所以我在引入ng-zorro之后,先注释了对bootstrap4的引用,一些公用样式,后面可以考虑自己写。
部署打包
src目录下有个environments文件夹,这里的文件是配置环境的,.angular-cli.json文件有配置两个默认环境,一个是开发一个是发布环境,在我们开发的时候,默认选择的是dev环境
在src下的main.ts里有这么一段代码,这里的意思是切换到生产模式时禁用开发环境下特有的检查(双重变更检测周期)来让应用运行得更快。
我们在开发项目的时候,也必然需要配置开发,测试和生产环境,不同的环境的接口或者其他设置肯定是不一样的。 我需要配置一个apiBase变量,代表不同环境的接口域名,在开发的时候ng4会运行ng serve在本地运行一个服务,域名是localhost,那么后端部署的接口肯定不在我们这个开发域名下,所以这个 apiBase 就是我们后端接口的域名 (需要后端支持跨域)。 当我们把打包好的代码部署到QA或者生产环境后,访问前端页面的url和后端接口都在同一域名,所以 。 那么dev和prod的environment代码分别如下:
创建的 里有一段注释,如下图。 意思是如果我们用 ng build 命令打包的时候,加上 ,将会把 替代为 environment.prod.ts ,那么 里的代码import { environment } from './environments/environment.prod'; 打印日志查询当前环境变量是否是我们需要的
因此,我们就只需要把相应的环境变量配置好,如下API接口的代码和 main.ts 文件一样,我引入了 ,在开发或者打包的时候,angular 配置的打包工具会自动载入相应的环境变量
结语
因为业务需求原因,太久没真正学习搭建新框架了,心里也一直不踏实,感觉再不学点都跟不上时代了, 所以这次项目的机会也算是了却自己一个心愿。 对比ng4和ng1,开发模式有了很大的变化,给我的感觉就是ng4的模块划分更干净,写法更舒服。 也可能是因为有一些angualr系列的基础, 能力也应该比前年学习ng1的时候更强,这次入门很快。 es6和typescript语法有时候分不清谁是谁,落后的知识趁着这次尽快补起来。 因为新鲜感的原因,写代码变得更有乐趣了,在页面细节和动画上,稍微多搞了一点东西进去(后台项目没有设计师,自由发挥)。转眼3个多月没写博客了,这之间学的新东西不多,但是回头补了一下基础的一些知识,也算是有很多收获和新的理解。 设计模式的博客写了一部分,后面会抽时间一步步完成,是一篇希望大家都能看懂的博客,尽请期待!
- 按照缩写规则,指令directive我应该写成xx.d.ts的,但是d.ts这个格式的文件似乎会被框架其他程序处理,就会报错,所以指令的命名我就没用d.ts这样的缩写了
- 指令在父模块declarations之后,发现在子模块里使用指令,根本没有反应。折腾了很久,发现declarations申明的只在当前模块才能使用,而我的懒加载的子模块无效,所以为指令定义一个独立的module,在需要使用指令的地方import这个module,如下图
-
在指令directive中要拿到当前使用指令的dom,需要使用ViewContainerRef
- 如果我们需要拿到当前控制器下某个dom节点,需要使用@ViewChild
console.log(<span style="color: #0000ff;">this<span style="color: #000000;">.xxx)
} <span style="color: #008000;">//<span style="color: #008000;">html
<div #xxx>