angular学习笔记

基础

ng new small-crm:新构建一个项目small-crm。

ng serve --open --port=4200 运行该服务,默认端口为4200。

ng build:打包命令。

ng generator:生成模块module、组件component、服务service、管道pipe等。

组件知识

属性详情
declarations那些属于本 NgModule 的组件指令管道
exports那些能在其它模块的组件模板中使用的可声明对象的子集。
imports那些导出了模块中的组件模板所需的类的其它模块。
providers本模块向全局服务中贡献的那些服务的创建器。这些服务能被本应用中的任何部分使用。(你也可以在组件级别指定服务提供者。)
bootstrap应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性。

Angular 服务为你提供了一种分离 Angular 应用程序。

绑定

Attribute 和 property的区别

HTML Attribute是HTML标签上的特性,值只能是字符串;其作用是初始化元素和指令的状态,HTML Attribute初始化DOM property后,任务就完成了。不推荐在初始化后修改Attribute的值。

DOM property是DOM中的属性,是JS中的对象。一般工作中,改变的是property值,且更改后attribute值不变。

属性property绑定

   [property]中括号  是从组件变量影响到模版属性。属性绑定就是将一个变量赋予到属性上,使该属性获得组件中变量的值,从而使属性值动态化。

为img标签中的src属性进行属性绑定的例子如下:

src/app/app.component.html 中:

<img [src]="itemIamgeUrl" alt="item">

src/app/app.component.ts中,为 itemImageUrl 赋值:

itemImageUrl = '../assets/phone.png';

上例中,将变量itemImageUrl绑定至src属性,变量可以在ts文件中修改。

事件绑定

(事件)是从模版至组件,事件发生时会调用组件中的方法,使用圆括号 ( ) 作为事件监听器,等号右侧是用引号包裹的函数。如下所示是完成列表功能的例子:

<form [formGroup]="applyForm" (submit)="submitApplication()">

submitApplication函数需要在ts文件中指定,如下:

  submitApplication() {
    this.housingService.submitApplication(
      this.applyForm.value.firstName ?? '',
      this.applyForm.value.lastName ?? '',
      this.applyForm.value.email ?? '',
    )
  }

文本内容赋值绑定

使用双层 {{}} ,常用于p标签、h标签。

属性attribute绑定

<p [attr.attribute-you-are-targeting]="expression"></p>

属性attribute的绑定与property的绑定不一样,一般对Attribute属性的修改不多。

*ngIf="condition"

*ngIf="condition"表示当condition为true时,保留执行该条HTML语句;当condition为false时,不执行该条HTML。

<div *ngIf="condition">condition为true时,执行该条HTML语句。</div>

then和else均可选。如果需要then和else模块,写成如下形式。注意分号、注意引号

<div *ngIf="condition;then thenBlock else elseBlock"></div>
<ng-template #thenBlock>condition为true时执行。</ng-template>
<ng-template #elseBlock>condition为false时执行。</ng-template>

组件

一个功能模块的组件由ts、html、css文件构成,创建组件的方法:ng generate component name,会自动生成这些文件。

在ts文件中,由两部分构成:@component和export class。

@component中,包含selector、templateUrl、styleUrls。其中selector表示为模版HTML确定要实例化的位置,比如:hello组件中,选择器为app-hello,则当<app-hello>出现在模版中时,选择器就会让angular实例化该组件。templateUrl、styleUrls分别指定了当前组件的模版和css样式。

组件的生命周期

将组件的生命周期视为一个齿轮,当齿轮转动时,齿轮上的突起也会发生变化。钩子函数是指与特定生命周期阶段绑定的函数,常见的钩子函数有如下几种:

constructor构建函数时永远首先被调用
ngOnInit组件初始化时被调用,通常在构建函数后被调用(class implement ngOnInit)
ngOnChanges输入属性变化时会触发,返回changes为改变的属性,包括属性当前值、之前值等。
ngDoCheck脏值检测,不仅仅是属性变化,会监测到鼠标等的变化。因此较为频繁。
ngAfterContentInit内容投影ng-content完成后调用
ngAfterContentCheck脏值检测后调用
ngAfterViewInit视图组件初始化完成,视图是指 本组件及其子组件的初始化完成
ngAfterViewCheck视图脏值检测

父子组件交互Input Output

 父组件向子组件传递

父组件通过input想子组件传递数据。为模拟真实情况,定义了一个ts文件用来定义接口和实现类,实现类中包含测试数据。

export interface Hero {
    name: string
}

export const HEROS: Hero[] = [
    {
        name: "Jack"
    },
    {
        name: "Mick"
    },
    {
        name: "Yona"
    },
]

子组件中:

首先import input、HEROS,注意这里引入的是实现类,不是接口也不是ts文件。然后使用@Input()装饰器,在class中接收相关属性,括号内可填写该属性在子组件中的重命名。属性的名称最好与父组件模版中的属性名称相同,最好不要重命名。

