昨天一个话题说关于AngularJS2之后版本的两个小技巧,不料引出了另一个话题,话题起始很简单:
“不少的前端框架并不复杂,好比JQuery,引入即用,实时看到效果,多好。到了Angular2一直到如今的版本5,一点改进没有,还要编译,还要部署,原有的JS脚本也不能用了。”
细想起来,这个话题的帽子并不小,至少牵扯出来一个关键,AngularJS2及之后的版本,其框架之下的JS代码,跟HTML中<script>
块之中的JS代码,究竟是什么关系?
我试着来回答一下:css
- 首先,在AngularJS2框架之中实际使用的是ES6,全称ECMAScript6,是Javascript的下一个版本。官方的例子则是基本采用TS,全称TypeScript,是JS的一个超集。之因此用起来没有明显区别的感受,由于的确从经常使用语法上,跟当前使用的JS,或者叫ES5 JS,差异很小,但即使再小,那也算的上不一样的语言了。
- 为何采用新的语言,而不是沿用当前的ES5,官网和社区已经有了不少解释了,新语言固然有新语言的优点,好比定义变量,能够指定类型,而在程序中用错类型,则会在编译过程当中就给出警告,不至于等到上线了才发现BUG。这些优点很是多,这里就不多此一举了。反正你确定能理解,新固然有新的好处。
- 既然采用了新的语言,为了跟当前的浏览器系统兼容,固然就有一个翻译过程,准确的说,甭管是TS仍是ES6,甚至未来可能的ES7,在当下,都要翻译成ES5,才能在当前流行的浏览器之中运行。这个翻译,行话上讲,也就是“编译”。
- 事实上,编译不只仅干这么一点事,不少的优化工做、查错工做,也是在这个阶段完成的,好比你使用了没有定义的变量、函数;好比你用错了函数类型;好比你使用了某个函数库但只是用了其中一小部分,那么多没用的部分应当排除掉避免占用宝贵的下载带宽,这些都是在编译过程作到的。
- 好了,既然通过了这么复杂的动做,这个编译也必不可少,那么实际上答案已经出来了:那就是,不少原有理所应当存在的东西,就好比你在HTML中定义的JS对象、变量、函数,那些都是在执行环节,浏览器中才存在的。而在编译阶段,那些东西还只是停留在字符状态,AngularJS固然并不知道他们存在,也就没法直接的、像原来咱们使用HTML-JS同样来使用它们了,这就如同上面那张图,看上去海天一色,互相映托,但在根本上,它们是在两个世界。
上面是从技术实现上的限制缘由,实际上还有一个设计哲学逻辑上的缘由:html
- AngularJS设计之初就不是为了单纯的在桌面浏览器中运行,还但愿可以在手机、移动设备甚至其它设备上执行。你可能会说,如今的手机浏览器也很发达啊,至少比不少IE6/IE7之流要强多了,稍等,这里说的移动设备、其它设备,可不必定是指仅仅浏览器,从这种设计逻辑出发,AngularJS成为一种跨平台的开发框架,直接编译成各类系统原生的代码,彻底是有可能实现的。试想,在那种状况下,你原来的JS代码极可能是连存在的空间都没有,又如何让AngularJS访问到呢?
————————————————————————————————————————————前端
那是否是原有的JS代码和技术都要做废掉,没法再使用了呢?
固然不是,你确定早看到了,大量的第三方模块和代码库,经过NPM的管理,共存于这个架构中,彼此友好的相处。你原有的工做,彻底能够用一样的方式来工做。
你也可能会说,可我有不少代码没有作到那么好的面向对象化包装,也不想作那么复杂,该怎么办呢?AngularJS也提供了至少3个方法,来完成两个世界的打通工做。
第一个方法,使用declare来预声明:
咱们来先看一个例子,使用ng new testExtJS
来新建一个工程,接着cd testJS
进入项目目录,使用cnpm install
来初始化依赖包。用cnpm的缘由是若是在中国,速度会快不少,这个在上一篇文章也说了。
接着修改index.html,这里只贴出最后的结果:web
<head> <meta charset="utf-8"> <title>TestExtJs</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <script> var webGlObject = (function() { return { init: function() { alert('webGlObject initialized'); } } })(webGlObject || {}) </script> <app-root>Loading...</app-root> </body> </html>
注意中间的<script>
块是咱们增长的部分,来模拟咱们在html本地已经有了一段js代码。
而后在app.component.ts中增长声明和调用的部分:npm
import { Component } from '@angular/core'; declare var webGlObject: any; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; constructor() { this.title="constructor works!" webGlObject.init(); } }
注意上面代码中的declare声明,和下面添加的constructor构造函数和其中对js对象的调用。
declare的意思就是告诉AngularJS,相信我,虽然如今你看不到对象webGlObject,但相信我,或早或晚,反正你必定会看到它的存在的,你正常编译、正常执行就好啦。固然这里的潜台词和反作用就是:诺,AngularJS,这部分代码我负责啦,你不用管它的对错,反正错了我也不会怪你。
使用这种方法,相似上一篇文章的问题,你也彻底能够声明一个window对象,而后直接访问其中的userAgent:segmentfault
... declare var window:any; ... console.log(window.navigator.userAgent);
问题又来了,既然直接能访问到window对象,那还用什么ng4-device-detector组件,直接从userAgent中判断设备类型很差吗?
这就牵涉到我上面解释的最后一条,未来这段AngularJS代码,极可能不是运行在一个浏览器,其中可能根本没有window/document对象,那时候,这段代码就出错了。固然你可能会说,不不不,我就是在浏览器运行,不考虑别的。OK,我也不较劲,你当我没说,你彻底能够就这么用。
可是比较规范的办法,应当是把window对象以及你须要的其它相似对象,写成一个服务,而后注入到app.component之中,这样,即使未来运行环境有变化,只修改服务部分代码,你的主程序彻底能够不用修改。
落实到代码,大体是这样,首先把window对象包装成一个服务:浏览器
import { Injectable } from '@angular/core'; function _window() : any { // return the global native browser window object return window; } @Injectable() export class WindowRef { get nativeWindow() : any { return _window(); } }
注册到provider:前端框架
import { WindowRef } from './WindowRef'; ... @NgModule({ ... providers: [ WindowRef ] }) export class AppModule{}
在须要的组件中,引用这个服务,而后就可使用了:架构
... import { WindowRef } from './WindowRef'; ... @Component({...}) class MyComponent { ... constructor(private winRef: WindowRef) { // 获得window对象 console.log('Native window obj', winRef.nativeWindow); } ... }
我得认可,这样是麻烦了很多,不过规范、可复用的代码,自己的确就多了不少限制。
参考资料:https://juristr.com/blog/2016/09/ng2-get-window-ref/app
————————————————————————————————————————————
AngularJS也一直在努力,尽力弥合这种鸿沟,其中HostListener和HostBinding就是具体的两个实现,也是咱们开始所说的3个方法中的后两个。
HostListener 是属性装饰器,用来为宿主元素添加事件监听,这个行为表示html端某个元素的事件,产生到达TS脚本的调用动做。好比:
import { Directive, HostListener } from '@angular/core'; @Directive({ selector: 'button[counting]' }) class CountClicks { numberOfClicks = 0; @HostListener('click', ['$event.target']) onClick(btn: HTMLElement) { console.log('button', btn, 'number of clicks:', this.numberOfClicks++); } }
使用counting装饰的button按钮,每次点击,都会产生一次计数行为,而且打印到控制的日志中去。
HostBinding 是属性装饰器,用来动态设置宿主元素的属性值,这个跟上面的动做相反,表示首先标记在html某元素的某属性,而后在TS脚本端,对这个属性进行设置、赋值。好比:
import { Directive, HostBinding, HostListener } from '@angular/core'; @Directive({ selector: '[exeButtonPress]' }) export class ExeButtonPress { @HostBinding('attr.role') role = 'button'; @HostBinding('class.pressed') isPressed: boolean; @HostListener('mousedown') hasPressed() { this.isPressed = true; } @HostListener('mouseup') hasReleased() { this.isPressed = false; } }
上面的代码表示,若是某个html元素用exeButtonPress属性修饰以后,会有一个.pressed属性,能够监控到鼠标按下、抬起的事件,这表现了html元素到ts端双向的互动。
HostListener和HostBinding有一个简写的形式host,以下所示:
import { Directive, HostListener } from '@angular/core'; @Directive({ selector: '[exeButtonPress]', host: { 'role': 'button', '[class.pressed]': 'isPressed' } }) export class ExeButtonPress { isPressed: boolean; @HostListener('mousedown') hasPressed() { this.isPressed = true; } @HostListener('mouseup') hasReleased() { this.isPressed = false; } }
看看,跟上一篇中快捷键绑定的方法很类似了?
这一部分的代码使用了http://www.javashuo.com/article/p-qjqwltji-bq.html的资料,这篇文章写的很细致,想详细了解的建议及早阅读。