基础
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)的使用 (gxlsystem.com)https://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的样式。可以如下表示:
- 采用字符串形式:[ngClass]="class1 class2"
- 采用数组形式:[ngClass]="['class1','class2']"
- 采用对象形式:[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参数。
结构如下:
未使用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等.
通用的准备:
首先在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、路由跳转替换点击跳转
这时不需要点击事件。