Angular入门教程学习:The Tour Of Heroes

Posted on 2022-07-14 17:27  Capterlliar  阅读(70)  评论(0编辑  收藏  举报

翻译自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名单包装一下,加上增删改查操作,最近用不到,也没有什么新的东西,就不再写下去了。

下班!