【问题标题】:Angular 6 call subscribe() for Observable multiple timesAngular 6 多次调用 Observable 的 subscribe()
【发布时间】:2018-09-19 16:22:44
【问题描述】:

我有两个组件:NewItemComponent 和 ListComponent。当我在相应组件中创建新项目时,我通知 ListComponent 以便它可以刷新它的数据模型:

export class NewItemComponent implements OnInit {

  constructor(private itemService: ItemService, private notificationService: NotificationService) {
  }

  ngOnInit() {
  }

  createNewItem(item: Item) {
    this.itemService.persist(item).subscribe((response: Item) => {
      console.log(response);
      this.notificationService.notifyNewItemHasBeenCreated(response);
    });
  }
}


export class ListComponent implements OnInit {

  items: Item[];

  constructor(private listService: ListService, private notificationService: NotificationService) {
  }

  ngOnInit() {
    this.loadItems();

    this.notificationService.item$.subscribe((item) => {
      if (item != null) {
        this.loadItems();
      }
    })
  }

  loadItems(){
    this.istService.getItems().subscribe((data: Item[]) => {
      this.items= data;
      console.log(this.items);
    });
  }
}


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

  private _item: BehaviorSubject<Item> = new BehaviorSubject<Item>(null);
  public  item$ = this._item.asObservable();

  constructor() {
  }

  notifyNewItemHasBeenCreated(item: Item) {
    this._item.next(item);
  }
}

让我担心的是 loadItems() 多次调用 subscribe()。这可以吗,还是有更好的方法根据通知重新获取项目?

  loadItems(){
    this.listService.getItems().subscribe((data: Item[]) => {
      this.items= data;
      console.log(this.items);
    });
  }

ListService 返回 Observable:

export class ListService {

  basePath = 'my-api.com';
  apiPath = "item";

  constructor(private httpClient: HttpClient) {
  }

  getItems(): Observable<Item[]> {
    return this.httpClient.get<Item[]>(this.basePath + '/' + this.apiPath);
  }
}

提前致谢,我们将不胜感激。

