Kris
Kris

Reputation: 111

Angular CDK connect overlay with scroll to container

(First post here, so bear with me)

I have a table inside a container where I display an icon for rows that meet certain criterias. Clicking the icon should open an overlay to display some information, and the overlay should stay open even if I scroll inside the container. The scroll initially followed the body of the page, so I started creating a custom strategy. However, I cannot get it to follow the table scroll. The overlay sticks in one place and does not move accordingly.

Overlay is generated as below. Any tips on how this is generally solved would be appreciated!

private createOverlay() {
  const positionStrategy = this.overlay.position()
    .flexibleConnectedTo(this.overlayorigin.elementRef)
    .withFlexibleDimensions(false)
    .withPush(false)
    .withPositions([
      {
        originX: 'start',
        originY: 'bottom',
        overlayX: 'start',
        overlayY: 'top'
      },
      {
        originX: 'start',
        originY: 'top',
        overlayX: 'start',
        overlayY: 'bottom',
       }
    ]);
    
  const overlayConfig = new OverlayConfig({
    hasBackdrop: false,
    scrollStrategy: this.overlay.scrollStrategies.reposition({autoClose: false}),
    positionStrategy: positionStrategy
  });
    
    
  this._overlayRef = this.overlay.create(overlayConfig);
  this._overlayRef.backdropClick().subscribe(_ => {
    this._overlayRef.detach();
    this.closeWarning.emit(true);
  });
    
  this._portal = new TemplatePortal(this.content, this.portal);
  this._overlayRef.attach(this._portal);
}

Upvotes: 5

Views: 6227

Answers (2)

chrisg
chrisg

Reputation: 1117

I think the magic you're looking for here may be to add the cdkScrollable directive, which can be applied to your scrollable parent container.

For example:

<div cdkScrollable>

This will mark the container as scrollable, and register it with the ScrollDispatcher so that the repositioning scroll strategy will pick up the container being scrolled.

Upvotes: 4

Dipen Shah
Dipen Shah

Reputation: 26075

I do not have your MVP but from code that you have shared, it seems to be working fine.

Take a look at this working stackblitz that I created using code that you shared and few pieces from material.angular.io.

import {
  Overlay,
  OverlayConfig,
  OverlayRef
} from "@angular/cdk/overlay";
import {
  TemplatePortal
} from "@angular/cdk/portal";
import {
  DataSource
} from "@angular/cdk/table";
import {
  Component,
  ElementRef,
  EventEmitter,
  Output,
  TemplateRef,
  ViewChild,
  ViewContainerRef
} from "@angular/core";
import {
  BehaviorSubject,
  Observable
} from "rxjs";

@Component({
  selector: "cdk-overlay-basic-example",
  templateUrl: "cdk-overlay-basic-example.html",
  styleUrls: ["cdk-overlay-basic-example.css"]
})
export class CdkOverlayBasicExample {
  @ViewChild("templatePortalContent")
  templatePortalContent: TemplateRef < unknown > ;

  @Output() closeWarning = new EventEmitter < boolean > ();

  isOpen = false;
  displayedColumns: string[] = [
    "action",
    "position",
    "name",
    "weight",
    "symbol"
  ];
  dataSource = new ExampleDataSource();

  private _overlayRef: OverlayRef;
  private _portal: TemplatePortal;

  constructor(
    private overlay: Overlay,
    private _viewContainerRef: ViewContainerRef
  ) {}

  click(overlayorigin: ElementRef, element: PeriodicElement) {
    console.log(arguments);
    element.isOpen = !element.isOpen;
    if (element.isOpen) {
      this.createOverlay(overlayorigin);
    } else {
      this._overlayRef.detach();
    }
  }

  private createOverlay(overlayorigin: ElementRef) {
    const positionStrategy = this.overlay
      .position()
      .flexibleConnectedTo(overlayorigin)
      .withFlexibleDimensions(false)
      .withPush(false)
      .withPositions([{
          originX: "start",
          originY: "bottom",
          overlayX: "start",
          overlayY: "top"
        },
        {
          originX: "start",
          originY: "top",
          overlayX: "start",
          overlayY: "bottom"
        }
      ]);

    const overlayConfig = new OverlayConfig({
      hasBackdrop: false,
      scrollStrategy: this.overlay.scrollStrategies.reposition({
        autoClose: false
      }),
      positionStrategy: positionStrategy
    });

    this._overlayRef = this.overlay.create(overlayConfig);
    this._overlayRef.backdropClick().subscribe(_ => {
      this._overlayRef.detach();
      this.closeWarning.emit(true);
    });

    this._portal = new TemplatePortal(
      this.templatePortalContent,
      this._viewContainerRef
    );
    this._overlayRef.attach(this._portal);
  }
}

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
  isOpen ? : boolean;
}

