Angular 权威教程 | 第1章 第一个Angular Web应用

Angular 权威教程 | 第1章 第一个Angular Web应用_第1张图片
Angular 权威教程

仿制Reddit网站

读完本章之后, 你将掌握如何构建基本的Angular应用。

  • 简单的应用将涵盖Angular中的大部分基本要素
    • 构建自定义组件;
    • 从表单中接收用户输入
    • 把对象列表渲染到视图中
    • 拦截用户的点击操作, 并据此作出反应
Angular 权威教程 | 第1章 第一个Angular Web应用_第2张图片
完成后的应用

起步

TypeScript[1]

  • 使用TypeScript, 首先需要安装Node.js

我必须用TypeScript吗? 并非如此! 要使用 Angular,TypeScript 并不是必需的, 但它可能是最好的选择。 Angular也有一套 ES5 API, 但Angular本身就是用 TypeScript 写成的, 所以人们一般也会选用它。 本书也将使用TypeScript, 因为它确实很棒, 能让 Angular 写起来更简单。 当然, 并不是非它不可

  • 使用NPM[2]安装TypeScript
   $ npm install -g typescript

angular-cli

Angular提供了一个命令行工具angular-cli, 它能让用户通过命令行创建和管理项目。在本章中, 我们就用它来创建第一个应用

  • 安装angular-cli
   //安装完毕之后, 你就可以在命令行中用ng命令运行它了
   $ npm install -g [email protected]
   //不带参数运行ng命令时, 它就会执行默认的help命令。 help命令会解释如何使用本工具
$ ng
Could not start watchman; falling back to NodeWatcher for file system events.
Visit http://ember-cli.com/user-guide/#watchman for more info.
Usage: ng 
  • OS X 用户
    * 安装Homebrew工具 通过 Homebrew 工具来安装 watchman
    * 安装 watchman 的工具,帮助 angular-cli 监听文件系统的变化

      ```javascript  
     // 安装Homebrew工具后 可使用此命令
     $ brew install watchman
     ```
    
  • Linux 用户:学习如何安装watchman

  • Windows 用户:不必安装任何东西, angular-cli将使用原生的 Node.js 文件监视器

示例项目

  • 运行ng new命令(最新版好像不能使用‘_’和‘angular’关键字,会提示警告)
   $ ng new angular2_hello_world //练习时命名 helloWorld 可以通过
   //运行之后输出:
   installing ng 2
   create .editorconfig
   create README.md
   create srcappapp.component.css
   create srcappapp.component.html
   create srcappapp.component.spec.ts
   create srcappapp.component.ts
   create srcappapp.module.ts
   create srcappindex.ts
   create srcappshared/index.ts
   create src/assets/.gitkeep
   create src/assets/.npmignore
   create src/environments/environment.dev.ts
   create src/environments/environment.prod.ts
   create src/environments/environment.ts
   create src/favicon.ico
   create src/index.html
   create src/main.ts
   create src/polyfills.ts
   create src/styles.css
   create src/test.ts
   create src/tsconfig.jsoncreate src/typings.d.ts
   create angular-cli.json
   create e2e/app.e2e-spec.ts
   create e2e/app.po.ts
   create e2e/tsconfig.json
   create .gitignore
   create karma.conf.js
   create package.json
   create protractor.conf.js
   create tslint.json
   Successfully initialized git.
   ( Installing packages for tooling via npm
   ```
   * **npm依赖的安装** (会自动安装)
   
   ```bash
   //提示这行代码表示安装依赖完成
   Installed packages for tooling via npm.
   ```
   * **进入angular2_hello_world目录**
   
   ```java
       $ cd angular2_hello_world
       $ tree -F -L 1
       .
       ├──README.md // an useful README
       ├──angular-cli.json // angular-cli configuration file
       ├──e2e/ // end to end tests
       ├──karma.conf.js // unit test configuration
       ├──node_modules/ // installed dependencies
       ├──package.json // npm configuration
       ├──protractor.conf.js // e2e test configuration
       ├──src/ // application source
       └──tslint.json // linter config file
       3 directories, 6 files
   ```
* **进入 src 目录 查看应用代码**
   
   ```bash
   $ cd src
   $ tree -F
   .|-- app/
   | |-- app.component.css
   | |-- app.component.html
   | |-- app.component.spec.ts
   | |-- app.component.ts
   | |-- app.module.ts
   | |-- index.ts
   | `-- shared/
   | `-- index.ts
   |-- assets/
   |-- environments/
   | |-- environment.dev.ts
   | |-- environment.prod.ts
   | `-- environment.ts
   |-- favicon.ico
   |-- index.html
   |-- main.ts
   |-- polyfills.ts
   |-- styles.css
   |-- test.ts
   |-- tsconfig.json
   `-- typings.d.ts
   4 directories, 18 files
   ```
* **编辑器打开index.html**
   
   ```html
   //声明了页面的字符集(charset) 、 标题(title) 和基础URL(base href) 
   
   
   
   
   Angular2HelloWorld
   
   
   
   
   
   // 应用将会在app-root标签处进行渲染 
     文本Loading...是一个占位符,应用加载之前会显示它,也可以是加载动画 
   Loading...
   
   

运行应用

//angular-cli有一个内建的HTTP服务器,根目录运行命令
$ ng serve
** NG Live Development Server is running on http://localhost:4200. **
// a bunch of debug messages
Build successful - 1342ms.

我们的应用正在localhost的4200端口上运行。 打开浏览器并访问 http://localhost:4200

Angular 权威教程 | 第1章 第一个Angular Web应用_第3张图片
运行中的应用

制作Component(组件)

Angular背后的指导思想之一就是组件化

Angular 权威教程 | 第1章 第一个Angular Web应用_第8张图片
表单

添加互动

  • 添加一个提交按钮

    //把事件的名字包裹在圆括号()中就可以告诉Angular: 我们要响应这个事件
    
    
  • 定义一个函数

    // 修改 app.component.ts
    export class AppComponent {
      addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
    console.log(`Adding article title: ${title.value} and link:${link.value}`);
    return false;
    }
    }
    
  • 修改模板

    Add a Link

    // input标签上使用了#(hash)来要求Angular把该元素赋值给一个局部变量
    // 通过把#title和#link添加到适当的元素上,就可以把它们作为变量传给按钮上的addArticle()函数

