Загрузка медленных подресурсов в Angular2

Компонент Angular2 отображает некоторый объект. В дополнение к базовой информации об объекте необходимо загрузить дополнительные данные через службу, которая возвращает Observable и работает довольно медленно.

Базовая часть информации об объекте должна отображаться немедленно, а дополнительные данные – как только они становятся доступными.

Что-то вроде этого:

SomeObjectComponent {

    someObject: SomeObject;
    extraData: ExtraData;

    @Input()
    set item(v: SomeObject) {
        this.someObject = v;
        backendService.getExtraData(v.id)
            .subscribe(d => this.extraData = d);
    }        

}

// in the template
<some-object [item]="currentlySelected"></some-object>

Когда загрузка медленная и пользователь перемещается между различными значениями SomeObject, асинхронная загрузка может загрузить и назначить неправильное extraData для текущего элемента.

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


person Vilmantas Baranauskas    schedule 24.11.2016    source источник
comment
Я не до конца понимаю проблему. Поэтому, когда вы перемещаетесь по одному и тому же компоненту, он загружается и передаются разные item. Я предполагаю, что вы хотите отменить запрос, когда данные еще не поступили, когда вы переходите к другому элементу. Что, если вернуться назад? Я предполагаю, что вы хотите получить элемент из кеша (из предыдущего запроса). Я бы создал службу, которая обеспечивает немедленную доступность ранее загруженных элементов. Например, как показано в stackoverflow.com/questions/36271899/   -  person Günter Zöchbauer    schedule 24.11.2016
comment
Кэш не решает исходной проблемы. Здесь инициируется только один экземпляр SomeObjectComponent, а затем значение свойства currentSelected контейнера изменяется и распространяется через привязку [item]. Одно решение — сохранить подписку и отменить ее, другое — проверить this.someObject === v внутри подписки. Я хотел знать, какие возможны варианты и какой из них будет лучшим, особенно когда у нас есть несколько дополнительных элементов данных. Есть ли здесь какое-то волшебное решение RxJS?   -  person Vilmantas Baranauskas    schedule 24.11.2016
comment
Хорошо, так становится понятнее. Кэш не решает исходную проблему, но мне было любопытно, является ли это требованием.   -  person Günter Zöchbauer    schedule 24.11.2016


Ответы (1)


Если я правильно вас понял, в основном то, что вы ищете, - это способ отменить «старые» вызовы отдыха, как только будет выбран новый элемент -> способ rxjs будет использовать .takeUntil(...)

SomeObjectComponent implements OnDestroy {
    // to clean up any open subscriptions it is helpful to have a destroy-event
    private destroyed$: Subject<any> = new Subject<any>();

    // your vars
    someObject: SomeObject;
    extraData: ExtraData;

    @Input()
    set item(v: SomeObject) {
        this.someObject = v;
        this.loadExtraData$.next(v.id);
    }        

    // a trigger to load extra data
    private loadExtraData$: Subject<number> = new Subject<number>();  // type "number" is just an assumption, this can be whatever type "v.id" is

    // the stream to load extra data
    private extraData$: Observable<any> = this.loadExtraData$
        .takeUntil(this.destroyed$) // when the component is destroyed this will ensure that any open subscription will be closed as well to prevent memory leaks
        .switchMap(id => {
            return this.backendService
                .getExtraData(id)
                .takeUntil(this.loadExtraData$);  // will discard the rest-call the next time loadExtraData$ is triggered (if it is still running)
        })
        .do(extraData => this.extraData = extraData) // if you only need the data in the template, then don't use this line and use the "| async"-pipe instead
        .share();

    // this is called by the angular2-lifecycle, when the component is removed
    ngOnDestroy() {
        this.destroyed$.next();
    }
}

Если вам просто нужны дополнительные данные в шаблоне, вы можете использовать канал async:

<span>Some extra data: {{(extraData$ | async)?.someAttribute}}</span>

В качестве небольшого примечания и немного не по теме: лучше не загружать дополнительные данные внутри компонента, а за его пределами и использовать @Input() для дополнительных данных, для ряд причин:

  • тестируемость намного эффективнее и проще, если компоненты инкапсулированы и не зависят от каких-либо служб или других компонентов.
  • особенно с остальными вызовами существует опасность создания избыточных запросов, если компонент добавляется дважды, например.
  • компоненты намного проще повторно использовать в разных контекстах, если они имеют только @Input()s и @Output()s

При этом, не зная вашей текущей архитектуры, это может потребовать более серьезного рефакторинга, который может не стоить времени, но об этом следует помнить для будущих приложений.

person olsn    schedule 26.11.2016