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.
- App Component (app.component.ts)
- Main Component
- Form Component
- Page Wrapper Component
- 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 theFormComponent
in the dialog. First, I check to see whether the screen size is less than medium (lt-md
) and navigate to page housing theFormComponent
.
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.
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>
Modal Wrapper Components
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.