Angular 应用,由组件构成,一颗由组件构成的树。由于组件是以树形结构组织起来的,当每个组件被渲染时,都会递归地渲染下级组件
每个组件:
- 组件注解(@Component)
- selector(选择器):告诉Angular要匹配哪个HTML元素
- template(模板):用来定义视图
- 视图
- 控制器
输入输出
组件注解
Angular核心特性:输入/输出
- 方括号
[]
:传递输入。数据通过输入绑定流入你的组件 - 圆括号
()
:处理输出。事件通过输出绑定流出你的组件
@Component({
selector: 'inventory-app',
template: `
<div class="inventory-app">
<products-list
[productList]="products"
(onProductSelected)="productWasSelected($event)">
</products-list>
</div>
`
})
-
在
products-list
组件中,设置名为productList
的输入。products
:希望将输入设置为products
表达式的值,即 InventoryApp 类中的this.products
-
(onProductSelected)
:我们要监听的输出的名称productWasSelected($event)
:当有新的输入时我们想要调用的方法$event
在此处是一个特殊的变量,用来表示输出的内容
productList
、onProductSelected
都是 products-list
组件中的变量
products
、 productWasSelected
则是当前组件的变量\函数
产品组件列表
组件的输入
可通过 inputs
配置项来指定组件希望接收哪些参数
@Input() name:string
组件的输出
要从组件中把数据传递出去,应使用 输出绑定,写法是: (output)="action"
在视图中,可以使用 (output)="action"
语法来监听事件
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
{{value}}
<button (click)="increase()">Increase</button>
<button (click)="decrease()">Decrease</button>
`,
styleUrls: ['./counter.component.css']
})
export class CounterComponent implements OnInit {
value: number;
constructor() {
this.value = 1;
}
ngOnInit() {
}
increase() {
this.value++;
return false;
}
decrease() {
this.value--;
return false;
}
}
(click)
、mousedown
等是按钮内置事件,也可触发自定义事件
EventEmitter 事件触发器
EventEmitter
只是一个帮你实现观察者模式的对象,是一个管理一系列订阅者并向其发布事件的对象
let ee = new EventEmitter();
ee.subscribe((name:string) => console.log(`Hello ${name}`));
ee.emit("Nate");
// --> "Hello Nate"
当把一个EventEmitter
赋值给一个输出的时候,Angular会自动帮我们订阅事件
Demo:
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'single-component',
styleUrls: ['./single-component.component.css'],
template: `
<button (click)="liked()">Like it?</button>
`
})
export class SingleComponentComponent implements OnInit {
@Output() putRingOnIt: EventEmitter<string>;
constructor() {
this.putRingOnIt = new EventEmitter();
}
ngOnInit() {
}
liked():void {
this.putRingOnIt.emit("ooo");
}
}
如果希望在一个父级组件中使用这个输出:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-club',
// templateUrl: './club.component.html',
styleUrls: ['./club.component.css'],
template: `
<div>
<single-component
(putRingOnIt)="ringWasReplaces($event)"
></single-component>
</div>
`
})
export class ClubComponent implements OnInit {
constructor() { }
ngOnInit() {
}
ringWasReplaces(message: string) {
console.log(`Put your hands up: ${message}`);
}
}
inventory-app记录
1. 新建项目,及前期准备
ng new inventory-app
将resource
中的images
文件夹复制进/assets
修改 index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ng-book 2: Inventory App</title>
<!-- 注意这一行,不加会报错 -->
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<!-- Menu Bar -->
<div class="ui menu">
<div class="ui container">
<a href="#" class="header item">
<img class="logo" src="/assets/images/ng-book-2-minibook.png">
ng-book 2
</a>
<div class="header item borderless">
<h1 class="ui header">
Angular 2 Inventory App
</h1>
</div>
</div>
</div>
<div class="ui main text container">
<app-root>Loading </app-root> <!-- <--- Our app loads here! -->
</div>
</body>
</html>
<base href="/">
修改全局css,并将 vendor
文件夹放到 ./app/vendor
目录下
style.css
@import './app/vendor/semantic.min.css';
.ui.menu .item img.logo {
margin-right: 1.5em;
}
.ui.items>.item {
border: 1px solid rgba(255, 255, 255, 0.0);
border-radius: 5px;
}
.ui.items>.item.selected {
border: 1px solid rgba(163, 51, 200, 0.41);
border-radius: 5px;
}
.ui.items>.item>.content>.header {
padding-top: 1em;
}
.products-list {
border-top: 1px solid black;
border-left: 1px solid black;
border-right: 1px solid black;
}
.product-row {
border-bottom: 1px solid black;
}
.product-image {
float: left;
}
.product-info {
padding: 30px;
float: left;
}
.price-display {
padding: 30px;
float: right;
}
.product-image {
width: 100px;
height: 100px;
padding: 20px;
}
.cf:before,
.cf:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.cf:after {
clear: both;
}
cf {
*zoom: 1;
}
2. 新建类 product.model
ng g class product.model
src/app/product.model.ts
export class Product {
constructor(
public sku:string,
public name:string,
public imageUrl:string,
public department:string[],
public price:number
){}
}
3. 修改app.component.ts
VSCode下格式化代码快捷键:alt+shift+F
import { Component } from '@angular/core';
import { Product } from './product.model';
@Component({
selector: 'app-root',
// templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
template: `
<div class="inventory-app">
<product-list [productList]="products"
(onProductSelected)="productWasSelected($event)">
</product-list>
</div>
`
})
export class AppComponent {
products: Product[];
constructor() {
this.products = [
new Product(
'MYSHOES',
'Black Running Shoes',
'../assets/images/products/black-shoes.jpg',
['Men', 'Shoes', 'Running Shoes'],
109.99),
new Product(
'NEATOJACKET',
'Blue Jacket',
'../assets/images/products/blue-jacket.jpg',
['Women', 'Apparel', 'Jackets & Vests'],
238.99),
new Product(
'NICEHAT',
'A Nice Black Hat',
'../assets/images/products/black-hat.jpg',
['Men', 'Accessories', 'Hats'],
29.99)
];
}
productWasSelected(product:Product){
console.log('Product clicked:',product);
}
}
4. 创建组件product-list.component.ts
用 ng g component
创建组件的好处是,会自动会在app.module.ts中import新创建的组件,并在declarations中引入
要使用在Angular中创建的新组建,它们必须对于当前模块是可访问的,也即若要在 InventoryApp 的template中通过 product-list 标签使用 ProductList 组件的话,就要保证 InventoryApp 满足以下两个条件之一:
- 和 ProductList 组件在同一模块中
- InventoryApp 所在的模块导入(import)了 ProductList 所在的模块
在这个例子里,我们将所有的组件都放在了同一模块里,它们彼此之间都是“可见”的
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Product } from '../product.model';
@Component({
selector: 'product-list',
// templateUrl: './product-list.component.html',
template: `<div class="ui items">
<product-row
*ngFor="let myProduct of productList"
[product]="myProduct"
(click)="clicked(myProduct)"
[class.selected]="isSelected(myProduct)">
</product-row>
</div>`,
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
/*
如果Input要使用别名可以这样定义 @Input('shortName') name:string;
则父组件使用 <parent-component [shortName]="myName"></parent-component>
*/
@Input() productList: Product[];
@Output() onProductSelected: EventEmitter<Product>;
private currentProduct: Product;
constructor() {
this.onProductSelected = new EventEmitter();
}
ngOnInit() {
}
clicked(product: Product) {
this.currentProduct = product;
this.onProductSelected.emit(product);
}
isSelected(product:Product){
if(!product || !this.currentProduct) return false;
return product.sku===this.currentProduct.sku;
}
}
[class.selected]="isSelected(myProduct)"
5、创建组件product-row.component.ts
import { Component, OnInit, Input, HostBinding } from '@angular/core';
import { Product } from '../product.model';
@Component({
selector: 'product-row',
// templateUrl: './product-row.component.html',
styleUrls: ['./product-row.component.css'],
template:`
<product-image [productImg]="product"></product-image>
<div class="content">
<div class="header">{{ product.name }}</div>
<div class="meta">
<div>SKU #{{ product.sku }}</div>
</div>
<div class="discription">
<product-department [productDep]="product"></product-department>
</div>
</div>
<price-display [price]="product.price"></price-display>
`
})
export class ProductRowComponent implements OnInit {
@Input() product: Product;
@HostBinding('attr.class') cssClass="item";
constructor() { }
ngOnInit() {
}
}
@HostBinding('attr.class') cssClass="item";
6、创建 组件product-image.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { Product } from '../product.model';
@Component({
selector: 'product-image',
// templateUrl: './product-image.component.html',
styleUrls: ['./product-image.component.css'],
template: `
<img class="product-image" [src]="productImg.imageUrl">
`
})
export class ProductImageComponent implements OnInit {
@Input() productImg: Product;
constructor() { }
ngOnInit() {
}
}
图片处,本可以这样写:
<img src="{{productImg.imageUrl}}">
但这样写是错的
为什么呢?因为如果浏览器在Angular运行起来之前就加载了这段模板,那么会尝试以字符串{{productImg.imageUrl}}
为url来加载图片,会得到 “404 not found” 的错误,Angular 运行起来之前,浏览器会在页面上显示一个破损的图像
通过 [src]
元素属性,告诉 Angular 我们希望使用 img
标签的 [src]
输入,一旦表达式解析完成,Angular 就会把 src
元素属性替换为表达式的值
7、创建组件 price-display.component.ts
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'price-display',
// templateUrl: './price-display.component.html',
styleUrls: ['./price-display.component.css'],
template:`
<!-- 在模板字符串中,$属于模板变量的特殊语法,在模板中出现$写法时注意要转义 -->
<div class="price-display">\${{price}}</div>
`
})
export class PriceDisplayComponent implements OnInit {
@Input() price;
constructor() { }
ngOnInit() {
}
}
8、创建组件product-department.component.ts
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'product-department',
// templateUrl: './product-department.component.html',
styleUrls: ['./product-department.component.css'],
template: `
<div class="product-department">
<!-- ngFor 迭代 productDep.department 中的每个分类,并赋值给 name -->
<!-- let i=index 是 ngFor 中取得迭代序号的方法-->
<span *ngFor="let name of productDep.department; let i=index">
<a href="#">{{name}}</a>
<!-- 三元运算符判断是否为最后一级分类 -->
<span>{{i<(productDep.department.length-1)?'>':''}}</span>
</span>
</div>
`
})
export class ProductDepartmentComponent implements OnInit {
@Input() productDep;
constructor() { }
ngOnInit() {
}
}
完成

数据架构
数据架构管理方面只有两个主要流派
- 使用基于观察者模式的架构,如RxJS
- 使用基于Flux的架构
网友评论