const ELEMENT_DATA: PeriodicElement[] = [{
    position: 1,
    name: "Hydrogen",
    weight: 1.0079,
    symbol: "H"
  },
  {
    position: 2,
    name: "Helium",
    weight: 4.0026,
    symbol: "He"
  },
  {
    position: 3,
    name: "Lithium",
    weight: 6.941,
    symbol: "Li"
  },
  {
    position: 4,
    name: "Beryllium",
    weight: 9.0122,
    symbol: "Be"
  },
  {
    position: 5,
    name: "Boron",
    weight: 10.811,
    symbol: "B"
  },
  {
    position: 6,
    name: "Carbon",
    weight: 12.0107,
    symbol: "C"
  },
  {
    position: 7,
    name: "Nitrogen",
    weight: 14.0067,
    symbol: "N"
  },
  {
    position: 8,
    name: "Oxygen",
    weight: 15.9994,
    symbol: "O"
  },
  {
    position: 9,
    name: "Fluorine",
    weight: 18.9984,
    symbol: "F"
  },
  {
    position: 10,
    name: "Neon",
    weight: 20.1797,
    symbol: "Ne"
  },
  {
    position: 11,
    name: "Hydrogen",
    weight: 1.0079,
    symbol: "H"
  },
  {
    position: 12,
    name: "Helium",
    weight: 4.0026,
    symbol: "He"
  },
  {
    position: 13,
    name: "Lithium",
    weight: 6.941,
    symbol: "Li"
  },
  {
    position: 14,
    name: "Beryllium",
    weight: 9.0122,
    symbol: "Be"
  },
  {
    position: 15,
    name: "Boron",
    weight: 10.811,
    symbol: "B"
  },
  {
    position: 16,
    name: "Carbon",
    weight: 12.0107,
    symbol: "C"
  },
  {
    position: 17,
    name: "Nitrogen",
    weight: 14.0067,
    symbol: "N"
  },
  {
    position: 18,
    name: "Oxygen",
    weight: 15.9994,
    symbol: "O"
  },
  {
    position: 19,
    name: "Fluorine",
    weight: 18.9984,
    symbol: "F"
  },
  {
    position: 20,
    name: "Neon",
    weight: 20.1797,
    symbol: "Ne"
  },
  {
    position: 21,
    name: "Hydrogen",
    weight: 1.0079,
    symbol: "H"
  },
  {
    position: 22,
    name: "Helium",
    weight: 4.0026,
    symbol: "He"
  },
  {
    position: 23,
    name: "Lithium",
    weight: 6.941,
    symbol: "Li"
  },
  {
    position: 24,
    name: "Beryllium",
    weight: 9.0122,
    symbol: "Be"
  },
  {
    position: 25,
    name: "Boron",
    weight: 10.811,
    symbol: "B"
  },
  {
    position: 26,
    name: "Carbon",
    weight: 12.0107,
    symbol: "C"
  },
  {
    position: 27,
    name: "Nitrogen",
    weight: 14.0067,
    symbol: "N"
  },
  {
    position: 28,
    name: "Oxygen",
    weight: 15.9994,
    symbol: "O"
  },
  {
    position: 29,
    name: "Fluorine",
    weight: 18.9984,
    symbol: "F"
  },
  {
    position: 30,
    name: "Neon",
    weight: 20.1797,
    symbol: "Ne"
  }
];

export class ExampleDataSource extends DataSource < PeriodicElement > {
  data = new BehaviorSubject < PeriodicElement[] > (ELEMENT_DATA);

  connect(): Observable < PeriodicElement[] > {
    return this.data;
  }

  disconnect() {}
}
.container {
  height: 400px;
  overflow: auto;
  border: 1px solid grey;
  margin: 10px 0;
}

table {
  width: 100%;
  border-collapse: collapse;
}

td,
th {
  padding: 5px;
}

.content {
  margin: 5px 0;
  background: green;
  height: 1000px;
}

.overlay-info {
  padding: 20px;
  border: 1px solid #865e0b;
  background-color: orange;
  color: white;
  font-size: 16px;
}
<div class="container">
  <table cdk-table [dataSource]="dataSource" border="1">
    <ng-container cdkColumnDef="action">
      <th cdk-header-cell *cdkHeaderCellDef> Action </th>
      <td cdk-cell *cdkCellDef="let element">
        <button (click)="click(origin, element)" type="button" cdkOverlayOrigin #origin>
          {{element.isOpen ? "Close" : "Open"}}
        </button>
      </td>
    </ng-container>

    <ng-container cdkColumnDef="position">
      <th cdk-header-cell *cdkHeaderCellDef> No. </th>
      <td cdk-cell *cdkCellDef="let element"> {{element.position}} </td>
    </ng-container>

    <ng-container cdkColumnDef="name">
      <th cdk-header-cell *cdkHeaderCellDef> Name </th>
      <td cdk-cell *cdkCellDef="let element"> {{element.name}} </td>
    </ng-container>

    <ng-container cdkColumnDef="weight">
      <th cdk-header-cell *cdkHeaderCellDef> Weight </th>
      <td cdk-cell *cdkCellDef="let element"> {{element.weight}} </td>
    </ng-container>

    <ng-container cdkColumnDef="symbol">
      <th cdk-header-cell *cdkHeaderCellDef> Symbol </th>
      <td cdk-cell *cdkCellDef="let element"> {{element.symbol}} </td>
    </ng-container>

    <tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
    <tr cdk-row *cdkRowDef="let row; columns: displayedColumns;"></tr>
  </table>
</div>

<div class="container">
  <div class="content">
    This is a very very long content!
  </div>
</div>

<div class="container">
  <div class="content">
    This is another very very long content!
  </div>
</div>

<ng-template #templatePortalContent>
  <div class="overlay-info">
    This is information panel!
  </div>
</ng-template>

Upvotes: 2

Related Questions