Saturday, May 19, 2018

Angular Material Full Size Dialog on Small Devices

I restarted a project recently and decided to use Angular Material to build it. So far it's been working out pretty well. The latest version of Material (6.0.1) works nicely. The documentation leaves a lot to be desired, but it's not that difficult to muck around in the source and find what I need.

Today I started working on the sign-in/sign-out dialog for my site. I want the dialog to open as a full-screen dialog when the user is on a small device, and open as some other value when the user is on a regular computer (laptop, desktop, whatever). This didn't end up being very hard, but I had to mash together a couple of solutions I found so I wanted to put this post up for future reference.

If you Google something like "angular material full screen dialog mobile" you'll get a bunch of responses where people are looking for this exact feature.I started with this comment on one of the Github issues that was opened and then tweaked it a little bit using the BreakpointObserver that comes with Material (at least in version 6 it does). I haven't finalized what I want my sign-in/sign-out page/dialog to look like yet so for now let's just say we want the dialog to open at half the width and half the height of the window on larger devices and full screen on smaller devices.

Here's the code, and I'll explain it a bit more afterward.
   1:  import { Component } from '@angular/core';
   2:  import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
   3:  import { Observable } from 'rxjs';
   4:  import { MatDialog, MatDialogRef } from '@angular/material';
   5:  import { SignInComponent } from '../sign-in/sign-in.component';
...snip...
  12:  export class SidenavComponent {
  13:    isExtraSmall: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.XSmall);
  14:  
  15:    constructor(private breakpointObserver: BreakpointObserver, private dialog: MatDialog) {}
  16:  
  17:    openSignInDialog(): void {
  18:      const signInDialogRef = this.dialog.open(SignInComponent, {
  19:        width: '50%',
  20:        height: '50%',
  21:        maxWidth: '100vw',
  22:        maxHeight: '100vh',
  23:      });
  24:  
  25:      const smallDialogSubscription = this.isExtraSmall.subscribe(result =< {
  26:        if (size.matches) {
  27:          signInDialogRef.updateSize('100%', '100%');
  28:        } else {
  29:          signInDialogRef.updateSize('50%', '50%');
  30:        }
  31:      });
  32:  
  33:      signInDialogRef.afterClosed().subcsribe(result =< {
  34:        smallDialogSubscription.unsubscribe();
  35:      });
  36:    }
  37:  }

The only real issue I have with this code is that I have to specify the exact height and width I want to use when the dialog is displayed on a large device. All things considered, that's not really a big deal to me.

This is also pretty self-explanatory (I think).

On line 17 we open the dialog with a size that is half the height and width of the current window. We also set the max height and max width of the dialog explicitly to match the height and width of the viewport (the device, essentially). If you leave that part off you'll end up with your dialog being pulled over to the left of the screen and not taking up the full width.

On line 25 we subscribe to the observable that's watching the devices size to see if it drops below the predefined XSmall breakpoint. There are other breakpoints we could have used just by changing out the definition on line 13. When we subscribe we immediately get the last value from the observable. On devices that fall under the XSmall breakpoint, our result variable has a matches property that is set to true. On other devices, matches is set to false. All we have to do is invoke the updateSize function on the dialog ref we received back when we opened the dialog.

Finally, on line 33 we make sure to unsubscribe from the observable. This is just a clean-up precaution to make sure we avoid memory leaks.

5 comments:

  1. Thx!

    My version :

    ------------------ popup.service.ts ------------------
    import { Injectable, TemplateRef } from '@angular/core';
    import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
    import { MatDialogRef, MatDialog, MatDialogConfig } from '@angular/material';
    import { ComponentType } from '@angular/cdk/portal';

    @Injectable({
    providedIn: 'root'
    })

    export class PopupService {

    constructor(private dialog: MatDialog, private breakpointObserver: BreakpointObserver) { }

    matDialogRef: any;
    smallDialogSubscription: any;

    open(componentOrTemplateRef: ComponentType | TemplateRef, mobileWidth: string, data?: MatDialogConfig): MatDialogRef {
    if (data) {
    data.maxWidth = '100vw';
    data.maxHeight = '100vh';
    }

    this.matDialogRef = this.dialog.open(componentOrTemplateRef, data);

    this.smallDialogSubscription = this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.Small])
    .subscribe(size => {
    if (size.matches) {
    this.matDialogRef.updateSize('100%', '100%');
    } else {
    this.matDialogRef.updateSize(mobileWidth, 'auto');
    }
    });

    return this.matDialogRef;
    }

    close(): void {
    this.smallDialogSubscription.unsubscribe();
    this.matDialogRef.close();
    }

    }
    ------------------------------------


    --------- account.component.ts --------
    ...
    editAccount() {
    this.popupservice.open(AccountEditComponent, '800px', {
    data: {}
    });
    }
    ...
    ------------------

    --------- account-edit.component.ts --------
    ...
    close() {
    this.popupservice.close();
    }
    ...
    ------------------

    Nicolas RAFFIN

    ReplyDelete
  2. Wowwww! thank you

    you made my day

    ReplyDelete
  3. I'd like to point out that my IDE complains about the line 25 "result =<" i corrected with this "result =>" and in the line 33 raises another error "subcsribe" should be "subscribe".

    ReplyDelete