How to create Shared Component - the Ionic way
Have you ever had a requirement of replicating similar functionality across multiple locations in your application?
One brute force way to tackle this problem is to create multiple ion-modal
components in your template files.
However, a smarter and more efficient approach is to create a shared component.
Hello everyone, I am Suyash Patil, a Member of Technical Staff at Fyle.
Recently, we introduced commute deduction feature for mileage expenses. This feature addresses the need to streamline how mileage expenses are handled, particularly focusing on saving commute details efficiently within our mobile application.
Notably, we implemented this feature across three distinct entry points within the app.
Each entry point required a modal dialog for entering commute details, and managing this solely through individual ion-modal
instances would have been cumbersome and prone to inconsistencies
Introducing Shared Components
To address this challenge effectively, we opted to create a shared ion-modal
component.
This shared component encapsulates the structure, behavior, and styling required for displaying commute details input dialog. By centralizing this functionality into a single component, we achieved several advantages:
Consistency: Ensured uniform user experience across all entry points. We need not to worry about the CSS for each and every modal. Write once, run anywhere.
Efficiency: Reduced duplication of code and maintenance efforts.
Scalability: Facilitated easy addition of new features or modifications without extensive rework.
Implementation Details
Creating the Shared Modal Component
The shared ion-modal
component was developed using Ionic's component-based architecture. You can refer the actual mobile app implementation here. This is a simplified example of how we approached it:
import { Component, Input } from '@angular/core';
import { ModalController } from '@ionic/angular';
@Component({
selector: 'app-commute-modal',
templateUrl: './commute-modal.component.html',
styleUrls: ['./commute-modal.component.scss'],
})
export class CommuteModalComponent {
@Input() data: any;
constructor(private modalController: ModalController) {}
close() {
// action can be success of cancel
this.modalController.dismiss({ action: 'SUCESS' });
}
}
This component's primary responsibility is to display a dialog for user input, which is then passed back to the invoking component via the modalController.dismiss()
method.
Integrating the Modal Component
In each entry point of our application where commute details needed to be entered, we simply invoked the CommuteDetailsComponent
through the ModalController
, passing necessary data and handling responses as required. You can refer the implementation here.
async openCommuteModal() {
const modal = await this.modalController.create({
component: CommuteDetailsComponent,
componentProps: {
data: { /* Optional data to pass to the modal */ }
}
});
await modal.present();
const { data } = await modal.onWillDismiss<{action: string}>();
if (data.action === 'SUCCESS') {
// Do something
} else {
// Handle fallback
}
}
onWillDismiss
vs onDidDismiss
Both the events allow you to handle actions before or after a modal is dismissed.
onWillDismiss
event occurs just before the modal is dismissed. You can use this event to perform actions or clean up resources that need to be completed before the modal disappears from view.onDidDismiss
event occurs after the modal has been dismissed and is no longer visible. You can use this event to handle any actions that should occur after the modal has completed its dismissal animation and is no longer in the DOM.
Defining the Safe Area
"safe area" refers to the area of the screen that is guaranteed to be visible and usable on all devices, regardless of their screen size or shape. This concept is crucial for ensuring that UI elements are positioned and displayed correctly without being obscured by device-specific elements such as the status bar, navigation bar, home indicator (on iPhone X and newer models), and rounded corners.
As you can see from the above pictures, safe area is the screen view which doesn’t get obstructed by status bar, notification bar etc.
You can prevent this by giving margin to ion-header using safe-area-inset-top
.
<ion-header>
<ion-toolbar class="commute-details--toolbar">
<ion-buttons slot="end">
<ion-button>
Save
</ion-button>
</ion-buttons>
<ion-title>
<div>Commute Details</div>
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<!-- Template logic goes here -->
</ion-content>
<ion-footer> </ion-footer>
.commute-details {
&--toolbar {
margin-top: calc(env(safe-area-inset-top));
}
}
This will ensure the safe area doesn’t include the status bar in android and ios.
Close the modal by swiping down
Sometimes, you may want user to close the modal by sliding it down. ModalController
provides a nice way to do this. We need to define initialBreakpoint
and breakpoints
array.
const modal = await this.modalController.create({
component: CommuteDetailsComponent,
componentProps: {
data: { /* Optional data to pass to the modal */ }
},
canDismiss: true,
initialBreakpoint: 1,
breakpoints: [0,1],
});
A
breakpoints
property of[0, 1]
would indicate that the sheet can be swiped to show 0% of the modal and 100% of the modal.When the modal is swiped to 0%, the modal will be automatically dismissed.
Hide the handle at the top of modal
Sometimes, you don’t want to show the handle (line at the top of modal). You can define handle
property as false
which will hide this from appearing. However, this will not come up if you don’t have breakpoints defined in your modal definition.
Declarations in your App Module
To ensure that your shared components are properly imported and accessible across your application, you need to declare them in your Angular app module (app.module.ts
). This step is essential for Angular's dependency injection mechanism to recognize and instantiate your components correctly.
In you app.module.ts
file, add declaration for this shared component to access it from any page.
@NgModule({
declarations: [AppComponent, EventModalComponent],
imports: [
IonicModule.forRoot(),
...
],
providers: [{
provide: RouteReuseStrategy,
useClass: IonicRouteStrategy
}],
bootstrap: [AppComponent],
})
export class AppModule {}
Conclusion
By leveraging shared components like our CommuteDetailsComponent
, we enhanced our development process significantly. This approach not only streamlined our codebase but also improved the maintainability and scalability of our application. As you navigate similar challenges in your projects, consider adopting shared components to optimize your development workflow and deliver consistent user experiences effectively.