export class TestComponent {
  //声明为Hero的接口类型
  @Input() hero!: Hero;
  // 声明为字符串类型(类型为自动推断的),初始值为空字符串,
  @Input() master='';
}

子组件的HTML模版中可以正常使用hero、master的值:

<p>Hero's name is {{hero.name}}</p>
<p>Master is {{master}}</p>

父组件中:

同样需要首先import HEROS类,并在class类中定义相关属性:

export class AppComponent {
  //此处引入的是hero.ts中的接口实现类HEROS
  heros = HEROS;
  master = 'myMaster';

在父组件的模版类中,实现的功能为遍历HERO和master。采用属性绑定的方法,在app-hero-child定位处的标签中的属性进行定义,如下所示:

<app-hero-child *ngFor="let hero of heros" [hero]="hero" [master]="master">

</app-hero-child>

其中,app-test标签表示,在此处执行selector为“app-test”的组件。[hero]="hero"表示将属性hero(同时也是传递给子组件的属性)绑定为heros数组中的hero,方括号中的hero为子组件中的hero属性,引号中的hero为父组件中的hero。master同理。

子组件向父组件传递

子组件向父组件传递参数则需要借助EventEmitter来监听事件。

EventEmitter的实际开发应用场景:子指令创建一个EventEmitter实例,并将其作为输出属性导出,子指令调用已创建的EventEmitter实例中的emit(payload)方法来触发一个事件,父指令通过事件绑定(eventName)的方式监听该事件,并通过$event对象来获取payload对象。例子如下:

子组件里定义一个EventEmitter作为事件载体:

@Output() newItemEvent = new EventEmitter();//newItemEvent为子组件的事件载体。

通过Event的方式发送数据给parent Component,在子组件中:

export class ItemOutputComponent {

    //定义一个EventEmitter<string>类型的对象
  @Output() newItemEvent = new EventEmitter<string>();

    //这里的addNewItem需要与HTML中一致。
  addNewItem(value: string) {
    //为上述对象增加负载
    this.newItemEvent.emit(value);
  }
}

子组件的html模版为,表明需要设置addNewItem的触发事件:

<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>

至此,子组件已经形成了带有payload的事件对象,父组件接受该对象,需要在html中指定:

//newItemEvent表示监听的子组件中定义的事件对象,addItem是父组件中的方法。
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

newItemEvent是子组件加了@Output注解的property名称,addItem是父Component的事件处理函数:

export class AppComponent {
  items = ['item1', 'item2', 'item3', 'item4'];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

模版在组件类中的引用

在组件中控制html标签中的内容,需要  “#”  给定名称,然后在ts文件中使用@ViewChild。

  <div class="image-slider" #imageSlider>
    <img *ngFor="let slide of sliders" [src]="slide.imgUrl" [alt]="slide.caption" />
  </div>
  @ViewChild('imageSlider', { static: true })imageSlider!: ElementRef;

这里的imageSlider就是获取到模版中image-slider的DOM对象,可以对其进行操作。

@ViewChild引用多个模版

使用@ViewChildren,这里的名称imgs一定要跟模版中匹配。

  @ViewChildren('imgs') imgs!: Array<ElementRef>;
    this.imgs.forEach(item=>{
      this.rd2.setStyle(item.nativeElement,'height','100px');//统一设置高度
    })

内容投影

<ng-content>是使用外部组件(包括父组件)内容的一个标记符。

在“父子数据共享-@Input装饰器”的学习中,父组件模版中加入了子组件的selector,但是并未写入任何内容。实际上,写入内容也不会执行,因为子组件并未指定<ng-content>,即并未在子组件中设置相应的位置容纳父组件传递的内容。内容示意如下:

父组件中:

<child> <p>父组件传递一些内容,一般为静态</p> </child>

子组件中若不进行处理,则无法加载上述p标签里的内容。

<ng-content>只是一个占位符,包括:单槽内容投影、多槽内容投影、条件内容投影。

单槽如上,重点是多槽和条件内容投影。

多槽内容投影

<ng-content>可以添加select属性,多槽内容投影通过多个<ng-content>实现。

例子如下,在子组件中:

@Component({
  selector: 'app-zippy-multislot',
  template: `
    <h2>Multi-slot content projection</h2>

    Default:
    <ng-content></ng-content>

    Question:
    <ng-content select="[question]"></ng-content>
  `
})

上述被插入内容的ts文件中,给了两个<ng-content>占位符,后一个指定了select属性,指向“question”这个property,意思是,包含question属性的dom才会被插入。父组件中,question如下:


<app-zippy-multislot>
  <p question>
    Is content projection cool?
  </p>
  <p>Let's learn about content projection!</p>
</app-zippy-multislot>

得到结果如下:

Default:

Let's learn about content projection!

Question:

Is content projection cool?

上述结果表明,select属性能够选择展示的标签或组件名称,如div、class等。

条件内容投影

如果在子组件中,需要有条件地渲染内容或多次渲染内容,则应配置该元件以接受一个 ng-template 元素,其中包含要有条件渲染的内容。

组件样式隔离

组件样式隔离是对css的补充,这里主要学习三个选择器的用法.

①  :host

:host表示选择当前元素, 作用范围在<app-root>标签之内. 而不会涉及到引入的子组件或者外部组件中的元素(nz-modal标签也不会涉及).如果后面不加任何DOM元素的话,就表示整个HTML.

如果后面加其他DOM元素,则表示样式仅在该DOM元素内部生效.

② :host ::ng-deep

:host ::ng-deep拓展了:host的用法,表示能对当前html中的子组件或第三方组件样式生效,但不会对其他HTML中同一个元素产生作用.

解释如下:

A引用了C,B也引用了C,如果要对A中的C的div组件进行特别设置,可以使用 ":host::ng-deep div{}" 的方式进行设置.

注意,::ng-deep最好不要单独使用,这表示直接操作第三方组件的样式.

③ :host-context

字面意思就是宿主的上下文信息,表示宿主的祖先元素.宿主一般用括号加入.如果直接加上HTML中的DOM元素,则使用功能默认宿主,即整个html页面.

:host-context h1{
    color:hotpink;
}

表示直接定位h1标签.

若要制定宿主,则用括号包裹.

:host-context (.theme-light) h1{
    color:hotpink;
}

表示定位(theme-loight)类中的标签.

依赖注入

angular为了解决全局数据共享的问题,构建了服务service的概念。服务也是一个ts文件,包含与视图无关的逻辑,如输入验证、获取服务器数据等业务逻辑

