Angular 组件的应用程序结构

概念化Angular应用程序设计的一个有用方法是将它看作一个嵌套组件的树,每个组件都有一个独立的作用域。例如,请考虑以下内容:

<rio-todo-app>
  <rio-todo-list>
    <rio-todo-item></rio-todo-item>
    <rio-todo-item></rio-todo-item>
    <rio-todo-item></rio-todo-item>
  </rio-todo-list>
  <rio-todo-form></rio-todo-form>
</rio-todo-app>

根节点rio-todo-app,包括一个rio-todo-list和一个rio-todo-form。 在列表中,我们有几个rio-todo-item。 这些组件中的每一个对于用户是可见的,他们可以与这些组件交互并且执行动作。

将数据传递到组件

有两种方式将数据传递到组件:“属性绑定”和“事件绑定”。 在Angular 2中,数据和事件变化检测从上到下发生从父级到子级。 然而对于Angular 2事件,我们可以使用DOM事件传递模型,其中事件从下到上从子到父。 因此,当涉及可撤消事件传播时,Angular 2事件可以像普通HTML DOM事件一样对待。

@Input()装饰器定义了一组可以从父组件传递的参数。例如,我们可以修改HelloComponent组件,以便name可以由父提供。

import { Component, Input } from '@angular/core';
@Component({
  selector: 'rio-hello',
  template: '<p>Hello, {{name}}!</p>',
})
export class HelloComponent {
  @Input() name: string;
}

制作组件的要点不仅是封装,而且是可重用性。@Input()允许我们配置组件的特定实例。

我们现在可以使用我们的组件

<!-- 绑定到原始字符串 -->
<rio-hello name="World"></rio-hello>
<!-- 绑定到父作用域 -->
<rio-hello [name]="helloName"></rio-hello>

查看示例

与Angular 1.x不同,这是单向绑定。

响应组件事件

Angular 2中的事件与它们在Angular 1.x中的工作类似。最大的变化是模板语法。

import {Component} from '@angular/core';
@Component({
  selector: 'rio-counter',
  template: `
    <div>
      <p>Count: {{num}}</p>
      <button (click)="increment()">Increment</button>
    </div>
  `
})
export class CounterComponent {
  num = 0;
  increment() {
    this.num++;
  }
}

View Example

要通过 outputs 从组件发送数据,请先定义outputs属性。它接受组件向其父组件公开的输出参数的列表。

app/counter.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
  selector: 'rio-counter',
  templateUrl: 'app/counter.component.html'
})
export class CounterComponent {
  @Input()  count = 0;
  @Output() result = new EventEmitter<number>();
  increment() {
    this.count++;
    this.result.emit(this.count);
  }
}

app/counter.component.html

<div>
  <p>Count: {{ count }}</p>
  <button (click)="increment()">Increment</button>
</div>

app/app.component.ts

import { Component, OnChange } from '@angular/core';
@Component({
  selector: 'rio-app',
  templateUrl: 'app/app.component.html'
})
export class AppComponent implements OnChange {
  num = 0;
  parentCount = 0;
  ngOnChange(val: number) {
    this.parentCount = val;
  }
}

app/app.component.html

<div>
  Parent Num: {{ num }}<br>
  Parent Count: {{ parentCount }}
  <rio-counter [count]="num" (result)="ngOnChange($event)">
  </rio-counter>
</div>

View Example

一组 input + output 绑定定义组件的公共API。在我们的模板中,我们使用 [方括号] 传递输入,使用(括号)来处理输出。

使用双向数据绑定

双向数据绑定使用ngModel指令将输入和输出绑定组合为单个符号。

<input [(ngModel)]="name" >

它幕后做的相当于:

<input [ngModel]="name" (ngModelChange)="name=$event">

要创建一个支持双向绑定的组件,你必须定义一个@Output属性匹配@Input,但后缀为Change,例如:

app/counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
  selector: 'rio-counter',
  templateUrl: 'app/counter.component.html'
})
export class CounterComponent {
  @Input() count = 0;
  @Output() countChange = EventEmitter<number>();
  increment() {
    this.count++;
    this.countChange.emit(this.count);
  }
}

app/counter.component.html

<div>
  <p>
    <ng-content></ng-content>
    Count: {{ count }} -
    <button (click)="increment()">Increment</button>
  </p>
</div>

View Example

从模板访问子组件

在我们的模板中,我们可能发现自己需要访问由我们用来构建自己的组件的子组件提供的值。

最直接的例子可能是处理表单或输入:

app/app.component.html

<section>
  <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
    <label for="name">Name</label>
    <input type="text" name="name" id="name" ngModel>
    <button type="submit">Submit</button>
  </form>
  Form Value: {{formValue}}
</section>

app/app.component.ts

import { Component } from '@angular/core';
@Component({
  selector: 'rio-app',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  formValue = JSON.stringify({});
  onSubmit (form: NgForm) {
    this.formValue = JSON.stringify(form.value);
  }
}

View Example

这不是一个只有表单或输入的神奇功能,而是一种引用模板中子组件实例的方法。使用该引用,您可以访问该组件的公共属性和方法。

app/app.component.html

<rio-profile #profile></rio-profile>
My name is {{ profile.name }}

app/profile.component.ts

@Component({
  selector: 'rio-profile',
  templateUrl: 'app/profile.component.html'
})
export class ProfileComponent {
  name = 'John Doe';
}

View Example

还有其他访问和连接子组件的方法,但是如果你只需要引用一个孩子的属性或方法,这可以是一个简单和直接的方法。