本系列从Java程序员的角度,带大家理解前端Angular框架。

本文是入门篇。笔者认为亲自动手写代码做实验,是最有效最扎实的学习途径,而搭建开发环境是学习一门新技术最需要先学会的技能,是入门的前提。

作为入门篇,本文重点介绍Angular的开发、编译工具:npm, yarn, Angular CLI,它们就像Java在中的Maven,同时顺便介绍一些Angular的概念。学习之后,希望你能够在自己的环境下练习、探索、编写出自己的第一个基于AngularWeb应用。

在开始介绍之前,先了解下一些背景知识,理解单页应用与传统基于模板的多页应用在Web开发思路的不同。

什么是单页应用(Single Page Application,SPA)

单页应用是Web前端发展的主流趋势。

服务端渲染

传统的Web前端是多页应用的,客户端浏览器向服务器提交请求,服务端通过处理HTML模板(如PHP,ASP,JSP等),生成HTML文本返回给浏览器,浏览器重新渲染并且展示。HTML完全有后端决定,因此称为“服务端渲染”。客户端每次拿到都是一个HTML页面,这样一个Web应用就有很多页面,因此叫多页应用。

服务器 ----(html页面)---> 浏览器
  • 1

由于浏览器每次都要解析整个HTML并渲染,因此效率较低,每次即使更新一个数据也要在网络上传输整个HTML文本,占用更多的带宽。

客户端渲染

不同于传统多页应用,在SPA应用中,客户端浏览器向服务器提交请求,服务端返回数据(通常是json格式)给浏览器,浏览器中的js更新相应部分的DOM,从而更新展示。渲染的过程是在客户端浏览器完成的。

服务器 ----(json数据)---> 浏览器
  • 1

优点:

  1. 局部刷新。无需每次都进行完整页面请求。
  2. 天然的前后端分离。后端不再有渲染展示逻辑,只要专注业务逻辑并能够向前端提供数据;而前端只关注如何展示数据、处理用户交互,提高用户体验。这很像C/S架构,富客户端,只不过客户端是浏览器中的js脚本。
  3. 计算量转移。原本需要后端渲染的任务转移给了前端,减轻了服务器的压力。

缺点:

  1. 首次打开较慢。由于是富客户端(js脚本),当客户端首次打开网页,需要先下载一堆jscss后才能看到页面。对于这一点,SPA也有应对方案,比如可以(1)分拆打包(把js拆成多个包,首屏用到的先传);(2)先传个友好的加载页面;(3)同构(第一次加载是采用后端渲染,以后用前端渲染)。
  2. 不方便搜索引擎。传统的搜索引擎会从HTML中抓取数据,导致前端渲染的页面无法被抓取。不过这一点,随着SPA的流行,Google爬虫也可以像浏览器一样理解js脚本了。

流行的SPA框架

流行的SPA框架有ReactVueAngular。本文基于Angular 2/4/5+(不是Angular 1.xAngularJS)。

node.js

就像Java开发需要JDKAngular开发需要node.jsJava编译出来的字节码需要Java虚拟机JRE执行,而如果想在浏览器以外执行JavaScript代码,也需要类似的虚拟机平台,也就是node.js

类似JDKnode.js下载之后也不需要安装,只要加到PATH路径下即可。这里需要node.js是因为很多前端开发工具是有JavaScript写成的,如npm,它们需要node.js这个虚拟机。

项目依赖管理工具

Java中的maven,开发Angular可以使用npm或者yarn。其中npmnode.js自带的,可以直接使用。

另外,npm还有maven不具备的能力,它可以从网上下载并安装软件,类似于Linux中的yum。比如,yarn可以通过npm下载安装:

npm install --global yarn
  • 1

npm作为项目依赖管理有个缺点,它没有本地仓库,对同一个依赖,不管其他项目是否已经下载过,只要这个项目没有,它都从网上下载。下载后存在每个项目下的node_modules/路径下。相比,maven有本地仓库,对同一个依赖只会下载一次,存在~/.m2/repository/下。

npm不同的是,yarn无需互联网连接就能安装本地缓存的依赖项,它提供了离线模式。只要其他项目已经下载过,就不会上网下载,但依然会拷贝到项目下的node_modules/路径。另外,yarn的运行速度得到了显著的提升,整个安装时间也变得更少。所以推荐使用yarn管理项目依赖。

