美文网首页程序员我爱编程
AngularJS官方教程解读攻略

AngularJS官方教程解读攻略

作者: 那就远走 | 来源:发表于2018-05-11 14:36 被阅读173次

攻略说明

  • 这是一篇来自angular官方的入门教程,构建一个“管理英雄”的应用。详情
  • 官网教程有太多专业名词,可能不适用于不了解一些概念的新手朋友(包括我自己),所以我照着官方入门教程的说明来一步步构建这个项目,同时记录我的开发过程,加入我自己的“大白话理解”。

安装nodeJS(npm)

# 这是用curl 添加源
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
# 这是安装
sudo apt-get install -y nodejs
# 这是个坑,反正我上次用cnpm装的脚手架工具写出来得代码不不能编译的,不过如果你愿意试试,可以装个cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

什么是angularJS

  • 为什么要用angularJS: 前端框架,到目前为止,我只用过bootstrap(解决css问题)和jquery(解决动画和Ajax请求)。用他们俩搭配开发的前端html页面(通常是我用在php框架中的视图)有以下几个缺点
    1. 页面很多:比如一个blog项目,我得有index.html主页,admin.html后台页,list.html博客列表页,article.html文章页以及通用的,或者某个界面单独的css、js。
    2. 纯静态导致高耦合,在php框架中(thinkphp | laravel),比如我希望某个控制器查询数据库,并将数据分页显示在某个视图上,如果我给视图传递的变量名叫$datas,那么我在视图必须去遍历的对象名字也就要$datas,没有做到真正的前后端分离(真正的项目开发中,万一只要求我处理控制器层的逻辑,我是不是得去问前端给我提供的视图的html结构是什么样的。或者只要求我去处理视图,我是不是得和后端沟通,问它给我的数据的结构是怎么样的,我才好在视图层合理美观的显示这些数据)
  • angularJS如何解决这些问题的:angularJS由TS语言编写,实现了“前端工程化”,前端写页面,后端写API,前后端用json(大部分时候)沟通即可。
    1. 解决页面很多:单页应用组件化,仔细想想,很多时候我之前使用php框架提供的视图也是在模块化视图:比如我的导航nav不变,header头不变,footer脚不变,唯一变的就是div#content内容。那么我把nav header footer写死写在布局模板中,其它模板继承布局模板,然后编辑div#content的内容即可。其实angular的组件化也是这个逻辑:我们可以把div#content看作一个组件(angular模板上的一个特定的标签),这个组件实现渲染某一区域不同的内容即可。
    2. 实现前后端分离: 上面说过了,前端处理用户请求和响应。后端提供API即可(每个控制器的每个方法return的都是json对象)。

安装cli脚手架 创建项目 启动服务以预览项目

  • 安装cli脚手架 npm install -g @angular/cli
  • 创建项目 ng new angular-heroes
  • 启动服务
# 进入项目目录
cd angular-heroes 
# 启动:默认可以在 localhost:4200 访问
ng serve

壳组件 AppComponent

  • 了解“组件3要素” => 模板.html / 样式.css / 类定义.ts (都位于 src/app/ 下)
    • 模板叫 app.component.html
    • 样式叫 app.component.css
    • 类定义叫 app.component.ts
  • 模板和样式我们已经非常了解了,很简单的html和css代码,重点是类定义,类定义分3块
// 这是第1块:导入核心类库 @angular/core 提供的组建类 Component (我喜欢把它叫导包,因为和java很像,同时实现的功能也类似php中的use 空间类元素)
import { Component } from '@angular/core';

// 这是第2块: 装饰器,这里装饰了Component(可以理解为在配置这个组件)
@Component({
    selector: 'app-root', //告诉组件要渲染的具体标签
    templateUrl: './app.component.html', //告诉组件视图模板存在的位置
    styleUrls: ['./app.component.css'] //告诉组件css样式的位置
})

