Angular HttpInterceptor Cache не запускает обнаружение изменений

Обновлен код для уточнения. Компонент TVC содержит компонент lightweight-charts для торгового представления.

Есть боковая навигация со списком предметов. И каждый раз, когда выбирается новый / другой элемент, он запускает this.data.getDataForSymbol () в компоненте основного содержимого. Диаграмма полностью перерисовывается, когда НЕ используется кеширование ... но когда кеш используется (и подтверждено, что он работает) ... граф не перерисовывается.

Вот компонент, который отображает диаграмму:

@Component({
  selector: 'tvc',
  template: '<div #chart></div>',
})
export class TvcComponent implements AfterViewInit {

  @ViewChild('chart') chartElem: ElementRef;

  @Input()
  data: (BarData | WhitespaceData)[] | null;

  chart: IChartApi = null;

  ngAfterViewInit() {
    this.buildChart();
  }

  buildChart() {
    this.chart = createChart(<HTMLElement>this.chartElem.nativeElement, {
      width: 600,
      height: 300,
      crosshair: {
        mode: CrosshairMode.Normal,
      },
    });

    this.chart.timeScale().fitContent();
    const candleSeries = this.chart.addCandlestickSeries();
    candleSeries.setData(this.data);
  }
}

А вот компонент, в котором размещается TvcComponent, предоставляющий данные для диаграммы:

@Component({
  selector: 'main-content',
  template: `
      <div *ngIf="monthly$ | async as monthly">
        <tvc
          [data]="monthly"
        ></tvc>
      </div>`
})
export class MainContentComponent implements OnInit {

  monthly$: Observable<any[]>;

  constructor(
    private route: ActivatedRoute,
    private itemStore: ItemStore,
    private data: DataService
  ) {}

  ngOnInit(): void {
    this.route.params.subscribe((params) => {
      let id = params['id'];
      this.itemStore.items$.subscribe((items) => {
        this.monthly$ = this.data.getDataForSymbol(id, 'monthly');
      });
    });
  }
}

Вот соответствующий код для службы перехватчика:

@Injectable({ providedIn: 'root' })
export class CacheInterceptor implements HttpInterceptor {
  constructor(private cache: HttpCacheService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const cachedResponse = this.cache.get(req.urlWithParams);

    if (cachedResponse) {
      console.log(`${req.urlWithParams}: cached response`);
      return of(cachedResponse);
    }
    return next.handle(req).pipe(
      tap((event) => {
        if (event instanceof HttpResponse) {
          this.cache.put(req.urlWithParams, event);
          console.log(`${req.urlWithParams}: response from server`);
        }
      })
    );
  }
}

и сервис кеширования:

@Injectable()
export class HttpCacheService {
  private cache = {};

  get(url: string): HttpResponse<any> {
    return this.cache[url];
  }
  put(url: string, resp: HttpResponse<any>): void {
    this.cache[url] = resp;
  }
}

Я реализовал HttpInterceptor для caching (пример из Angular Github), и я кэширую HttpResponse для данных, которые затем подписываются с помощью асинхронного канала в шаблоне - и передаются как свойство input дочернему компоненту. Наблюдаемый содержит данные, отображающие диаграмму.

Данные (в основном) статичны, и выбор различных элементов запускает новый запрос Http. Таким образом, если кто-то будет переключаться между несколькими графиками, он будет делать несколько (повторных) вызовов без необходимости. Следовательно, кеширование.

Проблема в том, что, хотя кеш прекрасно работает, как определено с помощью логирования консоли) ... график не обновляется / не перерисовывается при доступе к данным из кеша. При первом выборе элемента A он получает данные с сервера и обрабатывается правильно. Если вы переместите элемент B (не в кеше), он сделает запрос к серверу, поместит ответ в кеш и отобразит правильный график. Проблема в том, что если вы переключитесь НАЗАД на элемент A, он получит правильные данные из кеша, но НЕ обновит график.

Я использую обнаружение изменений по умолчанию.


