Search code examples
angularangular-materialoverlayangular-material2angular-cdk

Change Angular material overlay position dynamically after attaching


Have a number of Overlays that need to re-position after window-resize event.

The current initialy configured PositionStrategy has logic that is dependent on outside variables to align Overalys similar to CSS Grid / Flexbox behavior.

This material2 github issue seems to indicate can only use OverlayRef.dispose() => OverlayRef.create().

Is there any solution that meets the following:

  • Can update position with non static values.
  • Does not destroy the component instance.

Preferably able to be called from service and not OverlayRef's component instance.

Edit 1 for clarity: Given the following PositionStrategy, OverlayRef.updatePosition() seems to not apply as need to re-calculate px. *Note window.innerWidth

      private setChannelPosition(): PositionStrategy {
        const chatWidth = 280;
        const chatSpace = 5;
        const distanceFromEdge = 80;

        const existingModalCount = this.channelModals.size;
        const numModalsCanFit = 
           Math.floor((window.innerWidth - 2 * distanceFromEdge) / (chatWidth + chatSpace));
        const distFromRight = 
           distanceFromEdge + ((existingModalCount % numModalsCanFit) * (chatWidth + chatSpace));
        return this.overlay.position().global().bottom('0px').right(`${distFromRight}px`);
      }

Edit 2: The modals are transient.. So a solution with flexibleConnectedTo() doesn't seem to apply as any modal may or may not be closed by user in any order.


Solution

  • Assuming you only call setChannelPosition() once when making the OverlayConfig, like:

    export class MyComponent implements OnInit {
    
      private overlayRef: OverlayRef;
    
      constructor(
        private overlay: Overlay
      ) { }
    
      public ngOnInit(): void {
        this.createOverlay();
      }
    
      @HostListener('window:resize')
      public onResize(): void {
        this.overlayRef.updatePosition();
      }
    
      private setChannelPosition(): PositionStrategy {
        const chatWidth = 280;
        const chatSpace = 5;
        const distanceFromEdge = 80;
    
        const existingModalCount = this.channelModals.size;
        const numModalsCanFit =  Math.floor((window.innerWidth - 2 * distanceFromEdge) / (chatWidth + chatSpace));
        const distFromRight = distanceFromEdge + ((existingModalCount % numModalsCanFit) * (chatWidth + chatSpace));
        return this.overlay.position().global().bottom('0px').right(`${distFromRight}px`);
      }
    
      private createOverlay() {
        const config = new OverlayConfig({
          positionStrategy: this.setChannelPosition()
        });
        this.overlayRef = this.overlay.create(config);
        this.overlayRef.attach(/* template or component*/);
      }
    
    }
    

    so the line this.overlay.position().global().bottom('0px').right(${distFromRight}px); only runs once creating a GlobalPositionStrategy with bottom 0px and rigth being what ever the value of distFromRight was at the time.

    OverlayRef.updatePosition() calls PositionStrategy.apply() which GlobalPositionStrategy implements and simply applyies css values based on its properties (in this case being bottom 0px and rigth being what ever the value of distFromRight when setChannelPosition() ran).

    The solution would be to have PositionStrategy that recalculates on PositionStrategy.apply().

    class MyPositionStrategy implements PositionStrategy {
      public apply(): void {
        /* recalculates */
      }
    }
    
    const config = new OverlayConfig({
     positionStrategy: instanceOfMyPositionStrategy 
    });
    

    ruff example

    or

    Change the PositionStrategy before calling OverlayRef.updatePosition() by calling OverlayRef.updatePositionStrategy() new to angular material 7 I think.

    @HostListener('window:resize')
    public onResize(): void {
      this.overlayRef.updatePositionStrategy(this.setChannelPosition());
      this.overlayRef.updatePosition();
    }
    

    So the setChannelPosition() runs and PositionStrategy changes every time the window resizes.

    ruff example

    Note:

    There was an old solution like

    @HostListener('window:resize')
    public onResize(): void {
      this.overlayRef.getConfig().positionStrategy = this.setChannelPosition();
      this.overlayRef.updatePosition();
    }
    

    but getConfig().positionStrategy is now readonly and doesn't work