// 这是第3块: 具体的类定义,我们把变量、函数等等都写在这里面
export class AppComponent { // AppComponent 就从这里来
    title = 'app';
}
  • 简单的“玩一玩”这个组件
    • 在 类定义.ts 中修改 title 的值
    export class AppComponent {
       title = '远走最帅'; //你可以写你想写的东西
    }
    
    • 在 模板.html 中 插值 => 页面显示“远走最帅”
    <h1>{{ title }}</h1> <!-- 这里的title就对应上面类定义中的title -->
    
    • 在 样式.css 中定义一些 样式 并使用它 => 页面显示紫色的“远走最帅”
    #css
    .handSomeName {
        color: purple;
        font-weight: bold;
    }
    
    #视图
    <h1 class="handSomeName">{{ title }}</h1>
    

    类定义.ts处理数据 / 模板.html展示数据 / 样式.css修饰模板 => 这就是一个组件Component(Angular生成的页面中的最小单位)

  • 回归主题,照着官方教程,来构建应用 => 这里做的跟我们上面一样,类定义中编辑一个变量title的值,在视图层用{{}}展示,然后写一个css修饰。(官网教程中提供了它写好的css,可以直接copy它的)
  • 这里的“壳组件” 可以理解为 “组件的根”,“程序的入口”,“index.html”。即我们访问angular应用时载入的第一个组件,执行的第一个程序。同时,可以将它的模板看作“布局模板”,样式看作“基础样式”。

第一个子组件 HeroesComponent

  • 组件具体作用: 展示英雄列表。
  • 创建组件 ng generate component heroes
  • 上面的命令可以简写为 ng g c heroes
  • 这时候在 src/app/ 下生成了 heroes/ 目录,打开一看,没有问题,还是“组件3要素”,模板 样式 类定义。
  • 主要看看类定义 @Component.selector
// 1导包
import { Component, OnInit } from '@angular/core';

// 2装饰器
@Component({
  selector: 'app-heroes', //再次注意这一句:告诉我们该组件渲染的标签
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})

// 3具体类定义
export class HeroesComponent implements OnInit {
  // 请暂时忽略构造函数
  constructor() { }
  
  // 请暂时忽略初始化函数
  ngOnInit() {
  }

}
  • 在“布局模板” app.component.html 中导入子组件 => 页面多显示 heroes works! (来自于 heroes.component.html)
<h1>{{ title }}</h1>
<app-heroes></app-heroes>
  • 模拟英雄数据

    • 在 src/app/ 下创建一个 hero.ts (创建“规范类”)
    export class Hero {
        // 制定规范
        id: number; //id: 得是数字
        name: string; //name: 得是字符串
    }
    
    • 在 src/app/ 下创建一个 mock-heroes.ts (创建模拟的“英雄列表”,实际应用中我们应该从后台应用程序接口获取这类数据)
    // 导入规范
    import { Hero } from "./hero";
    
    // 实例化一个常量数组
    export const HEROES: Hero[] = [
    // 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' }
    ];
    

    主要是注意语法:“变量: 数据类型”

  • HeroesComponent 导入模拟的数据,在模板中显示,然后美化模板

    • 类定义.ts中导入数据
    // 第1块中 导入 我们模拟的 英雄列表
    ...
    import { HEROES } from '../mock-heroes'; //导入数据
    
    // 第3块中 将数据赋值给变量heroes
    export class HeroesComponent implements OnInit {
        // 将导入的数据复制给一个变量 heroes
        heroes = HEROES;
    
        constructor() { }
    
        ngOnInit() {
        }
    }
    
    • 在模板中显示数据
    <h2>My Heroes</h2>
    <ul class="heroes">
        <!-- *ngFor="let 单个对象 of 对象数组即我们在类定义中定义的heroes" -->
        <li *ngFor="let hero of heroes">
            <!-- 单个对象.具体属性 -->
            <span class="badge">{{hero.id}}</span> {{hero.name}}
        </li>
    </ul>
    
    • 美化视图,copy官方教程提供的css,复制到heroes.component.css

HeroesComponent “点击事件”,展示英雄详情。

  • 模板绑定点击事件
<!-- (click)="触发函数onSelect(参数hero)" -->
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
  • 类定义中编辑 onSelect() 函数
  // 定义一个变量 被选中的英雄: Hero类型的
  selectedHero: Hero; //注意:此时 selectedHero = undefined
  // 定义一个 onSelect() 方法 给 selectedHero 赋值
  onSelect(hero): void { //函数名(参数列表): 返回值类型 {函数体}
    this.selectedHero = hero; //注意:此时 selectedHero = 参数传递进来的hero
  }
  • 视图中展示被选中的英雄的“详情”
<!-- 英雄详情 -->
<!-- *ngIf="selectedHero有值即为true" -->
<div *ngIf="selectedHero">
  
  <!-- 左边的值 | 调用右边的管道 uppercase全部大写 --> 
  <h2>{{ selectedHero.name | uppercase }} Details</h2>
  <div><span>id: </span>{{selectedHero.id}}</div>
  <div>
    <label>name:
      {{selectedHero.name}}
    </label>
  </div>

