【问题标题】:Typescript: Subscribe data got vanished打字稿:订阅数据消失了
【发布时间】:2020-12-06 18:45:13
【问题描述】:

[投票赞成的人:我不想把所有东西都放在ngOnit中-因为我告诉过你:我需要在很多函数中重用API响应和模型数组,所以我需要写一些东西我可以重复使用

你知道,我实际上可以解决每个问题,只需调用 subscribe 中的所有内容或在每个函数中一次又一次地订阅。

我已经尝试过其他 SO 问题,其中大多数都放在 ngOnIt 中。我只想在那里调用必要的函数,所以请不要弄乱那个地方

请帮助我编写一个函数,以便我可以重用我的 API 响应,或者我可以重用由 API 响应 this.menu = data; 初始化的模型]

我想从我的 API 响应中打印一个菜单。

此外,我需要在多个函数中多次使用响应,但是当我超出 subscribe 块时,我得到空值

这是我的代码:

import { Component, OnInit } from '@angular/core';

// import { LoginModel } from "../../models/login/login-model";
import { MenuModel } from "../../models/menu/menu-model";
import { SubmenuModel } from "../../models/submenu/submenu-model";
// import { AccessModel } from "../../models/access/access-model";

import { MenuService } from "../../services/menu/menu.service";
import { SubmenuService } from "../../services/submenu/submenu.service";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {

  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
    this.printMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu = data;
      console.log("first use : ");
      console.log(data);

      console.log("second use : ");
      console.log(this.menu);
    })
  }

  printMenu(){
    console.log("third use : ");
    console.log(this.menu);
  }
}

这是输出:

printMenu() 函数看到我的所有响应都是空的。但为什么?我之前确实订阅并保存了该值。

那么我怎样才能从 API 响应中永久保存一个值呢?

【问题讨论】:

  • 还告诉我应该使用 subscribe 还是应该使用 async-await
  • 其实你可以试试demo。编写一个函数,首先执行控制台日志,然后编写订阅,然后在内部订阅执行控制台日志,然后再在外部订阅一个日志。然后观察顺序。大多数情况下你会得到你的答案。在打字稿中,订阅之外的任何内容都将继续执行。
  • 我没有投反对票,但是,我明白为什么这是反对票。可能是因为您的示例实际上并不是真实世界的实现——另外,您似乎还没有掌握异步概念。因此,即使您的问题听起来很有道理,但它向许多人表明您并没有费心去实际查看此类场景在实际应用程序中是如何完成的。

标签: angular typescript asynchronous rxjs


【解决方案1】:

(我发布此解决方案的唯一原因是:将来像我这样的初学者不必每天[比喻地]用头撞墙或厌倦可观察到的超过 2 周的项目发现它

我也想和你分享两件事 - 只有你是初学者才会明白 [不要评论它 - 我请求你 - 我有权表达我对我的问题的感受 - 请不要评判我]

1.优点:

他们中的大多数人不明白我的意思。我相信他们实际上知道这个答案,但似乎他们不明白我想要什么,只有他们中的一些人试图给我建议(我为此感谢他们)但他们中的大多数人并不关心或不理解甚至懒得问什么就明白了。所以初学者:你只需要找到一个幸运的日子,人们真正理解你在问什么

2。文档

在镜头中 - 一个非常大的笑话(对于初学者)

长篇大论 - [在我的情况下 - angular] 文档不会帮助你,除非你没有获得一定程度的编程知识,就像我之前从未使用过 observablengOnit 这样的方式,但是有人指出了它是如何工作的。我仍然不知道observablengOnit 的大部分内容,但现在我知道了4 或5 件事。

所以我对初学者的建议是:留下文档(如果需要或者如果你难以理解)并转到 youtube 教程)

问题原因:

还记得我的 ngOnIt 吗?

ngOnInit(): void {

    //line 1

    this.getMenu();

    //line 2

    this.printMenu();
    
}

ngOnIt 不会逐行执行,如果确实如此,我实际上可以第三次更正值而不是空值(参见屏幕截图中的值)。 ngOnit 一次全部执行(不是逐行执行)。所以虽然看起来第 2 行将在第 1 行之后执行,但它不是,因为我们不在正常功能中,我们在 ngOnit

解决方案

当我得到解决方案时,我很惊讶(并且很高兴知道这一点)实际上没有编码错误,我只需要在 @987654334 上初始化 getMenu() 之后在 ngOnit 之外调用我的 printMenu() @

那我怎么能在外面打电话呢?我需要活动

如何触发事件?最简单的方法是制作一个带有点击事件的按钮

这是我的 .html

<button (click)="this.printMenu()"> Lets test it </button>

.ts

