Comments 26
Немного странная статья, вначале говорится, что операторов много и тяжело запомнить, потом непонятный переход к ошибкам, потом предложение использовать стороннюю либу без каких либо объяснений (давайте добавим ещё один оператор, а то что то их мало).
На самом деле описанный подход не учитывает, что помимо данных и ошибки у нас часто есть ещё некий флаг, говорящий о том, что данные грузятся или уже загружены.
Можете рассказать, сколько потребуется правок, чтобы добавить в разметку блок, который будет отображаться в момент загрузки данных?
Прочитав статью (заметку?), я так и не понял, что хотел донести до нас автор: что у него не реализовано отслеживание статуса загрузки, или то, что новая библиотека поможет с решением того, что можно решить без это библиотеки?
Всегда в таких случаях возникает вопрос: зачем тянуть ещё библиотеки, если половину из описываемого можно решить без них?
Пользуюсь активно Rx (для JS и C#) - каких-либо проблем не наблюдаю с загрузкой данных... То есть, я могу сказать, что проблема искусственная(?)..
Это будет в следующей статье, и там для этого будет и новый контейнер, и другой компонент. Текст постараюсь сегодня подправить, чтобы переход был понятен.
Вроде в туториалах ангуляра вот такой подход используется. Чем он не устроил?
<ng-template [ngIf]="profile$ | async as profile; else profileLoadingOrError">
{{profile}}
</ng-template>
<ng-template #profileLoadingOrError>
<ng-template [ngIf]="profileError$ | async as error; else profileLoading">
{{error}}
</ng-template>
</ng-template>
<ng-template #profileLoading>
Loading...
</ng-template>
profile$ = ...; // .pipe(shareReplay(1))
profileError$ = profile$.pipe(catchError(error => of(error)));
Покрывает все три состояния лоадинг/ошибка/результат и лишнего бойлерплейта почти нет.
Всё это конечно можно попробоывать завернуть в одну структурную директиву и вообще будет красиво.
Этот подход и есть пример из статьи, который мне не нравится.
Он громоздкий как в темплейте, так и в коде
Его придется вручную проверять в случае, когда на основе первых данных придется делать вызовы для загрузки следующих - if (error) else
Придется постоянно держать в уме все случаи, когда надо менять флажки - тот же isLoading не может быть true в случае когда данные загружены, а модель это обычно позволяет.
Дополню только что данная статья только про важность ошибок и способа работы с ними, а вот структурная директива с поддержкой лоадинга и прочего будет в следующей статье
В том же Ангуляре можно писать вообще так:
<div *showWaitingIndicatorAndErrorMessageAutomatically>
<div>Name: {{name}}</div>
<div>Age: {{age}}</div>
</div>
get data() { return fetchJSON( '/profile' ) }
get name() { return this.data.name }
get age() { return currentYear - this.data.birth.year }
Почему все так не делают, для меня остаётся загадкой. Возможно дело в стокгольмском синдроме из-за RxJS.
А как будет выглядеть код showWaitingIndicatorAndErrorMessageAutomatically
хотя бы примерно?
Это оптимизированная версия ngIf + AsyncPipe, для обычных обcерваблов не подойдет. rxLet
из rx-angular решает почти все вопросы из этой статьи и сомневаюсь, что можно сделать что-то лучше на данный момент.
Оно к RX вообще никакого отношения не имеет.
Да, Reactive context | RxAngular (rx-angular.io) - решает последствия проблемы (показ ошибки), но не причину (падение сети и подписок), хотя с ней будет попроще. Мое решение будет в следующей статье
Почему же это так нравится тем, кто это уже изучил?
Я изучил, но мне не нравится. Что со мной не так?
Потому что вы начинаете понимать все могуществао RxJS, когда парой операторов вы можете сделать то, что в императивном коде писали бы полдня на двух страницах.
const FilterSource = new BehaviorSubject< null | ( toy: Toy )=> boolean >( null )
const Filter = FilterSource.pipe( distinctUntilChanged() , debounce(0) , shareReplay(1) )
const ToysSource = new BehaviorSubject( [] )
const Toys = ToysSource.pipe( distinctUntilChanged() , debounce(0) , shareReplay(1) )
const ToysFiltered = Filter.pipe(
switchMap( filter => {
if( !filter ) return Toys
return Toys.pipe( map( toys => toys.filter( filter ) ) )
} ) ,
distinctUntilChanged() ,
debounce(0) ,
shareReplay(1) ,
)
class Toys {
@mem filter( next = null as null | ( toy: Toy )=> boolean ) { return next }
@mem toys( next = [] ){ return next }
@mem toysFiltered() {
if( !this.filter() ) return this.toys()
return this.toys().filter( this.filter() )
}
}
в случае первых красиво падать, в случае вторых - уметь с ними работать и показывать нашим пользователям что-то
А можно в обоих случаях не падать, а продолжать корректную работу?
Код, соответственно, выглядит вот так:
Шёл 2022 год, шаблоны всё ещё писали без подсветки синтаксиса. И выкладывали в виде скриншотов.
Я изучил, но мне не нравится. Что со мной не так?
Полагаю то, что вы эксперт mol, а не Angular.
Шёл 2022 год, шаблоны всё ещё писали без подсветки синтаксиса. И выкладывали в виде скриншотов.
Подсветка везде есть, насколько вижу, да и код у меня через редактор хабра получался больше по размеру, поэтому скриншоты.
Ссылки на рабочий код, который можно скопировать, также везде присутствуют.
Подскажите, чем either
отличается от оператора materialize()?
Полагаю имелся в виду eitherify оператор? Потому что сама структура данных отличается фактически всем :)
materialize вообще используется крайне редко, но вот например семпл в чем его отличие от eitherify - вместо 5 next + 1 complete будет значительно больше. Основное отличие в документации - When the source Observable emits error
, the output will emit next
as a Notification of type "error", and then complete
. То есть на каждую ошибку вы будете опять делать complete вместо продолжения потока. Кроме того, ответ будет изначально одинаковый для разных HttpErrorCode, в то время как eitherify каждый может подправить под себя исходя из того, какой код пришел - 400, 401, 403, 500
Вы каждое значение в диапазоне оборачиваете в отдельный поток, поэтому у вас получается 5 complete. Но зачем?! В обычном потоке complete прилетает один раз в конце по завершению потока. В остальном же поведение eitherify и materialize как по мне одинаковые. Ну а получать на выходе union тип или же notification это уже вкусовщина.
Потому что Ангуляр с HttpClient тоже вызывает complete
- вот демо. И materialize
вызывает два раза next
, что совсем не то что нужно при подписке.
Не Ангуляр с HttpClient вызывает дополнительный complete, это вы его вызываете подписавшись через объект с complete обработчиком. Вот вам альтернативный пример без дополнительного вызова complete stackblitz.com
У меня складывается впечатление, что вы пытаетесь критиковать стандартный оператор без попытки понять как он работает. Я предлагаю закончить эту дискуссию, потому что я получил ответ на свой первоначальный вопрос.
Откройте свое демо, нажмите кнопку Run ОК
и откройте консоль - будет два записи, а не одна.
Первая строка в случае получения данных, вторая - закрытие успешного потока, третья - ошибка. В этом нет complete после каждого next-а, как в вашем первом примере и нет дубликации complete как в вашем втором примере. Откройте в конце концов документацию и прочитайте её внимательно.
Откройте ваше демо
Нажмите Run OK - то что должно успешно загрузить пользователя. Подписчик ожидает получить в случае успеха 1 запись с профилем, но так как ангуляр вызывает комплит для хттп вызова, а materialize сконвертирует комплит в ещё один next, то подписчик получит 2 вызова next в сабскрайбе
Документацию я конечно же прочитал
RxJS: When the source Observable emits
complete
, the output Observable will emitnext
as a Notification of type "complete", and then it will emitcomplete
as well.Я не имею в виду, что materialize дублирует любой next источника. Я говорю о том что в связке с хттп клиентом Ангуляра он будет делать дубликаты.
Если вы все еще не понимаете почему materialize вызовет дважды next у подписчика Angular HttpClient то, наверное, действительно стоит закончить дискуссию.
От родного ангуляровского HttpClient отказался именно из-за убогой реализации ошибок, когда толком непонятно что за ошибка из него вылетает, когда он сам решает какой респонс код считать ошибкой, когда вынуждает писать мутные catchError и тп.
В целом, я считаю, что ангуляр и rxjs-way это адовая смесь: будет сложно, будут десятки операторов, будет совершенно непрозрачная логика, будут бесполезные стектрейсы, будут корявые до ужаса реактивные формы, будет куча асинкпайпов в шаблоне, будут лишние структурные директивы и темплейт-переменные.
И как бы в целом да, правильное направление, но до чего оно все квадратное...
На хорошем проекте такие вещи можно решить через кодогенератор на основе OpenApi схемы от бэкенда, используя например https://github.com/cyclosproject/ng-openapi-gen
Ошибки, RxJS & Angular