</div>

绑定点击事件 (click)="函数(参数)"

*ngIf="判断条件" 为true 则展示div和div内部的数据

{{value | grep}} => 调用管道函数, uppercase将字符串字符全部转换为大写字母显示

  • 排错经验
# Chrome控制台报错 _co.onSelect is not a function(onSelect不是一个函数)
# 找了半天,原来是 onSelect() 函数名写成了 onselect() s小写了

HeroesComponent “双向数据绑定”

  • 先了解一下“模块”:位于 src/app/app.module.ts 由多个组件组成的东西:和组件大同小异的 “3部分”
// 1导包
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';

// 2装饰器
@NgModule({
  // 使用的组件
  declarations: [
    AppComponent,
    HeroesComponent
  ],
  // 使用的其他模块
  imports: [
    BrowserModule
  ],
  // 服务提供
  providers: [],
  // “引导组件”
  bootstrap: [AppComponent]
})

// 3类定义
export class AppModule { }

  • 在AppModule中导入一个模块 FromsModule
// 1
...
import { FormsModule } from '@angular/forms'; //导入FormsModule

// 2在装饰器中真正投入使用
  ...
  imports: [
    BrowserModule,
    FormsModule //让FormsModule真正投入使用
  ],
  ...
  • 在视图中实现双向数据绑定:[(ngModel)]="被绑定的数据"
<div>
    <label>name:
    <!-- [(ngModel)]="被绑定的数据" -->
    <input [(ngModel)]="selectedHero.name" placeholder="name">
    </label>
</div>

这里把 selectedHero.name 属性绑定到了 HTML 的 input 元素上,以便数据流可以双向流动:从 selectedHero.name 属性流动到 input,并且从 input 流回到 selectedHero.name 。

将“英雄详情” 作为一个单独的小组件

  • 创建组件 ng g c hero-detail
  • 分离视图,将HeroesComponent视图模板中的“英雄详情部分”剪切到HeroDetailComponent的模板中,将'selectedHero'全部替换为'hero' (因为这个组件不再仅用于展示"被选中的"英雄的详情了)
<!-- 英雄详情 -->
<!-- *ngIf="hero有值即为true" -->
<div *ngIf="hero">

  <!-- 左边的值 | 调用右边的管道 uppercase全部大写 --> 
  <h2>{{ hero.name | uppercase }} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
      <label>name:
        <!-- [(ngModel)]="被绑定的数据" -->
        <input [(ngModel)]="hero.name" placeholder="name">
      </label>
  </div>

</div>
  • 重点部分 : 使用核心库提供的 Input 类,实现了HeroesComponent.selectedHero与HeroDetailComponent.here的数据绑定

    • 在 HeroDetailComponent 中导入 Input 和 Hero规范类 ,并定义一个 @Input() 属性
    // 1
    ...
    import { Component, OnInit, Input } from '@angular/core'; //Input在核心库中 所以直接在这里导入
    import { Hero } from '../hero'; //规范类
    
    // 3
    ...
    export class HeroDetailComponent implements OnInit {
    
        @Input() hero: Hero; //这样定义该属性以在两个组件之间实现数据绑定
    
        constructor() { }
    
        ngOnInit() {
        }
    
    }
    
    • 在 HeroesComponent 的模板中 HeroDetail中的[hero] 等于 Heroes中的"selectedHero"
    <!-- 被选择英雄详情 -->
    <app-hero-detail [hero]="selectedHero"></app-hero-detail>
    

    莫名其妙的,AppModule崩了,不知道为什么有的字符串跑另一行去了,自己照着语法改了下才正常了。

    分离子组件的好处就是 1提高HeroDetailComponent的复用性 2减少HeroesComponent的耦合

服务

  • 创建服务hero ng g s hero
  • 创建的服务位于 /src/app/hero.service.ts , 同样是3部分
// 1导包
import { Injectable } from '@angular/core';

// 2装饰器
@Injectable({
  providedIn: 'root'
})

// 3具体定义
export class HeroService {

  constructor() { }
}
  • 在服务中,我们依然使用模拟的数据
// 1
...
import { Hero } from "./hero"; //导入规范类
import { HEROES } from "./mock-heroes"; //导入模拟的数据

