Angular Flex Layout is a powerful layout tool available for Angular. It provides an API than can be easily used in both component templates and class allowing you to adaptively react to the device screen size. If you are new to Angular Flex Layout and Adaptive Layout design, please check out my earlier post here.

Introduction

In today’s example, I will create a simple example that will display a form in either modal window or new page based on screen size.  In small screen sizes – smartphones & phablets – it will show the form in a new page while in large devices it will display a simple modal window with the form. The form component will be the same in both instances, ensuring consistency and making it easy to update.

Why?

The reason for this is quite simple: A modal window provides a much better user experience when screen real estate is not an issue while in smaller devices it just makes things worse. With adaptive layout design you get to achieve both without compromising a lot, just some extra code to check the screen size.

Getting Started

In this example, we will be using both Angular Flex Layout and Angular Material. This post assumes you know the basics of Angular and Angular Material. You can install both using either NPM or Yarn Package Manager as shown below.

NPM:

npm install @angular/flex @angular/material @angular/cdk

Or Yarn:

yarn add @angular/flex @angular/material @angular/cdk

Go ahead and import Angular Flex Layout to your project.

import { FlexLayoutModule } from '@angular/flex-layout';

In the case of Angular Material, we are only going to require two modules – MatDialog and MatSnackbar Modules. Import them to your project too.

import { MatSnackBarModule, MatDialogModule } from '@angular/material';

And then Add them to the list of imports

@NgModule({
  imports: [
    BrowserModule,
    FlexLayoutModule,
    MatSnackBarModule,
    MatDialogModule,
    MatButtonModule
    RouterModule
  ],
  bootstrap: [AppComponent]
})

How it works

We are going to have 4 components in our project.

  1. App Component (app.component.ts)
  2. Main Component
  3. Form Component
  4. Page Wrapper Component
  5. Dialog Wrapper Component

The two-wrapper component are there to make thing simple: providing a container for the form component while also injecting any data the Form Component may require. This is necessary since a dialog uses data attribute to pass data to dialog component while pages use URL query parameters. Checking whether it was loaded as a page or modal dialog at the form component just complicates everything. So, in our case, when loading a dialog - we load the Dialog Wrapper Component which in turn loads the form component as a child. And when loading the page – We load the Page Wrapper Component which in turn loads the form component as a child.

Code Highlights

I won’t go over all details in the code, I will share the Github repo, but let me highlight the key parts that you should take note:

Main Component

This is where the open button is located. Make sure to inject ObservableMedia, MatDialog and Router in to the component under constructor. ObservableMedia is from Angular Flex Layout and will allow you to query the screen size in real time as shown below:

open(message?: string): void {
    //query screen size
    if (this.media.isActive("lt-md")) {
      //redirect
      this.router.navigate(["/open-form"]);
    } else {
      //show dialog
      const d = this.matDialog.open(ModalWrapperComponent, {
        hasBackdrop: true,
        data: {
          message: message
        }
      });

d.afterClosed().subscribe(results => {
        //do something like show a message box
        this.router.navigate(["/open", message]);
      });
    }
  }

NB: I Am loading the ModalWrapperComponent instead of the FormComponent in the dialog. First, I check to see whether the screen size is less than medium (lt-md) and navigate to page housing the FormComponent.

In all other cases I load a modal dialog of the FormCompponent instead. Please refer to the image below for different breaking points for Angular Flex Layout.

Default Breakpoints for Angular Flex Layout

The message for modal dialog is passed under data inside config options while its passed a router parameter when navigating to another page.

Form Component

The form component for now will just show the message passed to it and will not have anything special to it. To do this it needs an input and an output for the form. The input allows the parent component (wrapper components in our case) to pass the message to the form. The output on the other hand allows the child component (Form component in our case) to pass events and data back to our parent component. This is done to keep things as simple as possible.

@Input() public message: string;
@Output() public output: EventEmitter<boolean> = new EventEmitter<boolean>();

Since our variables are public, you can use them directly on the template as shown below:

<div fxFlex="100" fxLayout="column">
  <p fxFlex="100" fxLayout="row wrap">{{ **message | titlecase** }}</p>
  <div fxFlex="100" fxFlex="row">
    <button (click)="**output.emit(true)**">Close</button>
  </div>
</div>

This wrapper component will be loaded when loading the form in modal window. The component will process the modal request and then load the form component with the necessary data and react to events such as close in the form component.

First, inject MatDialogRef and MAT_DIALOG_DATA into the component and then set global message variable using the message supplied from the component data.

public message: string;

constructor(
  private dialogRef: MatDialogRef<ModalWrapperComponent>,
  @Inject(MAT_DIALOG_DATA) public data: any)
  {
    this.message = data.message;
  }

close(results): void {
this.dialogRef.close(results);
}

ngOnInit() {}

And finally add the form component into your template and inject the message and output as shown below:

<div fxFlex="500px" fxLayout="column">
  <app-form [message]="message" (output)="close($event)"> </app-form>
</div>

Page Wrapper Component

This will resemble the Modal Wrapper Component closely with a few key differences. First, instead of injecting MAT_DIALOG_DATA and MatDialogRef, it will take in router and activatedRoute. This is because in this case, data is passed via route parameters. Here is how it looks:

public message: string;

constructor(private router: Router, activatedRoute: ActivatedRoute) {
this.message = activatedRoute.snapshot.params["message"];
}

close(results): void {
this.router.navigate(["/"]);
}

The template doesn’t change at all:

<div fxFlex="500px" fxFlex.xs="100" fxLayout="column">
  <app-form [message]="message" (output)="close($event)"> </app-form>
</div>

You can find the full example here on GitHub.

Final Thoughts This is a simple example of how you can apply adaptive layout

design in Angular with Flex Layout. And since Angular Flex Layout is independent from Angular Material, you can use this with a host of other UI Libraries. You can now expand on this and build amazing UX for your users by mixing both Responsive and Adaptive layout design. If you have any questions regarding this topic or any angular related topic, you can use the comment section below.