 依赖注入的几个要素

依赖注入的要素包括:@Injectable()、@Inject()、注入器、提供者。后文均会涉及到。

创建可注入服务

创建一个HeroService的服务类作为被注入者:ng generate service hero,内容如下:

包含@Injectable,表示该类是可注入的。root表示当前类在根目录中提供,即全局可以被注入。

在其他服务中注入依赖服务

constructor中调用,或者: 

class MyClass{
constructor(@Injector HeroService heroservice : HeroService){
}
}

服务提供者providers

暂时未用到,后续补充。

Angular样式设定-NgClass NgStyle

精通Angular之NgClass和NgStyle - 知乎原文链接原作者:Erxk Verduin 译者: 知乎用户精通 Angular 模板语法基础知识之二。 源代码: erxkv-ngclass-ngstyle - StackBlitz本文目标:学习 Angular 的 NgClass 和 NgStyle 指令。 本文主题: 探讨 NgClass 和…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/95490706

Angular directive 之 ngClass、ngStyle | 更文挑战第7天 - 掘金这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战 ngClass 用法,不同情况添加不同的 class 有如下css样式 1.直接传入一个 class 字符串 渲染为 2. 传icon-default.png?t=N7T8https://juejin.cn/post/7029201055640453127

Angular学习之详解样式绑定(ngClass和ngStyle)的使用 (gxlsystem.com)icon-default.png?t=N7T8https://www.gxlsystem.com/qianduan-2378859.html

ngClass

ngClass通过选择类属性的方式来设置当前标签对应的样式,选择不同的类,会确定对应的样式。简单来说,ngClass可以定义当前标签的类名,可以选择多个类名(后文有解释),这样就可以灵活设置样式了.ngClass支持的格式有:字符串、数组、 对象。如下:

其中,isDangerProp均为类名。举个例子:

假设有如下样式:

.class1 {  color : red }
.class2 {  width : 10px }
.class3 {  height : 10px }
.class4 {  opacity : .5 }

若要渲染为:<p class="class1 class2"></p>,表示使用class1和class2的样式。可以如下表示:

  1. 采用字符串形式:[ngClass]="class1 class2"
  2. 采用数组形式:[ngClass]="['class1','class2']"
  3. 采用对象形式:[ngClass]="[ 'class1':true , 'class2':true , 'class3':fasle ]"

例子如下:在要设置样式的元素上,添加 [ngClass]并将其设置为等于某个表达式。在这里,是在 app.component.ts 中将 isSpecial 设置为布尔值 true。因为 isSpecial 为 true,所以ngClass就会把special类应用于此div上。

<!-- toggle the "special" class on/off with a property -->
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

还可以将ngClass看作属性property,采用属性绑定的形式。

<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
currentClasses: Record<string, boolean> = {};
/* . . . */
setCurrentClasses() {
  // CSS classes: added/removed per current state of component properties
  this.currentClasses =  {
    saveable: this.canSave,
    modified: !this.isUnchanged,
    special:  this.isSpecial
  };
}
//使用表达式
<div [ngClass]="'svg-notification-' + type">11</div>
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

//使用对象
<div [ngClass]="{'status-flag-process': data.controlStatus === 1, 'status-flag-success': data.controlStatus === 2}">22</div>
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>

this.currentClasses =  {
      saveable: true,
      modified: false,
      special:  true
    };         

ngStyle

ngStyle必须与对象绑定,一般采用{}包裹。

例子:设定块的高度。模版中使用ngStyle: [ngStyle]="{'height':'sliderheight'}"。

<div class="container">
  <div class="image-slider" #imageSlider [ngStyle]="{'height':'sliderheight'}">
    <img #imgs *ngFor="let slide of sliders" [src]="slide.imgUrl" [alt]="slide.caption" />
  </div>
  <div class="nav-section">
    <span class="slide-button" *ngFor="let _ of sliders;let index = index"></span>
  </div>
</div>

ts文件中需要Input:  @Input() sliderheight = '130px';   注意: 名称需要一致。

ngModel

ngModel主要用来实现模版属性 和 组件变量之间的双向绑定,一般在input中或者是form中使用。注意:使用双向绑定,必须要引入FormsModule

在input中使用

<!-- <input type="text" [value]="inputVal" (input)="inputVal = $event.target.value" /> -->
<input type="text" [(ngModel)]="inputVal" name="name">

<p>inputVal:{{inputVal}}</p>

在form中使用

<form>
  <input type="text" [(ngModel)]="formVal">
  <p>双向绑定input使用案例2:{{formVal}}</p>
</form>

注意,上述使用不生效。form中的使用必须采用以下两种形式:

Example 1: <input [(ngModel)]="person.firstName" name="first">
Example 2: <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}">

指令

指令分为组件、结构性指令、属性型指令。其中组件就是带模版的指令;结构性指令能改变宿主文档结构;属性型指令可以更改DOM元素和angular组件的外观和行为。

内置型指令包括属性型指令:ngClass、ngStyle、ngModel等。

还包括结构性指令:ngIf、ngFor、ngSwitch。

自定义属性型指令

属性型指令可以更改DOM元素和angular组件的外观和行为。

路由

angular应用是单页应用,路由是用来切换不同组件的桥梁。主要步骤分为定义路由数组、路由占位符、路由模版。路由的顺序很重要。

一般是构建新的路由模块,承载整个项目的路由,module.ts文件如下所示:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';


import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroesListComponent } from './heroes-list/heroes-list.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

// 定义路由数组
const appRoutes: Routes = [
  { path: 'crisis-list', component: CrisisListComponent },
  { path: 'heroes-list', component: HeroesListComponent },
  // 子路径为空时,默认导向到heroes页面
  { path: '', redirectTo: 'heroes-list', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

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

    //使用js数组定义路由,forRoot方法表示只产生单例对象
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true }
    ),
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule { }

一般会将路由文件独立,从而避免启动文件的臃肿。因此新建routingconfig文件保存路由信息,如下:

export const myrouting :Routes=[
    { path: 'first-component', component: FirstComponent },
    { path: 'second-component', component: SecondComponent }
]

在module.ts文件中,导入myrouting即可。

基本配置:

路由数组:

类似于字典,记录了组件与路由的配对信息:

    RouterModule.forRoot([
      { path: 'crisis-list', component: CrisisListComponent },
      { path: 'heroes-list', component: HeroesListComponent },
      // 子路径为空时,默认导向到heroes页面
      { path: '', redirectTo: 'heroes-list', pathMatch: 'full' },
      { path: '**', component: PageNotFoundComponent },
    ]),

路由插座:

<router-oulet>是路由插座,规定了下级路由插入的锚点。在上级模版中增加该路由插座,则会导入路由对应的下级模块。在父组件中:

<nav>
  <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton">Crisis
    Center</a> \
  <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton">Heroes Center</a>
</nav>
<router-outlet></router-outlet>

路由模版:

下述模版用来确定点击后跳转的路由,以及点击后设置样式的钩子函数。routerLink是路由特有属性,赋值为路由数组中的path路径;routerLinkActive表示当前被激活的路由,activebutton是被激活的动态路由名,可作为DOM节点设置css样式,格式与class格式(此处是 ".activebutton" )一致。

  <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton">Crisis
    Center</a>

路由的通配符:** 

      { path: '**', component: PageNotFoundComponent },

高级路由配置(地址路由、查询路由)

包含路径参数

还包含其他参数

管道

管道使用简单的方式,将输入值转化为合适格式的输出值.常用的api如下:

  • DatePipe:根据本地环境中的规则格式化日期值。

  • UpperCasePipe:把文本全部转换成大写。

  • LowerCasePipe:把文本全部转换成小写。

  • CurrencyPipe:把数字转换成货币字符串,根据本地环境中的规则进行格式化。

  • DecimalPipe:把数字转换成带小数点的字符串,根据本地环境中的规则进行格式化。

  • PercentPipe:把数字转换成百分比字符串,根据本地环境中的规则进行格式化。

比如使用DatePipe,表示将数据转换为对应的日期格式,该格式还能自定义:{{myDate | date:'yyyy-MM-dd'}}.

管道还能串联,直接叠加在后面即可.

服务service

Angular中,数据最好不要都写在组件中,而是推荐写在service中,因此,service提供了数据服务。

本例中,目标是给出模版需求的heros数组,有两种方式,一是直接input了ts文件中的herolist;二是使用service。

