系统:Windows
浏览器:chrome
编写你的第一个Angular Web应用
一、准备环境
1.安装Node.js(使用Typescript)
Node.js官网
2.安装Cygwin
Cygwin官网
安装教程
3.安装Typescript、angular-cli
npm install -g typescript
npm install -g @angular/cli
之前安装过了,所以这里显示的是update(更新)
4.试运行
ng
以上,环境已经准备就绪。
二、示例项目
ng new angular2-hello-world
它会询问两个问题:
一个是:是否添加angular的路由,我选择yes
第二个:使用哪一种层叠样式,我选择了CSS
静静等它下载
使用tree指令可以查看目录结构
运行应用
在修改之前,先把这个自动生成的初始应用加载到浏览器中。
angular-cli有一个内建的HTTP服务器,可以用它来启动应用。
①回到根目录:angular2-hello-world
②输入指令:ng serve
③打开地址查看:
http://localhost:4200/
制作Component
Angular背后的指导思想之一就是组件化
组件化背后的基本思想:教浏览器认识一些拥有自定义功能的新标签。
利用angular-cli来创建新组件:使用generate(生成)命令。
eg:生成hello-world组件:
ng generate component hello-world
最基本的组件包括两部分:
(1)Component注解
(2)组件定义类
查看一下组件的代码:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
解析:
- 导入依赖
import语句定义了写代码时要用到的那些模块。
@angular/core:告诉程序到哪里查找所需的这些依赖。
import { Component, OnInit } from '@angular/core';
//'@angular/core'定义并导出了两个JavaScript/TypeScript对象,名字分别是Component和OnInit
OnInit:在组件初始化阶段运行某些代码
格式:
import{things} from wherever
{things}:解构写法
- Component注解
@Component({
selector: 'app-hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.css']
})
注解:让编译器为代码添加功能的途径之一。
添加到代码上的元数据。
当在HelloWorld类上使用@Component时,就把HelloWorld“装饰”(decorate)成了一个Component。
- 用templateUrl添加模板
templateUrl: './hello-world.component.html',
//将从该组件同目录的hello-world.component.html文件中加载模板。
hello-world.component.html
<p>hello-world works!</p>
- 添加template
两种定义模板的方式:
①使用@Component对象中的template属性
@Component({
selector:'app-hello-world',
template:`
<p>
hello-world works inline!
</p>
`
})
`......`是ES6中的新特性,允许使用多行字符串。
是否将模板放在代码文件中视情况而定。模板内容不多时可以。这样可以同时看到逻辑和视图部分,便于理解它们之间的如何互动。
②指定templateUrl属性
- 用styleUrls添加CSS样式
styleUrls: ['./hello-world.component.css']
//使用hello-world.component.css文件中的CSS作为该组件的样式。
Angular使用一项叫作样式封装(style-encapsulation)的技术,它意味着在特定组件中指定的样式只会应用于该组件本身。
接收一个数组型参数
- 加载组件
把组件的标签添加到一个将要渲染的模板中去。
打开app.component.html
将<app-hello-world>
标签加入
<h1>
{{title}}
<app-hello-world></app-hello-world>
</h1>
我加到末尾了
把数据添加到组件中
定义一个组件用来显示用户名字:
①创建组件
ng generate component user-item
②添加到app.component.html中
<h1>
{{title}}
<app-hello-world></app-hello-world>
<app-user-item></app-user-item>
</h1>
我们要显示的是用户名字,所以需要引入name并声明为组件的一个新属性。
③在UserItemComponent类上引入一个属性,来声明该组件有一个名为name的局部变量
打开user-item.component.ts,加入以下代码
export class UserItemComponent implements OnInit {
name: string; //声明name属性
constructor() {
this.name = 'Felipe'; //设置name属性
}
ngOnInit() {
}
}
name属性:添加到UserItemComponent类中。
格式:属性名(name): 属性的类型(string)
构造函数:constructor
这个函数会在创建UserItemComponent类实例时自动调用。
④渲染模板
用模板语法在模板中显示该变量的值
打开user-item.component.html
<p>Hello! {{name}}</p>
使用数组
对一组名字进行问好
①创建会渲染用户列表的新组件
ng generate component user-list
②修改app.component.html
<h1>
{{title}}
<app-hello-world></app-hello-world>
<app-user-list></app-user-list>
</h1>
③给UserItemComponent添加names属性
export class UserListComponent implements OnInit {
names: string[];
constructor() {
this.names = ['Ari','Carlos','Felipe','Nate'];
}
ngOnInit() {
}
}
④用*ngFor对列表进行迭代,为列表中的每一个条目生成一个新标签。
打开user-list.component.html
<ul>
<li *ngFor="let name of names">Hello! {{name}}</li>
</ul>
*ngFor="let name of names"
:
*ngFor表示使用NgFor指令。相当于遍历
let name of names:在数组names中逐个取出一个元素赋值给一个名为name的变量。
NgFor会重复渲染所在的元素。
使用UserItemComponent组件
不直接重复渲染li标签,而是让UserItemComponent来为列表中的每个条目指定模板(和功能)
三件事:
①配置UserListComponent来(在它的模板中)渲染UserItemComponent。
user-list.component.html:
<ul>
<app-user-item
*ngFor="let name of names">
</app-user-item>
</ul>
错误原因:没有把数据传给组件。
②配置UserItemComponent来接收name变量作为输入。
user-item.component.ts
import { Component,
OnInit,
Input //添加
} from '@angular/core';
@Component({
selector: 'app-user-item',
templateUrl: './user-item.component.html',
styleUrls: ['./user-item.component.css']
})
export class UserItemComponent implements OnInit {
@Input() name: string; //声明name属性,添加Input注释
constructor() {
//this.name = 'Felipe'; //设置name属性,注释掉,不需要设置
}
ngOnInit() {
}
}
修改了name属性,使其具有一个@Input注解。可以从父模板中传进来一个值。
③配置UserListComponent的模板来把用户名传给UserItemComponent
user-list.component.html:
<ul>
<app-user-item
*ngFor="let individualUserName of names"
[name]='individualUserName'>
</app-user-item>
</ul>
给标签添加新属性[name]=‘individualUserName’。
Angular中,添加一个带方括号的属性意味着把一个值传给该组件上同名的输入属性。
①在names中迭代
②为names中的每个元素创建一个新的UserItemComponent
③把当前名字的值传给UserItemComponent上名叫name的Input属性
“启动”速成班
angular-cli是基于webpack工具的。
当通过下面指令启动应用:
ng serve
ng会查阅angular-cli.json文件来找出该应用的入口点。
大体会有以下流程
- angular-cli.json指定一个“main”文件,这里是main.ts
- main.ts是应用的入口点,并且会引导(bootstrap)我们的应用
- 引导过程会引导一个Angular模块
- 使用AppModule来引导该应用,它是在src/app/app.module.ts中指定的
- AppModule指定了将哪个组件用作顶层组件,这里是AppComponent
- AppComponent的模块中有一个
<app-user-list>
标签,它会渲染出我们的用户列表
当引导一个Angular应用时,并不是直接引导一个组件,而是创建了一个NgModule,它指向了你要加载的组件。
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { UserItemComponent } from './user-item/user-item.component';
import { UserListComponent } from './user-list/user-list.component';
@NgModule({
declarations: [
AppComponent,
HelloWorldComponent,
UserItemComponent,
UserListComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
@NgModule注解,为AppModule类添加了元数据。
三个属性:
declarations:指定了在该模块中定义的组件。(使用ng generate时自动将生成的组件添加到此列表中)
思想:要想在模板中使用一个组件,你必须首先在NgModule中声明它。
imports:描述该模块有哪些依赖。
bootstrap:当使用该模块引导应用时,要把AppComponent加载为顶层组件。
扩展你的应用
①分析结构:两个组件。
②创建应用:ng new angular2-reddit
③添加CSS
下载源代码
复制first_app/angular2-reddit下以下文件:
- src/index.html
- src/styles.css
- src/app/vendor
- src/assets/images
④应用程序组件
创建新组件,它将:
(1)存储当前文章列表
(2)包含一个表单,用来提交新的文章
打开app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'angular2-reddit';
}
修改app.component.html,使其包含一个表单,用于添加链接。
会从semantic-ui包中借用一点样式来让这个表单美观。
<form class="ui large form segment">
<h3 class="ui header">Add a Link</h3>
<div class="field">
<label for="title">Title:</label>
<input name="title">
</div>
<div class="field">
<label for="link">Link:</label>
<input name="link">
</div>
</form>
⑤添加互动
添加一个提交按钮,当提交该表单时,调用一个函数来创建并添加一个链接。
<button (click)="addArticle()"
class="ui positive right floated button">
Submit link
</button>
事件名包裹在圆括号()中就可以告诉Angular:我们要响应这个事件。
addArticle(newtitle, newlink)这个函数,我们需要在AppComponent类中定义这个函数。
app.component.ts
export class AppComponent {
title = 'angular2-reddit';
addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
console.log(`Adding article title: ${title.value} and link: ${link.value}`);
return false;
}
}
${title.value}放在反引号字符串中,最终会被title.value的值替代。
修改app.component.html,给表单中的input元素添加一个特殊的语法来获取模板变量。
<form class="ui large form segment">
<h3 class="ui header">Add a Link</h3>
<div class="field">
<label for="title">Title:</label>
<input name="title" #newtitle> <!-- changed -->
</div>
<div class="field">
<label for="link">Link:</label>
<input name="link" #newlink> <!-- changed -->
</div>
<!-- added this button -->
<button (click)="addArticle(newtitle, newlink)"
class="ui positive right floated button">
Submit link
</button>
</form>
在input标签上使用了#(hash)来要求Angular把该元素赋值给一个局部变量。
此例中newtitle是一个DOM元素,类型是HTMLInputElement。用newtitle.value表达式来获取这个输入框的值。
⑥添加文章组件
新建一个组件,用来展示提交过的文章。
- ng generate component article
- 创建ArticleComponent的template
article.component.html
<div class="four wide column center aligned votes">
<div class="ui statistic">
<div class="value">
{{ votes }}
</div>
<div class="label">
Points
</div>
</div>
</div>
<div class="twelve wide column">
<a class="ui large header" href="{{ link }}">
{{ title }}
</a>
<ul class="ui big horizontal list voters">
<li class="item">
<a href (click)="voteUp()">
<i class="arrow up icon"></i>
upvote
</a>
</li>
<li class="item">
<a href (click)="voteDown()">
<i class="arrow down icon"></i>
downvote
</a>
</li>
</ul>
</div>
具体样式怎么做的不剖析。
{{ votes }}和{{ title }}来展示votes和title,来自ArticleComponent类中的votes和title属性。
- 创建ArticleComponent
article.component.ts
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css'],
host: {
class: 'row'
}
})
host: {
class: ‘row’
}
是为了让每个app-article都独占一行。
在Angular中,组件的宿主就是该组件所附着到的元素。
host选项告诉Angular,我们要在宿主元素(app-article标签)上设置class属性,使其具有row类。
host好处:我们可以在组件内部配置宿主元素,不必要求父视图中的页面脚本有相应属性。
- 创建组件定义类ArticleComponent
article.component.ts
export class ArticleComponent implements OnInit {
votes: number;
title: string;
link: string;
constructor() {
this.title = 'Angular 2';
this.link = 'http://angular.io';
this.votes = 10;
}
voteUp() {
this.votes += 1;
}
voteDown() {
this.votes -= 1;
}
ngOnInit() {
}
}
- 使用app-article组件
app.component.html
<div class="ui grid posts">
<app-article>
</app-article>
</div>
并未显示
原因:
AppComponent组件目前还不知道这个ArticleComponent组件。
解决方法:
把新的ArticleComponent组件引荐给AppComponent,即将ArticleComponent添加到NgModule的declarations列表中。
6. 引荐
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ArticleComponent } from './article/article.component';
@NgModule({
declarations: [
AppComponent,
ArticleComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
- 阻止冒泡导致页面刷新
解决方法:让click事件处理器返回false。
voteUp() {
this.votes += 1;
return false;
}
voteDown() {
this.votes -= 1;
return false;
}
- 渲染多行
将正在使用的数据结构从组件代码中隔离出来。
用一个数据结构表示单个文章。
创建article.model.ts
export class Article {
title: string;
link: string;
votes: number;
constructor(title: string, link: string, votes?: number) {
this.title = title;
this.link = link;
this.votes = votes || 0;
}
voteUp(): void {
this.votes += 1;
}
voteDown(): void {
this.votes -= 1;
}
domain(): string {
try {
const link: string = this.link.split('//')[1];
return link.split('/')[0];
} catch (err) {
return null;
}
}
}
普通类,不是Angular组件,在MVC模式中,被称为模型(model)
9. 修改article.component.ts
使用新的Article类
import { Article } from './article.model';
- 修改article.component.html,使其正确获取值
<div class="four wide column center aligned votes">
<div class="ui statistic">
<div class="value">
{{ article.votes }}
</div>
<div class="label">
Points
</div>
</div>
</div>
<div class="twelve wide column">
<a class="ui large header" href="{{ article.link }}">
{{ article.title }}
</a>
<div class="meta">({{ article.domain() }})</div>
<ul class="ui big horizontal list voters">
<li class="item">
<a href (click)="voteUp()">
<i class="arrow up icon"></i>
upvote
</a>
</li>
<li class="item">
<a href (click)="voteDown()">
<i class="arrow down icon"></i>
downvote
</a>
</li>
</ul>
</div>
迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.
12. 修改article.component.ts
export class ArticleComponent implements OnInit {
@Input() article: Article;
constructor(){
this.article = new Article(
'Angular',
'http://angular.io',
10);
}
voteUp(): boolean {
this.article.voteUp();
return false;
}
voteDown(): boolean {
this.article.voteDown();
return false;
}
ngOnInit() {
}
}
13. 存储多篇文章
修改app.component.ts,使其拥有一份文章集合。
import { Component } from '@angular/core';
import { Article } from './article/article.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
articles: Article[];
constructor() {
this.articles = [
new Article('Angular 2', 'http://angular.io', 3),
new Article('Fullstack', 'http://fullstack.io', 2),
new Article('Angular Homepage', 'http://angular.io', 1),
];
}
addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
console.log(`Adding article title: ${title.value} and link: ${link.value}`);
this.articles.push(new Article(title.value, link.value, 0));
title.value = '';
link.value = '';
return false;
}
sortedArticles(): Article[] {
return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
}
}
articles: Article[];
表示articles是Article数组,还可以使用泛式的写法Array<Article>
表示集合是有类型的,只能存放Article类型的对象。
- 使用inputs配置ArticleComponent
利用方括号传递变量值
<app-article [article]="myArticle"></app-article>
使用@input接收传入的值。
@Input() article: Article;
修改article.component.ts
export class ArticleComponent implements OnInit {
@Input() article: Article;
voteUp(): boolean {
this.article.voteUp();
return false;
}
voteDown(): boolean {
this.article.voteDown();
return false;
}
ngOnInit() {
}
}
- 渲染文章列表
使用NgFor指令在articles数组上进行迭代,并为其中的每一个渲染一份app-article
修改app.component.html
<div class="ui grid posts">
<app-article
*ngFor="let article of articles"
[article]="article">
</app-article>
</div>
可能遇到问题:
article.component.ts
使用新的Article类
import { Article } from './article.model';
写成
import { Article } from './article.model.ts';
报错:
import { Article } from ‘./article/article.model.ts’;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/app/article/article.component.ts:6:25 - error TS2691: An import path cannot end with a ‘.ts’ extension. Consider importing ‘./article.model’ instead.
16. 添加新文章
修改addArticle方法:
app.component.ts
addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
console.log(`Adding article title: ${title.value} and link: ${link.value}`);
this.articles.push(new Article(title.value, link.value, 0));
title.value = '';
link.value = '';
return false;
}
17. 基于分数重新排序
在AppComponent上创建一个新方法sortedArticles
app.component.ts
sortedArticles(): Article[] {
return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
}
在ngFor中,在sortedArticles()上迭代:
<div class="ui grid posts">
<app-article
*ngFor="let article of sortedArticles()"
[article]="article">
</app-article>
</div>