File

src/app/shared/components/drawer/container/container.component.ts

Description

Main container for drawer components.

Implements

AfterViewInit OnDestroy

Metadata

Index

Properties
HostBindings
Accessors

Constructor

constructor(messageService: MessageService, cdr: ChangeDetectorRef)

Creates an instance of container component.

Parameters :
Name Type Optional Description
messageService MessageService No

The service used to send event messages.

cdr ChangeDetectorRef No

The change detector reference.

HostBindings

class
Type : "ccf-drawer-container"
Default value : 'ccf-drawer-container'

HTML class

Properties

Readonly className
Type : string
Default value : 'ccf-drawer-container'
Decorators :
@HostBinding('class')

HTML class

Accessors

hasWrappedContent
gethasWrappedContent()

Whether the content was wrapped.

Returns : boolean
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  HostBinding,
  OnDestroy,
  QueryList,
  ViewChildren,
} from '@angular/core';
import { Subscription } from 'rxjs';
import { startWith } from 'rxjs/operators';

import { ContentComponent } from '../content/content.component';
import { DrawerComponent } from '../drawer/drawer.component';
import { Message, MessageChannel, MessageService } from '../messages';

/**
 * Helper function for creating drawer errors.
 *
 * @param position The position of the drawer.
 * @throws {Error} Error with useful message is always thrown.
 */
function throwDuplicateDrawersError(position: 'start' | 'end'): never {
  throw new Error(`Multiple drawers in position ${position}`);
}

/**
 * Main container for drawer components.
 */
@Component({
  selector: 'ccf-drawer-container',
  exportAs: 'ccfDrawerContainer',
  templateUrl: './container.component.html',
  styleUrls: ['./container.component.scss'],
  providers: [MessageService],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContainerComponent implements AfterViewInit, OnDestroy {
  /** HTML class */
  @HostBinding('class') readonly className = 'ccf-drawer-container';

  /** Drawer components in this container. */
  @ContentChildren(DrawerComponent, { descendants: true })
  private readonly drawers!: QueryList<DrawerComponent>;

  /** Content component if provided already wrapped. */
  @ContentChildren(ContentComponent, { descendants: true })
  private readonly content1!: QueryList<ContentComponent>;
  /** Content component if provided without wrapping. */
  @ViewChildren(ContentComponent)
  private readonly content2!: QueryList<ContentComponent>;
  /** Resolves the content component. */
  private get content(): ContentComponent {
    return this.content1.first ?? this.content2.first;
  }

  /** Whether the content was wrapped. */
  get hasWrappedContent(): boolean {
    return this.content1.length !== 0;
  }

  /** The connected message channel. */
  private readonly channel: MessageChannel;
  /** All subscriptions managed by the container. */
  private readonly subscriptions = new Subscription();

  /**
   * Creates an instance of container component.
   *
   * @param messageService The service used to send event messages.
   * @param cdr The change detector reference.
   */
  constructor(
    messageService: MessageService,
    private readonly cdr: ChangeDetectorRef,
  ) {
    this.channel = messageService.connect(this);
    this.subscriptions.add(
      this.channel.getMessages().subscribe((msg) => {
        if (this.handleMessage(msg)) {
          cdr.markForCheck();
        }
      }),
    );
  }

  /**
   * Sets up all listeners after all content has been projected.
   */
  ngAfterViewInit(): void {
    this.drawers.changes.pipe(startWith(null)).subscribe(() => {
      const drawers = this.validateDrawers();
      this.channel.sendMessage({
        type: 'drawer-containers-changed',
        drawers,
      });
      this.cdr.markForCheck();
    });

    this.content1.changes.pipe(startWith(null)).subscribe(() => {
      this.channel.sendMessage({
        type: 'content-container-changed',
        content: this.content,
      });
      this.cdr.markForCheck();
    });
  }

  /**
   * Cleans up all subscriptions.
   */
  ngOnDestroy(): void {
    this.subscriptions.unsubscribe();
  }

  /**
   * Processes event messages.
   *
   * @param _msg The event.
   * @returns true if change detection needs to be run.
   */
  private handleMessage(_msg: Message): boolean {
    return true;
  }

  /**
   * Validates the number of drawers and their positions.
   *
   * @returns A tuple containing the start and end drawers.
   */
  private validateDrawers(): [DrawerComponent | undefined, DrawerComponent | undefined] {
    const drawers = this.drawers.toArray();
    const startDrawers = drawers.filter((drawer) => drawer.position === 'start');
    const endDrawers = drawers.filter((drawer) => drawer.position === 'end');

    if (startDrawers.length > 1) {
      throwDuplicateDrawersError('start');
    }
    if (endDrawers.length > 1) {
      throwDuplicateDrawersError('end');
    }

    return [startDrawers[0], endDrawers[0]];
  }
}
<ng-content select="ccf-drawer"></ng-content>

<ng-content select="ccf-drawer-content"></ng-content>
<ccf-drawer-content *ngIf="!hasWrappedContent">
  <ng-content></ng-content>
</ccf-drawer-content>

./container.component.scss

:host {
  display: block;
  position: relative;
  z-index: 1;
  overflow: hidden;
  -webkit-overflow-scrolling: touch;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""