Проблема
Вы хотите, чтобы ваш фильтр отображался в разных местах макета в зависимости от размера экрана.
Ваше текущее решение
Ваш дочерний элемент управления заботится о макет - ХОРОШО
Ваш дочерний элемент управления запрашивает несколько версий фильтра - ПЛОХО
Почему это плохо?
Второй момент плох, потому что вы хотите сохранить один экземпляр компонента фильтра по соображениям производительности.
Если бы мы подумали о том, как ваш дочерний компонент имеет API (что он эффективно делает), он бы выглядел так:
child: {
title: string;
filterTop: FilterComponent;
filterBottom: FilterComponent;
}
Это плохой дизайн на любом языке *, так как родитель не заботится о том, где будет отображаться фильтр, он просто заботится о передаче фильтра. Ваша реализация просочилась из вашего дочернего компонента.
* На любом языке ОО мы будем использовать ту же ссылку, если мы хотим сослаться на один экземпляр. К сожалению, мы говорим о HTML здесь.
Так в чем же решение?
Решением для передачи в одном экземпляре является передача только в одном экземпляре. Это звучит очевидно, но причины этого более интересны. Вы должны подумать об упрощении API вашего дочернего компонента и думать об отдельных элементах.
Вопрос:
Какие компоненты нужны моему ребенку?
Ответ:
Заголовок и фильтр!
Ответ, вероятно, не "Заголовок и один фильтр на точку останова".
Ваш родительский компонент должен выглядеть примерно так:
<app-filter-details (backClicked)="goBack()" [whiteBackground]="true">
<filter-details-title>
{{ contentOfDetailsTitle }}
</filter-details-title>
<filter-details>
<app-whatever-content></app-whatever-component>
</filter-details>
</app-filter-details>
Здесь есть как минимум 3 решения, о которых я могу подумать
- Вручную прикрепить фильтр к родителю в DOM в зависимости от размера экрана.
Тьфу - это не очень Angular.
CSS - упорядочение flexbox + медиазапросы
В зависимости от сложности вашего макета (я полагаю, вы упростили его для вопроса), используйте flexbox и медиазапросы для изменения порядка контейнера элементы внутри вашего дочернего компонента. Если вы используете CSS framework, у них уже могут быть утилиты для этого.
Простая демонстрация переупорядочения (с использованием класса, а не медиазапроса):
var container = document.getElementById('container');
var toggle = document.getElementById('toggle');
toggle.addEventListener('click', toggleOrder);
function toggleOrder() {
container.classList.toggle('reorder');
}
.container {
display: flex;
flex-direction: column;
}
.reorder .first {
order: 3;
}
.reorder .third {
order: 1;
}
<div id="container" class="container">
<div class="first">
1
</div>
<div class="second">
2
</div>
<div class="third">
3
</div>
</div>
<button id="toggle">
Toggle order
</button>
Сохранение состояния фильтра в службе (и сохранение текущего макета)
Это, вероятно, наиболее Angular решение. Ваш сервис отвечает за сохранение текущего состояния. Ваш элемент управления фильтра отвечает за указание службе делать новые запросы API (после события, управляемого пользователем). Ваш родитель несет ответственность за указание службе выполнить первый запрос API при загрузке (если это необходимо).
Ключом к этому является разделение идеи о том, что подписка на службу напрямую отвечает за запрос HTTP. Вы можете просто использовать шаблон, подобный этому, для разделения подписок по запросам данных:
export class SearchService {
constructor(private http: HttpClient) {}
// TODO: emit null value when parent component is destroyed?
private search$: Subject<SearchResults> = new ReplaySubject<SearchResults>(1);
subscribeSearchResults(): Observable<SearchResults> {
return this.search$.asObservable();
}
submitSearchRequest(criteria): void {
const url = this.getSearchUrl(criteria); // TODO: implement
this.http.get<SearchCriteria>(url).subscribe(response => {
this.search$.next(response);
});
}
}
И ваш компонент будет взаимодействовать со службой так:
results: SearchResults;
private destroyed: Subject<void> = new Subject<void>();
ngOnInit() {
this.searchService.subscribeSearchResults().pipe(
takeUntil(this.destroyed)
).subscribe(result => {
this.results = results;
});
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}
onSearchSubmit(): void {
this.searchService.submitSearchRequest(this.criteria);
}
Заключение
Моим предпочтением будет решение 2. Это проблема пользовательского интерфейса, которая может быть решена простым решением пользовательского интерфейса. Решение 3 потребует достаточного количества дополнительного кода и потенциальной боли.