Angular 权威教程 | 第1章 第一个Angular We

作者: 东东少将 | 来源:发表于2017-05-18 10:35 被阅读937次
    Angular 权威教程

    仿制Reddit网站

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

    • 简单的应用将涵盖Angular中的大部分基本要素
      • 构建自定义组件;
      • 从表单中接收用户输入
      • 把对象列表渲染到视图中
      • 拦截用户的点击操作, 并据此作出反应
    完成后的应用

    起步

    TypeScript[1]

    我必须用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 angular-cli@1.0.0-beta.18
    
       //不带参数运行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 <command (Default: help)>
    
    • 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) 
       <!doctype html>
       <html>
       <head>
       <meta charset="utf-8">
       <title>Angular2HelloWorld</title>
       <base href="">
       <meta name="viewport" content="width=device-width, initial-scale=1">
       <link rel="icon" type="imagex-icon" href="favicon.ico">
       </head>
       <body>
       // 应用将会在app-root标签处进行渲染 
         文本Loading...是一个占位符,应用加载之前会显示它,也可以是加载动画 
       <app-root>Loading...</app-root>
       </body>
       </html>
    

    运行应用

    //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

    运行中的应用

    制作Component(组件)

    Angular背后的指导思想之一就是组件化<select><form><video>都是由浏览器的开发者预先定义好的,我们要教浏览器认识一些拥有自定义功能的新标签

    • 使用angular-cli 的 generate 指令创建新组建

       $ ng generate component hello-world
       installing component
       create srcapphello-world/hello-world.component.css
       create srcapphello-world/hello-world.component.html
       create srcapphello-world/hello-world.component.spec.ts
       create srcapphello-world/hello-world.component.ts
      
    • 定义新组建

    • Component注解

    • 组件定义类

    打开第一个TypeScript文件:srcapphello-world/hello-world.component.ts,接下来就会一步步讲解它。

    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() {
    }
    }
    

    TypeScript文件的后缀是.ts而不是.js。 问题在于浏览器
    并不知道该如何解释TypeScript文件。 为了解决这个问题, ng
    serve命令会自动把.ts文件编译为.js文件

    导入依赖

    • import 语句定义了依赖的模块

    • @angular/core 部分告诉程序到哪里查找所需的这些依赖

    • import 语句的结构:

      //从另一个模块中拉取这些依赖,并且让这些依赖在当前文件中可用
      import { things } from wherever
      

    Component注解

    导入依赖后, 声明该组件

    //注解其实是让编译器为代码添加功能的途径之一
    //可以把注解看作添加到代码上的元数据。 
    //当在HelloWorld类上使用@Component时,
    //就把HelloWorld“装饰”(decorate) 成了一个Component。
    @Component({
    //selector属性用来指出该组件将使用哪个DOM元素
    selector: 'app-hello-world',
    })
    

    用templateUrl添加模板

    @Component({
    //Angular加载该组件时,就会读取此文件的内容作为组件的模板
    templateUrl: './hello-world.component.html',
    })
    

    添加template

    @Component({
    selector: 'app-hello-world',
    //传入template选项来为@Component添加一个模板
    template: `
    <p>
    hello-world works inline!
    </p>
    `
    })
    
    • ` ... `反引号定义多行字符串,ES6中的一个新特性

    templateUrl 和 template 选择哪种写法?
    视情况而定,把代码和模板分开。对一些开发团队来说更容易,不过某些项目会增加成本,因为不得不在一大堆文件之间切换
    如果模板行数短于一页,更倾向于把模板和代码放在一起(也就是.ts文件中)。同时看到逻辑和视图部分,便于理解它们的互动。
    内联写法缺点:编辑器不支持对内部HTML字符串进行语法高亮

    用styleUrls添加CSS样式

    Angular使用一项叫作样式封装(styleencapsulation) 的技术,它意味着在特定组件中指定的样式只会应用于该组件本身。

    @Component({
    //引入 CSS 作为该组件的样式
    同一个组件可以加载多个样式表
    styleUrls: ['./hello-world.component.css']
    })
    

    加载组件

    把该组件的标签添加到一个将要渲染的模板中

    //<app-hello-world>标签添加到app.component.html中
    <h1>
    {{title}}
    <app-hello-world></app-hello-world>
    </h1>
    
    正常运行

    把数据添加到组件中

    • 创建新组建
       //显示用户的名字
       ng generate component user-item
    
    ```html
     //app-user-item标签添加到app.component.html中  
        <h1>
          {{title}}
          <app-hello-world></app-hello-world>
          <app-user-item></app-user-item>
        </h1>
    ```
    

    UserItemComponent显示一个指定用户的名字

    • name属性

      • 我们往 UserItemComponent 类添加了一个 name 属性
      • name指定类型是TypeScript中的特性,用来确保它的值必须是string
    • 构造函数

      • 这个函数会在创建这个类的实例时自动调用
      • 可以用模板语法[3]({{ }})在模板中显示该变量的值
      export class UserItemComponent implements OnInit {
      //name是我们想设置的属性名,而string是该属性的类型
          name: string; // <-- added name property
          constructor() {
             // 组件被创建时, 把name设置为'Felipe'
              this.name = 'Felipe'; // set the name
          }
          ngOnInit() {
          }
      }
      //useritem.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>
    
    • 修改 user-list.component.ts
    export class UserListComponent implements OnInit {
        //语法表示names的类型是string构成的数组
        //另一种写法是Array<string>。
        names: string[];
        constructor() {
            this.names = ['Ari', 'Carlos', 'Felipe', 'Nate'];
        } 
        ngOnInit() {
        }
    }
    
    • 修改 user-list.component.html
    <ul>
    //循环处理 names 中的每一个元素并将其逐个赋值给一个名叫 name 的局部变量
    //( name 是一个局部变量 可以更换 如foobar )
    <li *ngFor="let name of names">Hello {{ name }}</li>
    </ul>
    

    NgFor指令将为数组 names 中的每一个条目都渲染出一个 li 标签,并声明一个本地变量 name 来持有当前迭代的条目。然后这个新变量将被插值到 Hello {{ name }}代码片段里
    如果你想进一步探索,可以直接阅读Angular源代码来学习Angular核心团队是如何编写组件的

    使用UserItemComponent组件

    用UserItemComponent作为子组件,为列表中的每个条目指定模板

    • 配置 UserListComponent 来渲染 UserItemComponent
    • 配置 UserItemComponent 来接收 name 变量作为输入
    • 配置 UserListComponent 的模板来把用户名传给
      UserItemComponent

    渲染UserItemComponent

    //把li标签替换为app-user-item
    标签
    <ul>
        <app-user-item *ngFor="let name of names">
        </app-user-item>
    </ul>
    
    image.png

    接收输入

     //修改 UserItemComponent
     // 引入 Input
     import { 
         Component, 
         OnInit, 
         Input // <--- added this } from '@angular/core';
    @Component({
        selector: 'app-user-item',
        templateUrl: './user-item.component.html',
        styleUrls: ['./user-item.component.css']
    })
    export class UserItemComponent implements OnInit {
        //添加 @Input 注解
        @Input() name: string; // <-- added Input annotation
        constructor() {
          // 不希望有默认值
         // removed setting name
        } ngOnInit() {
        
        }
    }
    

    传入Input值

    **为了把一个值传入组件,就要在模板中使用方括号[]语法。 **

    // 修改 userlist.component.html
    <ul>
        <app-user-item *ngFor="let name of names" [name]="name">
        </app-user-item>
    </ul>
    

    添加一个带方括号的属性(比如[foo])意味着把一个值传给该组件上同名的输入属性(比如 foo)

    // name 右侧的值来自 ngFor 中的 let name ...语句
    <app-user-item *ngFor="let individualUserName of names" 
        [name]="individualUserName">
    </app-user-item>
    

    [name]部分指定的是 UserItemComponent 上的 Input。注意,我们正在传入的并不是字符串字面量"individualUserName", 而是individualUserName 变量的值, 也就是 names 中的每个元素。

    执行过程

    • 在 names 中迭代
    • 为 names 中的每个元素创建一个新的 UserItemComponent
    • 把当前名字的值传给 UserItemComponent 上名叫 name 的 Input属性

    启动速成班

    Angular应用是如何启动的?
    每个应用都有一个主入口点。该应用是由 angular-cli 构建的,而
    angular-cli 则是基于一个名叫 webpack 的工具。 你不必理解webpack 就能使用 Angular, 但理解应用的启动流程是很有帮助的。

    // 通过运行下列命令来启动
    // ng会查阅angular-cli.json文件来找出该应用的入口点
        ng serve
    

    大体流程

    • angular-cli.json指定一个"main"文件, 这里是main.ts;
    • main.ts 是应用的入口点, 并且会引导(bootstrap) 我们的应用;
    • 引导过程会引导一个Angular模块——我们尚未讨论过模块, 不过很快就会谈到;
    • 我们使用 AppModule 来引导该应用, 它是在srcappapp.module.ts中指定的;
    • AppModule 指定了将哪个组件用作顶层组件, 这里是 AppComponent;
    • AppComponent 的模板中有一个<app-user-list>标签, 它会渲染出我们的用户列表

    Angular有一个强大的概念: 模块。当引导一个 Angular 应用时,并不是直接引导一个组件, 而是创建了一个 NgModule,它指向了你要加载的组件。

    // 为 AppModule 类添加了元数据
    @NgModule({
        declarations: [
            AppComponent,
            HelloWorldComponent,
            UserItemComponent,
            UserListComponent
        ],
        imports: [
            BrowserModule,
            FormsModule,
            HttpModule
        ],
        providers: [],
        bootstrap: [AppComponent]
        })
    export class AppModule { }
    

    @NgModule注解有三个属性

    • declarations(声明):指定了在该模块中定义的组件。要想在模板中使用一个组件,你必须首先在NgModule中声明它
    • imports :描述了该模块有哪些依赖。我们正在创建一个浏览器应用,因此要导入BrowserModule
    • bootstrap 告诉Angular, 当使用该模块引导应用时, 我们要把AppComponent加载为顶层组件

    扩展你的应用

    应用的逻辑组件

    构造两个组件

    1. 整体应用程序,包含一个用来提交新文章的表单
    2. 每个文章
    // 创建一个新的应用
    ng new xxx( 应用名字 )
    

    添加CSS

    在本项目中,我们将使用 Semantic-UI 来帮助添加样式。Semantic-UI 是一个CSS框架, 类似于Zurb FoundationTwitterBootstrap

    完成版示例代码中复制以下文件到你的应用目录下:

    • src/index.html
    • src/styles.css
    • srcappvendor
    • src/assets/images

    应用程序组件

    构建一个新的组件

    • 存储我们的当前文章列表
    • 包含一个表单, 用来提交新的文章。
    // 修改 app.component.html
    <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>
    
    表单

    添加互动

    • 添加一个提交按钮

      //把事件的名字包裹在圆括号()中就可以告诉Angular: 我们要响应这个事件
      <button (click)="addArticle()"
      class="ui positive right floated button">
      Submit link
      </button>
      
    • 定义一个函数

      // 修改 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;
      }
      }
      
    • 修改模板

      <form class="ui large form segment">
          <h3 class="ui header">Add a Link</h3>
          <div class="field">
              <label for="title">Title:</label>
              // input标签上使用了#(hash)来要求Angular把该元素赋值给一个局部变量
              <input name="title" #newtitle> <!-- changed -->
          </div>
          <div class="field">
              <label for="link">Link:</label>
              <input name="link" #newlink> <!-- changed -->
          </div>
          // 通过把#title和#link添加到适当的<input/>元素上,就可以把它们作为变量传给按钮上的addArticle()函数
           <!-- added this button -->
          <button (click)="addArticle(newtitle, newlink)"
          class="ui positive right floated button">
          Submit link
          </button>
      </form>
      

    四项修改

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

    • 绑定input的值

      // 注意, 第一个输入标签是这样的:
      <input name="title" #newtitle>
      

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

    • 把事件绑定到动作

      • addArticle是组件定义类AppComponent里的一个函数。
        (2) newtitle来自名叫title的<input>标签上的解析
        (#newtitle) 。
        (3) newlink来自名叫link的<input>标签上的解析
        (#newlink)
    <button (click)="addArticle(newtitle, newlink)"
    class="ui positive right floated button">
    Submit link
    </button>
    
    • 定义操作逻辑
      • 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;
    }
    
    点击按钮

    添加文章组件

    生成一个新组件

    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 库
    <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>
    

    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的模板中
    <button (click)="addArticle(newtitle, newlink)" class="ui positive right floated button">
        Submit link
    </button>
    </form>
    <div class="ui grid posts">
        <apparticle>
        </apparticle>
    </div>
    

    在 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()`
    

    渲染多行

    创建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
    <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>
        <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>
    

    当前的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<Article>
    // 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
    </button>
    </form>
    <!-- start adding here -->
    <div class="ui grid posts">
        <apparticle *ngFor="let article of articles" [article]="article">
        </apparticle>
    </div>
    <!-- end adding here -->
    
    最终效果

    添加新文章

    // 修改按钮 思路:
    // 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的模板
    <div class="twelve wide column">
        <a class="ui large header" href="{{ article.link }}">
           {{ article.title }}
        </a>
    <!-- right here -->
    <div class="meta">({{ article.domain() }})</div>
    <ul class="ui big horizontal list voters">
        <li class="item">
            <a href (click)="voteUp()">
    

    基于分数重新排序

    //AppComponent上创建一个新方法 sortedArticles
    sortedArticles(): Article[] {
        return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
    }
    
    // app.component.html 
    <div class="ui grid posts">
        <apparticle *ngFor="let article of sortedArticles()" 
            [article]="article">
        </apparticle>
    </div>
    

    全部代码

    总结

    Angular程序的写法

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

    (第一章完结)

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

    自己练习效果
    1. TypeScript 是 JavaScript ES6 版的一个超集, 增加了类型支持。

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

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

    相关文章

      网友评论

      本文标题:Angular 权威教程 | 第1章 第一个Angular We

      本文链接:https://www.haomeiwen.com/subject/hnfsxxtx.html