// 3
export class HeroService {

  // 定义一个方法:获取所有的英雄数据
  getHeroes(): Hero[] {
    return HEROES;
  }

  constructor() { }
}
  • 将服务注册到 AppModule src/app/app.module.ts
// 1
import { HeroService } from "./hero.service"; //导入HeroService

// 2
...
  // 服务提供
  providers: [
    HeroService, //让HeroService真正投入使用
  ],
...
  • 其实我们创建的时候可以通过命令完成服务注册 ng g s hero --module=app
  • 现在我们由HeroService为我们提供数据,所以在HeroesComponent中,我们不需要常量“英雄列表“ HEROES,删除导入它的代码。在构造函数中,使用”依赖注入DI“,构建HeroService。变量 heroes ”英雄列表“的获取方式修改为通过函数 getHeroes() 获取。ngOnInit()方法中调用getHeroes() 方法,使组件被调用时,就能获取英雄列表
// 1删除 HEROES 的导入 导入服务 HeroService
...
import { HeroService } from "../hero.service"; //导入服务

// 3修改 heroes获取的过程
export class HeroesComponent implements OnInit {
  heroes: Hero[]; //修改heroes 这里只声明它的数据类型为 Hero类所实例化对象的集合

  // 构造函数中使用依赖注入的方式 实例化一个私有变量 heroService 为 HeroService 对象
  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.getHeroes(); //在初始化组件时获取”英雄列表“
  }

  // 函数: 获取英雄列表
  getHeroes(): void {
    this.heroes = this.heroService.getHeroes();
  }

  // 定义一个变量 被选中的英雄: Hero类型的
  selectedHero: Hero; //注意:此时 selectedHero = undefined
  // 定义一个 onSelect() 方法 给 selectedHero 赋值
  onSelect(hero): void { //函数名(参数列表): 返回值类型 {函数体}
    this.selectedHero = hero; //注意:此时 selectedHero = 参数传递进来的hero
  }
}
  • 注意上面的过程是一直同步进行的:HeroesComponent组件被加载的时候,实例化 heroService , 初始化函数 ngOninit() 调用组件内定义的 getHeroes() 函数, getHeroes() 函数其实是函数内部调用了 HeroService 提供的 getHeroes() 函数。但是:我们的数据是模拟的,可以保证数据100%获得(因为我们直接写死写在了本地),但是实际开发中,这些数据应该是后台提供的,所以我们从加载组件到通过组件调用服务,通过服务获取数据,最后显示在页面上,是一定会等待的。(也就会出现浏览器白屏等待数据加载的过程),为了提升体验,我们应该是异步完成这些动作的,所以我们使用 Observable 的方式获取数据

    • HeroService 服务导入 Observable
    // 1
    ...
    import { Observable, of } from "rxjs"; //导入 Observable 和 of() 函数
    
    // 3
    export class HeroService {
    
        // 定义一个方法:获取所有的英雄数据
        getHeroes(): Observable<Hero[]> { //这里声明数据类型为 Observable<Hero[]> “可观察的Hero对象集合”
            return of(HEROES); //这里用of(HEROES)返回数据
        }
    
        constructor() { }
    }
    
    • 在HeroComponent中订阅
    // 函数: 获取英雄列表
    getHeroes(): void {
        this.heroService.getHeroes()
            // 订阅数据 heroes 对应 本组件内的heroes
            .subscribe(heroes => this.heroes = heroes);  
    }
    

    同步服务:创建服务->AppModule中注册(providers)->组件中导入(constructor())

    异步服务:从 rxjs 导入 Observable, of ->修改类型为可观察的 Observable<变量类型> -> 返回数据用 of(数据) ->在组件导入用 .subscribe() 订阅

显示信息

  • 具体作用:在页面下方显示提示信息
  • 新建组件MessagesComponent ng g c messages
  • 在壳组件的“布局模板” app.component.html 中添加该组件 => 页面多显示 messages works!
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
  • 新建服务 MessageService 并注册到模块中 ng g s message --module=app

我在创建组件的时候发现 cli 自动帮我导入的代码有问题,有的代码跑到另外一行去了,可能是因为我写过注释,自己排过板的原因导致自动写入的代码不正确

在创建服务并注册的时候,我协商了--module=app 也不能写如AppModule中,只有我自己去导包然后在服务提供中声明

  • 编辑 MessageService
// 3
export class MessageService {
  
  constructor() { }