仓库

maven有中央仓库,也可以创建私服;npm,yarn同样都有,可以配置:

npm config set registry http://registry.npmjs.org/
yarn config set registry https://registry.yarnpkg.com/
  • 1
  • 2

科学上网

国内用户可以通过淘宝镜像提高下载速度:

npm config set registry https://registry.npm.taobao.org
yarn config set registry https://registry.npm.taobao.org
  • 1
  • 2

代理

如果上网需要代理的话,可以在~/.bashrc加入如下内容:

######################
# User Variables (Edit These!)
######################
username="myusername"
password="mypassword"
proxy="mycompany:8080" ######################
# Environement Variables
# (npm does use these variables, and they are vital to lots of applications)
######################
export HTTPS_PROXY="http://$username:$password@$proxy"
export HTTP_PROXY="http://$username:$password@$proxy"
export http_proxy="http://$username:$password@$proxy"
export https_proxy="http://$username:$password@$proxy"
export all_proxy="http://$username:$password@$proxy"
export ftp_proxy="http://$username:$password@$proxy"
export dns_proxy="http://$username:$password@$proxy"
export rsync_proxy="http://$username:$password@$proxy"
export no_proxy="127.0.0.10/8, localhost, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" ######################
# npm Settings
######################
npm config set registry http://registry.npmjs.org/
npm config set proxy "http://$username:$password@$proxy"
npm config set https-proxy "http://$username:$password@$proxy"
npm config set strict-ssl false
echo "registry=http://registry.npmjs.org/" > ~/.npmrc
echo "proxy=http://$username:$password@$proxy" >> ~/.npmrc
echo "strict-ssl=false" >> ~/.npmrc
echo "http-proxy=http://$username:$password@$proxy" >> ~/.npmrc
echo "http_proxy=http://$username:$password@$proxy" >> ~/.npmrc
echo "https_proxy=http://$username:$password@$proxy" >> ~/.npmrc
echo "https-proxy=http://$username:$password@$proxy" >> ~/.npmrc ######################
# yarn Settings
######################
yarn config set registry https://registry.yarnpkg.com/
yarn config set proxy "http://$username:$password@$proxy"
yarn config set https-proxy "http://$username:$password@$proxy"
yarn config set strict-ssl false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

脚手架

脚手架可以简化开发,创建工程框架。比如maven可以使用archetype:generate来创建基于maven约定的java工程。

脚手架创建的项目,一般包含以下内容: 
1. 编译脚本、依赖定义 
2. 源代码路径及样例 
3. 单元测试路径及样例 
4. 资源路径 
5. 环境配置(开发环境、生产环境、测试环境) 
6. 编译目标路径(编译后生成)

Angular脚手架

Angular CLIAngular官方提供的命令行工具,可以帮助我们创建项目、编译、测试、运行,就像Java界的Maven,另外还可以创建组件、管道、指令、服务、模块、类、接口、枚举。。。

可以用npm安装Angular CLI

npm install --global @angular/cli
  • 1

创建Angular项目:

ng new <project_name>
  • 1

脚手架会帮我们创建以下文件:

|-- package.json                   # 编译脚本、依赖包管理,类似maven的pom.xml。
|-- src # 原代码,类似maven的src/main/java
| |-- app # 该应用的模块路径,angular支持模块化!
| | |-- app.component.css # 组件样式(作用域只在该组件内部有效)
| | |-- app.component.html # 组件模板(视图)
| | |-- app.component.spec.ts # 组件单元测试
| | |-- app.component.ts # 组件控制代码(控制器)
| | |-- app.module.ts # 模块管理代码
| |-- assets # 资源文件夹,类似maven的src/main/resources
| |-- environments
| | |-- environment.prod.ts # 生产环境配置
| | `-- environment.ts # 开发环境配置
| |-- favicon.ico # 网页的图标
| |-- index.html # 网页的HTML,使用根组件(一般无需修改)
| |-- main.ts # 入口代码,引导根模块(一般无需修改)
| |-- styles.css # 全局样式
| |-- test.ts # 单元测试入口(一般无需修改)
| |-- ...
|-- tsconfig.json # typescript配置,比如开发使用的ES版本,编译生成的目标ES版本(默认是ES5,即目前广泛的javascript,一般无需修改)
|-- tslint.json # typescript的语法规则,方便IDE检查(一般无需修改)
|-- node_modules # 项目的依赖包仓库,相当于maven的本地仓库
|-- dist # 编译时才生成的目标文件夹(拷贝里面内容到Web服务器即可使用),相当于maven工程下的target目录
|-- .gitignore # git ignore(一般无需修改,node_modules和dist以默认排除)
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

依赖管理

当我们需要更新项目依赖时,可以修改package.json文件,然后运行yarn install。它会解析package.json并下载还没有的依赖。这种方式跟maven完全一样。

添加/删除依赖

还可以用命令行来添加/删除依赖:

yarn add/remove <package_name>[@<version>] [--dev/-D]
  • 1

[]表示可选的,不写为最新版本。命令执行后会自动更新package.json文件。--dev-D表示包只是开发时候需要,最终产品打包是不包含的,类似与Maven中的provided scope

开发阶段:运行

对于前后端分离的应用,前端开发与后端独立开,前端应用在没有启动后端服务器的情况下也应该能够运行。

Angular CLI提供支持,如下:

ng serve [--host 0.0.0.0] [--port 4200]
  • 1

[]表示可选的,不写端口号默认为4200。打开浏览器,输入localhost:4200可以看到你开发的网页。

代码更新检测

代码文件更新不需要重启服务,当文件内容变化时,网页会自动刷新,开发者可以看到更新后的页面。

Mock 后端服务

在后端服务还没有准备好之前,当前端需要向后端服务发出请求(GET/POST/PUT/DELETE),让后端更新数据,并且返回给前端时,可以造个假的。

可以安装一个json-server

npm install --global json-server
  • 1

写个json文件,把假数据填上去。启动json服务:

json-server <your_mock_data.json> [--port 3000]
  • 1

就可以作为服务器接受客户端的RESTful请求(GET/POST/PUT/DELETE)了。

单元测试

mvn testAngular CLI可以一键运行所有单元测试:

ng test
  • 1

编译成目标文件

mvn compileAngular CLI可以一键运行编译导出目标文件(默认为ES5,即传统的javascript):

ng build [-prod]
  • 1

可选参数-prod表示生产环境,输出目标会小很多。输出目标为dist文件夹:

$ ls -l dist
total 413
-rw-r--r-- 1 weliu 1049089 3293 Jan 15 08:50 3rdpartylicenses.txt
-rw-r--r-- 1 weliu 1049089 5430 Jan 15 08:50 favicon.ico
-rw-r--r-- 1 weliu 1049089 597 Jan 15 08:50 index.html
-rw-r--r-- 1 weliu 1049089 1445 Jan 15 08:50 inline.08a75f8119356113a22d.bundle.js
-rw-r--r-- 1 weliu 1049089 341557 Jan 15 08:50 main.e25b64f979f240da775b.bundle.js
-rw-r--r-- 1 weliu 1049089 61268 Jan 15 08:50 polyfills.65fe1626e31e03d17f8e.bundle.js
-rw-r--r-- 1 weliu 1049089 0 Jan 15 08:50 styles.d41d8cd98f00b204e980.bundle.css
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

不如maven compile的地方,是ng build就只做编译,在编译前也不会先去下载未缓存的依赖包,因此完整的编译前需要手动调用yarn install

部署

将编译生成的dist文件夹中的所有文件放在Web服务器即可。

也可以与服务端代码一起编译,打成一个包,方便部署。

与Java后端一起部署(使用Maven)

对于使用Spring Boot的后端Java代码,只提供RESTful的数据服务,可以打包成一个独立可执行的jar/war包:

$ cd back-end
$ mvn clean package
...
BUILD SUCCESS
... $ ls target/
...
my-web-server.war
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

目前这个war包还只是一个提供RESTful的数据服务器,还没有网页界面。

可以写个脚本把前端编译后dist中的内容,通过jar命令打包进上面的war包(或者jar包),脚本很简单,如下:

$ cat add_to_war.sh
cd dist/
jar uf ../../back-end/target/my-web-server.war *
  • 1
  • 2
  • 3

这样就war包就带有网页界面了,可以放在Tomcat服务器下部署,或者直接启动(Spring Boot默认会内嵌Tomcat服务器):

java -jar back-end/target/my-web-server.war
  • 1

以上过程,可以写一个总的编译脚本build.sh,一键编译整个前后端应用:

# build back-end by maven or gradle
cd back-end
mvn clean package # or: gradle clean build # build back-end by yarn and Angular CLI
cd ../front-end
yarn install -prod
ng build -prod # add to war by jar command
cd dist/
jar uf ../../back-end/target/my-web-server.war *
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

与Java后端一起部署(使用Gradle)

如果使用Gradle编译打包整个前后端应用,就更简单,因为已经有Gradle插件com.moowork.node直接支持。

在前端Angular项目(比如名为front-end)中,加入build.gradle文件,内容如下:

plugins {
id "com.moowork.node" version "0.13"
} apply plugin: 'java' // configure gradle-node-plugin
node {
version = '6.5.0'
npmVersion = '3.10.7'
// If true, it will download node using above parameters.
// If false, it will try to use globally installed node.
download = false // true
workDir = file("${project.projectDir}/node")
}
task compileTypeScript(type: NpmTask) {
// install the express package only
args = ['run-script', "tsc"]
} // clean node/node_modules/dist
task npmClean(type: Delete, group: 'node') {
final def webDir = "${project.projectDir}"
//delete "${webDir}/node"
//delete "${webDir}/node_modules"
delete "${webDir}/dist"
} clean.dependsOn(npmClean) task npmStart(type: NpmTask) {
args = ['start']
group = "node"
dependsOn("npmInstall")
} task npmBuild(type: NpmTask) {
args = ['run', 'build']
group = "node"
dependsOn("npmInstall")
} jar {
dependsOn("npmBuild")
from(fileTree("dist")) {
into "META-INF/resources"
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

在前端Angular项目的根目录中直接执行:

gradle build
  • 1

就可以编译、打包,在Gradle约定的输出目录build/libs/下生成目标jar文件,包含所有Web资源。

front-end
| build
| |-- libs
| | |-- front-end-0.0.1.jar
  • 1
  • 2
  • 3
  • 4

在后端Java项目back-end中的build.gradle中加入对前端Angular项目的依赖即可。

dependencies {
compile project(":front-end")
...
  • 1
  • 2
  • 3

为了一键编译、打包整个前后端工程,可以在前后端项目的父目录中加入build.gradle:

task wrapper(type: Wrapper) {
gradleVersion = '3.0'
}
  • 1
  • 2
  • 3

settings.gradle

rootProject.name = 'my-web-product'
include 'front-end'
include 'back-end'
  • 1
  • 2
  • 3

在此根目录中,可以一键直接执行:

gradle build
  • 1

这样编译、打包整个前后端工程,在后端项目中生成最终包含Web资源的文件:

my-web-product
| back-end
| | build
| | |-- libs
| | | |-- my-web-product-0.0.1.war
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到,就像mavengradle一样,Angular CLI可以帮我们创建脚手架、编译、测试、运行,但她的能力可远不只这些,在开发过程中依然是个好助理,比如可以帮我们创建:组件、服务、管道、指令、模块等等。

组件

组件是一个独立的、可复用的、可组合的UI控件,网页界面就是一系列组件的有机结合。

组件由组件名、视图、控制器组成。

组件相当于面向对象中的“类”,组件实例相当于“对象”。在Angular中,组件就是一个装饰了@Component注解的类。

脚手架创建组件

下面语句创建一个名为hello的组件(同时自动将组件注册到模块app.module.ts中,这样同模块的其它组件才可以使用):

ng g[enerate] c[omponent] hello [--inline-template] [--inline-style] [--spec false]
  • 1

[]为可选,默认情况下生成下面几个文件:

src
| app
| |-- hello
| | |-- hello.component.css # 组件样式(作用域只在该组件内部有效)
| | |-- hello.component.html # 组件模板(视图)
| | |-- hello.component.spec.ts # 组件单元测试
| | |-- hello.component.ts # 组件控制代码(控制器)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

嫌文件太多,也可以把HTML模板和CSS都内联进TypeScript,并且不创建单元测试:

src
| app
| |-- hello
| | |-- hello.component.ts # 组件全部代码:控制器、视图、样式
  • 1
  • 2
  • 3
  • 4

内联了模板、CSS的文件hello.component.ts长这个样子:

import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-hello', // 组件选择器(组件名)
template: ` // 组件模板(视图)
<p>
hello works!
</p>
`,
styles: [] // 样式CSS(作用域只在该组件内部有效)
})
export class HelloComponent implements OnInit { // 组件控制代码(控制器) constructor() { } ngOnInit() {
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以在模块内其它组件HTML模板中使用该组件:

<app-hello></app-hello>
  • 1

服务

前端与后端交互的代码逻辑,在Angular中一般设计成一个服务。服务是一个可以依赖注入的(非常像Spring),容器管理的类。

Angular CLI可以帮我们生成一个服务的代码,下面的命令在src/app/services/目录下创建一个名为job的服务(同时自动将组件注册到模块app.module.ts中,这样同模块的其它组件才可以使用):

ng g[enerate] s[ervice] services/job [--spec false]
  • 1

创建的文件为:src/app/services/job.service.ts。添加代码后可以与后端进行交互(GET/POST/PUT/DELETE)。

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Server } from '../model/server';
import { JobDetail } from '../model/job.detail'; @Injectable()
export class JobService { private readonly headers = new Headers({
'Content-Type': 'application/json'
}); constructor(private http: Http) {} jobs(host: string, port: number): Observable<JobDetail[]> {
return this.get('jobs', {
host: host,
port: port
});
} runJob(job: string[], servers: Server[]) {
if (job.length === 0 || servers.length === 0) {
return;
}
const serverHostPorts = servers.map(server => `${server.host}:${server.port}`);
console.log(`run job ${job} on servers ${serverHostPorts}`);
this.post('run', {
job: job,
hostPorts: serverHostPorts
}).subscribe();
} private get(action: string, params: any = {}): Observable<any> {
return this.http.get(`api/${action}`, {
headers: this.headers,
params: params
})
.map(res => res.json());
} private post(action: string, data: any = {}, params: any = {}): Observable<any> {
return this.http.post(`api/${action}`, data, {
headers: this.headers,
params: params
});
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

虽然Angular CLI都会给服务类修饰@Injectable注解,但并不是服务的标识,也不是必要的。它的作用是让Angular框架能够向该类注入其它服务,比如构造函数中Http服务就是由Angular框架通过依赖注入进来的。这种机制与Spring非常相似。

Angular是基于rxjs的,操作http会得到一个可观察对象Observerable<any>,异步编程、事件处理都非常简单。熟悉RxJava的同学觉得非常亲切。

Angular是一个类似于Spring的框架,其容器管理服务和组件,所有服务都是可以依赖注入的(只提供构造器注入,这也是不可变成员变量的最佳实践)。所有,在组件或其它服务中使用服务就非常简单,只要在构造函数把服务传进来即可,下面演示了servers组件使用上面定义的job服务:

@Component({
selector: 'app-servers',
templateUrl: './servers.component.html',
styleUrls: ['./servers.component.css']
})
export class ServersComponent { jobs: JobDetail[] = []; constructor(private jobService: JobService) {
jobService.jobs().subscribe(jobs => this.jobs = jobs);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

下面演示rxjs的好处,比如我们想每隔5秒,异步地去服务器请求数据,拿到后刷新jobs控件,一句话搞定:

constructor(private jobService: JobService) {
Observable.interval(5000).subscribe(evt =>
jobService.jobs().subscribe(jobs => this.jobs = jobs));
  • 1
  • 2
  • 3
  • 4

管道

先看个例子来理解管道,比如我们在控制器typescript文件中有一个成员变量(字符串数组类型):

command: string[] = ['java', '-jar', 'hello.jar'];
  • 1

在视图html模板中显示:

<div>
{{ command }}
</div>
  • 1
  • 2
  • 3

会看到显示的内容是:

java, -jar, hello.jar
  • 1

这是string[]类型的toString()方法输出的,但逗号很不好看,希望它输出是这样的:

java -jar hello.jar
  • 1

有两种办法,第一种是直接在html中({{ }}里面可以是一个表达式):

<div>
{{ command.join(' ') }}
</div>
  • 1
  • 2
  • 3

第二种办法可以创建一个名叫join的管道,类型Linux管道一样使用:

<div>
{{ command | join: ' ') }}
</div>
  • 1
  • 2
  • 3

Angular CLI有创建管道的方法,下面的命令在src/app/pipes目录下创建了一个叫join的管道(同时自动将组件注册到模块app.module.ts中,这样同模块的其它组件才可以使用):

ng g[enerate] p[ipe] pipes/join [--spec false]
  • 1

由注解@Pipe修饰的类称为管道类,在生成的src/app/pipes/join.pipe.ts中,我们在加入transform方法中加入join的逻辑。

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'join' // 管道的名字以供HTML模板使用
})
export class JoinPipe implements PipeTransform { transform(input: any, character: string = ''): string {
if (!Array.isArray(input)) {
return input;
}
return input.join(character);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

内置管道

当然,Angular提供的内置管道非常好用,看下面的代码:

class Server {
host: string;
port: number; toString(): string { // 作用完全等同Java的toString方法
return `${host}:${port}`; // 与Kotlin一样也支持模板字符串语法
}
}
myServer: Server = { // 用json直接赋值,比Java方便不少吧
host: "127.0.0.1", // 如果变量名或者赋值类型的值写错了,类型系统会提示出错
port: 8080
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

HTML模板:

<div>{{ myServer }}</div>
  • 1

显示结果为:

127.0.0.1:8080
  • 1

如果想要把下面的对象myServer按照json格式打印出来,可以用json管道:

<div>{{ myServer | json }}</div>
  • 1

显示结果为:

{
"host": "127.0.0.1",
"port": 8080
}
  • 1
  • 2
  • 3
  • 4

如果上面的json是服务端发送过来的,那么结果是一个rxjs可观察对象Observable<Server>

myServer$: Observable<Server> = http.get('api/server').map(res => res.json());
  • 1

Observable类型的变量名以$结尾,是Angular的一种编程习惯,因为rxjs使用频率太高,这是为了区分普通对象和可观察对象。

可以不需要在代码中手动订阅,也不需要考虑什么时候去解除订阅,在HTML模板中用async管道,

<div>{{ myServer$ | async }}</div>
  • 1

当可观察对象有新值(onNext)时会在网页中更新,显示结果为:

127.0.0.1:8080
  • 1

就像Linux中的管道一样,Angular的管道也可以级联,上面的结果很容易以json格式显示出来:

<div>{{ myServer$ | async | json }}</div>
  • 1

显示结果为:

{
"host": "127.0.0.1",
"port": 8080
}
  • 1
  • 2
  • 3
  • 4

模块

可以看到,Angular CLI可以帮我们创建组件、服务、管道、指令等,非常方便,它们都有共同的特点,就是都会更改模块定义文件(app.module.ts)。

那么,什么是模块?

类似于Java 9OSGi的模块化,Angular应用天生就是模块化,概念都是差不多的。模块,就是在类和包之上在做封装,通常把协同完成某个功能的一组组件、服务作为一个整体,就是一个模块。打个比方,如果把模块想象成一个班级,那么组件、服务就是班上的同学。

模块具有封装性和隔离性,模块外部无法访问模块内部的组件、服务,除非导入该模块。这一点,用过OSGiEclipse RCP很熟悉,一个模块相对于一个Eclipse插件。如果想在一个插件中使用另一个插件的类,常用的做法是在插件声明文件MANIFEST.MF中声明为Require-Bundle(当然Eclipse不只这一种方法,比如还可以动态导入、或只import包名让OSGi容器自动去匹配插件,目前Angular模块化还做不到这一点)。

Angular CLI脚手架创建的src/app/app.module.ts,定义了一个由注解@NgModule装饰的类(模块类),用于管理该模块。这个文件就好比Eclipse插件中的MANIFEST.MF。一个模块类的代码如下:

import { BrowserModule } from '@angular/platform-browser';

@NgModule({                   // 模块类的标识由该注解决定
declarations: [ // 声明组件、管道、指令,类型由具体类的注解来区分,用Angular CLI创建时会自动填入
AppComponent, MyPipe, MyDirective ...
],
imports: [ // imports指定要导入的模块,这样才可以使用其它模块内的组件、指令、管道、服务
BrowserModule, ...
],
providers: [ // 声明服务,只有声明的服务才会被`Angular`框架创建出来,才能注入到其它服务或组件
MyService, ... // 用Angular CLI创建时会自动填入
],
bootstrap: [AppComponent] // 引导组件
})
export class AppModule { } // 模块类定义
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一个Angular应用中可以有多个模块,用Angular CLI可以很容易创建一个模块:

ng g module your-new-module-name
  • 1

Angular CLI创建TypeScript

创建普通类:

ng g class my-new-class
  • 1

创建接口:

ng g interface my-new-interface
  • 1

创建枚举:

ng g enum my-new-enum
  • 1

IDE

免费版本推荐使用微软的Visual Studio Code (VSCode), 很好用,但有条件一定要买付费的IDEJetBrainWebStorm。原因就跟Java IDE的选择一样,要免费的Eclipse,还是商业使用需付费的IntelliJ IDEA,看你或你的公司是不是土豪。

笔者是穷屌丝,使用VSCode,编写Angular代码,推荐安装下面的插件:

  1. Angular 5 Snippets:Angular开发必备,这里我使用的是Angular 5
  2. TSLint:TypeScript基于最佳实践的编程规范检测插件(类似于JavaCheckStyle),以红线实时提示你的不规范代码。
  3. TypeScript Importer:在编写TypeScript代码时,当你使用一个新的类,自动插入import语句。

Typescript类型系统非常好的地方是IDE的提示非常好,比如能提示出类有哪些方法、哪些属性,再加上语法和方法与Java太像,使用起来非常自然、亲切。

总结

读到这里,相信你已经有了Angular的开发环境,并且对一些Angular的概念有了初步理解,能够与服务端一起编译并部署到Web服务器上。

也许你会问我,有那么多Web前端框架,为什么偏爱Angular

选择Angular的原因

笔者作为一个Java背景的软件开发人员,选择Angular的原因有:

使用TypeScript编程

TypeScript,其实并不是一种新语言,她仍然还是JavaScript,是一种带有类型系统的、面向对象版本的JavaScript,是ES6/7/8+的超集:

  1. 语法类似Java/Kotlin,很多语言特性、关键字、类方法名都是一样的;
  2. 类型系统可以在编译期做检查,避免敲错字;
  3. 类型系统可以帮助IDE分析代码,代码跳转、引用分析、出错实时提醒等;
  4. 类型系统可以提高代码的可读性、可维护性、类型安全性,调用方法时可以避免传入不期望的类型;
  5. TypeScriptES6+的超集,是ES6规范的实现,在未来的浏览器很可能直接支持;
  6. TypeScript可以直接编译成ES5运行于旧浏览器,在现有版本的Angular是默认编译目标,无需引入任何编译依赖和配置;
  7. TypeScript与其编译出的ES5 javascript代码是一一对应的,很多时候就是类型擦除,无需额外库,不像Kotlin.js那么重。
  8. 拼爹时代,这点还是要考虑的:微软的TypeScript,加上谷歌的Angular,非常看好它的前景。
  9. JavaScript编写的库可以在TypeScript使用,缺乏类型声明部分,社区以提供大量第三方js库的类型定义,一般不需要自己做,当然,想自定义个类型也不难。

如果你是Java程序员,应该会赞成这句话:“TypeScript一点都不难,JavaScript才叫难!”。

当然如果你真的不习惯写类型,那不写也可以,因为TypeScriptES6+的超集,ES6+的代码在TypeScript完全合法,不过就没有了编译时类型检查,IDE也不会帮你跳转和引用分析,也不会做方法提示,你能接受缺乏类型安全机制的痛苦吗?我不能!

很像Spring Boot框架

Angular框架很像Spring Boot框架(都无需额外配置),有组件、服务、依赖注入、基于注解的配置,容器管理组件、服务的生命周期等特性;

基于rxjs

rxjs可以方便编写异步响应式代码、处理事件总线、大量可组合的操作符、等等(其实就是流行的RxJavaTypeScript版本)。

模块化

模块化对于构建大型复杂的Web系统来说尤为重要。

所以

对于JavaKotlin等静态强类型语言背景的开发人员来说,Angular学习门槛较低,因为很多特性、概念都太Java了。

而对于JavaScriptPython等动态语言背景的开发人员,入手ReactVue可能更容易,因为它们都是基于ES6的无类型系统。

——- 本博客所有内容均为原创,转载请注明作者和出处 ——-

作者:刘文哲

联系方式:liuwenzhe2008@qq.com

博客:http://blog.csdn.net/liuwenzhe2008

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liuwenzhe2008/article/details/79125406

(转载)从Java角度理解Angular之入门篇:npm, yarn, Angular CLI的更多相关文章

  1. 从Java角度理解Angular之入门篇:npm, yarn, Angular CLI

    本系列从Java程序员的角度,带大家理解前端Angular框架. 本文重点介绍Angular的开发.编译工具:npm, yarn, Angular CLI,它们就像Java在中的Maven,同时顺便介 ...

  2. Java中的IO流 - 入门篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的IO流-入门篇>,希望对大家有帮助,谢谢 由于Java的IO类有很多,这就导致我刚开始学的时候,感觉很乱,每次用到都是上网搜,结果 ...

  3. Java中的集合List - 入门篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的集合List - 入门篇>,希望对大家有帮助,谢谢 简介 说实话,Java中的集合有很多种,但是这里作为入门级别,先简单介绍第一种 ...

  4. Java中的映射Map - 入门篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的映射Map - 入门篇>,希望对大家有帮助,谢谢 简介 前面介绍了集合List,这里开始简单介绍下映射Map,相关类如下图所示 正 ...

  5. Java中的集合Set - 入门篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的集合Set - 入门篇>,希望对大家有帮助,谢谢 简介 前面介绍了集合List,映射Map,最后再简单介绍下集合Set,相关类如下 ...

  6. Java工程师学习指南(入门篇)

    Java工程师学习指南 入门篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我之前写的文章都 ...

  7. Angular快速入门篇

    简介 AngularJS 是一个为动态WEB应用设计的结构框架,提供给大家一种新的开发应用方式,这种方式可以让你扩展HTML的语法,以弥补在构建动态WEB应用时静态文本的不足,从而在web应用程序中使 ...

  8. mybatis对java自定义注解的使用——入门篇

    最近在学习spring和ibatis框架. 以前在天猫实习时做过的一个小项目用到的mybatis,在其使用过程中,不加思索的用了比较原始的一种持久化方式: 在一个包中写一个DAO的接口,在另一个包里面 ...

  9. 微信公众号开发java框架:wx4j(入门篇)

    导航 入门 http://www.cnblogs.com/2333/p/6617819.html WxServlet介绍 MaterialUtils 素材工具类使用说明 http://www.cnbl ...

随机推荐

  1. 集中化管理平台Saltstack安装配置

    salt是一个异构平台基础设置管理工具(虽然我们通常只用在Linux上),使用轻量级的通讯器ZMQ,用Python写成的批量管理工具,完全开源,遵守Apache2协议,与Puppet,Chef功能类似 ...

  2. Haskell语言学习笔记(20)IORef, STRef

    IORef 一个在IO monad中使用变量的类型. 函数 参数 功能 newIORef 值 新建带初值的引用 readIORef 引用 读取引用的值 writeIORef 引用和值 设置引用的值 m ...

  3. 趣味编程:FizzBuzz(Swift版)

    func toFizzBuzzExpr(n: Int) -> String { return n % 3 == 0 && n % 5 == 0 ? "FizzBuzz& ...

  4. scala-学习 1

    目录 变量定义 scala定义两种变量: var 可变 初始化之后,可以多次被重新赋值 val 不可变 一旦被初始化 就不能再赋值. var firstarg :java.lang.String = ...

  5. mysql 列转行

    第一种方法:使用序列化表的方法实现列转行 第一种方法:使用UNION的方法实现列转行 第二种方法:使用序列化表的方法实现列转行

  6. Numpy数据存取

    Numpy数据存取 numpy提供了便捷的内部文件存取,将数据存为np专用的npy(二进制格式)或npz(压缩打包格式)格式 npy格式以二进制存储数据的,在二进制文件第一行以文本形式保存了数据的元信 ...

  7. 87. Scramble String (String; DP)

    Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrin ...

  8. [leetcode]236. Lowest Common Ancestor of a Binary Tree 二叉树最低公共父节点

    Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According ...

  9. Python学习记录day7

    目录 Python学习记录day7 1. 面向过程 VS 面向对象 编程范式 2. 面向对象特性 3. 类的定义.构造函数和公有属性 4. 类的析构函数 5. 类的继承 6. 经典类vs新式类 7. ...

  10. 【深度好文】多线程之WaitHandle-->派生-》Mutex信号量构造

    bool flag = false; System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", ...