四项修改

  1. 在模版中创建了一个 button 标签, 向用户表明应该点击哪里
  2. 新建了一个名叫 addArticle 的函数, 来定义按钮被点击时要做的事情
  3. 在 button 上添加了一个(click)属性, 意思是“只要点击了这个按钮, 就运行 addArticle 函数”
  4. 在两个 标签上分别添加了#newtitle 和 #newlink 属性

  • 绑定input的值

    // 注意, 第一个输入标签是这样的:
    
    

    Angular把这个绑定到变量 newtitle 上。 #newtitle 语法被称作一个解析(resolve),其效果是让变量 newtitle 可用于该视图的所有表达式中 。newtitle 现在是一个对象, 它代表了这个inputDOM元素(更确切地说,它的类型是 HTMLInputElement )。由于newtitle是一个对象,我们可以通过newtitle.value表达式来获取这个输入框的值

  • 把事件绑定到动作

    • addArticle是组件定义类AppComponent里的一个函数。
      (2) newtitle来自名叫title的标签上的解析
      (#newtitle) 。
      (3) newlink来自名叫link的标签上的解析
      (#newlink)

  • 定义操作逻辑
    • title和link 都是 HTMLInputElement 类型的对象
    • 从 input 中获取值, 就得调用title.value
//${title.value}放在了字符串中, 它最终会被
替换成title.value的值
addArticle(title: HTMLInputElement, link: HTMLInputElement): 
boolean {
console.log(`Adding article title: ${title.value} and link: ${link.value}`);
return false;
}
Angular 权威教程 | 第1章 第一个Angular Web应用_第9张图片
点击按钮

添加文章组件

生成一个新组件

  1. 在模板中定义了 ArticleComponent 的视图
  2. 通过为类加上 @Component 注解定义了 ArticleComponent 组件的元数据
  3. 定义了一个组件定义类(ArticleComponent) , 其中是组件本身的逻辑
一篇文章
ng generate component article
  • 创建 ArticleComponent 的 template
// 修改 article.component.html
// 左侧是投票的数量
// four wide column 和 twelve wide column 这两个 CSS 类
//来指定这两列。它们来自 Semantic UI 的 CSS 库
{{ votes }}
Points
// 右侧是文章的信息

a 标签的 href 属性中:href="{{ link }}"。在这种情况下,href 的值会根据组件类的 link 属性的值进行动态插值计算得出

  • 创建ArticleComponent
// 修改 article.component.ts
@Component({
    selector: 'apparticle',
    templateUrl: './article.component.html',
    styleUrls: ['./article.component.css'],
    host: {
        //apparticle都独占一行 Semantic UI 用来表示行的CSS类 
        class: 'row'
    }
})
  • 创建组件定义类ArticleComponent
// 创建了以下三个属性
// 1. votes: 一个数字,用来表示所有“赞”减去所有“踩”的数量之和。
// 2. title: 一个字符串, 用来存放文章的标题。
// 3. link: 一个字符串, 用来存放文章的URL
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() {
    }
}
  • 使用apparticle组件
// AppComponent的模板中


在 AngularJS 中,指令的匹配是全局的;而Angular中,你需要明确指定要使用哪个组件,意味着我们不必被迫在全局命名空间中共享这些指令选择器。

// app.module.ts
import { AppComponent } from './app.component';
import { ArticleComponent } from './article/article.component.ts';
@NgModule({
    declarations: [
    AppComponent,
    ArticleComponent // <-- added this
],

默认情况下, JavaScript会把click事件冒泡到所有父级组件中。因为click事件被冒泡到了父级,浏览器就会尝试导航到这个空白链接,于是浏览器就重新刷新了。
解决:我们得让click的事件处理器返回false。这能确保浏览器不会尝试刷新页面。

voteDown(): boolean {
    this.votes -= 1;
    return false;
}
// and similarly with `voteUp()`
Angular 权威教程 | 第1章 第一个Angular Web应用_第10张图片

渲染多行

创建Article类

// 此目录下创建文件 article/article.model.ts
// 在MVC模式中, 它被称为模型(model) 
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;
    }
}
// article.component.ts
import { Article } from './article.model';
export class ArticleComponent implements OnInit {
    article: Article;
    constructor() {
        this.article = new Article(
        'Angular 2',
        'http://angular.io',
        10);
    } 
    voteUp(): boolean {
        this.article.votes += 1;
        return false;
    }
    voteDown(): boolean {
        this.article.votes -= 1
        return false;
    } 
    ngOnInit() {
    }
}
// 视图模型 article.component.html
{{ article.votes }}
Points

当前的voteUp和voteDown违反了迪米特法则。迪米特法则是指:一个对象对其他对象的结构或属性所作的假设应该越少越好。问题在于ArticleComponent组件了解太多Article类的内部知识了

// 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;
    }
    }
}
// article.component.ts
export class ArticleComponent implements OnInit {
    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() {
    }
}