  // 定义信息变量
  messages: String[] = [];

  // 定义添加信息方法
  add(message: string) {
    this.messages.push(message); //在数组中添加元素
  }

  // 定义清空信息方法
  clear() {
    this.messages = [];
  }
}
  • 编辑 HeroService 让它可以发送一条提示信息
// 1
import { MessageService } from "./message.service"; //导入MessageService

// 3 
...
// 构造函数中 依赖注入 实例化 MessageService
constructor(private messageService: MessageService) { }

getHeroes(): Observable<Hero[]> { //这里声明数据类型为 Observable<Hero[]> “可观察的Hero对象集合”
    this.messageService.add('提示信息: 查询英雄成功'); //当获取数据成功的时候,添加一条提示信息
    return of(HEROES); //这里用of(HEROES)返回数据
} 
...
  • 编辑 MessagesComponent
// 1
...
import { MessageService } from "../message.service"; //导入服务

// 3
export class MessagesComponent implements OnInit {
  //依赖注入: 实例化服务
  constructor(public messageService: MessageService) { } // 注意这里得是公开的属性:Angular 只会绑定到组件的公共属性。

  ngOnInit() {
  }

}

  • 视图显示信息 messages.component.html
<div *ngIf="messageService.messages.length">

  <h2>提示信息</h2>
  <button class="clear" (click)="messageService.clear()">clear</button>
  <div *ngFor='let message of messageService.messages'> {{message}} </div>

</div>
  • 美化视图 messages.component.css 拷贝官网教程上的

路由的使用

  • 添加路由 ng generate module app-routing --flat --module=app

--flat是让路由文件生成在 /src/app 下,而不是 /src/app/app-routing。(注意是--flat,我写了-flat给我创建了一个app-routing/目录)

--module=app 是自动在 AppModule 的 imports[] 中注册路由,然后我基本可以断定:别在AppModule里面添加注释和排版,否则它自动添加的代码会出错,一段代码可能会跑到另外一段代码里面去。

  • 看看路由的代码 src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})

export class AppRoutingModule { }
  • 官网教程说一般不会在路由模块中生命组件,所以可以不导入 CommonModule 并且可以删除 @NgModule.declarations(那你自动生成干嘛?),你需要从 @angular/router 中导入 RouterModule 和 Routes(那你干嘛不自动生成?),然后暴露RouterModule,处理后的代码是这样的
// 1导包
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from "@angular/router"; //导入RouterModule和Routes

// 2装饰器
@NgModule({
  exports: [
    RouterModule //暴露RouterModule以让AppModule可以使用它
  ]
})

// 3具体定义
export class AppRoutingModule { }

  • 添加路由定义:1引入组件 2定义路由常量列表 3在装饰器中导入路由
...
// 导入组件
import { HeroesComponent } from './heroes/heroes.component';
// 路由定义
const routes: Routes = [
  // {path: 路由地址, component: 指向组件}
  {path: 'heroes', component: HeroesComponent},
];

// 2装饰器
@NgModule({
  imports: [
    RouterModule.forRoot(routes) //这里的routes是上面的常量routes,所以路由定义必须写在前面
  ],
  ...
})
...
  • 更新“布局模板”,添加路由链接 a.routerLink 和路由出口 <router-outlet>
<h1>{{title}}</h1>
<nav>
    <!-- routerLink="路由地址" -->
    <a routerLink="/heroes"> 英雄列表 </a>
</nav>
<!-- <app-heroes></app-heroes>  现在不需要这句了 --> 
<!-- 将点击 -->
<router-outlet></router-outlet>
<app-messages></app-messages>
  • 此时访问主页为一片空白,需要点击“英雄列表”才能看到相关内容

  • 创建“仪表盘”组件(一个只显示4个最强英雄的图表) ng g c dashboard

    • 3要素:视图 dashboard.component.html
    <h3>Top Heroes</h3>
    <div class="grid grid-pad">
      <a *ngFor="let hero of heroes" class="col-1-4">
        <div class="module hero">
          <h4>{{hero.name}}</h4>
        </div>
      </a>
    </div>
    
    • 3要素:类定义 dashboard.component.ts
    import { Component, OnInit } from '@angular/core';
    // 导入 Hero 规范类, 导入 HeroService
    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[]; //定义变量 英雄列表
    
      // 构造函数帮我们实例化 HeroService
      constructor(private heroService:HeroService) { }
    
      ngOnInit() {
        this.getHeroes(); //初始化函数调用获取英雄数据方法
      }
    
      // getHeroes() 获取英雄数据
      getHeroes(): void {
        this.heroService.getHeroes()
          // 只获取前5个 
          .subscribe(heroes => this.heroes = heroes.slice(0, 4));
      }
    
    }
    
    • 3要素:样式 (官网copy,路由那一节有所有成型的组件需要的所有 css 全部拷贝)
  • 添加仪表盘路由和默认路由 app-routing.module.ts

