Предотвращение двойных вызовов API от дублированного компонента при включении (показан только один из них) - PullRequest
1 голос
/ 25 февраля 2020

Я использую transclusion в angular, чтобы определить фиксированный шаблон представления и определить слоты для динамического c контента.

Компонент - app-filter-details, а его шаблон:

<div id="details-wrapper">
    <div class="details-nav-bar">
        {{ navbarContent }}
    </div>
    <div class="details-wrapper">
        <div class="details-top">
            <ng-content select="filter-details-title"></ng-content>
        </div>
        <div *ngIf="!mobile()>
            <ng-content select="filter-details-content"></ng-content>
        </div>
    </div>
</div>
<app-bottom-sheet *ngIf="mobile() && mapStateService.isMaximized()">
    <div class="details-body moute-scrollable">
        <ng-content select="filter-details-content-bottomsheet"></ng-content>
    </div>
</app-bottom-sheet>

А затем другие компоненты используют его следующим образом:

<app-filter-details (backClicked)="goBack()" [whiteBackground]="true">
    <filter-details-title>
        {{ contentOfDetailsTitle }}
    </filter-details-title>
    <filter-details-content>
        <app-whatever-content></app-whatever-component>
    </filter-details-content>
    <filter-details-content-bottomsheet>
        <app-whatever-content></app-whatever-component>
    </filter-details-content-bottomsheet>
</app-filter-details>

Это в основном загружает содержимое filter-details-title, filter-details-content и filter-details-content-bottomsheet. И я должен установить всегда одинаковое содержимое на filter-details-content и filter-details-content-bottomsheet, и оно будет отображать одно или другое в зависимости от того, просматривается ли на экране мобильного телефона:

mobile() {
    return (window.innerWidth <= 576);
}

Пока все хорошо, он загружает содержимое обоих и только один отображается в зависимости от ширины окна. Проблема в том, что этот компонент содержимого app-whatever-content загружается дважды и выполняет некоторые вызовы API для init. Я вижу, что все вызовы API выполняются дважды (один раз для компонента в нижней части листа и один раз для другого).

Изменения в мобильной версии не просто переупорядочивают компонент, это совершенно другой макет, на мобильном телефоне он показывает плавающую эмуляцию нижнего листа (material.io/components/sheets-bottom), тогда как в немобильном отображается внутри родительского компонента.

Есть ли способ, которым я мог бы загрузить только один из два компонента или помешать ему делать дополнительные вызовы API как-то?

Ответы [ 2 ]

1 голос
/ 25 февраля 2020

Я думаю, вы могли бы создать вход и передать, если он отображается на мобильном телефоне.

@Input() showInMobile: boolean;

, чтобы вы могли установить его значение в макете компонента:

<app-filter-details (backClicked)="goBack()" [whiteBackground]="true">
    <filter-details-title>
        {{ contentOfDetailsTitle }}
    </filter-details-title>
    <filter-details-content>
        <app-whatever-content [showInMobile]="false"></app-whatever-component>
    </filter-details-content>
    <filter-details-content-bottomsheet>
        <app-whatever-content [showInMobile]="true"></app-whatever-component>
    </filter-details-content-bottomsheet>
</app-filter-details>

, а затем добавить проверка onResize ():

@HostListener('window:resize', ['$event'])
onResize() {
    if (this.showInMobile === this.mobile()) {
        if (!this.searching) {
            this.searching = true;
            // subscribe to api calls
        }
    } else {
        // unsubscribe if needed
        this.searching = false;
    }
}

И вам нужен личный переменный поиск, инициализированный как false:

private searching = false;
1 голос
/ 25 февраля 2020

Проблема

Вы хотите, чтобы ваш фильтр отображался в разных местах макета в зависимости от размера экрана.

Ваше текущее решение

Ваш дочерний элемент управления заботится о макет - ХОРОШО

Ваш дочерний элемент управления запрашивает несколько версий фильтра - ПЛОХО

Почему это плохо?

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

Если бы мы подумали о том, как ваш дочерний компонент имеет 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 решения, о которых я могу подумать

  1. Вручную прикрепить фильтр к родителю в 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 потребует достаточного количества дополнительного кода и потенциальной боли.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...