【问题标题】:CRUD using observables without refreshing pageCRUD 使用 observables 而不刷新页面
【发布时间】:2021-04-15 13:56:39
【问题描述】:

我正在尝试使用 observables 以角度实现 CRUD,而不在 ts 文件中订阅它们,但在 html 中使用 async

我的目标是获取、添加、编辑和删除从服务器获取的项目,而无需在每次请求后刷新页面。

我尝试使用BehaviorSubjectReplaySubjectAsyncSubject 进行此操作,并且还寻找材料,但我没有发现任何有用的东西。

有人可以举例说明如何以最佳模式实现这种 crud 吗?

下面我粘贴了从我之前的尝试中清除的代码以及从 API 返回的数据结构。

提前致谢。

documents.service.ts

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  getDocuments(): Observable<any[]> {
    return this.http.get<any[]>(`https://localhost:5001/docs`)
  }

  postDocument(item: any): Observable<any> {
    return this.http.post<any[]>(`https://localhost:5001/docs`, item)
  }

  putDocument(id: number, item: any): Observable<any> {
    return this.http.put<any[]>(`https://localhost:5001/docs/${id}`, item)
  }

  deleteDocument(id: number): Observable<any> {
    return this.http.delete<any>(`https://localhost:5001/docs/${id}`)
  }
}

documents.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { DocumentsService } from 'src/app/services/documents.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {

  documents$: Observable<any> = new Observable()

  constructor(private documentsService: DocumentsService) { }
  
  ngOnInit() {
    this.getDocuments();
  }

  getDocuments(): void {
    this.documents$ = this.documentsService.getDocuments()
  }

  postDocument(): void {
    this.documentsService.postDocument({
      "title": "title",
      "description": "lorem ipsum",
      "imageUrl": "https://images.pexels.com/photos/2765111/pexels-photo-2765111.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
      "madeBy": {
        "userName": "mike mock",
        "email": "mike@mock.com"
      }
    })
  }

  putDocument(): void {
    this.documentsService.putDocument(4, {
      "title": "title",
      "description": "lorem ipsum",
      "imageUrl": "https://images.pexels.com/photos/2765111/pexels-photo-2765111.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
      "madeBy": {
        "userName": "mike mock",
        "email": "mike@mock.com"
      }
    })
  }

  deleteDocument(): void {
    this.documentsService.deleteDocument(5)
  }
}

documents.component.html

<p routerLink="/">home works!</p>

<button (click)="postDocument()">add me</button>
<button (click)="putDocument()">edit me</button>
<button (click)="deleteDocument()">delete me</button>


<p *ngFor="let document of documents$ | async">
    {{ document | json }}
</p>

HTTP GET 响应:

[
  {
    "id": 47,
    "title": "docker",
    "description": "lorem ipsum",
    "imageUrl": "https://images.pexels.com/photos/3464632/pexels-photo-3464632.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "madeBy": {
      "username": "mike mock",
      "email": "mike@mock.com"
    }
  },
  {
    "id": 49,
    "title": "linux",
    "description": "second description",
    "imageUrl": "https://images.pexels.com/photos/3900437/pexels-photo-3900437.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "madeBy": {
      "username": "mike mock",
      "email": "mike@mock.com"
    }
  }
]

HTTP POST/PUT/DELETE 响应:

{
  "id": 50,
  "title": "some text",
  "description": "lorem ipsum",
  "imageUrl": "https://images.pexels.com/photos/2765111/pexels-photo-2765111.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
  "madeBy": {
    "username": "mike mock",
    "email": "mike@mock.com"
  }
}

【问题讨论】:

  • 这对你来说可能有点过头了,但我建议包括一个商店。每个 CRUD 操作都是您可以在单击时调度的操作。请求由效果发送,您的组件仅从存储中选择文档。
  • @MoxxiManagarm,我认为向正在构建简单 CRUD 应用程序的新手建议状态管理选项是不合适的。

标签: javascript angular typescript rxjs


【解决方案1】:

Observables 是惰性的,在订阅之前不会执行。

将您的组件更新为:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, BehaviorSubject } from 'rxjs';
import { DocumentsService } from 'src/app/services/documents.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {

  documents$: BehaviorSubject<any> = new BehaviorSubject(undefined);

  constructor(private documentsService: DocumentsService) { }
  
  ngOnInit() {
    this.getDocuments();
  }

  getDocuments(): void {
    this.documentsService.getDocuments().subscribe(this.documents$);
  }

  postDocument(): void {
    this.documentsService.postDocument({
      "title": "title",
      "description": "lorem ipsum",
      "imageUrl": "https://images.pexels.com/photos/2765111/pexels-photo-2765111.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
      "madeBy": {
        "userName": "mike mock",
        "email": "mike@mock.com"
      }
    }).subscribe(_ => this.getDocuments());
  }

  putDocument(): void {
    this.documentsService.putDocument(4, {
      "title": "title",
      "description": "lorem ipsum",
      "imageUrl": "https://images.pexels.com/photos/2765111/pexels-photo-2765111.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
      "madeBy": {
        "userName": "mike mock",
        "email": "mike@mock.com"
      }
    }).subscribe(_ => this.getDocuments());
  }

  deleteDocument(): void {
    this.documentsService.deleteDocument(5).subscribe(_ => this.getDocuments());
  }
}

我还强烈建议您强烈键入您的 api 并删除 any 的使用。

【讨论】:

  • 感谢您的建议,我通常使用强类型但是对于这个问题,我将类型更改为any,以免使这个问题复杂化。
  • 异步管道为您完成订阅。这就是使用异步管道的全部意义所在。请更新您的答案,这只是错误的
  • 我意识到我没有解决您关于获取更新数据的部分问题。您可以从其他方法中调用 this.getDocuments() 。我会更新我的答案以显示。
  • @Jean-XavierRaynaud,请看作者原文的postDocument、putDocument和deleteDocument方法。作者问题中的异步管道仅执行其 CRUD 应用程序的读取部分。
  • @mikolajs,如果您想在出错时刷新数据,也可以在此用例中使用管道 finalize。
【解决方案2】:

问题在于您的服务。在您的服务中,您的 getDocuments() 只是简单地调用您的后端。当您在组件中编写 this.documents$ = this.documentsService.getDocuments() 时,它将调用该函数一次,读取数据并且以后不再更新。

因此,您需要创建一个主题以将数据存储在您的服务中,使用 .asObservable() 使其在外部可用,将您的组件链接到该可观察对象,并在每次 http 调用后更新主题的内容。

类似这样的:

export class DocumentsService {
  private _documents$ = new BehaviorSubject<Array<Document>>([]);
  public documents$: Observable<Array<Document>>;

  constructor(private http: HttpClient) { 
    this.documents$ = this._documents$.asObservable()
  }

  getDocuments(): Observable<any[]> {
    this.http.get<any[]>(`https://localhost:5001/docs`).switchmap(
      (response) => this._documents$.next(response)
    )
  }

  postDocument(item: any): Observable<any> {
    # update _documents$ here
  }

  putDocument(id: number, item: any): Observable<any> {
    # update _documents$ here
  }

  deleteDocument(id: number): Observable<any> {
    # update _documents$ here
  }
}

然后在您的组件中简单地更新您定义文档的行

this.documents$ = this.documentsService.documents$

它应该可以工作。

【讨论】:

  • 作者的 postDocument 、 putDocument 和 deleteDocument 方法在没有订阅的情况下不会调用服务方法。请使用更新的组件部分更新您的答案。
  • @JohnD 他们基本上是微不足道的,只需读取服务器的响应并在 switchman 或排气图中更新行为主题。
  • postDocument(item: any): Observable&lt;any&gt; 不会在作者的当前消费组件中执行,因为它返回一个 Observable 并且需要订阅。组件中的postDocument(): void 也需要更新。
猜你喜欢
  • 1970-01-01
  • 2011-12-22
  • 2011-04-28
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多