简体   繁体   English

Angular 表:来自日期范围主题的管道可观察数据

[英]Angular Table: piping observable data from a date range subject

I've a code that is working as expected but nonetheless I doubt whether the code is wrong and it is only working by accident due to an undocumented feature or the code is right and it is working as expected by design.我有一个按预期工作的代码,但我怀疑代码是否错误,它只是由于未记录的功能而偶然工作,或者代码是正确的并且它按设计预期工作。

What I want is a material date range picker updating the datasource of another component's table.我想要的是一个更新另一个组件表的数据源的材料日期范围选择器。

I assume that the part passing the values through the template app.component.html我假设通过模板app.component.html传递值的部分

  <app-mytable ID="MyTable1" 
    [Start]="rangeFormGroup.value.start" 
    [End]="rangeFormGroup.value.end" ></app-mytable>

and then forwarding such values from the table mytable.component.ts to the mytable-datasource.ts然后将这些值从表mytable.component.ts转发到mytable-datasource.ts

  private _Start: Date | undefined;
  @Input()
  public get Start(): Date | undefined {
    return this._Start;
  }
  public set Start(value: Date | undefined) {
    this._Start = value;
    this.dataSource.start.next(value);
  }
  private _End: Date | undefined;
  @Input()
  public get End(): Date | undefined {
    return this._End;
  }
  public set End(value: Date | undefined) {
    this._End = value;
    this.dataSource.end.next(value);
  }

is working and correct.正在工作并且正确。

Now in the datasource I have the 2 dates/subject现在在数据源中我有 2 个日期/主题

export class MytableDataSource extends DataSource<MytableItem> {
  data: MytableItem[] = EXAMPLE_DATA;
  start: Subject<Date> = new Subject<Date>();
  end: Subject<Date> = new Subject<Date>();

and finally here is my doubt and my question, all about the part where I trigger the changes.最后,这是我的疑问和问题,所有关于我触发更改的部分。