person Kevin    schedule 02.01.2021    source источник
comment
Мммм код необходим, чтобы проверить, что происходит с вашим обновлением ..   -  person Victor Martinez Calvo    schedule 03.01.2021
comment
Я бы предположил, что это вообще не проблема обнаружения изменений. Думаю, проблема в том, что компонент tvc не реагирует на изменения данных. он обновляет данные только при воссоздании (ngAfterViewInit вызывается только один раз), что, вероятно, не произойдет в описанном вами случае. Если это действительно обнаружение изменений, тогда вызов angular.io/api/core/ChangeDetectorRef#detectChanges после загрузки данных устранит проблему. Если это не обнаружение изменений, вам, вероятно, понадобится установщик на tvc.data для обновления графика.   -  person x4rf41    schedule 04.01.2021
comment
Спасибо за ввод @ x4rf41. Это помогло мне приблизиться к решению. Я заметил интересную вещь ... когда ответ fromthis.monthly$ = this.data.getDataForSymbol(id, 'monthly'); (в MainContentComponent выше) НЕ находится в кеше (и, следовательно, выполняет HTTP-запрос), срабатывает ngAfterViewInit. Когда тот же вызов возвращает данные из кеша, ngAfterViewInit НЕ запускается. Как я могу определить, что вызывает срабатывание ngAfterViewInit?   -  person Kevin    schedule 05.01.2021
comment
ngAfterViewInit срабатывает, только если создается компонент tvc. Это может произойти только тогда, когда либо *ngIf="monthly$ | async as monthly" изменяется с нуля на что-то, либо если родительский компонент создается, например, из-за изменения маршрута, но только если маршрут изменяется от другого компонента, а не только при изменении параметров, которое, как я предполагаю, происходит в вашем кейс)   -  person x4rf41    schedule 05.01.2021


Ответы (1)


Я предполагаю, что переменная monthly$: Observable<any[]> изменяется правильно по сравнению с тем, что вы написали, и что Observable получает новое значение (вы можете проверить это, зарегистрировав). В этом случае привязка [data]="monthly" будет правильно обновлена ​​при обнаружении изменений.

Это будет означать, что ваша проблема в том, что компонент tvc не обновляется правильно, потому что он не реагирует на изменения в @Input() data. Если вы измените компонент на следующий, он должен работать:

(Могут быть синтаксические ошибки с моей стороны, потому что я пишу это, не имея возможности проверить, потому что вы не предоставили рабочий пример кода)

@Component({
  selector: 'tvc',
  template: '<div #chart></div>',
})
export class TvcComponent implements AfterViewInit {

  @ViewChild('chart') chartElem: ElementRef;
  
  private _data: (BarData | WhitespaceData)[] | null;

  get data(): (BarData | WhitespaceData)[] | null {
     return this._data;
  }

  @Input()
  set data(value: (BarData | WhitespaceData)[] | null) {
     // this gets called by the change-detection when the value of monthly changes from the [data]="monthly" binding
     // with that, we can use it to refresh the data
     // because the data is not bound to the chart by angular through a template, we have to do it manually. the change-detection only goes so far
     this._data = value;
     this.refreshData(); 
  }

  chart: IChartApi = null;
  candleSeries: any = null; // I don't know the correct type so I use any. You should change that

  ngAfterViewInit() {
    this.buildChart();
    this.refreshData();
  }

  buildChart() {
    this.chart = createChart(<HTMLElement>this.chartElem.nativeElement, {
      width: 600,
      height: 300,
      crosshair: {
        mode: CrosshairMode.Normal,
      },
    });

    this.chart.timeScale().fitContent();
    this.candleSeries = this.chart.addCandlestickSeries();
  }

  refreshData() {
    if (!this.candleSeries) return; // might not be initialized yet
    // I don't know the library, so I can't be sure that this is enough to update the data. 
    // You may have to do more. You can put a log here to see if it triggers
    this.candleSeries.setData(this.data);
  }
}

Надеюсь, это сработает для вас. Просто убедитесь, что установщик data вызывается правильно при изменении данных. Остальное можно обработать с помощью метода refreshData().

person x4rf41    schedule 05.01.2021