翻译自https://angular.io/tutorial,看每一节的完整代码似乎清晰一些
!!!注意
快写完了发现翻的是v2版本,现在都v14了还得重修一遍,导致这篇文章1-4主要是v2版本教程,只更新了一些函数,一开始把所有文件混在一起,后面逐步分开,很不合理,但这个代码复制到博客园比较清晰,再加上最终代码是v14的最新版,所以就没怎么改(才不是懒呢),呈现结果是个缝合怪,不影响学习,但不要跟着做,(如果真的有人看的话)
老是打hero好羞耻啊脑子里有一万个阿尔弗雷德在说话
0. 介绍
这个app将覆盖Angular核心技术。
功能:Dashboard展示top heroes信息,Heroes展示目前已有的hero,点击列表,显示详细信息,同时能编辑。

1. 制作资料卡,并添加输入
import { Component } from '@angular/core';
export class Hero {
id: number;
name: string;
}
//定义一个hero的类
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name">
//注1
</div>
`
})
//页面元素
export class AppComponent {
title = 'Tour of Heroes';
hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
//紧跟着的class为上面的Component服务,其中用到了hero类
注1:[(ngModel)]是hero.name和Input之间的双向绑定,使用时需要import { FormsModule } from '@angular/forms';
2. 展示多位heroes
import { Component } from '@angular/core';
export class Hero {
id: number;
name: string;
}
const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
//用一个数组列举heroes
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
//注2
[class.selected]="hero === selectedHero"
//注4.2
(click)="onSelect(hero)">
//注3.1
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
//注4.1
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
</div>
`,
//具体样式忽略
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
...
}
.heroes li {
...
}
.heroes li.selected:hover {
...
}
.heroes li:hover {
...
}
.heroes .text {
...
}
.heroes .badge {
...
}
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
//使用上面的数组
selectedHero?: Hero;
//注3.2
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
注2:*ngfor
ngfor代表着<li>和它的子元素构成了一个主模板,来展示hero的信息。let {item} of {vairable} ,hero是heroes的元素。
注3:选择一个hero
1. 增加一个点击事件,click后是对应的函数。
2.增加一个Hero变量,为selectedHero,当点击它的时候,为selectedHero赋值。? 表示变量不一定存在。
注4:*ngIf
1. 当没有选择hero的时候,当然不显示对应名称。当selectedHero不为空时,显示这块内容。
2. 将 [class.selected] 绑定给<li>上去,当hero===selectedHero时,它是selected的。
3. 代码复用
把上面的代码按功能分隔开来,以及多文件组合。文件名有一个style guide,但好像用不用都行,就不写了。
将上面一大坨代码拆成了四个文件,分别为hero-detail.component.ts、app.component.ts、hero.ts、app.module.ts
1. hero.ts
先把大家都要用的类拎(抽象)出来。
export class Hero {
id: number;
name: string;
}
2. app.component.ts
再拎出主体
import { Component } from '@angular/core';
//要用Component,先引用
import { Hero } from './hero';
//还有hero class
const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
//由于把hero-detail当作子节点单独抽出来了,所以要给它传个参,把selectedHero传过去
`,
styles: [`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
3. hero-detail.component.ts
上面提到的子节点
import { Component, Input } from '@angular/core';
//要用Component,先引用
import { Hero } from './hero';
//还有hero class
@Component({
selector: 'hero-detail',
//CSS选择器名字
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
//这一块的html
})
export class HeroDetailComponent {
@Input() hero: Hero;
//作为一个子节点,需要从父节点AppComponent那里继承一个Hero
}
4. app.module.ts
类似于声明用的文件
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
//要用的
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
//涉及到的自己写的文件
@NgModule({
imports: [
BrowserModule,
FormsModule
],
//引用的Module
declarations: [
AppComponent,
HeroDetailComponent
],
//包含了哪些Components,也可以是pipes或directives
bootstrap: [ AppComponent ]
})
export class AppModule { }
//空着就行
4. Services
现在呢我们要在另一个地方展示编辑的heroes,但那里没有heroes的数据,所以要写一些services把heroes传过去。
先把那个数组拎出来:
mock-heroes.ts
import { Hero } from './hero';
export const HEROES: Hero[] = [
{id: 11, name: 'Mr. Nice'},
{id: 12, name: 'Narco'},
{id: 13, name: 'Bombasto'},
{id: 14, name: 'Celeritas'},
{id: 15, name: 'Magneta'},
{id: 16, name: 'RubberMan'},
{id: 17, name: 'Dynama'},
{id: 18, name: 'Dr IQ'},
{id: 19, name: 'Magma'},
{id: 20, name: 'Tornado'}
];
起一个新的文件,叫hero.service.ts。上面的component文件前面也是加 . 的,但其实改成下划线啥的也没问题,对编译没有影响。
hero.service.ts
import { Injectable } from '@angular/core';
//要把服务注入(inject)到组件中
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable()
//有三种方法将服务注入到组件中,不要new HeroService
//1. @injectable({providedIn: 'root'})
//意为将服务注入根中,这样全局都可以用,只要import一下,再在构造函数里写一句constructor(private heroService:HeroService)就好
//2. @injectable()
//空着,什么都不写,但需要在NgMoudle加providers: [HeroService]
//3. @injectable()
//然后在@Component中加providers: [HeroService]
export class HeroService {
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES);
}
//将调用服务改为异步,防止页面卡死,这时就要用到Promises。这个函数return一个promise,可以把它理解为一个容器,里面装着HEROES。用Promise.resolve()来代替new Promise()
}
然后来看接收这个Promise的文件:
app.components.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
styles: [
...
],
providers: [HeroService]
})
export class AppComponent implements OnInit {
title = 'Tour of Heroes';
heroes: Hero[];
//注意Hero[]是HEROES的类型
selectedHero: Hero;
constructor(private heroService: HeroService) { }
getHeroes(): void {
this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes);
//由于接受到的是一个Promise,所以改写成这样
//subscribe()里面是Promise的回调函数,意为调用成功后,执行这个函数
//执行的内容为赋值
}
ngOnInit(): void {
this.getHeroes();
}
//不要在构造函数里调用方法,换到这里
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
5. 切换页面(Router)
现在有两个功能,编辑和展示。页面设计了一个类似按钮的东西,点一下就能切换过去,接下来要实现它。
实现主要是用路由(router),即URL后面加/,表示这个页面的不同部分,用路由导航过去。
介于两个功能现在在一个页面里,还要拆分下代码。
- 把app.component.ts改名为heroes.component.ts
- 把AppComponent改名为HeroesComponent
- 把my-app改名为my-heroes
1. Router
现在来加router,也就是页面跳转要用的。
首先先确定base页面,也就是源目录。确定index.html中有<base href="/">,也就是当前页面。
定义一个route给heroes component:
import { RouterModule } from '@angular/router';
RouterModule.forRoot([
{
path: 'heroes',
//给URL里加的
component: HeroesComponent
//点了它应该创建的东西
}
])
然后给它新开一个文件,叫app-routing.module.ts
2. 加一个指示板(dashboard)
开始写对应的页面,建一个新的dashboard.component.ts
dashborad.component ---------------html---------------- <h2>Top Heroes</h2> <div class="heroes-menu"> <a *ngFor="let hero of heroes" routerLink="/detail/{{hero.id}}"> //点一下就会去对应页面 {{hero.name}} </a> </div> ------------ts--------------------- import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; import { HeroService } from '../hero.service'; @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html', styleUrls: [ './dashboard.component.css' ] }) export class DashboardComponent implements OnInit { heroes: Hero[] = []; constructor(private heroService: HeroService) { } ngOnInit(): void { this.getHeroes(); } getHeroes(): void { this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes.slice(1, 5)); //要第二个到第四个就够了 } }
//给它加个路由 .module.ts { path: 'dashboard', component: DashboardComponent },
现在打开页面,就会在/下,这没有内容,重定向到dashboard:
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
3. html和ts分离
@Component({ selector: 'my-dashboard', templateUrl: './dashboard.component.html', //用这个代替大段html })
4. 相应改动
把和router有关的都放到app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
其中,herodetail自己占了一个页面,把herodetail也绑定一下:
{ path: 'detail/:id', //加个: ,表示后面跟hero的id component: HeroDetailComponent },
但这样一来我们就没法给它直接传参了,得再写个函数。直接看最终代码:
先看hero那边
hero.component ----------------html----------------------- <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <div *ngIf="selectedHero"> <h2> {{selectedHero.name | uppercase}} is my hero //注1 </h2> <button (click)="gotoDetail()">View Details</button> </div> -------------------ts--------------------------------- import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Hero } from './hero'; import { HeroService } from './hero.service'; @Component({ selector: 'my-heroes', templateUrl: './heroes.component.html', styleUrls: [ './heroes.component.css' ] }) export class HeroesComponent implements OnInit { heroes: Hero[]; selectedHero: Hero; constructor( private router: Router, //多用一个服务 private heroService: HeroService) { } getHeroes(): void { this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes); } ngOnInit(): void { this.getHeroes(); } onSelect(hero: Hero): void { this.selectedHero = hero; } gotoDetail(): void { this.router.navigate(['/detail', this.selectedHero.id]); //导航到对应页面,传参数为hero的id } }
注1: | 是管道(pipe)的意思,意为将name转换为大写形式
hero-detail.component --------------html-------------- <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div> <label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name" /> </div> <button (click)="goBack()">Back</button> //加一个返回按钮 </div> -------------ts----------------- import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { Hero } from '../hero'; import { HeroService } from '../hero.service'; @Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: [ './hero-detail.component.css' ] }) export class HeroDetailComponent implements OnInit { hero: Hero | undefined; constructor( private route: ActivatedRoute, private heroService: HeroService, private location: Location ) {} ngOnInit(): void { this.getHero(); } getHero(): void { const id = Number(this.route.snapshot.paramMap.get('id')); //route.snapshot是对应component创建后route的信息 //paraMap是从URL中获得的参数信息,也就是之前传的id,可以说是通过URL传参 //Number是把括号里的String强行转换为数字 this.heroService.getHero(id) .subscribe(hero => this.hero = hero); //通过id找hero,待会要说这个service } goBack(): void { this.location.back(); //利用location服务,往前退一格 } }
services里加用id找hero到函数:
getHero(id: number): Observable<Hero> { // For now, assume that a hero with the specified `id` always exists. // Error handling will be added in the next step of the tutorial. const hero = HEROES.find(h => h.id === id)!; //this.messageService.add(`HeroService: fetched hero id=${id}`); return of(hero); //用of()创建一个Observable }
5. MessageService
这是v14新写的一个东西,目的在于在页面底部显示操作,以及加上http后打印日志。
http部分就是把heroes名单包装一下,加上增删改查操作,最近用不到,也没有什么新的东西,就不再写下去了。
下班!
浙公网安备 33010602011771号