NativeScript aka {N} Angular Modals
Overview
This tutorial will guide you through mastering modals using the updated NativeDialogService in NativeScript with Angular. We’ll explore data passing, modal control, best practices, and pitfalls to avoid.
Get Sean Kelly’s stories in your inbox
Join Medium for free to get updates from this writer.
For additional resources, see:
Why Was ModalDialogService Replaced?
The old ModalDialogService was replaced starting from Angular 12 with a more rebust, modern and flexible NativeDialogService. This new service is inspired by Angular Material’s MatDialog, providing better integreation and control across platforms.
The Modal Parent Component
In NativeScript, while the developer works primarily in JavaScript and TypeScript, the code ultimately interacts with native mobile components. Let’s explore how data passes between parent components and modals when using Angular.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NO_ERRORS_SCHEMA } from '@angular/core';
import { NativeDialogConfig, NativeDialog, NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
import { PresidentModalComponent } from '../modals/president-modal/president-modal.component';
import { PresidentModel } from '../interfaces/president-model';
@Component({
selector: 'president',
templateUrl: './president.component.html',
styleUrls: ['./president.component.scss', '../app.component.scss'],
imports: [NativeScriptCommonModule, NativeScriptRouterModule],
schemas: [NO_ERRORS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PresidentComponent {
@Input() president: PresidentModel;
constructor(
private readonly modal: NativeDialog,
private readonly changeDetectorRef: ChangeDetectorRef
) {}
openModal(): void {
const modalOptions: NativeDialogConfig = {
data: this.president,
nativeOptions: {
fullscreen: false,
animated: false,
ios: {
presentationStyle: UIModalPresentationStyle.Custom
}
}
};
const ref = this.modal.open(PresidentModalComponent, modalOptions);
ref.afterClosed().subscribe( (isSeen: boolean) => {
this.president.isSeen = isSeen;
this.changeDetectorRef.markForCheck();
});
}}In the majority of cases, when using the NativeDialog service two functions are used. Those being .open and .afterClosed(), an example of both can be seen in the Angular component code above.
The class NativeDialogConfig is developed from Angular Materials’s MatDialog and functions effectively as an interface for structuring a model for how the modal should look and behave in both iOS and Android.
Important Considerations
Data Passing: To reliably pass data into modal, avoid using route parameters ( ActivatedRoute ) due to inconsistent behaviour and memory management issues. Instead use:
- Injecting data directly:
inject(NATIVE_DIALOG_DATA) - Shared service files
- Presentation styles on iOS
- Native Options: For consistent modal appearance across iOS and Android, explicity set options of
fullscreenandanimatedtofalse
For iOS, typically using presentationStyle: UIModalPresentationStyle.OverCurrentContext is sufficient. For presentationStyle, the enum selection goes from -2 to 8 and you can ctrl click or command click to see the full list.
declare const enum UIModalPresentationStyle {
FullScreen = 0,
PageSheet = 1,
FormSheet = 2,
CurrentContext = 3,
Custom = 4,
OverFullScreen = 5,
OverCurrentContext = 6,
Popover = 7,
BlurOverFullScreen = 8,
None = -1,
Automatic = -2
}Using .afterClosed() enables you to handle data returned from the modal. With Angular’s OnPush change detection, manually trigger updates using markForCheck() or detectChanges().
The Modal Component
Here is how the modal itself might look:
import { ChangeDetectionStrategy, Component, ElementRef, inject, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
import { NATIVE_DIALOG_DATA, NativeDialogRef, NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
import { GridLayout } from '@nativescript/core';
import { isIOS } from "@nativescript/core/platform";
import { PresidentModel } from '~/app/interfaces/president-model';
@Component({
selector: 'president-modal',
templateUrl: './president-modal.component.html',
styleUrls: ['./president-modal.component.scss', '../../app.component.scss'],
imports: [NativeScriptCommonModule, NativeScriptRouterModule],
schemas: [NO_ERRORS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PresidentModalComponent {
dialogRef = inject(NativeDialogRef);
president = <PresidentModel>inject(NATIVE_DIALOG_DATA);
isIos: boolean = false;
constructor() {
if(isIOS) {
this.isIos = true;
}
}
close(): void {
this.dialogRef.close(true);
}
containerRef: GridLayout;
@ViewChild("container") set containerContent (elRef: ElementRef) {
if(elRef) {
this.containerRef = <GridLayout>elRef.nativeElement;
setTimeout( () => {
this.containerRef.animate({
opacity: 1,
duration: 350,
curve: 'easeIn'
});
}, 100);
}
}
}Explanation:
NativeDialogRefreferences the dialog modal in use, providing control over the modal lifecycle and data transfers.- Injecting
NATIVE_DIALOG_DATAensures reliable data receipt - The animation used within the
@ViewChildsetter provides a smooth blendinging into appearance animation. This can be changes to whatever style of appearance animations you wish to create.
Alternative Approach
Another simpler method is to manually create modals using child components and boolean flags to toggle their visibility, overlaying them using a z-index higher than the parent within a *ngIfGridLayout.
- Uses standard Angular component logic (
*ngIf) - A backup for the possibility there is an issue with using
NativeDialogService
Best Practices
- Reliable Data Handling: Prefer direct injection over route parameters
- Explicit Native Options: Define all options explicity for consistency
- Manual Change Detection: Manage UI updates explicitly when using
OnPush
Conclusion
The NativeDialogService in NativeScript Angular provides robust, cross-platform modal capabilities. By following this structured approach, you’ll create reliable modals with consistent behaviour across Android and iOS.
Thank you for reading!