方式二的例子如下:使用service。首先定义空数组 heros ;构造函数中将heroservice私有化,然后定义getHeros方法为本ts中的heros赋值;最后在钩子方法ngOnInit中调用该方法,表示组件初始完成后,数组资源也加载完毕。

  // @Input() heros = heroeslist;
  heros: Hero[] = [];
  myhero = '';
  selecthero?: Hero;

  selectbutton(hero: Hero) {
    // 捕获点击事件后,设置selecthero
    this.selecthero = hero;
    console.log('当前点击hero为', this.selecthero.name);
  }
  constructor(private heroservice: HeroService) {

  }

  getHeroes(): void {
    this.heros = this.heroservice.getHeroes();
  }
  ngOnInit(): void {
    this.getHeroes();
  }

方法一的做法是直接import了herolist数组。看似简单,但耦合度过高,无法异步加载数据。

使用异步返回hero数据

hero.service.ts文件中的getHeroes方法在某些情况下可能不会与主函数同步,因此需要将其包装为异步方法。

首先 改造 hero.service.ts 文件中的getHeroes方法,将heroeslist包装为Observable对象。

  // 返回一个Observable的泛型对象。
  getHeroes(): Observable<Hero[]> {
    const heroes = of(heroeslist);
    return heroes;
  }

然后 调整heroes.component.ts中的getHeroes方法:

  getHeroes(): void {
    this.heroservice.getHeroes().subscribe(heroes => this.heros = heroes);
  }

实操:英雄之旅

demo结构

该案例包含参数路由,还包含一些父子组件通信、异步服务的练习。首页包含两个component:DashBoard、Heroes。DashBoard时单页面路由,点击DashBoard路由后会展示dashboard组件模版以及hero组件模版(前三名hero信息),点击hero名称会跳转至detail路由,此路由路径中包含id参数。

结构如下:

View navigations

未使用http的情况

表示使用axjs的of函数模拟http的observable结果。

步骤:routing编写;下游子组件编码获取id:通过订阅方式进行;修改service相应方法的逻辑,返回observable;上游父组件设置routerLink。

首先是my-routing自定义路由组件的内容:

import { Routes, RouterModule } from '@angular/router';
import { HeroComponent } from './hero/hero.component';
import { CrisisComponent } from './crisis/crisis.component';
import { PageNotFoundComponent } from './pageNotFound/pageNotFound.component';
import { HeroDetailComponent } from './heroDetail/heroDetail.component';
import { DashboardComponent } from './dashboard/dashboard.component';

const routes: Routes = [

  // { path: 'crisis', component: CrisisComponent },
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
  { path: 'detail/:id', component: HeroDetailComponent },//路径参数路由的格式
  { path: 'dashboard', component: DashboardComponent },
  { path: 'hero', component: HeroComponent },
  { path: '**', component: PageNotFoundComponent },
];

export const MyRouteRoutes = RouterModule.forRoot(routes);

其中,detail/:id是携带路径参数 id 的路由。在detail组件中,首先需要获取该id,从而得到 id 对应的hero。detail组件如下:

import { Component, Input, OnInit } from '@angular/core';
import { Hero } from '../hero/heroRes';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from '../hero/hero.service';

@Component({
  selector: 'app-heroDetail',
  templateUrl: './heroDetail.component.html',
  styleUrls: ['./heroDetail.component.css']
})
export class HeroDetailComponent implements OnInit {
  hero?: Hero;

  getHero(): void {
    // 首先取当前路径中的id参数
    const id = Number(this.acticatedRoute.snapshot.paramMap.get('id'));
    this.heroService.getHero(id).subscribe(hero => this.hero = hero);
  }

  constructor(private acticatedRoute: ActivatedRoute, private location: Location, private heroService: HeroService) { }

  ngOnInit() {
    this.getHero();
  }

}

注意,getHero() 方法包含了订阅函数subscribe,因此要在ngOnInit钩子方法中调用getHero方法,保证在组件初始化后就能运行。

上述getHero()方法还调用了this.heroService中的getHero()方法,该方法是用来模拟返回一个observable对象(模拟远程http调用行为)。如下:

import { Injectable } from '@angular/core';
import { Hero, myHeroList } from './heroRes';
import { Observable, of } from 'rxjs';


@Injectable({
  providedIn: 'root'
})
export class HeroService {

  // getHeroList(): Hero[] {
  //   return myHeroList;
  // }
  getHeroList(): Observable<Hero[]> {
    const hero = of(myHeroList)
    return hero;
  }

  getHero(id: number): Observable<Hero | undefined> {
    // const hero = myHeroList.find(h => h.id === id);
    return of(myHeroList.find(h => h.id === id));
  }
  constructor() { }

}

再复习一下detail模版的功能,主要是展示hero具体信息,并提供重命名功能,主要用到双向绑定。如下:

<div *ngIf="hero">
  <span>{{hero.name}}</span>
  <p>Id:{{hero.id}}</p>
  <div>
    <input [(ngModel)]="hero.name">
  </div>
</div>

detail组件的内容基本完成,还是需要router-outlet标签进行占位 ,表示路由下游的组件的插入位置. 如下:

dashboard:

<ul class="topHeroes">
  <li class="topHero" *ngFor="let topThreeHero of topThreeHeroes" routerLink="/detail/{{topThreeHero.id}}">
    {{topThreeHero.name}}</li>
</ul>

hero:

<ul>
  <li *ngFor="let hero of herolist">
    <!-- 同样需要设置detail的跳转路由 -->
    <a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </a>
  </li>
</ul>

加入http功能

http功能的加入主要是对service中使用of函数的两个方法产生影响。

  getHeroList(): Observable<Hero[]> {
    return this.http.get<Hero[]>(this.heroesUrl);
  }

  getHero(id: number): Observable<Hero> {
    // const hero = myHeroList.find(h => h.id === id);
    // return of(myHeroList.find(hero => { hero.id === id }));
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url);
  }

注意,url需要使用反引号  ` ` 。

使用get、post、put请求进行增删查改

get进行查询

“英雄之旅”项目中,使用httpModule中的get方法进行查询有两个应用场景:一是:在home页面,根据id查询detail参数;二是:在dashboard页面的搜索框,根据字段查询detail参数。

第一种场景比较简单,service文件的编码如下:

首先要获取id:

  getHero(): void {
    // 首先取当前路径中的id参数
    const id = Number(this.acticatedRoute.snapshot.paramMap.get('id'));
    this.heroService.getHero(id).subscribe(hero => this.hero = hero);
  }

调用get方法时,可以只传入url。

  getHero(id: number): Observable<Hero> {
    // const hero = myHeroList.find(h => h.id === id);
    // return of(myHeroList.find(hero => { hero.id === id }));
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url);
  }

第二种场景:在dashboard页面增加搜索框,当输入内容时,悬浮展示结果,并绑定路由。

悬浮结果如下:

 步骤一:首先在heroSercice中,增加字符查询的逻辑,就是将get请求中的url设定为基础url+name=nameValue的形式。

  // 搜索功能
  searchHero(term: string): Observable<Hero[]> {
    return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`);
  }

步骤二:构建一个新的组件,用来承载悬浮结果:heroSearch.component。

heroSearch组件的模版中,应该包含input模块和结果模块,如下所示:

<input #inputVal (input)="search(inputVal.value)">

<ul>
  <li *ngFor="let hero of heroes$|async">
    <a routerLink="delete/{{hero.id}}">
      {{hero.name}}
    </a>
  </li>
</ul>

其中,heroes$是查询到的结果,来源于http响应的observable,因此heroes后缀为$(是一个约定)。同时,async表示的是angular的管道,会自动订阅observable。这是固定用法。

在searchHero.component.ts中,相应的,需要定义heroes$,并调用heroService中的查询方法。

export class HeroSearchComponent implements OnInit {
  heroes$!: Observable<Hero[]>;
  private searchTerms = new Subject<string>();
  constructor(private heroService: HeroService) { }

  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit() {
    this.heroes$ = this.searchTerms.pipe(
      debounceTime(500),//键入关键词后,500毫秒生效
      distinctUntilChanged(),//监测到改变才执行搜索
      switchMap((term: string) => this.heroService.searchHero(term))//当监测到改变时,执行该条语句
    )
  }
}

代码中,重点是switchMap:angular switchMap的用法 (leavescn.com)

post进行添加

在hero页面增加英雄元素时,借助post请求。

service.ts文件如下,定义了一个模版变量heroName。还包含click事件绑定,因此需要定义addHero。

<div>
  <input type="text" #heroName>
  <button type="button" (click)="addHero(heroName.value);heroName.value=''">添加英雄</button>
</div>

hero.component文件如下:

  addHero(name: string): void {
    this.heroService.addHero({ name } as Hero).subscribe(res => {
      this.herolist?.push(res);
      console.log(this.herolist);
    })
  }

service文件如下:

  // 使用post请求进行添加
  addHero(hero: Hero): Observable<Hero> {
    return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions);
  }

http.post请求需要传入url、body、httpOption,返回类型为hero。本项目中,将options定义为:

  private httpOptions = { headers: new HttpHeaders({ 'Context-Type': 'application/json' }) };

put进行修改

使用http的put方法对herodetail页面的英雄元素进行修改。

HTML模版如下:

  <button type="button" (click)="save()">保存英雄名称</button>

绑定了click事件,ts文件如下:

  save(): void {
    if (this.hero) {
      this.heroService.updateHero(this.hero).subscribe();
    }
  }

service文件如下:

  // 使用put请求进行更新
  updateHero(hero: Hero): Observable<any> {
    return this.http.put(this.heroesUrl, hero, this.httpOptions);

put请求需要三个参数,不限定返回类型。

delete进行删除

    <button type="button" class="delete" (click)="delete(hero)">x</button>

  delete(hero: Hero): void {
    // 删除步骤:首先从heroes列表中移除该元素
    this.herolist = this.herolist?.filter(h => h.id !== hero.id);
    // 从服务器中删除该元素
    this.heroService.deleteHero(hero.id).subscribe();
    console.log(this.herolist)
  }

  // 使用delete请求进行删除
  deleteHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.delete<Hero>(url, this.httpOptions);
  }

http通信

使用angular进行http通信时,主要利用HttpCLientModule。

[注意,在不确定返回值时,可以设置返回类型为Observable<any>],但是observe还是得按需设置:body request等.

Angular HttpClient Tutorial & Example - TekTutorialsHubIN this Angular HttpClient guide learn how to use HttpClient Get, Post, Put, etc to query/post data to the external data sourceicon-default.png?t=N7T8https://www.tektutorialshub.com/angular/angular-httpclient/

通用的准备:

首先在service中注册HttpClient。

  constructor(private http: HttpClient) { }

然后创建相应的get、put方法。

get请求

创建方法:getMethod(url){return this.http.get(url)}。

上述的get方法中包含以下参数,其中必须参数为URL,返回Observable泛型,可指定泛型的类型。返回Observable表示,组件调用该方法时,可调用subscribe,异步获取数据。

get(url: string, options?: {
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
        context?: HttpContext;
        observe?: 'body';
        params?: HttpParams | {
            [param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
        };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<Object>;

重要的选项为observe=body | event | response(用来指定要返回的响应内容,若定义为response,则返回所有response;若定义为body,则返回响应中的body模块;body返回带有事件的响应)和responseType='arraybuffer' | 'blob' | 'json' | 'text'(用来指定返回的类型)。

例子如下:

在service中创建了getConfig方法,返回http.get()方法,仅包含url参数。此外,在该函数中还可以织入其他逻辑。

  constructor(private http: HttpClient) { }

  getConfig(configUrl: string) {
    return this.http.get(configUrl);
  }

在component.ts中,导入service方法;在ngOnInit中调用service中的函数。

  constructor(private configService: ConfigService) {

  }

  ngOnInit() {
    // this.configService.getConfig(this.configUrl).subscribe((res: any) => (this.results = res));

完整的get请求

在HttpClient.get()方法中,除了url,还可以包含options,选取了observe和responseType参数。如下:

  getDataResponse(dataUrl: string): Observable<HttpResponse<PutRes>> {
    return this.http.get<PutRes>(dataUrl, { observe: 'response', responseType: 'json' });
  }

在component.ts中,调用subscribe方法,分别得到response、header、body等内容。代码如下:

  datas: any;
  headerKeys?: string[];
  headerDatas?: string[];
  status?: number;
  
ngOnInit() {
    this.configService.getDataResponse(this.configUrl).subscribe({
      next: (res: HttpResponse<PutRes>) => {
        console.log('Response是', res);
        this.headerKeys = res.headers.keys();
        this.headerDatas = this.headerKeys.map(key => `${key}: ${res.headers.get(key)}`);
        this.status = res.status;
        this.datas = res.body;

        console.log('headerKeys是', this.headerKeys);
        console.log('headerDatas是', this.headerDatas);
        console.log('status是', this.status);
        console.log('datas是', this.datas);
      },
      error: (e) => {
        switch (e.status) {
          default:
            console.log('error:', e);
            break;
        }
      },
      complete: () => console.info('complete.'),
    })
  }

结果如下:

如果要将body中的数据展示到页面中,则采用属性绑定的方法,修改模版如下: 

    <li *ngFor="let data of datas">
        {{data.id}}{{data.title}}
    </li>

 结果如下:

  • 1Angular Localization Using ngx-translate
  • 2How to Use the Navigation or Shadow Property in Entity Framework Core
  • 3Getting Value from appsettings.json in .NET Core
  • 4Embedding Beautiful Reporting into Your ASP.NET MVC Applications
  • 5Select Tag Helper in ASP.NET Core MVC

post请求

post与get请求类似,使用post请求完成了一个功能如下:

可以看出,包含了post和get两种类型的请求。编码如下:

 service.ts中,定义了get和add的两种方法:在get中的返回值可以是People[] 数组,put中可以不指定,也可以设置为People等强类型

  getPeople(url: string): Observable<People[]> {
    return this.http.get<People[]>(url);
  }

  addPeople(url: string, people: People): Observable<any> {
    const body = JSON.stringify(people);
    const headers = { 'content-type': 'application/json' }
    return this.http.post(url, body, { 'headers': headers });
  }

app.component.ts文件中相应的给出了两种方法,add方法会调用get方法,表示在增加后元素后会显示出来。其中,还定义了一个person对象,用来承载HTML模版中input的双向绑定关系

   ngOnInit() {
    //初始化后需要加载所有people
    this.getPeople();
}

 getPeople() {
    this.configService.getPeople(this.configUrl).subscribe(
      (res) => {
        console.log(res);
        this.peopleRes = res;
      }
    )
  }

  person = new Person();
  
  addPeople() {
    this.configService.addPeople(this.configUrl, this.person).subscribe(
      (res) => {
        console.log(res);
        this.getPeople();
      }
    )
  }

此外,在HTML模版中,使用了事件绑定和双向绑定:

<ul>

    <li *ngFor="let peopleRe of peopleRes">
        {{peopleRe.id}}的标题为{{peopleRe.title}}
    </li>
    <div>
        <input type="text" [(ngModel)]="person.title" />
        <button (click)="addPeople()">add</button>
    </div>

</ul>

当点击button时,调用addPeople方法,将input的元素加入;addpeople同时也调用get方法,利用双向绑定将数据完整立即出来。

put请求

创建方法:putMethod(url,body,... ){return this.http.get(url,body,...)}。

上述的get方法中包含以下参数,其中必须参数为URL和body(any类型),返回Observable泛型,可指定泛型的类型。返回Observable表示,组件调用该方法时,可调用subscribe异步获取数据。结果如下:

put<T>(url: string, body: any | null, options?: {
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
        context?: HttpContext;
        observe?: 'body';
        params?: HttpParams | {
            [param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
        };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<T>;

例子如下:

service文件中:


  putConfig<PutRes>(putUrl: string, body: any) {
    // put需要三个参数:url、param、options
    // const putHeader:{
    //   'auth': 'anth'
    // }
    return this.http.put<PutRes>(putUrl, body);
  }

component.ts文件中:

  private putUrl = 'https://jsonplaceholder.typicode.com/posts/1';
  putBody = {
    title: 'this is title'
  }
  ngOnInit() {

    this.configService.putConfig<PutRes>(this.putUrl, this.putBody).subscribe(data => (this.putResults = data));
  }

component.html文件中:仅用来简单展示结果。

<ul>
    {{putResults.id}}
</ul>

使用put修改英雄的例子

使用put请求,会匹配原列表中的信息与提交待修改的信息。使用put请求,一般返回一个Observable<any>值

hero.service.ts增加updateHero方法:

  updateHero(hero: Hero): Observable<any> {
    return this.http.put(this.heroesUrl, hero, this.httpOptions);
  }

detail.component.ts中,需要引用service中的方法:

  save(): void {
    if (this.hero) {
      this.heroService.updateHero(this.hero).subscribe();
    }
  }

put与get请求相似,重点在于模版与组件的交互。在HTML模版中,已经含有hero双向绑定(说明ts文件中已经有一个hero变量),因此定义一个click事件绑定,来调用上述save()方法。如下:

  <button type="button" (click)="save()">保存英雄名称</button>

修改后,返回至hero列表,发现更新成功。

HTTP拦截器

http拦截主要使用HttpInterceptor,其作用于前端向后端的请求之间,可以修改请求的内容,一般是对请求头进行操作,如补充token等.

关键在于:①创建interceptor服务,②以及如何让其他请求经过interceptor的处理.

对于①,推荐将interceptor单独作为ts文件:

import { Injectable } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs";

@Injectable()
export class AppHttpInterceptor implements HttpInterceptor {
    constructor() {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        //修改req中的请求头,也可以采用append方法
        console.log('修改前request为:', req);
        const newReq = req.clone({ headers: req.headers.set('Content-Type', 'application/json') })
        //还有一种快捷方式:
        const newReq1 = req.clone({ setHeaders: { 'Content-Type': 'application/json' } })
        console.log('修改后request为:', newReq1);
        return next.handle(newReq1);
    }
}

每个http请求都会调用该方法.intercept方法传入两个参数:类型为HttpRequest和HttpHandler.

前者HttpRequest是当前http请求中的完整请求.在修改请求头时,推荐将原request克隆一份,修改后返回新request,格式如下:

//修改req中的请求头,也可以采用append方法
const newReq = req.clone({ headers: req.headers.set('Content-Type', 'application/json') })
// req = req.clone({ headers: req.headers.append('Content-Type', ' application / json') })
//还有一种快捷方式:
const newReq1 = req.clone({ setHeaders: { 'Content-Type': 'application/json' } })

后者HttpHandler是拦截后的处理,一般调用其handle方法,方法参数为newReq,重新发送请求.

上述例子完成前后的请求对比如下:

bug记录

1、class.selected不生效

<p>
  here are some heroes!
</p>
<ul *ngFor="let hero of heros">
  <li>
    <button (click)="selectbutton(hero)" [class.selected]="selecthero===hero">
      <span>{{hero.name}}</span>
    </button>
  </li>
</ul>

<!-- 若selectedhero有值,则输出 -->
<p *ngIf="selecthero">

  当前英雄{{selecthero.name}}的id为{{selecthero.id}}
</p>

<!-- 练习双向绑定 -->
<!-- <p>your inputed heros name:{{myhero}}</p>
<input type="text" [(ngModel)]="myhero" placeholder="hero name"> -->

2、路由跳转替换点击跳转

这时不需要点击事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值