【问题讨论】:

    标签: angular typescript


    【解决方案1】:

    作为一个实验,如果您执行以下操作:

    this.httpClient.get("<some url>")
      .subscribe({
        next: () => {
          console.log("received response")
        },
        error: err => {
          console.log("error occurred")
        },
        complete: () => {
          console.log("subscription completed")
        },
      })
    

    你应该看到:

    received response
    subscription completed
    

    这意味着每个 Web 请求的 observable 都在请求完成后完成,因此不取消订阅是安全的,因为这是在 observable 完成时自动完成的。

    【讨论】:

    • 但似乎订阅是多次创建的(每次我调用 loadItems())。不确定这是否对性能有好处
    • 每次调用 loadItems 时,您都是通过 HttpClient 发出新的 Web 请求,这是一个新的 observable - 您需要以一种或另一种方式为新请求订阅新的 observable。
    【解决方案2】:

    如果您多次订阅调用,则在组件销毁时取消订阅。

    像这样改变你的组件:

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Subscription } from 'rxjs';
    
    export class ListComponent implements OnInit, OnDestroy {
    
      items: Item[];
    
      constructor(private listService: ListService, private notificationService: NotificationService) {
      }
    
      subscriptions: Subscription[] = [];
    
      ngOnInit() {
         this.subscriptions.push(this.loadItems());
    
         this.subscriptions.push(this.notificationService.item$.subscribe((item) => {
           if (item) {
             this.loadItems();
           }
         }));
      }
    
      ngOnDestroy() {
        this.subscriptions.forEach(x => x.unsubscribe());
      }
    
      loadItems(){
        this.istService.getItems().subscribe((data: Item[]) => {
          this.items= data;
          console.log(this.items);
      });
     }
    }
    

    【讨论】:

      【解决方案3】:

      loadItems() 只调用一次subscribe
      实际发生的情况是,您在notificationService.item$ 的订阅中多次调用loadItems()
      如果您仅出于此目的需要NotificationService,我建议您进行少量重构。

      new-item.component.ts

      export class NewItemComponent {
      
        constructor(private itemService: ItemService, private listService: ListService) {
        }
      
        createNewItem(item: Item) {
          this.itemService.persist(item).subscribe((response: Item) => {
            this.listService.addItem(item);
          });
        }
      }
      

      list.service.ts

      export class ListService {
      
        basePath = 'my-api.com';
        apiPath = 'item';
      
        private itemsSubject: BehaviorSubject<Item[]> = new BehaviorSubject<Item[]>([]);
        private items$: Observable<Item[]>;
      
        constructor(private httpClient: HttpClient) {
        }
      
        getItems(): Observable<Item[]> {
          const http$ = this.httpClient.get<Item[]>(this.basePath + '/' + this.apiPath);
          return combineLatest(http$, this.items$).pipe(
            map(([httpResponse: Item[], localItems: Item[]]) => httpResponse.concat(localItems)),
          );
        }
      
        addItem(item: Item) {
          this.itemsSubject.next([...this.itemsSubject.value, item]);
        }
      
      }
      

      【讨论】:

      • 嗨 Eluzive。感谢你的回答。你能描述一下为什么 subscribe() 只被调用一次吗?是 Observable 的生命周期吗?
      • 我的意思是subscribeloadItems 中只被调用一次,但最后它会被调用多次,因为您在notificationsService.item$ 的订阅中多次调用loadItems。在我的解决方案中,您可以摆脱 NotificationService,因为您将始终拥有最新的项目列表。
      【解决方案4】:

      编辑:

      环顾四周后,我发现了这些帖子:
      Is it necessary to unsubscribe from observables created by Http methods?
      Prevent memory leaks in Angular 2?

      cmets 和答案解释了HttpClient.get() 订阅会自行清理,因此您无需取消订阅。这意味着对它们多次调用.subscribe() 就可以了。

      【讨论】:

      • 什么是take(1)?在 Angular 6 / 语法错误中找不到它
      • take 是一个 Observable 方法。您可以使用它来指定发出多少项。
      • 第二个例子不起作用——第一次调用后的 if() 总是 false 并且数据不会刷新。
      • 抱歉,我不确定 HttpClient observables 是如何工作的。我做了一些研究并更新了我的答案以显示。
      【解决方案5】:

      你现在正在做的是:

      1. 制作新物品,发送到服务器
      2. 服务器将成功响应与项目一起发送到项目服务。它通知通知服务。您实际上并没有对这个项目做任何事情,而是验证它。
      3. 您的通知服务获取项目,通知您的列表组件。
      4. 您的列表组件检查项目是否有效,然后向服务器发送另一个请求以获取更新的列表
      5. List 组件订阅更新列表的列表服务。及时更新。

      因此,简而言之,对于您创建的每个新项目,您都会向服务器发出 2 个请求并获得 2 个响应。

      您可以看到这里有很多冗余。让我们试着简化一下:

      1. 制作新物品,发送到服务器
      2. 服务器发送带有更新的项目列表的成功响应。 只有在收到的商品有效时才会发送此商品列表,否则会发回错误响应
      3. 您的通知服务已订阅此内容并获取已由服务器验证的项目列表,然后将这个新项目列表发送给您的列表服务。
      4. 列表组件使用新数据更新视图。

      现在您每次只发出一个请求,而不是两个。而且您不必再处理这么多 Observables。

      【讨论】:

      • 不幸的是,在创建项目以及并发时存在一些服务器端逻辑和服务器端缓存。这意味着我需要在单独的调用中坚持并在坚持后重新加载项目。总之谢谢!
      • 不管怎样,您的代码中仍有相当多的冗余。一方面,这里问题的根源是 loadItems 每次都发出 http 请求并进行新订阅。让您的通知服务在获取项目时自行发出请求,并在您获取项目时将其传递给服务主题,以重定向到需要它的组件中的一次性初始化订阅。
      • 我不喜欢在一项服务中混合数据配置和通知的想法 -> 概念分离。在我的情况下,两个 UI 组件也应该尽可能地隔离。在这种情况下,我想解决的是避免进行新订阅
      • 就像我一直说的,问题的根源是 loadItems() - 它同时发出请求和订阅!分开,重读我上一条关于如何做的评论,你就不会有新订阅的问题了。
      • 对不起,我不明白 -> 这就是我在这里问的原因。我来自非函数式编程世界,如果没有代码列表,我很难理解
      猜你喜欢
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 2019-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-06
      相关资源
      最近更新 更多