import { DashboardComponent } from "./dashboard/dashboard.component";

{ path: 'dashboard', component: DashboardComponent },
// 添加一条默认路由,即 localhost:4200 后面啥也没有的路由
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }
  • 添加“仪表盘”路由链接到“布局模板”
...
<a routerLink="/dashboard"> 仪表盘 </a>
...
  • 英雄详情: 现在再是在 “英雄列表”HeroesComponent下的一个小组件了,而是无论从“仪表盘”还是“英雄列表”中点击,都独立展示英雄详情的一块

    • 从HeroesComponent的模板中删除标签 <app-hero-detail [hero]="selectedHero"></app-hero-detail>
    • 给HeroService 添加一个方法 : 根据id获取英雄详情 并将获取的数据对象作为返回值
    // 3
    ...
    // 获取单个英雄的数据
    getHero(id: number): Observable<Hero> {
      this.messageService.add(`查看编号为${id}的英雄详情`); //添加提示信息
      return of(HEROES.find(hero => hero.id === id)); //根据id查询讯息 HEROES.find
    }
    
    • 3要素:模板 hero-detail.component.html
    <!-- 英雄详情 -->
    <!-- *ngIf="hero有值即为true" -->
    <div *ngIf="hero">
    
      <!-- 左边的值 | 调用右边的管道 uppercase全部大写 --> 
      <h2>{{ hero.name | uppercase }} Details</h2>
      <div><span>id: </span>{{hero.id}}</div>
      <div>
          <label>name:
            <!-- [(ngModel)]="被绑定的数据" -->
            <input [(ngModel)]="hero.name" placeholder="name">
          </label>
      </div>
    
      <!-- 添加一个返回的按钮 -->
      <button (click)="goBack()">返回上层</button>
    
    </div>
    
    • 3要素:类定义 hero-detail.component.ts
    // 1
    ...
    import { ActivatedRoute } from '@angular/router'; //该类主要处理url中参数
    import { Location } from '@angular/common'; //导入服务后才可以利用 它的通过id获得详情的函数 getHero(id)
    import { HeroService }  from '../hero.service'; //该类主要处理浏览器的功能实现
    
    // 3
    export class HeroDetailComponent implements OnInit {
    
      @Input() hero: Hero; //定义该属性
    
      constructor(
        // 依赖注入 实例化上面导入的类
        private route: ActivatedRoute,
        private heroService: HeroService,
        private location: Location
      ) { }
    
      ngOnInit(): void {
        // 初始化方法中 组件被激活则调用查询英雄数据的方法
        this.getHero();
      }
      
      getHero(): void {
        // 通过ActivatedRoute.snapshot.paramMap.get() 方法取得 参数id(英雄编号)
        const id = +this.route.snapshot.paramMap.get('id');
        this.heroService.getHero(id) //将id作为参数订阅英雄信息
          .subscribe(hero => this.hero = hero);
      }
    
      goBack(): void {
        // 通过 Location.back() 返回上一级页面
        this.location.back();
      }
    
    }
    
    • 创建路由 app-routing.module.ts
    import { HeroDetailComponent } from "./hero-detail/hero-detail.component";
    // 带参数的路由 path:'路由地址/:参数占位符'
    { path: 'detail/:id', component: HeroDetailComponent },
    
    • 在“英雄列表”和“仪表盘” 的视图上 给他们绑定链接,显示“英雄详情”
    # 英雄列表
    <!-- routerLink="地址/参数" -->
    <a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </a>
    
    #仪表盘
    <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}">
    

创建路由定义文件 ng generate module app-routing --flat --module=app , --flat让该文件创建在 AppModule 同级目录下, --module=app 让 cli 帮我们完成路由在 AppModule 中的自动写入

路由定义文件应该是这样的

// 1
import { RouterModule, Routes } from "@angular/router"; //导入RouterModule和Routes

