Angular2学习笔记(1)——Hello World
1. 写在前面
之前基于Electron写过一个Markdown编辑器。就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成;但就代码而言,之前主要使用的是jQuery,由于本人非专业前段,代码写的自己都感觉是“一塌糊涂”,十分混乱。现在看到Angular2十分火爆,跑了跑它的The Tour of Heroes的例子,感觉非常不错,代码组织的井井有条,于是乎决定学习一下Angular2,然后用它将之前的NiceMark重写一下。
2. 整体感知
它组织代码的方式,引用其官方文档里的话就是:
You write Angular applications by composing HTML templates with Angularized markup, writing component classes to manage those templates, adding application logic in services, and boxing components and services in modules. ——GUIDE- 4. Architecture
大意就是说,你能这么去写一个Angular应用,用Angular扩展语法写HTML模板,写组件类去管理这些模板,用服务添加应用逻辑,用模块打包组件和服务。
用官方文档里的图说明就是:
简单解释一下该图:
- 图的中间部分,在component的上面画了Metadata和Template,是说一个Component(组件)主要由元数据(Metadata)和Template(模板)构成。Property Binding(属性绑定)箭头表示可以在component里定义property(属性),这些参数可以作为模板渲染的数据,而binding(绑定)的意思是说,一旦模板里的数据(比如一个输入框里数据)被修改了,它对应的property会自动地修改,或者反过来property被修改了,模板的相应内容也会跟着发生变化。Event Binding也类似,只不过针对的是Event(事件):你可以在模板中写代码去监听某一事件(当然Angular有自己的“监听语法”),并指定该事件被触发时调用component类中哪个方法(函数)。想象一下,我们可以在该方法中去修改某些property,根据前面Property Binding描述的,模板也会跟着发生变化。是的,在Angular中,你再也不用(也不要)直接的去修改DOM,而是修改property,它就是一个变量,修改起来多么的直观方便,你再也不必写像jQuery那样(比如,
$xxx.text('姓名: '+ user.name +',年龄:'+ user.age)
)又臭又长的“恶心”代码了,尽管这可以用一些手段来优化,但总是不那么直观,简便。 - 图中两个虚线箭头是解释说明的意思,左边箭头解释的是,我们在编写一个component时,会依赖一些服务,而这些服务类不用自己去
new
,可以让Angular注入进来。比如我们要编写一个用来展示用户信息列表的组件,为此我们需要这些用户的信息,而这些用户信息需要通过发送HTTP请求去服务器中获取,这时我们可以写一个UserService
类,写一个getUsers
方法专门负责获取这些用户信息,component只需要调用UserService
的getUsers
方法,便可以拿到这些信息,而获得UserService
实例的方式最好不要自己去new
,而是交给Angular去管理,让它帮我们注入进来。这点跟Spring依赖注入的意思是一样的。 - 右边的虚线箭头说的是,我们在编写模板时会用到一些Directive(指令),这些指令被用来指导Dom的渲染。
- 最后还剩下图的左上角部分,是一个一个的Module(模块)。它们像集装箱一样将Component,Service等较小的构成元素封装起来堆叠在一起,构成了一个完整的应用。
如果你还没有接触过Angular或者类似的框架,可能你还是不太明白Angular是怎么玩的 。不过没关系,以上内容只是让你对Angular有个大致的印象。实际上,它玩来玩去就是围绕以上提到的几个概念展开的,它们分别是:
- module (模块)
- component (组件)
- template (模板)
- metadata (元数据)
- data binding (数据绑定)
- directive (指令)
- service (服务)
- dependency injection (依赖注入)
只要明白了以上几个概念的意思,掌握了其用法,那就上道了。下文会结合几个小例子来解释这几个概念的含义的用法。
2. Hello World
老规矩,从Hello World开始。
有一点忘记说了,下面学习的是Angular的TypeScript版本。TypeScript是JavaScript的超集,它在JavaScript的语法上做了扩展,如果你对TypeScript还不了解,建议先简单看看TypeScript的语法,中文站和英文站都有。Angular的文档也是既有中文站,也有英文站,本文就是学习自Angular中文站,个人感觉翻译的非常好,并且点击每段文字都会出现英文原文,这点非常不错!😄。
先是环境的搭建。我用的是Ubuntu系统,IDE使用的是Idea。你需要提前装好Nodejs和npm,这应该是前端人员的必备工具了,这里假设你已经装好了。另外,npm最好使用淘宝镜像,否则会很慢很慢……由于是Hello World,我们在最原始的工程上手动搭建。
首先是建立一个Static Web工程,点击Next:
填好项目的名字,路径后,项目默认是这样,光秃秃的,什么也没有:
我们先用npm初始化一下项目,生成配置文件。点击底部的Terminal,输入npm init
,回车:
然后会提示你填一些项目相关信息,填好后刷新一下项目,会生成一个package.json的文件,它是npm的项目配置文件,里面可以写一些脚本命令和一些需要依赖的第三方库(类似于Maven的pom.xml文件)。
下面添加Angular及其它库,并编写运行脚本。在package.json
中加入如下代码:
{
...
"scripts": {
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
"e2e": "tsc && concurrently \"http-server -s\" \"protractor protractor.config.js\" --kill-others --success first",
"lint": "tslint ./app/**/*.ts -t verbose",
"lite": "lite-server",
"pree2e": "webdriver-manager update",
"test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
"test-once": "tsc && karma start karma.conf.js --single-run",
"tsc": "tsc",
"tsc:w": "tsc -w"
},
"dependencies": {
"@angular/common": "~2.4.0",
"@angular/compiler": "~2.4.0",
"@angular/core": "~2.4.0",
"@angular/forms": "~2.4.0",
"@angular/http": "~2.4.0",
"@angular/platform-browser": "~2.4.0",
"@angular/platform-browser-dynamic": "~2.4.0",
"@angular/router": "~3.4.0",
"angular-in-memory-web-api": "~0.2.4",
"systemjs": "0.19.40",
"core-js": "^2.4.1",
"rxjs": "5.0.1",
"zone.js": "^0.7.4"
},
"devDependencies": {
"concurrently": "^3.1.0",
"lite-server": "^2.2.2",
"typescript": "~2.0.10",
"canonical-path": "0.0.2",
"http-server": "^0.9.0",
"tslint": "^3.15.1",
"lodash": "^4.16.4",
"jasmine-core": "~2.4.1",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~4.0.14",
"rimraf": "^2.5.4",
"@types/node": "^6.0.46",
"@types/jasmine": "^2.5.36"
}
}
最后package.json
的结构是这样的:
接着在Terminal中执行npm install
,这个命令会根据当前目录下的package.json
文件中的dependencies
和devDependencies
key的值去仓库下载相应依赖库,这些库会下载到当前目录的node_modules文件夹中。
做完以上工作,环境就搭建好了。下面可以开始写Hello World。
首先在项目的根目录下新建一个index.html
页面。里面引入相关js库,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function (err) {
console.error(err);
});
</script>
</head>
<body>
</body>
</html>
其中的systemjs.config.js
还没有给出,如下,同样也是放在根目录下:
/**
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'node_modules/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);
这个JS是用来引入Angular相关库的,我们可以大致看一下里面的内容。首先它配置了npm管理的包所在的目录是node_modules
;它还配置了我们app的根目录为app目录,因此待会我们需要在根目录下新建一个app文件夹,用来放编写的Angular相关文件;之后配置了Angular库文件的位置。
这个文件来自这里。
以上就把Angular引入页面了。下面我们在index.html
的body中加上一个标签:
<app></app>
这个标签是我们自己定义的,它对应着一个视图和它包含的处理逻辑,比如一个用户列表,列表可以进行增删改查操作。可以想象,假如我们想要在页面显示两个相同的用户列表,我们只需要在相应的地方写两个<app></app>
标签,这多么的优雅!记住这个标签的名字,接下来我们要把它编写出来。
以上代码基本上是固定,无论你如何的修改你的功能,它们都可能不会变化。下面编写Hello World的Angular代码。
我们在systemjs.config.js
中配置了Angular的工作目录app: 'app'
,因此我们需要在根目录中创建一个叫app的文件夹;我们还指定了main: './main.js'
(相当于C语言或Java语言中指定主函数,它是程序运行的入口),因此我们在app文件夹下新建一个main.ts
文件,注意后缀是.ts
,它是一个TypeScript文件。然后在里面编写:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
以上代码的意思是将AppModule
模块作为入口。代码首选导入了platformBrowserDynamic
和AppModule
,其中platformBrowserDynamic
是Angular自身提供的;AppModule
是我们接下来要编写的,它定义在app.module.ts文件中。注意import ... from ...
是TypeScript的语法,与Angular无关。
app.module.ts的代码如下:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
上面的代码先导入NgModule
和BrowserModule
两个Angular内置的模块,和我们之后要编写的AppComponent
组件(定义在app.component.ts中)。然后定义了一个类AppModule
,并用export
关键字导出,这也是为什么我们在main.ts中用import { AppComponent } from './app.component';
能将其导入的原因。
注意到AppModule
类的上面写了一个类似Java注解一样的东西,它在TypeScript中被称为decorator(装饰器),其参数被称为metadata(元数据),它有3个属性,至于含义,先不用管。总之,我们现在要记住的是,要想定义一个模块,需要在模块类的上面加上NgModule
装饰器。
上面我们导入过AppComponent
,下面是它所在的文件app.component.ts的编写:
import { Component } from '@angular/core';
@Component({
selector: 'app',
template: `<h1>Hello {{name}}</h1>`
})
export class AppComponent {
name = 'World';
}
同样先导入Component
装饰器,被该装饰器修饰的类便变成为了Angular中所谓的Component(组件),在该类中我们还定义了一个property(属性)——name
,其值为World。
该元数据中定义了两个属性,注意其中selector
的值app
,就是我们在index.html
中写的自定义标签<app></app>
;该标签对应的模板(template属性)是<h1>Hello {{name}}</h1>
,其中用{{}}
括起来的变量name
正是我们在AppComponent
类中定义的属性name
,其值为World
。
写到这里,Hello World就OK了。上面说了这么多废话,实际上只是在/app目录下写了3个文件,如下:
|-HelloWorld
|-app
|-app.component.ts
|-app.module.ts
|-main.ts
现在我们可以运行它了。运行之前需要将TypeScript文件翻译成JavaScript文件,这需要先安装TypeScript包:
npm install -g typescript
由于Angular使用了TypeScript的装饰器,这一特性还没有纳入正式的标准,需要写一个TypeScript的配置文件tsconfig.json
,里面做如下配置:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
接下来,只要在根目录执行tsc命令,便能将app目录下的.ts文件翻译成.js文件了。
然后我们执行./node_modules/lite-server/bin/lite-server
,会自动开启浏览器,显示Hello World。
你可能会想,每次我们改动代码之后都要进行翻译,重启服务器操作,太麻烦了,有没有更简便的方法呢?当然有了,并且已经写好了,就定义在package.json
中:
...
"scripts": {
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
...
}
...
因此我们只需要执行npm start
即可。并且在start
命令中,我们加了一个tsc -w
命令,它能够监视当前目录中文件的变化,然后通知lite-server
刷新页面。其中还用到了另一个命令concurrently
,它可以让多个命令同时运行,可以参考这里或这里(淘宝镜像)了解相关信息。
以上便是Angular2的Hello World,你跑起来了吗?如果没有,对比一下这里的代码。
下一篇会继续学习Angular各个构成部件的作用和用法,写一个TODO小应用。