angular
架构概览
我们是这样写 Angular 应用的:用 Angular
扩展语法编写 HTML 模板
, 用组件
类管理这些模板,用服务
添加应用逻辑, 用模块
打包发布组件与服务。
然后,我们通过引导
根模块来启动该应用。 Angular
在浏览器中接管、展现应用的内容,并根据我们提供的操作指令响应用户的交互。
这个架构图展现了Angular
应用中的 8 个主要构造块:
模块 (module)
组件 (component)
模板 (template)
元数据 (Metadata)
数据绑定 (data binding)
指令 (directive)
服务 (service)
依赖注入 (dependency injection)
模块 (module)
Angular
应用是模块化的,并且Angular
有自己的模块系统,它被称为 Angular
模块或NgModules
。
每个 Angular
应用至少有一个模块(根模块),习惯上命名为AppModule
。
Angular
模块(无论是根模块还是特性模块)都是一个带有@NgModule
装饰器的类。
装饰器是用来修饰 JavaScript 类的函数。 Angular 有很多装饰器,它们负责把元数据附加到类上,以了解那些类的设计意图以及它们应如何工作。
NgModule
是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。其中最重要的属性是
- declarations
- 声明本模块中拥有的视图类。Angular 有三种视图类:components
,directives
,和 pipes
.
- exports
- declarations
的子集,可用于其它模块的组件模板。
- imports
- 本模块声明的组件模板需要的components
,directives
所在的其它模块。
- providers
- 服务的创建者,并加入到全局服务列表中,可用于应用任何部分。
- bootstrap
- 指定应用的主视图root component
,通过它管理其它视图。只有root module
才能设置bootstrap
属性
src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],providers: [ Logger ],declarations: [ AppComponent ],exports: [ AppComponent ],bootstrap: [ AppComponent ]
})
export class AppModule { }
AppComponent的export语句只是用于演示如何导出的,它在这个例子中并不是必须的。根模块不需要导出任何东西,因为其它组件不需要导入根模块。
我们通过引导根模块来启动应用。 在开发期间,你通常在一个main.ts
文件中引导AppModule
,就像这样:
src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Angular
模块 vs. JavaScript
模块
Angular
模块(一个用@NgModule
装饰的类)是 Angular
的基础特性。
JavaScript
也有自己的模块系统,用来管理一组JavaScript
对象。 它与Angular
的模块系统完全不同且完全无关。
JavaScript
中,每个文件是一个模块,文件中定义的所有对象都从属于那个模块。 通过export
关键字,模块可以把它的某些对象声明为公共的。 其它JavaScript
模块可以使用import
语句来访问这些公共对象。
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
export class AppModule { }
Angular libraries
每个 Angular
库的名字都带有@angular
前缀。
用 JavaScript
的import
语句导入其中某些部件。
从@angular/core
库中导入Component
装饰器
import { Component } from '@angular/core';
还可以使用 JavaScript
的导入语句从 Angular
库中导入 Angular
模块。
import { BrowserModule } from '@angular/platform-browser';
在上面那个简单的根模块的例子中,应用模块需要BrowserModule
的某些素材。要访问这些素材,就得把它加入@NgModule
元数据的imports
中
imports: [ BrowserModule ],
这种情况下,你同时使用了 Angular
和 JavaScript
的模块化系统。
Templates
用bootstrap
编写的简单模板
编辑src/app/app.my-navbar.css
.bs-docs-nav .navbar-toggle .icon-bar {
background-color: #563d7c;
}
.bs-docs-nav .navbar-brand,.bs-docs-nav .navbar-nav > li > a {
font-weight: 500;
color: #563d7c;
}
编辑src/app/app.my-navbar.html
<header class="navbar navbar-static-top bs-docs-nav"> <div class="containter"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-navbar" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a href="../" class="navbar-brand">Angular</a> </div> <nav class="collapse navbar-collapse" id="bs-navbar"> <ul class="nav navbar-nav"> <li *ngFor="let navLink of navLinks"> <a [href]="navLink.href">@H_301_525@{{navLink.name}}</a> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" (keyup)="changeTitle($event)" placeholder="Search"> </div> <div class="form-group"> <input type="text" class="form-control" [(ngModel)]="title" name="title" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <ul class="nav navbar-nav navbar-right"> <li> <a href="./">@H_301_525@{{ title }}</a> </li> </ul> </nav> </div> </header>
Metadata
元数据告诉 Angular
如何处理一个类.
AppNavBarComponent
真的只是一个类。直到我们告诉 Angular
它是一个组件。
要告诉 Angular
AppNavBarComponent
是个组件,只要把元数据附加到这个类。
在TypeScript
中,我们用装饰器 (decorator)
来附加元数据。
import { Component } from '@angular/core';
@Component({
selector: 'app-navbar',templateUrl: './app.my-navbar.html',styleUrls: ['./app.my-navbar.css']
})
export class AppNavBarComponent {
title = 'Angular Demo';
navLinks = [{href:"#",name:"FEATURES"},{href:"#",name:"DOCS"}];
changeTitle = function(event:any){
this.title = event.target.value;
}
}
selector
: CSS 选择器,它告诉 Angular
在父级 HTML 中查找<app-navbar>
标签,创建并插入该组件。 例如,如果应用的 HTML
包含<app-navbar></app-navbar>
, Angular
就会把AppNavBarComponent
的一个实例插入到这个标签中。
Data binding
<li *ngFor="let navLink of navLinks"> <a [href]="navLink.href">@H_301_525@{{navLink.name}}</a> </li>
单向绑定
{{navLink.name}}
插值表达式在<a>
标签中显示组件的name
属性的值。
[href]
绑定数据到属性里面,数据从component
到view
(keyup)="changeTitle($event)"
绑定keyup
输入事件,数据从view
到component
双向绑定
在双向绑定中,数据属性值通过属性绑定从组件流到输入框。用户的修改通过事件绑定流回组件,把属性值设置为最新的值。
<input type="text" class="form-control" [(ngModel)]="title" name="title" placeholder="Search">
Directives
Angular
模板是动态的。当 Angular
渲染它们时,它会根据指令提供的操作对 DOM
进行转换。
组件是一个带模板的指令;@Componen
t装饰器实际上就是一个@Directive
装饰器,只是扩展了一些面向模板的特性。
还有两种其它类型的指令:结构型(Structural)
指令和属性(attribute)
型指令。
结构型(Structural)
指令
结构型(Structural)
指令通过在 DOM
中添加、移除和替换元素来修改布局。
<li *ngFor="let navLink of navLinks"> <a [href]="navLink.href">@H_301_525@{{navLink.name}}</a> </li>
*ngFor
告诉 Angular
遍历navLinks
生成<li>
标签。
属性(attribute)
型指令
<input type="text" class="form-control" [(ngModel)]="title" name="title" placeholder="Search">
ngModel
指令就是属性型指令的一个例子,它实现了双向数据绑定。ngModel
修改现有元素(一般是)的行为:设置其显示属性值,并响应 change
事件。
Services
几乎任何东西都可以是一个服务。 典型的服务是一个类,具有专注的、明确的用途
。它应该做一件特定的事情,并把它做好。
例如:
- 日志服务
- 数据服务
- 消息服务
下面是一个服务类的范例,用于把日志记录到浏览器的控制台:
编辑src/app/service/logger.service.ts
import {Injectable} from '@angular/core';
@Injectable()
export class Logger {
log(msg:any) {
console.log('From logger class: ' + msg);
}
}
编辑src/app/app.module.ts
import { Logger } from './logger.service';
@NgModule({
...
providers: [Logger],...
})
Dependency injection
Dependency injection
is a way to supply a new instance of a class with the fully-formed dependencies it requires. Most dependencies are services. Angular uses dependency injection to provide new components with the services they need.Angular can tell which services a component needs by looking at the types of its
constructor parameters
. For example,the constructor of your HeroListComponent needs a HeroService:When Angular creates a component,it first asks an
injector
for the services that the component requires.An
injector
maintains acontainer of service instances
that it has prevIoUsly created. If a requested service instance is not in the container,the injector makes one and adds it to the container before returning the service to Angular. When all requested services have been resolved and returned,Angular can call the component's constructor with those services as arguments
. This isdependency injection
.
用依赖注入模拟数据
编辑src/app/model/User.ts
export class User{
constructor(
private name:string,private age:number,private email:string
){}
}
编辑src/app/mock/user-data.mock.ts
import {User} from '../model/User';
export const Users:User[] = [
new User("angular1",21,"2290910211@qq.com"),new User("angular2",22,new User("angular3",23,new User("angular4",24,new User("angular5",25,new User("angular6",26,]
创建一个获取用户数据的服务
编辑app/services/user.service.ts
import {Injectable} from '@angular/core';
import {Users} from '../mock/user-data.mock';
import {Logger} from "./logger.service";
@Injectable()
export class UserService{
constructor(private Log:Logger){
}
getUsers(){
this.Log.log("Get User!");
return Users;
}
}
@Injectable()
标志着一个类可以被一个注入器实例化
我们要在@Component
的元数据中使用providers
声明我们所需要的依赖,还要引入User
类来帮助我们声明数据的类型.
import { Component } from '@angular/core';
import { Logger } from './service/logger.service';
import { User } from './model/User';
import { UserService } from './service/user.service';
@Component({
selector: 'app-navbar',styleUrls: ['./app.my-navbar.css'],providers:[
Logger,UserService
]
})
export class AppNavBarComponent {
title:'Angular Demo';
users: User[];
constructor(
private Log:Logger,private userService:UserService
){
this.users = userService.getUsers();
}
navLinks = [{href:"#",name:"DOCS"}];
changeTitle = function(event:any){
this.title = event.target.value;
}
}
如果这个时候你试图把user.service.ts
的@Injectable
注释掉的话,整个程序是没有报错的,但是我们建议为每个服务类都添加@Injectable()
,包括那些没有依赖所以技术上不需要它的.因为:(1)面向未来,没有必要记得在后来添加了一个依赖的时候添加@Injectable()
.(2)一致性,所有的服务都遵循同样的规则,并且我们不需要考虑为什么少一个装饰器.
引入bootstrap
样式
npm install jquery bootstrap --save
安装类型描述文件
npm install @types/jquery --save-dev
修改angular-cli.json
"apps": [
{
"root": "src","outDir": "dist","assets": [
"assets","favicon.ico"
],"index": "index.html","main": "main.ts","test": "test.ts","tsconfig": "tsconfig.app.json","testTsconfig": "tsconfig.spec.json","prefix": "app","styles": [
"styles.css","../node_modules/bootstrap/dist/css/bootstrap.min.css","../node_modules/bootstrap/dist/css/bootstrap-theme.min.css"
],"scripts": [
"../node_modules/jquery/dist/jquery.js","../node_modules/bootstrap/dist/js/bootstrap.min.js"
],}
]
修改tsconfig.app.json
将jquery
添加到types
中
{
"extends": "../tsconfig.json","compilerOptions": { "outDir": "../out-tsc/app","module": "es2015","baseUrl": "","types": ["jquery"] },"exclude": [ "test.ts","**/*.spec.ts" ] }
如果样式没有加载,删除node_modules
文件夹重新npm install
,不要用cnpm install