// 紧接着导入组件 和 配置路由列表
// 导入组件
import { HeroesComponent } from './heroes/heroes.component';
import { DashboardComponent } from "./dashboard/dashboard.component";
import { HeroDetailComponent } from "./hero-detail/hero-detail.component";
// 路由定义
const routes: Routes = [
  // {path: 路由地址, component: 指向组件}
  { path: 'heroes', component: HeroesComponent },
  { path: 'dashboard', component: DashboardComponent },
  // 带参数的路由 path:'路由地址/:参数占位符'
  { path: 'detail/:id', component: HeroDetailComponent },
  
  // 添加一条默认路由,即 localhost:4200 后面啥也没有的路由
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
];

// 2装饰器中
// 2装饰器
@NgModule({
  imports: [
    RouterModule.forRoot(routes) //实现路由功能:这里的routes是上面的常量routes,所以路由定义必须写在前面
  ],
  exports: [
    RouterModule //暴露RouterModule以让AppModule可以使用它
  ]
})

在页面调用路由的时候:

# 在布局模板应该用路由出口标签
<router-outlet> </router-outlet>
# 路由的链接a标签的写法应该是
<a routerLink="路由地址/{{参数如果有的话}}">

单机版开发总结

为什么叫单机版: 因为我们的“英雄列表”是在本地建了一个hero.ts作规范,mock-heroes.ts根据规范模拟出来的数据。没有真正的和服务器沟通。

  1. 组件 Component ng g c 组件名
  • 壳组件 AppComponent
    • 模板 html => 必须
    • 定义 ts => 必须
    • 样式 css => 可有可无
    • 测试 spec.ts => 可有可无
    • 壳组件就是“根组件”,它的模板就是“布局模板”,它的样式就是“通用样式”
  • 子组件 HeroesComponent / HeroDetailComponent / DashboardComponent / MessagesComponent => 前期子组件通过它们装饰器中.selector定义的标签的形式插入到根组建的模板中,后期我们使用路由后用< router-outlet> 路由出口标签实现载入组件。
  • 一个组件的定义通常分3部分
// 1 : 导入需要使用的"包"
import { Something } from '包的相对路径';

// 2 : 装饰器,声明组件要渲染的标签,组件使用的模板,样式文件的地址
@Component({
  selector: '标签',
  templateUrl: '模板',
  styleUrls: ['样式1', '样式2']
})

// 3 : 具体定义,默认自带构造函数,初始化函数,我们还可以在其中自定义变量和函数
export class DashboardComponent implements OnInit {

  // 我们通常在 构造函数 中完成依赖注入
  constructor() { }

  ngOnInit() {
    // 我们通常在 初始化函数 中调用那些组件一旦载入就需要使用到的方法
  }

}
  1. 模块 Module 我们只有一个模型 app.module.ts
// 1导包
import { Something } from 'Somewhere';

// 2装饰器
@NgModule({
  // 使用的组件
  declarations: [],
  // 使用的其他模块
  imports: [],
  // 服务提供
  providers: [],
  // “引导组件”
  bootstrap: []
})

// 3类定义
export class AppModule { }
  1. 模板 视图 即 组件的html 组件名.component.html
  • 模板插值 {{变量名}}
  • 路由导航 <a routerLink="path"> 导航名字 </a>
  • 路由出口 <router-outlet></router-outlet>
  • 载入子组件,标签名由 子组件的装饰器 定义 <app-messages></app-messages>
  • 遍历数据 *ngFor="let 提取出来的单个对象 of 对象集合"
  • 显示被遍历的数据的具体属性 {{当前对象.属性}}
  • 绑定点击事件 (click)="函数名(参数列表)" 事件触发后的函数写在组件的第3部分具体定义中。
  • if判断 *ngIf="条件" 写在标签的属性里,则当条件为true的时候,宿主标签才会在页面上显示。
  • 管道函数的调用 {{ 参数 | 管道函数 }} ,我们用过 uppercase :将字符串的小写字母全部转换为大写。
  • 数据双向绑定
# 在 模块中 导入 FormsModule
import { FormsModule } from '@angular/forms';
# 在 组件视图上绑定该数据 
<input [(ngModel)]="hero.name" placeholder="name"> <!-- [(ngModel)]="对象.具体属性" -->
  1. 组件与组件之间通讯(后面我们没用了)
  • 父组件定义了一个变量 selectedHero 和一个方法 onSelect() ,同时给所有被遍历得列表项绑定了点击事件,当他们被点击的时候给触发 onSelect() ,给 selectedHero 赋值。
