一直到目前,我们的 component 仍然使用写死的物件当作资料来源,今天,我们就要来串起我们的前後端,用 HttpClient 取得资料然後再用 component 帮我们把资料显示在浏览器上。
现在这个阶段如果我们马上用 Angular 发 http request 给我们的 .NET API,我们会因为 CORS 而被浏览器挡下这个 request,所以在我们开始使用 Angular 的 HttpClient 之前,我必须先来修改一下我们的 .NET API。
我们需要做的事情很简单,就是在 Startup.cs 里加入与 CORS 相关的 service 与 middleware,不过这里要注意一下,app.UseCors("xxx");
需要加在 app.UseRouting();
之後、app.UseEndpoints()
之前。设定 CORS 的更多细节请参考这篇文章。
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(option =>
{
option.AddPolicy("ironmanPolicy", policy =>
{
policy.WithOrigins("http://localhost:4200")
.WithOrigins("https://mydomain.tw")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
}
在 Angular 中,内层的 component 要拿到资料有两种做法,第一种是我们在 Day26 介绍的属性系结,让外层的 component 把资料传给内层的 component。另一种方法,是在内层的 component 中直接注入处理资料的 service,让 service 直接帮我们取得资料。今天,我们就来示范利用注入的方式,让 http request 帮我们的 component 取得资料。
在我们开始之前,我们先来新增另外一个资料来源
// ironman-list.component.ts
userListFromApi: IronmanUser[] = [];
像 http 这种这麽常用的东西,Angular 当然会提供内建的东西给我们用啦,而这个内建的东西就是 HttpClient 类别,要使用 HttpClient 类别,首先我们得要引用它,到 app.module.ts 中,新增引入 HttpClientModule 的程序码
// app.module.ts
import { HttpClientModule } from '@angular/common/http';
//...
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule // HttpClientModule 要放在 BrowserModule 後面
],
//...
接着,我们要让 Angular 帮我们注入 HttpClient 给我们的 component。Angular 的注入与 .NET 很相似,要在建构式中加入所依赖的类别当作参数,然後 Angular 看到建构式有参数,就会在服务容器中找到这个依赖,然後把实体注入给我们 component class。与 .NET 不同的是,在 Angular 中要在建构式使用依赖注入,这个参数必须要有 private 或 public 修饰词
// ironman-list.component.ts
constructor(private http: HttpClient) { }
注入 HttpClient 之後,我们就可以在 component class 中使用它了
// ironman-list.component.ts
ngOnInit(): void {
this.http.get<IronmanUser[]>(this.apiUrl + '/api/User')
.subscribe(data => {
this.userListFromApi = data;
})
}
我们来看一下这段程序码,this.http 就是刚刚透过依赖注入取得的 HttpClient 实体,我们呼叫这个实体的 get 泛型方法,从我们的 api 取得型别为「IronmanUser阵列」的资料。
HttpClient 使用观察者模式设计来处理非同步,使用 http.get()<> 方法会得到一个 Observable<资料型别> 的「可观察物件」,我们必须订阅这个可观察物件,并指定当任务完成时要做什麽後续处理。
观察者模式就像订报纸一样,我们打电话给报社说我们要订报纸,打完电话我们不会马上拿到报纸,但是我们知道明天报纸一印好报社就会把报纸送来给我们,我们可以先把柳橙汁放冰箱、培根先买好,隔天报纸一送到我们就能把柳橙汁跟培根拿到餐桌,配报纸享受悠闲的早晨。而在我们上面的例子中,我们透过 HttpClient 跟我们的 .NET API 订阅一份资料,等到这个资料送达,我们就把这份资料存到 this.userListFromApi 变数,然後 html 页面再帮我们用内嵌系结把资料显示在 table 里。
填坑
这边又有一个笔者挖的坑:之前在 DB 里verified 栏位在 API 使用 bool 型态来接,所以从 API 取回来的 verified 都会是 trule/false,会造成几个BUG:(1) 编辑的按钮跑不出来,因为之前用了三个等号(===),true 与 1 不相等。(2) 同样因为 verified 变成 true/false,ngSwitch 会无法显示红色 "BUG" 字样。(3) POST, PUT request 会变成 400 bad request,因为不符合 API 参数的预期资料型态。
如果要修正这些错误,必须把前後端含资料库的型态统一,可以考虑把 Angular app 里的 verified 属性改成 boolean 型态
刚刚我们是直接在 component 里引入 HttpClient 来替我们打 API,但有些时候我们可能会希望把所有打 user API 的功能写在一起,然後让多个 component 共用这个 service,这时候我们就会需要注入我们自己写的 service,现在我们来稍微修改一下我们的程序,把打 "User" API 的功能写成一个 service。
首先,先用 ng 指令或点右键新增一个 ironman-service
ng g s ironman # g=generate s=service
然後,把 HttpClient 注入给这个 service,再把 打 API 取得使用者资讯的 function 写在这个 service 里。
@Injectable({
providedIn: 'root'
})
export class IronmanService {
apiUrl = 'https://mydomain.tw/api';
httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
constructor(private http: HttpClient) { }
getUserList(): Observable<IronmanUser[]> {
return this.http.get<IronmanUser[]>(`${this.apiUrl}/User`, this.httpOptions);
}
getUserDetail(id: number): Observable<IronmanUser> {
return this.http.get<IronmanUser>(`${this.apiUrl}/User/${id}`, this.httpOptions);
}
addUser(userModel: IronmanUser): Observable<void> {
return this.http.post<void>(`${this.apiUrl}/User`, userModel, this.httpOptions);
}
updateUser(userModel: IronmanUser): Observable<void> {
return this.http.put<void>(
`${this.apiUrl}/User/${userModel.userId}`, userModel, this.httpOptions);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/User/${id}`, this.httpOptions);
}
}
上面的程序码中,有三个稍微需要留意的地方
service 准备就绪之後,我们再把它加到 app.module.ts 的 provider 阵列里
// ...
providers: [IronmanService],
// ...
最後,在 component 的建构式注入 IronmanService,然後呼叫 service 的 function 并订阅,就能从 API 取回资料
constructor(private ironmanService: IronmanService) { }
ngOnInit(): void {
this.ironmanService
.getUserList()
.subscribe(data => {
this.userListFromApi = data;
});
}
最後最後,有一个东西很重要,一定要强调三次
没有人订阅,request 就不会发出去
没有人订阅,request 就不会发出去
没有人订阅,request 就不会发出去
笔者不止一次花 30 分钟找没有订阅造成的 BUG,谨以血泪提醒各位邦友一定要记得订阅(按赞加分享)。
>>: Day16 - 语音辨识神级工具-Kaldi part1
部落格的 Blueprint 和会员验证时候的做法一样 部落格页面应该列出所有文章,允许已登入的会员...
DAY23 Experiment, Run, MLflow 今天开始的几天内,会进入 Azure M...
我习惯理解一个东西,可以套用日常的生活经验,找出类比、拟人化会帮助我更好理解,今天的议题是最近看到 ...
"The Great Movie Experience" as Myron En...
display:Inline、Block、Inline-Block 前言 display是用来设置每...