Странная проблема Rxjs с BehaviorSubject

Я испытываю любопытное поведение с BehaviorSubject в моем угловом (7) приложении.

Я создал службу, которая потребляет некоторые остальные вызовы. Чтобы представить, что происходит в коде, вот вы:

export class MyService {
    private dataFromServer = new BehaviorSubject<IDataFromServer[]>(null);

    constructor(private http: HttpClient) { }

    getAll(): Observable<IDataFromServer[]> {
        this.http.get<IDataFromServer[]>('/api/rest')
        .pipe(
            tap(data => this.dataFromServer.next(data))
        );
    }
    return this.dataFromServer.asObservable();
}

Все идет нормально. Когда мне нужно использовать эту услугу, я подписываюсь на метод getAll(), например

this.myService.getAll().subscribe(console.info);

и вывести данные на консоль.

Теперь мне нужно добавить данные из интерфейса в остальные API.

export class MyService {
    private dataFromServer = new BehaviorSubject<IDataFromServer[]>(null);

    // snip...

    add(item: IDataFromServer): Observable<any> {
        return this.http.post<IDataFromServer>('/api/manage', item).pipe(
            tap(data => {
                let internal = this.dataFromServer.getValue();
                if (!internal) {
                    internal = new Array<IDataFromServer>();
                }
                internal.push(material);
                this.dataFromServer.next(internal);
            })
        );
    }
}

Вот тут и начинаются проблемы. Подписка выше печатается в первый раз, но не получает новые данные, инициированные функцией next().

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

Я явно что-то делаю не так, но я не понимаю, почему и где.

Спасибо за любую вашу помощь.

Где называется add? пожалуйста, включите это в свои фрагменты

ggradnig 03.05.2019 09:34

В вашем последнем фрагменте тот факт, что ничего не подписалось на BehaviorSubject, кажется подозрительным.

miqh 03.05.2019 09:38

из вашего кода видно, что вы подписываетесь на метод add, а не на dataFromServer, поскольку он частный, и вы не сможете подписаться на него изнутри..

Vitalii Chmovzh 03.05.2019 09:40

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

ggradnig 03.05.2019 09:40

Не видя этого, я бы предположил, что результат add не подписан. Поэтому tap никогда не вызывается.

ggradnig 03.05.2019 09:40

См.: stackoverflow.com/questions/52188795/…

ggradnig 03.05.2019 09:41
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
6
304
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Проблема в том, что вы звоните this.http.get<IDataFromServer[]>('/api/rest') внутри getAll, но никто никогда не подписывается на него.

Первый журнал, который вы упомянули, должен быть null, потому что вы инициализируете свой BehaviorSubject нулем.

Что вам нужно, так это просто вернуть http.get из getAll, чтобы любой, кто вызывает этот метод и подписывается на него, запускал HTTP-вызов. Кроме того, tap позаботится о том, чтобы ваши данные в BehaviorSubject были обновлены.

 getAll(): Observable<IDataFromServer[]> {
    return
        this.http.get<IDataFromServer[]>('/api/rest')
        .pipe(
            tap(data => this.dataFromServer.next(data))
        );
 }

Спасибо за Ваш ответ. Если я добавлю возврат и удалю возврат this.dataFromServer.asObservable(); как вы предложили, работает в первый раз, но последующие обновления по-прежнему не работают.

Valerio 03.05.2019 10:12

Хотя вы не включили часть, где вы вызываете функцию add, я почти уверен, что вы не подписываетесь на ее результат.

У вас может быть строка кода, которая говорит что-то вроде

myService.add(myItem);

Теперь это вернет Observable. Однако обратите внимание, что если никто не подпишется на этот Observable, подключенные каналы не будут активированы.

Следовательно, у вас есть два решения.

Первое: не используйте tap, а подписывайтесь. Я почти уверен, что это то, что вы хотите. Это выглядит так:

add(item: IDataFromServer): void {
    this.http.post<IDataFromServer>('/api/manage', item)
        .subscribe(data => {
            let internal = this.dataFromServer.getValue();
            if (!internal) {
                internal = new Array<IDataFromServer>();
            }
            internal.push(material);
            this.dataFromServer.next(internal);
        }
    );
}

Второе: Подпишитесь на результат add. Что-то вроде этого:

myService.add(myItem).subscribe();

Проблема возникает из-за распространенного неправильного понимания Observables. Может этот пост поможет.

Если я подпишусь на add(), я получу ошибку, которая не является наблюдаемой.

Valerio 03.05.2019 10:29

Вы можете сделать либо первое, либо второе решение. Если вы хотите подписаться на него (решение 2), то add необходимо вернуть наблюдаемое, как показано в исходном фрагменте.

ggradnig 03.05.2019 10:30

Вы правы, и вместе с ответом @bracco23 прояснили мне мою ошибку. Мое единственное сожаление, что я не могу отметить ваш комментарий, решение среди ответов

Valerio 03.05.2019 10:34
Ответ принят как подходящий

Вместе с отличными ответами на вопрос, почему ваше решение не работает, я добавлю свое мнение о том, что не так с вашим подходом.

Из того, что я вижу, вы пытаетесь предоставить единственную наблюдаемую из вашей службы, которая будет использоваться при каждом вызове REST, позволяя представлениям отделять и повторно использовать логику визуализации данных от входа в систему обновления данных (а именно, им просто нужно будет отображать данные один раз и каждый раз, когда они обновляют данные, будет вызываться одна и та же логика).

Проблема в том, что httpClient Observables ленивы, фактически отправляют запросы, только если кто-то подписывается.

Я сделал то же самое, и обычно я скрываю http-вызовы в своем сервисе и подписываюсь внутри. Заимствуя ваши классы, это будет выглядеть так:

export class MyService {
    private dataFromServer = new BehaviorSubject<IDataFromServer[]>(null);

    constructor(private http: HttpClient) { }

    getAll(): Observable<IDataFromServer[]> {
        this.http.get<IDataFromServer[]>('/api/rest')
                 .subscribe(data => this.dataFromServer.next(data));
        return this.dataFromServer.asObservable();
    }

    add(item: IDataFromServer): Observable<any> {
        return this.http.post<IDataFromServer>('/api/manage', item)
                        .subscribe(data => this.dataFromServer.next(data));
    }
}

Я также предполагаю, что ваша конечная точка следует принципам REST, поэтому путь /api/manage возвращает в качестве ответа полный список, обновленный новыми добавленными данными, точно так же, как вызов /api/rest/ возвращается после вызова POST.

Это помогло. Спасибо и спасибо @ggradnig за то, что указал мне на правильный способ думать о моей ошибке.

Valerio 03.05.2019 10:35

Другие вопросы по теме