AngularJS2+调用原有的js脚本(AngularJS脚本跟本地原有脚本之间的关系)

昨天一个话题说关于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的资料,这篇文章写的很细致,想详细了解的建议及早阅读。

相关文章
相关标签/搜索