#angular #pipe #angular-changedetection
#angular #канал #angular-changedetection
Вопрос:
TL: DR; — Чистый канал не запускается при поступлении новой ссылки, но должен. Есть идеи?
Мы не хотим использовать ngOnChanges или doCheck, потому что раньше мы заканчивали с красивым беспорядком кода sphagetti. Наш вариант использования достаточно прост, и мы просто хотим иметь возможность распространять данные на все дерево DOM.
OnPush не работает, уже пробовал
Мы проводим рефакторинг нашего приложения Angular, которое стало достаточно большим, чтобы иметь достаточно ошибок и проблем из-за дополнительной сложности событий (и событий websockets, обновляющих данные).
Поэтому мы решили создать службу передачи данных для каждой главной страницы приложения. Он сохранит состояние этой страницы и будет дескриптором перехода для обработки любых событий, которые может захотеть запустить любой подкомпонент, а также всех событий websocket, которые поступают из соединения
Проблема в том, что у нас много проблем с распространением данных. И что это устаревший код, поэтому мы не хотим изменять все подкомпоненты, поэтому нам нужно выполнить преобразование данных для ввода, требуемого подкомпонентами.
Мы уже знали о том, что Angular не может обнаружить изменения во внутренних элементах объекта, но это было исправлено достаточно быстро, (плохо) заставляя наш DataService создавать новые ссылки на объекты в каждом обновлении, поэтому у нас не было бы никаких проблем с тем, что Angular не обнаруживает их
export interface ProjectData {
list?: PageObject<Project>;
active?: Project;
}
...
private _state: ProjectData = {};
private _stateSubject: BehaviorSubject<ProjectData> = new BehaviorSubject(this._state);
public state$: Observable<ProjectData> = this._stateSubject.asObservable();
...
//We save the _state internally for checks on valid state updates (is the item active? is the item on the list?)
requestList(options: QueryOptions) {
this.projectService
.list$(options)
.pipe(
map(page => {
this._state.list = page;
this._state = this.replicateState(this._state);
return this._state;
})
)
.subscribe(this._stateSubject);
}
...
// Use to force a new reference for the state object.
// To be improved to actually make the logic work in an immutable way. For now, we want
// to get this working, that's all.
replicateState(oldState: ProjectData): ProjectData {
return {
list: oldState.list,
active: oldState.active
};
}
Затем это состояние $ извлекается основным компонентом страницы (он же родительский компонент). Он будет передавать свои данные каждому подкомпоненту. В связи с этим вопросом у нас в настоящее время возникают проблемы с компонентом таблицы, в котором перечислены объекты, разбитые на страницы.
<sv-table-v2
[listPage]="(data$ | async).list"
[headers]="headers$ | async"
...other inputs
></sv-table-v2>
И затем, у этого sv-table-v2 есть компонент, который отображает список. Хотя это из старой версии, и мы не хотели продолжать рефакторинг, поэтому мы хотели преобразовать нашу страницу в класс Rows, который компонент использует во входных данных
<sv-table-list
#rows
[loaded]="listPage"
[rows]="listPage.data | createRows: headers"
...other inputs
></sv-table-list>
Нам пришлось создать чистый канал, потому что мы не могли вызвать функцию в шаблоне, не вызывая ее до бесконечности. Предположительно, канал кэширует входные данные и будет повторно запускаться только при изменении входной ссылки. Но в данном случае это так и не работает.
Мы хотим иметь возможность распространять данные без необходимости копаться в onChanges или doCheck. Мы считаем их хакерскими, если вы не знаете достаточно хорошо, что с ними делать, не имеете сложного случая для управления или проблем с производительностью, которых у нас нет ни для чего, кроме передачи входных данных T_T . Мы также получили целую загрузку кода sphaguetti раньше, поэтому мы пытаемся избежать этого, даже если многие ответы пытаются взломать такие вещи с помощью этих методов жизненного цикла
На самом деле, мы только хотим иметь возможность распространять изменения из службы данных на все дерево DOM. Вот и все.
Мы уже пробовали стратегию OnPush вместо конвейера, но она тоже не работает. И, насколько я прочитал, нам все равно нужно, чтобы Angular заметил, что ссылка изменилась.
Также: все это РАБОТАЕТ, если мы не используем канал. Данные не будут отображаться в таблице (потому что это неправильный тип), но список обновит его размер в соответствии с размером страницы, который мы запрашиваем, поэтому мы знаем, что данные могут распространяться правильно. Вот почему канал является нашим главным подозреваемым, это единственное изменение.
Редактировать:
Код для канала, который я забыл
import { Pipe, PipeTransform } from '@angular/core';
import { Resource } from 'src/app/core/models/resource';
import { ColumnHeader, Table } from 'src/app/core/models/table-data';
@Pipe({
name: 'createRows'
})
export class CreateRowsPipe implements PipeTransform {
transform(data: Resource[], headers: ColumnHeader[]): any {
return new Table(data, headers).rows;
}
}
Комментарии:
1. Я не уверен, что без надлежащей отладки вашего кода, но вы могли бы использовать оператор распространения, чтобы заметить изменение ссылки, следовательно, попробуйте
return { ...oldState, list: oldState.list, active: oldState.active }
свойreplicateState
метод.
Ответ №1:
Итак, мы нашли решение еще в прошлом, но я чувствую его хакерским, особенно потому, что эта практика была заимствована из SO, как правильный способ заставить субъекта подписаться на observable, чтобы получить от них все события.
Итак:
requestList(options: QueryOptions) {
this.projectService
.list$(options)
.pipe(
map(page => {
this._state.list = page;
this._state = this.replicateState(this._state);
return this._state;
})
)
.subscribe(this._stateSubject);
}
Этот метод и каналы будут вызываться каждый раз при вызове метода и выполнении HTTP-запроса, но подписка не будет отправлять новое событие (результат HTTP-запроса) в this._stateSubject после неизвестного события. Это похоже на это._stateSubject просто перестает отправлять данные.
Но если у вас есть базовый способ отправки его вручную (что я делал до того, как получил ответ так давно)
requestList(options: QueryOptions) {
this.projectService
.list$(options)
.pipe(
map(page => {
this._state.list = page;
this._state = this.replicateState(this._state);
return this._state;
})
)
.subscribe(state => this._stateSubject.next(state));
}
Тогда объект никогда не перестанет работать и будет отправлять любые новые данные http-запросов, которые выполняются при вызове этой функции.
Теперь, когда я пишу это, я думаю, что проблема может заключаться в том, что подписка на подобную тему, у которой есть весь наблюдаемый интерфейс, может прекратиться, потому что HTTP-запрос отправляет сигнал завершения, поэтому тема также завершается через неизвестное время. Но это то, что я понял, когда писал это, и я не уверен, что это основная проблема.
TLDR: просто отправьте его с помощью next, если у вас возникли проблемы. Я не уверен, в чем заключается основная проблема.