Определяем контрольные точки в Angular

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

Содержание

В чем состоит план

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

  • Видимый только на xs: .d-block .d-sm-none;
  • Видимый только на sm: .d-none .d-sm-block .d-md-none;
  • Видимый только на md: .d-none .d-md-block .d-lg-none;
  • Видимый только на lg: .d-none .d-lg-block .d-xl-none;
  • Видимый только на xl: .d-none .d-xl-block.

CSS свойство display будет переключаться на значение none или block. Применим эти классы к элементам HTML.

Каждый раз, когда изменяется размер экрана, мы будем перебирать элементы, пока не найдем HTML- элемент со свойством display: block. Так определяется текущая контрольная точка.

Реализация: компонент

Создаем компонент size-detector в Angular.

HTML-шаблон компонента:

<!-- size-detector.component.html -->
<div *ngFor="let s of sizes" class="{{s.css + ' ' + (prefix + s.id) }}">{{s.name}}</div>

Код компонента TypeScript:

// size-detector.component.ts
...
export class SizeDetectorComponent implements AfterViewInit {
  prefix = 'is-';
  sizes = [
    {
      id: SCREEN_SIZE.XS, name: 'xs', css: `d-block d-sm-none`
    },
    {
      id: SCREEN_SIZE.SM, name: 'sm', css: `d-none d-sm-block d-md-none`
    },
    {
      id: SCREEN_SIZE.MD, name: 'md', css: `d-none d-md-block d-lg-none`
    },
    {
      id: SCREEN_SIZE.LG, name: 'lg', css: `d-none d-lg-block d-xl-none`
    },
    {
      id: SCREEN_SIZE.XL, name: 'xl', css: `d-none d-xl-block`
    },
  ];

  @HostListener("window:resize", [])
  private onResize() {
    this.detectScreenSize();
  }

  ngAfterViewInit() {
    this.detectScreenSize();
  }

  private detectScreenSize() {
    // пропишем эту логику позже
  }
}

Но откуда взято значение SCREEN_SIZE. *. Это перечисление. Создадим перечисление screen size enum и поместим его значения в тот же файл компонента.

// screen-size.enum.ts

/_ Перечисление, определяющее все размеры экрана, которые поддерживает приложение _/
export enum SCREEN_SIZE {
  XS,
  SM,
  MD,
  LG,
  XL
}

Не забудьте добавить в проект Bootstrap. Его можно добавить через npm или yarn. но в этом примере мы используем более простой способ. Добавьте ссылку на него в файл index.html.

<!-- index.html -->
<link rel="stylesheet" 
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

Пояснение к коду:

  1. Сначала определяем список поддерживаемых размеров (sizes) и CSS-классы, которые используются для определения каждой контрольной точки.
  2. В HTML перебираем список размеров, создаем элемент div, назначаем CSS и отображаем его. Также обратите внимание, что для каждого div назначается уникальный CSS-класс is-<SIZE_ENUM>.
  3. В функции detectScreenSiz прописываем логику того, как будут обнаруживаться изменения размера экрана.
  4. Запускаем этот механизм каждый раз, когда меняется размер экрана. Используем декоратор HostListener для прослушивания события window resize.
  5. Также нужно запустить логику при первой инициализации приложения во время хука жизненного цикла компонента AfterViewInit.

Реализация: сервис и компонент

Теперь у нас есть практически готовый код компонента. Поэтому приступим к реализации сервиса изменения размера окна просмотра.

// resize.service.ts

@Injectable()
export class ResizeService {

  get onResize$(): Observable<SCREEN_SIZE> {
    return this.resizeSubject.asObservable().pipe(distinctUntilChanged());
  }

  private resizeSubject: Subject<SCREEN_SIZE>;

  constructor() {
    this.resizeSubject = new Subject();
  }

  onResize(size: SCREEN_SIZE) {
    this.resizeSubject.next(size);
  }

}

Пояснение к коду:

  1. Создаем объект rxjs resizeSubject.
  2. Метод onResize получает size в качестве параметра. Затем он отправит значение в поток. Мы вызовем этот метод позже в компоненте size-detector.
  3. Мы используем оператор DifferentUntilChanged, чтобы сократить количество ненужных уведомлений. Например, когда размер экрана изменяется с 200 на 300 пикселей, в bootstrap он все равно считается размером xs. В этом случае уведомления не нужны.
  4. Экспортируем поток изменения размера через onResize$. Любые компоненты, службы, директивы и т.д. могут подписаться на этот поток, чтобы получать уведомления при изменении размера экрана.

Теперь вернемся к компоненту size-detector и обновим логику detectScreenSize.

// resize.service.ts

@Injectable()
export class ResizeService {

  get onResize$(): Observable<SCREEN_SIZE> {
    return this.resizeSubject.asObservable().pipe(distinctUntilChanged());
  }

  private resizeSubject: Subject<SCREEN_SIZE>;

  constructor() {
    this.resizeSubject = new Subject();
  }

  onResize(size: SCREEN_SIZE) {
    this.resizeSubject.next(size);
  }

}

Проанализируем логику:

  1. В компонент нужно добавить ElementRef и недавно созданный элемент ResizeService.
  2. В любой момент времени будет виден только один HTML-элемент. Перебираем массив sizes и находим его.
  3. Для каждого размера из массива sizes используем селектор запросов элемента HTML5, чтобы найти элемент по уникальному классу CSS, который мы определили ранее в is-<SIZE_ENUM>.
  4. Как только находим текущий видимый элемент, вызываем метод onResize и тем самым уведомляем службу ресайзинга.

Использование сервиса и компонента

Компонент size-detector можно разместить под корневым компонентом appcomponent. Например:

<!-- app.component.html -->

<hello name="{{ name }}"></hello>
<!— здесь нужно разместить компонент size-detector -->
<app-size-detector></app-size-detector>

В примере, приведенном выше, я поместил hello-component в app-component. Благодаря этому теперь можно использовать ResizeService везде (директивы, компоненты, службы и т. д.).

Допустим, что я хочу обнаружить изменения размера экрана в hello-component. Это можно сделать, если вставить ResizeService в конструктор, затем подписаться на наблюдаемый onSizeChange$ и сделать то, что нужно.

// hello.component.ts

@Component({
  selector: 'hello',
  template: `<h1>Hello {{size}}!</h1>`,
})
export class HelloComponent  {

  size: SCREEN_SIZE;

  constructor(private resizeSvc: ResizeService) { 
    // подписываемся на поток изменения размера    this.resizeSvc.onResize$.subscribe(x => {
      this.size = x;
    });
  }

}

В приведенном выше коде мы обнаруживаем изменения размера экрана и отображаем его текущее значение. В мобильной версии предпочтительнее свернуть все панели аккордеона и показывать только активную. Однако на ПК можно развернуть всю панель.

Заключение

Таким образом, можно определить изменение размеров экрана без контрольных точек в JavaScript. Посмотрите исходный код.

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

Также можно удалить компонент, переместить detectScreenSize в службу и немного изменить логику. Реализовать это не сложно.

На этом все. Удачной верстки!

Данная публикация представляет собой перевод статьи «Detect Responsive Screen Sizes in Angular» , подготовленной дружной командой проекта Интернет-технологии.ру

Меню