从 Angular 中的 BehaviorSubject 访问嵌套数据

Acessing nested data from BehaviorSubject in Angular

对于我的项目,我必须从 api.
中获取一个 json 文件 然后将此数据放入 Angular 服务中,以便为我的组件提供数据。

codesandbox 可以在这里找到:https://codesandbox.io/s/angular-7khq9

外部 API 正在提供数据。出于测试目的,我将其作为 .json 文件包含在此处。
测试数据为:

{
    "meta": {
        "a": 42,
        "b": 43
    }
}

我希望组件获取最新数据,因此我使用了基于 this 文章的 BehaviorSubject(使用最后一种方法),因为订阅的组件会自动获取最新数据。

export class DataService {
    private dataSource = new BehaviorSubject({});
    currentData = this.dataSource.asObservable();

    constructor(private http: HttpClient) {
      this.http
          .get("https://7khq9.codesandbox.io/assets/data.json")
          .subscribe(data => {
               this.changeData(data);
          });
    }

    changeData(data) {
        this.dataSource.next(data);
    }
}

这是我目前用来提供当前数据的服务,这部分工作正常。


我的组件现在想要订阅数据并将其打印出来以供测试。

export class AppComponent {
    title = "CodeSandbox";

    constructor(private ds: DataService) {
        this.ds.currentData.subscribe(data => {
            console.log("test");
            console.log(data); // works
            console.log(data.test); // works
            //console.log(data.test.a); // does not work
        });
    }
}

这首先给了我一个空对象(根据 new BehaviorSubject({})),然后它继续 datadata.test

但是当我尝试打印 data.test.a 时它不起作用并提供错误。 (只需取消注释 app.component.ts:17 即可重现)

ERROR TypeError: Cannot read property 'a' of undefined

现在我正在尝试获取 a 的值,但我做不到。


想法

由于它正在打印默认对象,因此代码可能会尝试获取 defaultObject.test.a,但由于它不存在,所以出现错误。但另一方面,它与 defaultObject.test 一起工作,即使它也不存在。
(刚刚看到它 return 未定义,但它仍然在 defaultObject.test)

上执行 return 某些操作

所以我尝试创建一个新的默认对象:

private dataSource = new BehaviorSubject({
    "meta": {
        "a": 99,
        "b": 99
    }
});

这工作正常并打印:

  1. Object {a: "99", b: "99"} // default object
  2. Object {a: "52", b: "62"} // changed object

现在显而易见的解决方案是将数据集的示例作为默认对象,对吗?

这似乎有点不对:
a) 它将这个巨大的 json 文件放入我的 js (ts) 文件
b) 我总是发送一个旧对象,它会立即被替换。

所以现在我想知道如何在不发送示例作为默认对象的情况下解决这个问题。

我愿意接受任何建议。

首先,我认为没有必要在这里使用BehaviorSubject。相反,在您的服务上创建一个方法 return 您的数据作为 Observable:

getMyData(): Observable<any>
  {
    return this.http
      .get("https://7khq9.codesandbox.io/assets/data.json");
  }

那么您应该可以根据需要访问您的数据:

ds.getMyData().subscribe(data => {
      // console.log("test");
      console.log(data); // works
      console.log(data.test); // works
      console.log(data.test.a); // does not work
    });

这是最好的方法。

查看更新后的 CodeSandBox here

好吧,打印这个 defaultObject.test.a 的问题是您将进入一个已经未定义的对象的另一层。由于 defaultObject.test 未定义,因此无法找到未定义的 'a'。在这种情况下,您需要检查 'a' 是否实际上是 defaultObject.test 的 属性 或者至少如果 defaultObject.test 不是未定义的。

defaultObject.test 打印未定义,因为您没有违反任何规则。您不是要在未定义的对象中查找 属性。 DefaultObject 已定义。唯一的问题是它没有测试 属性,因此它打印出 undefined.

如果你能帮我解释一下你打算用这个实现什么,我可以进一步帮助你。截至目前,解决方案是将其放入 if 块中,仅当定义了 defaultObject 时才打印值,并且您可以在其上找到 属性 'a'。

只需在管道中添加过滤器运算符,并将null设置为主题

的初始值
     this.ds.currentData
        .pipe(
            filter(data => data)
        )
        .subscribe(data => {
            console.log("test");
            console.log(data);
            console.log(data.test);
            console.log(data.test.a);
        });

或者如果您不想重新获取数据,您可以将 http 调用映射到管道 subject 内部

currentData$ = this.http.get('url').pipe(
   shareReplay(1) // cache your data with ReplaySubject
);

您对 BehaviourSubject 的用法有误。请改用 Subject

您的 observable 初始值等于 {}。所以不能显示test.a。当 GET 挂起时,您的代码会尝试从 {} 显示 a。为了让它工作,observable 中的初始值和下一个值应该有相同的接口。

如果不需要初始值,请使用 Subject 而不是 BehaviourSubject

export class DataService {
  private dataSource = new Subject();
  currentData = this.dataSource.asObservable();

  constructor(private http: HttpClient) {
    this.http
      .get("https://7khq9.codesandbox.io/assets/data.json")
      .subscribe(data => {
        this.changeData(data);
      });
  }

  changeData(data) {
    this.dataSource.next(data);
  }
}