为什么模型和组件中都有一个voteUp函数?
这两个函数所做的事情略有不同。ArticleComponent 上的 voteUp() 函数是与组件的视图有关的,而 Article 模型上的 voteUp() 定义了模型上的变化。
我们把大量逻辑移出组件,放进了模型中。与此对应的MVC指南应该是胖模型,皮包骨的控制器;其核心思想是,我们要把大部分领域逻辑移到模型中,以便让组件只做尽可能少的工作。

存储多篇文章

// 让 AppComponent 拥有一份文章集合
// 引入模型
import { Article } from './article/article.model';

export class AppComponent {
//articles 是 Article 的数组。另一种写法是 Array
// Array 是一个集合,它只能存放 Article 类型的对象 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; } }

使用inputs配置ArticleComponent

有了一个Article模型的列表, 该怎么把它们传给ArticleComponent组件呢?
这里我们又用到了 Input。以前 ArticleComponent 类的定义是下面这样的

// article.component.ts
export class ArticleComponent implements OnInit {
    article: Article;
    constructor() {
        //构造函数中硬编码了一个特定的Article; 而制作组件时, 不但要能封装, 还要能复用
        this.article = new Article(
        'Angular 2',
        'http://angular.io',
        10);
}
// 修改 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() {
    }
}

渲染文章列表

// 修改 AppComponent 模板
// 1. articles是一个Article的数组, 由AppComponent组件定义
// 2. foobar是一个articles数组中的单个元素(一个Article对象)由NgFor定义
// 3. article是一个字段名, 由ArticleComponent中的inputs属性定义。
Submit link



Angular 权威教程 | 第1章 第一个Angular Web应用_第11张图片
最终效果

添加新文章

// 修改按钮 思路:
// 1. 创建一个具有所提交标题和URL的Article新实例
// 2. 把它加入Article数组;
// 3. 清除input字段的值
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));
    //修改value属性时, 页面中的input标签也会跟着
    改变
    title.value = '';
    link.value = '';
    return false;
}

最后的修整

显示文章所属的域名

// article.model.ts
domain(): string {
    try {
        // 注意:URL必须包含http://
        const link: string = this.link.split('//')[1];
        return link.split('/')[0];
    } catch (err) {
        return null;
    }
}
// ArticleComponent的模板
{{ article.title }}
({{ article.domain() }})

基于分数重新排序

//AppComponent上创建一个新方法 sortedArticles
sortedArticles(): Article[] {
    return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
}

// app.component.html 

全部代码

总结

Angular程序的写法

  1. 把应用拆分成组件
  2. 创建视图
  3. 定义模型
  4. 显示模型
  5. 添加交互。

(第一章完结)

自己练习的代码,希望对你有用

自己练习效果


  1. TypeScript 是 JavaScript ES6 版的一个超集, 增加了类型支持。 ↩

  2. npm是Node.js的一部分。 如果你的系统中没有npm命令, 请确认你安装的Node.js是包含它的版本 ↩

  3. 模板标签中间的任何东西都会被当作一个表达式来展开 ↩

你可能感兴趣的:(Angular 权威教程 | 第1章 第一个Angular Web应用)