Thank you for reading my blog posts, I am no longer publishing new content on this platform. You can find my latest content on either mainawycliffe.dev or All Things Typescript Newsletter (✉️)
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.
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.
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.
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]
})
We are going to have 4 components in our project.
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.
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:
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.
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>
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.
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.
Introduction You probably have heard about Responsive Layout design but let me remind you anyway. Responsive layout is where components and …
Read MoreA few weeks ago, I demonstrated how to implement Sign in with Facebook with a REST API. Today, I will show you how to Sign-in with Google in …
Read MoreToday, I am going to show you how to create an Angular App with a REST API that uses Facebook Login to sign in/up users. For this …
Read MoreToday, I will show you how to optimize your Angular 5 and above for search engine and other crawlers. It is important to note that, pages …
Read MoreIcons are a necessity when building modern-day web apps and sometimes they can be frustrating. While icon sets like Font Awesome and …
Read MoreToday, we are going to create a responsive Navbar using Toolbar and Sidenav Components from Angular Material together with Angular Flex …
Read More