import { MenuService } from "../../services/menu/menu.service";
import { SubmenuService } from "../../services/submenu/submenu.service";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {

  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu = data;
      //console.log("first use : ");
      //console.log(data);

      //console.log("second use : ");
      //console.log(this.menu);
    })
  }

  printMenu(){
    console.log("third use : ");
    console.log(this.menu);
  }
}

几乎看不到任何变化,我只需要从ngonit 中删除printmenu,然后通过事件(按钮或您喜欢的任何其他内容)调用它

[我还在代码中注释了第一次使用和第二次使用]

【讨论】:

  • 您似乎仍然不明白为什么在订阅之外打印变量时未定义/为空。你可以参考我的回答来快速了解一下。
  • @MichaelD 好的,我会调查的,感谢您的努力
【解决方案2】:

从您的回答看来,当您在订阅之外打印时,您似乎仍然无法理解为什么该变量未定义。 this.menu 是异步分配的。如果您花时间阅读我在评论中附加的this 答案,您会了解更多。

本质上,当您在printMenu() 函数中打印该变量时,它不会被分配任何值。你的回答仍然是错误的。它假定在​​您按下按钮时定义了变量,但情况可能并非如此。 你永远不能假设已经定义了一个异步变量

模拟错误

您可以通过使用 RxJS delay 运算符人为地诱导延迟来模拟此错误。我正在诱导 10 秒的延迟。因此,如果您在应用程序启动后 10 秒内按下按钮,printMenu() 仍将打印null。在实际场景中,这种延迟实际上可能是由 GetAllMenu() 函数引起的,因为前端无法控制它的响应时间。

import { delay } from "rxjs/operators";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {
  menu: MenuModel[] = null;
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().pipe(
      delay(10000)             // <-- simulate delay of 10 seconds
    ).subscribe((data: MenuModel[]) => {
      this.menu = data;        
    });
  }

  printMenu() {
    console.log(this.menu);    // <-- would still print `null` if called within 10 seconds
  }
}

正确的解决方案是使所有后续语句也异步。

解决方案:使用 RxJS ReplaySubject

ReplaySubject 是一个多播 observable,它保存/缓冲最后发出的 n 值,并在订阅时立即发出它。对于这种情况,缓冲区大小 1 应该足够了。您可以将来自 GetAllMenu() 的源通知推送到其他依赖项将订阅的可观察对象。

import { ReplaySubject } from "rxjs";

@Component({
  selector: 'app-admin-access',
  templateUrl: './admin-access.component.html',
  styleUrls: ['./admin-access.component.css']
})
export class AdminAccessComponent implements OnInit {
  menu$: ReplaySubject<MenuModel[]> = new ReplaySubject<MenuModel[]>(1);
  submenu: SubmenuModel[] = null;

  constructor(private menuService: MenuService, private submenuSerive: SubmenuService) { }

  ngOnInit(): void {
    this.getMenu();
    this.printMenu();
  }

  getMenu() {
    this.menuService.GetAllMenu().subscribe((data: MenuModel[]) => {
      this.menu$.next(data);        // <-- push value to `ReplaySubject` observable
      console.log("first use : ");
      console.log(data);
    });
  }

  printMenu(){
    this.menu$.subscribe((data: MenuModel[]) => {     // <-- subscribe to the `ReplaySubject` observable
      console.log("third use : ");
      console.log(data);
    });
  }
}

【讨论】:

  • 关于您的第一个解决方案:看我说的问题,如果我需要一次又一次地订阅,我不会发布这个问题。我可以通过一次又一次地订阅或在订阅块下写入我的所有代码来解决每个错误,我想订阅一次并稍后使用结果。 [简而言之,我不想让我的代码复杂,将所有内容收集在一个函数或一个订阅块中。] 但是,您的第二个解决方案非常有趣。我想尽快测试一下
  • 不幸的是,您的第二个解决方案不起作用。现在菜单显示空白输出(而不是未定义或空)ibb.co/TTs29bz
  • @Frost:你需要冷静下来。第一个订阅与第二个订阅不同。第一个订阅是冷可观察的,而第二个订阅是热可观察的。在处理异步变量时,您要么需要依赖 observables (subscribe) 要么依赖 promises (then)。如果您担心多个订阅流,您可以通过管道输入take(1)takeUntil() 来处理内存泄漏。 您不能使异步变量同步。请忽略第二种解决方案。我已经更新了答案。
  • 它有效,所以我接受了,ibb.co/d04RqV9 但有 2 个错误 1) 看截图:第二次使用不打印,(它以前打印过,但我不需要打印它顺便说一句- 所以我忽略了 2)在你的重播主题之前放一个新的,否则代码错误不起作用 3)在第二次使用中你必须 this.menu$ 因为我们没有任何 this.menu ,添加 $ 符号,更新你的代码。感谢您的所有努力
  • @Frost:我已经更新了答案以删除被忽略的错别字。
猜你喜欢
  • 1970-01-01
  • 2018-07-30
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多