# 父组件模板中导入子组件
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<!-- [子组件的变量hero] = "父组件的变量selectedHero" -->
  • 子组件到如Input , 并且定义变量 hero 的时候 加上 @Input() 修饰
// 1导入Input
import { Component, OnInit, Input } from '@angular/core';

// 3定义变量时加上修饰
@Input() hero: Hero;
  1. 服务 Service ng g s 服务名
  • 创建服时直接注册该服务到模块 ng g s 服务名 --module=模块名
  • 如果没写 --module=app 的话,需要在AppModule中自己导入和注册
// 1导包
import { XxService } from "./xx.service"; 

// 2装饰器中真正投入使用
...
// 服务提供
providers: [
  XxService, //让XxService真正投入使用
],
...
  • 服务定义也是 3部分
// 1导包
import { Injectable } from '@angular/core';

// 2装饰器
@Injectable({
  providedIn: 'root'
})

// 3具体定义
export class HeroService {

  // 构造函数
  constructor() { }

}
  • 服务主要作用是和后台沟通,处理数据,但是我们是用得本地的数据,所以实际上服务的作用没有体现出来
  • Observable 和 of() 的使用 (使数据的获取是异步的)
// 1 服务定义里面导包 "rxjs"
import { Observable, of } from "rxjs";  

// 3 服务定义里面具体的用法
函数(): Observable<返回值类型> {
  return of(返回的数据);
}
  • 在组组件中使用服务:导入服务(确保已经在模块中注册),构造函数中用依赖注入的方法实例化服务。
// 1 导入服务
import { XxService }  from '../xx.service';

// 3 构造函数中实例化服务
constructor(
  // 依赖注入 实例化上面导入的类
  权限修饰符 属性: 导入的服务类;
  权限修饰符 属性: 导入的服务类;
  权限修饰符 属性: 导入的服务类;
) { }
  • 在 组件 中 订阅subscribe() 服务 提供的数据
// 写一个方法调用
函数名(参数列表): 返回值类型 {
  this.依赖注入时实例化的服务类.服务类中提供的数据获取方法()
    .subscribe(数据 => this.定义的变量 = 服务类方法提供的数据)
    .subscribe(hero => this.hero = hero); // 获取全部数据
    .subscribe(heroes => this.heroes = heroes.slice(0, 4)); //获取前5条
} 
  • 在将函数写进 ngOnInit() 函数中以便在组件生成时候直接调用
ngOnInit() {
  this.函数();
}
  1. 路由
  • 创建路由定义文件 ng generate module app-routing --flat --module=app

    • --flat 是让该文件生成在 /src/app 下而不是单独新建一个文件
    • --module=app 是将该文件直接导入 AppModule 中
    // 1
    ...
    import { AppRoutingModule } from './/app-routing.module';
    
    // 2
    // 使用的其他模块
    imports: [
      BrowserModule,
      FormsModule,
      AppRoutingModule
    ],
    
  • 路由的定义 (其实我们是创建了一个模块)

// 1导包
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from "@angular/router"; //导入RouterModule和Routes

// 导入组件
import { HeroesComponent } from './heroes/heroes.component';
import { DashboardComponent } from "./dashboard/dashboard.component";
import { HeroDetailComponent } from "./hero-detail/hero-detail.component";

// 路由定义
const routes: Routes = [
  
  // {path: '路由地址', component: 指向组件}
  { path: 'heroes', component: HeroesComponent },
  { path: 'dashboard', component: DashboardComponent },
  
  // 带参数的路由 path:'路由地址/:参数占位符'
  { path: 'detail/:id', component: HeroDetailComponent },
  
  // 添加一条默认路由,即 localhost:4200 后面啥也没有的路由
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
];

// 2装饰器
@NgModule({
  imports: [
    RouterModule.forRoot(routes) //这里的routes是上面的常量routes,所以路由定义必须写在前面
  ],
  exports: [
    RouterModule //暴露RouterModule以让AppModule可以使用它
  ]
})

// 3具体定义
export class AppRoutingModule {}

定义路由时 path: '不能有/'

使用路由时 routerLink="/可以有而且官方教程上有但是我试过也可以没有"

相关文章

网友评论

    本文标题:AngularJS官方教程解读攻略

    本文链接:https://www.haomeiwen.com/subject/rjefdftx.html