1,
@Component
是个装饰器函数,用于为该组件指定 Angular 所需的元数据。
CLI 自动生成了三个元数据属性:
-
selector
— 组件的选择器(CSS 元素选择器) -
templateUrl
— 组件模板文件的位置。 -
styleUrls
— 组件私有 CSS 样式表文件的<h2>
<h2>{{hero.name | uppercase}} Details</h2> 绑定表达式中的 uppercase
位于管道操作符( | )的右边,用来调用内置管道 UppercasePipe
。管道 是格式化字符串、金额、日期和其它显示数据的好办法。 Angular 发布了一些内置管道,而且你还可以创建自己的管道。
2,
当用户输入时,输入框应该能同时显示和修改英雄的 name
属性。 也就是说,数据流从组件类流出到屏幕,并且从屏幕流回到组件类。
要想让这种数据流动自动化,就要在表单元素 <input>
和组件的 hero.name
属性之间建立双向数据绑定。
[(ngModel)] 是 Angular 的双向数据绑定语法。
这里把 hero.name
属性绑定到了 HTML 的 textbox 元素上,以便数据流可以双向流动:从 hero.name
属性流动到 textbox,并且从 textbox 流回到 hero.name
。
3,
顶级类 AppModule 的 imports
数组中,这里是该应用所需外部模块的列表。
4,
<li *ngFor="let hero of heroes">
*ngFor
是一个 Angular 的复写器(repeater)指令。 它会为列表中的每项数据复写它的宿主元素。
在这个例子中
-
<li>
就是*ngFor
的宿主元素 -
heroes
就是来自HeroesComponent
类的列表。 -
当依次遍历这个列表时,
hero
会为每个迭代保存当前的英雄对象。
不要忘了 ngFor
前面的星号(*
),它是该语法中的关键部分。
5,
Angular 的 CSS 类绑定机制让根据条件添加或移除一个 CSS 类变得很容易。 只要把 [class.some-css-class]="some-condition"
添加到你要施加样式的元素上就可以了。
在 HeroesComponent
模板中的 <li>
元素上添加 [class.selected]
绑定,代码如下:
heroes.component.html (toggle the 'selected' CSS class)
[class.selected]="hero === selectedHero"
如果当前行的英雄和 selectedHero
相同,Angular 就会添加 CSS 类 selected
,否则就会移除它。
6,
[hero]="selectedHero"
是 Angular 的属性绑定语法。
这是一种单向数据绑定。从 HeroesComponent
的 selectedHero
属性绑定到目标元素的 hero
属性,并映射到了 HeroDetailComponent
的 hero
属性。
7,
服务;
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。(MVP模式)
不要使用 new
来创建此服务,而要依靠 Angular 的依赖注入机制把它注入到构造函数中
服务是在多个“互相不知道”的类之间共享信息的好办法
@Injectable() 服务
注意,这个新的服务导入了 Angular 的 Injectable
符号,并且给这个服务类添加了 @Injectable()
装饰器。 它把这个类标记为依赖注入系统的参与者之一。HeroService
类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖
HeroService
可以从任何地方获取数据:Web 服务、本地存储(LocalStorage)或一个模拟的数据源。
从组件中移除数据访问逻辑,意味着将来任何时候你都可以改变目前的实现方式,而不用改动任何组件。 这些组件不需要了解该服务的内部实现。
在要求 Angular 把 HeroService
注入到 HeroesComponent
之前,你必须先把这个服务提供给依赖注入系统。稍后你就要这么做。 你可以通过注册提供商来做到这一点。提供商用来创建和交付服务
现在,你需要确保 HeroService
已经作为该服务的提供商进行过注册。 你要用一个注入器注册它。注入器就是一个对象,负责在需要时选取和注入该提供商。
默认情况下,Angular CLI 命令 ng generate service
会通过给 @Injectable
装饰器添加元数据的形式,用根注入器将你的服务注册成为提供商。
如果你看看 HeroService
紧前面的 @Injectable()
语句定义,就会发现 providedIn
元数据的值是 'root':
content_copy@Injectable({
providedIn: 'root',
})
当你在顶层提供该服务时,Angular 就会为 HeroService
创建一个单一的、共享的实例,并把它注入到任何想要它的类上。 在 @Injectable
元数据中注册该提供商,还能允许 Angular 通过移除那些完全没有用过的服务来进行优化。
创建一个函数,以从服务中获取这些英雄数据。
service里面:getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
在 ngOnInit
中调用它
你固然可以在构造函数中调用 getHeroes()
,但那不是最佳实践。
让构造函数保持简单,只做初始化操作,比如把构造函数的参数赋值给属性。 构造函数不应该做任何事。 它当然不应该调用某个函数来向远端服务(比如真实的数据服务)发起 HTTP 请求。
而是选择在 ngOnInit 生命周期钩子中调用 getHeroes(),之后交由 Angular 处理,它会在构造出 HeroesComponent 的实例之后的某个合适的时机调用 ngOnInit。
调用的component里面:
ngOnInit() {
this.getHeroes();
}
HeroService.getHeroes()
必须具有某种形式的异步函数签名
它可以使用回调函数,可以返回 Promise
(承诺),也可以返回 Observable
(可观察对象)。
这节课,HeroService.getHeroes()
将会返回 Observable
,因为它最终会使用 Angular 的 HttpClient.get
方法来获取英雄数据,而 HttpClient.get()
会返回 Observable
。
可观察对象版本的 HeroService
Observable
是 RxJS 库中的一个关键类。
在稍后的 HTTP 教程中,你就会知道 Angular HttpClient
的方法会返回 RxJS 的 Observable
。 这节课,你将使用 RxJS 的 of()
函数来模拟从服务器返回数据。
打开 HeroService
文件,并从 RxJS 中导入 Observable
和 of
符号。
src/app/hero.service.ts (Observable imports)
import { Observable, of } from 'rxjs';
getHeroes(): Observable<Hero[]> { return of(HEROES); }
of(HEROES)
会返回一个 Observable<Hero[]>
,它会发出单个值,这个值就是这些模拟英雄的数组
HeroService.getHeroes
方法之前返回一个 Hero[]
, 现在它返回的是 Observable<Hero[]>
。
你必须在 HeroesComponent
中也向本服务中的这种形式看齐。
找到 getHeroes
方法,并且把它替换为如下代码:
getHeroes(): void {
this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes);
}//此处heros 是异步返回的body
如果在模板中绑定到服务,则这个 messageService
属性必须是公共属性。public
8,
添加路由定义
路由定义 会告诉路由器,当用户点击某个链接或者在浏览器地址栏中输入某个 URL 时,要显示哪个视图。
典型的 Angular 路由(Route
)有两个属性:
-
path
:一个用于匹配浏览器地址栏中 URL 的字符串。 -
component
:当导航到此路由时,路由器应该创建哪个组件。 -
const routes: Routes = [ { path: 'heroes', component: HeroesComponent } ];
如果你希望当 URL 为 localhost:4200/heroes
时,就导航到 HeroesComponent
。
首先要导入 HeroesComponent
,以便能在 Route
中引用它。 然后定义一个路由数组,其中的某个路由是指向这个组件的。
RouterModule.forRoot()
你必须首先初始化路由器,并让它开始监听浏览器中的地址变化。
把 RouterModule
添加到 @NgModule.imports
数组中,并用 routes
来配置它。你只要调用 imports
数组中的 RouterModule.forRoot()
函数就行了。
imports: [ RouterModule.forRoot(routes) ],
这个方法之所以叫 forRoot()
,是因为你要在应用的顶级配置这个路由器。 forRoot()
方法会提供路由所需的服务提供商和指令,还会基于浏览器的当前 URL 执行首次导航。
<router-outlet>
会告诉路由器要在哪里显示路由的视图。
添加路由链接 (routerLink
)
不应该让用户只能把路由的 URL 粘贴到地址栏中。他们还应该能通过点击链接进行导航。
添加一个 <nav>
元素,并在其中放一个链接 <a>
元素,当点击它时,就会触发一个到 HeroesComponent
的导航。 修改过的 AppComponent
模板如下:
src/app/app.component.html (heroes RouterLink)
<h1>{{title}}</h1>
<nav>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
routerLink
属性的值为 "/heroes"
,路由器会用它来匹配出指向 HeroesComponent
的路由。routerLink
是 RouterLink
指令的选择器,它会把用户的点击转换为路由器的导航操作。 它是 RouterModule
中的另一个公共指令。
添加默认路由
当应用启动时,浏览器的地址栏指向了网站的根路径。 它没有匹配到任何现存路由,因此路由器也不会导航到任何地方。 <router-outlet>
下方是空白的。
要让应用自动导航到这个仪表盘,请把下列路由添加到 AppRoutingModule.Routes
数组中。
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
这个路由会把一个与空路径“完全匹配”的 URL 重定向到路径为 '/dashboard'
的路由。
浏览器刷新之后,路由器加载了 DashboardComponent
,并且浏览器的地址栏会显示出 /dashboard
这个 URL。
参数化路由添加到 AppRoutingModule.routes
数组中,它要匹配指向英雄详情视图的路径。
{ path: 'detail/:id', component: HeroDetailComponent },
path
中的冒号(:
)表示 :id
是一个占位符,它表示某个特定英雄的 id
ActivatedRoute
保存着到这个 HeroDetailComponent
实例的路由信息。 这个组件对从 URL 中提取的路由参数感兴趣。 其中的 id
参数就是要显示的英雄的 id
。
HeroService
从远端服务器获取英雄数据,本组件将使用它来获取要显示的英雄。
location
是一个 Angular 的服务,用来与浏览器打交道。 稍后,你就会使用它来导航回上一个视图。
const id = +this.route.snapshot.paramMap.get('id');
route.snapshot
是一个路由信息的静态快照,抓取自组件刚刚创建完毕之后。
paramMap
是一个从 URL 中提取的路由参数值的字典。 "id"
对应的值就是要获取的英雄的 id
。
路由参数总会是字符串。 JavaScript 的 (+) 操作符会把字符串转换成数字,英雄的 id
就是数字类型。
9,
http
npm
中安装这个内存 Web API 包(译注:请使用 0.5+ 的版本,不要使用 0.4-)
npm install angular-in-memory-web-api --save (本项目路径下)
当你的真实服务器就绪时,请移除这个内存 Web API,应用的请求就会直接发给服务器。
npm uninstall angular-in-memory-web-api