  let _start : Date 
  let _end : Date
  this.start.subscribe(update => _start = update);
  this.end.subscribe(update => _end = update);
  return merge(
      observableOf(this.data) ,this.paginator.page, this.sort.sortChange, 
      this.start.pipe(), this.end.pipe())
    .pipe(map(() => {
      console.log('merge piped');
      return this.getPagedData(
        this.getSortedData(
        this.filterByDateRange(_start, _end,
          [...this.data ])));
    }));

Even if my tests say the results are correct and the changes are propagated fine, I can't see in the code what guarantees the updates to _start and _end are always done before they are passed to filterByDateRange .即使我的测试表明结果是正确的并且更改传播得很好,我也无法在代码中看到什么保证_start_end的更新总是在它们传递给filterByDateRange之前完成。

I think that those inside the parenthesis are two asynchronous events, therefore how is the sequence of the first action ( update =>... following the start/end's subscribe ) and the second action ( this.filterByDateRange(... following the start/end's pipe() in the merge ) really guaranteed to happen in the right order, the latter always after the former?我认为括号内的那些是两个异步事件,因此第一个动作( update =>...在开始/结束subscribe之后)和第二个动作( this.filterByDateRange(...在开始之后)的顺序如何/end's pipe() in the merge ) 真的保证以正确的顺序发生,后者总是在前者之后?

I'm much more convinced that the following is instead the correct approach by replacing the subscriber with a map, as explained in a similar answer .我更相信以下是正确的方法,即用 map 替换订户,如类似答案中所述。

You can't return an observable from subscribe but if you use map instead of subscribe then an Observable is returned.您不能从 subscribe 返回 observable,但如果您使用 map 而不是 subscribe 则返回 Observable。

  let _start : Date 
  let _end : Date
  return merge(
      // observableOf(this.data) ,
      // BTW you don't need this above
      this.paginator.page, this.sort.sortChange, 
      this.start.pipe(map(update => {
        _start = update;
        return update;})), 
      this.end.pipe(map(update => {
        _end = update;
        return update;})))
    .pipe(map(() => {
      console.log('merge piped');
      return this.getPagedData(
        this.getSortedData(
        this.filterByDateRange(_start, _end,
          [...this.data ])));
    }));

But

Since the _start/_end updates are side effects, I believe it's better to make them happen only in the unique final, merged stage:由于 _start/_end 更新是副作用,我相信最好只在唯一的最终合并阶段发生它们:

  let _start : Date 
  let _end : Date
  return merge(
      this.paginator.page, this.sort.sortChange, 
      this.start.pipe(map(update => {
        return {'start':update};})), 
      this.end.pipe(map(update => {
        return {'end':update};})))
    .pipe(map((merged) => {
      if ('start' in merged ) {
        _start = merged.start;
      }
      if ('end' in merged ) {
        _end = merged.end;
      }
      console.log('merge piped');
      return this.getPagedData(
        this.getSortedData(
        this.filterByDateRange(_start, _end,
          [...this.data ])));
    }));

Lossy backpressure有损背压

Another reason why mapping could be better than the original subscriber is to apply a debounce (to user input), so that the consumer (maybe running queries) does not get overwhelmed (but that's outside the scope of this question).映射可能比原始订阅者更好的另一个原因是应用去抖动(对用户输入),以便消费者(可能正在运行的查询)不会不知所措(但这不在这个问题的 scope 范围内)。

IO Effect IO 效果

Another real-world example is the http IO Effect, that would require (in functional language dictionary) a flatMap (obsolete, it's mergeMap ) composed with a cache (when the date range filter is included in the previous fetched data)另一个真实世界的例子是 http IO 效果,这将需要(在功能语言字典中)一个 flatMap (已过时,它是mergeMap )与一个缓存组成(当日期范围过滤器包含在先前获取的数据中时)

  let _start : Date 
  let _end : Date
  return merge(
      this.paginator.page, this.sort.sortChange, 
      this.start.pipe(map(update => {
        return {'start':update};})), 
      this.end.pipe(map(update => {
        return {'end':update};})))
    .pipe(map((merged) => {
      var mustLoad = false;
      if ('start' in merged ) {
        if (merged.start) {
          if (!_start || _start.getTime() > merged.start.getTime()) {
            mustLoad = true;
          }
          _start = merged.start;  
        }
      }
      if ('end' in merged ) {
        if (merged.end) {
          if (!_end || _end.getTime() < merged.end.getTime()) {
            mustLoad = true;
          }
          _end = merged.end;  
        }
      }
      if (mustLoad && _start && _end) {
        return {"cache":[], "mustLoad":true}
      } else {
        mustLoad = false;
      }
      return {"cache":this.data, "mustLoad":false};
    })).pipe(mergeMap ((a) => {
      if (a.mustLoad) {
        if (!this.database) {
          console.log("Database not initialized!");
          return observableOf([]);
        }
        console.log('firing API call');
        return this.database.getBrokerFees(_start,_end);
      }
      return observableOf( a.cache);
    })
      ).pipe(map(a => {
        const filtered = this.filterByDateRange(_start, _end, a);
        this.data = filtered;
        const sorted = this.getSortedData(filtered);
        var paged = [...sorted] // needs copy of sorted 
        paged  = this.getPagedData(paged); // paged works inline and modify input
        return paged;
      }));

Other Higher-Order Operators其他高阶运算符

See also this Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap).另请参阅高阶 RxJs 映射运算符的 综合指南:switchMap、mergeMap、concatMap(和 excludeMap)。

Furthermore, since you are dealing with Reactive Forms , it's important that you realize that:此外,由于您正在处理反应式 Forms ,因此您必须意识到:

Every control in the form (select, input, checkbox, etc…) has a valueChanges property, an Observable that we can subscribe to in order to observe changes over time and transform values with RxJS operators in a functional way.表单中的每个控件(选择、输入、复选框等)都有一个valueChanges属性,一个我们可以订阅的 Observable 以便观察随时间的变化并使用 RxJS 运算符以功能方式转换值。

Hence I guess you could avoid the custom date subjects in your question if you directly feed the standard form observable into the merge map, though you might prefer the already defined subjects to implement your specific caching strategy.因此,我想如果您直接将可观察的标准表单输入到合并 map 中,您可以避免问题中的自定义日期主题,尽管您可能更喜欢已经定义的主题来实现您的特定缓存策略。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM