In this post, I explain how I developed a multiplatform app (both for web and mobile) with Angular 8, Nativescript and Firebase. The Github repository with the code I did is https://github.com/PatriciaJuane/MultiplatformApp.
Hello everyone!
When it first came to my mind that I wanted to create a multiplatform app, I knew I was going to use Angular but didn’t know-how. It would be an application to store data related to horse riding competitions (trophies, results, horses, riders and clubs) where all users could read that data, but only logged-in users would be able to write it.
Doing some research, I found out the most popular option was the Ionic framework, but suddenly I discovered Nativescript. In my opinion, it was not only the easiest option but the most powerful one.
But, regardless of personal opinions, why did I choose it?
- I had already created a web application and I wanted to extend it to mobile. So, this option was perfect to reuse my previous Angular components and most part of the already existing Typescript code.
- Even if I didn’t already have finalized the web, it was still a great option. I found magic in these amazing posts https://www.nativescript.org/blog/code-sharing-between-web-and-mobile-with-angular-and-nativescript and https://blog.angular.io/apps-that-work-natively-on-the-web-and-mobile-9b26852495e7-> the component’s logic was exactly the same, and in most cases, I didn’t have to duplicate or modify the code.
- Internationalization: I could reuse the Nginx-translate library to translate my user interface elements inside JSON files for every language, using i18n.
- It’s pretty well documented and has a lot of community support on Google. Something even more relevant, knowing that the multiplatform library was created in 2018, so it’s quite fresh!
- Besides, with the Nativescript Sidekick application, even if you don’t have a Mac PC, you can deploy your app on iOS, if you have the required Apple developer license.
FIREBASE
Before starting to explain Nativescript, let me briefly show you the backend I chose. I used Firebase, a Google’s platform, which allowed me to use:
- Cloud Firestore: the NoSQL cloud database of Firebase. With it, I stored and retrieved the data of my application in collections of data. I called the collections and subcollections I wanted to in an Angular service and brought all the data I needed.
- Cloud Storage: the cloud database for documents and multimedia in Firebase. There, I stored the pictures of my application. It works similarly to Firestore.
- Firebase Authentication: it allowed me to use the Google Account as a sign-in/out method for my app.
Firebase has indeed lots of functionalities available, but due to this post is not about Firebase but Nativescript, I will just let you here the articles I found really interesting to learn how to properly use it:
- https://angular-templates.io/tutorials/about/angular-crud-with-firebase: brief Angular tutorial where they explain how to perform a CRUD operation in an Angular application using cloud firestore as a database.
- https://angular-templates.io/tutorials/about/firebase-authentication-with-angular: another tutorial where they show how to add Firebase authentication to an Angular application. A lot of options are available but personally, I chose the Google Account one.
- https://firebase.google.com/docs: Firebase official documentation is perfectly explained and easy to understand. Here https://firebase.google.com/docs/web/setup you can see how to add Firebase to your javascript project.
ADD NATIVESCRIPT
So, once I had my Angular web application finished, with Firebase as my backend as a service, I had to do the following to extend to mobile:
- Go to the root folder of my Angular project and install Nativescript with
npm install -g nativescript
. This would create the nativescript files (those with the .tns extension) for each component:example.component.tns.html
,example.component.tns.css
(keeping the .ts file for both apps). It would also create a corresponding nativescript module for each one already existing, this time with the .tns extension. - In the new nativescript module, I would change the previous Angular imports and add the nativescript corresponding ones. For example:
HttpClientModule
in the web module was now changed toNativeScriptHttpClientModule
in the Nativescript module. - The file AndroidManifest.xml was created. There, I had to declare the permissions I needed to use:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/>
- The fuild build.gradle was created too. I had to add the next:
buildscript { repositories { google() // Google's Maven repository jcenter() } dependencies { classpath 'com.google.gms:google-services:4.2.0' } } allprojects { repositories { google() // Needed for Firebase } }
- To build and run the code in mobile version, I would use the commands
tns build
andtns doctor
(needed to check if our machine has all the requirements to be able to execute the Android app), andtns run android
(ortns run ios
if you have the Apple license -> check the documentation about Nativescript sidekick). - Finally, due to I was using Firebase as the backend as a service, I had to install the corresponding nativescript plugin: nativescript-plugin-firebase. Because of this, to run my application locally I could not use an Android device with the Nativescript Playground app, because the plugin is not allowed for it, and I had to use an Android emulator on my local machine.
And here is the important thing: the nativescript-firebase-plugin still permits my mobile app to use Firebase, but the syntax varies from the web version.
NATIVESCRIPT AND FIREBASE
So, what did that mean? Did I have to delete all the previous work I did in my already existing Angular services using Firebase? The answer was: no. But I had to do some refactoring:
- I had to create an interface for my already-existing Angular service and make that previous service extend from that interface: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/services/firebase.service.ts.
- That would allow me to create another implementation of that service for mobile https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/services/firebasemobile.service.ts, besides the previously existing one for web: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/services/firebaseweb.service.ts.
- Now in the mobile module, (
app.module.tns.ts
) I would insert the correct dependency:
providers: [
{
provide: FirebaseService,
useClass: FirebaseMobileService
}
],
The same for the web module (app.module.ts
):
providers: [
{
provide: FirebaseService,
useClass: FirebaseWebService
},
{
provide: AuthService,
useClass: AuthWebService
},
],
- Each time my application would run on mobile or web, the interface would use the correct implementation.
If you want to check the syntax of the nativescript-firebase-plugin, it’s perfectly well documented on its GitHub page: https://github.com/eddyverbruggen/nativescript-plugin-firebase.
Besides, I had to create my mobile application in my Firebase Console and add the generated google-services.json file of Firebase in the App_Resources folder of my project for connecting my mobile app with Firebase. Here it is perfectly explained: https://firebase.google.com/docs/android/setup?authuser=0
The same procedure was used for creating the Firebase Authentication service, to utilize the Google Account sign-in on my application, provided by Firebase: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/services/authentication.service.ts.
So, once I managed to make both services work perfectly, I concluded that:
- If you know from the beginning that you will do a multiplatform application and not web-only, it’s better to develop in parallel each functionality for web and mobile, and not extend it to mobile at the end. It will save you a lot of time!
- BUT: this was a particular occasion due to the nativescript-firebase-plugin. If I didn’t use it, I wouldn’t have to create a concrete service for mobile. I could’ve reused the 100% of it!
MOBILE COMPONENTS
Once I managed to extend my modules and services to mobile, there was a final thing missing: the mobile components. That was, in fact, the easiest part to do. I just had to import the Nativescript elements that I wanted to use on the UI in my Nativescript modules, and then use them in the concrete components needed. Let me show you an example to explain it better:
- In a specific component, for the web, I used an Angular material table to show the competitions with its data source property to set the data I wanted to display, as you can see here: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/competitions/competitions.component.ts
- On the same component, I set an array property with those same values that I will use for mobile, where I will show the elements in a ListView, not in a table.
- The web HTML showed that Angular Material Table: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/competitions/competitions.component.html
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8"> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'competitions.name' | translate }}</th> <td mat-cell *matCellDef="let competition">{{ competition.name }}</td> </ng-container> <ng-container matColumnDef="location"> <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'competitions.location' | translate }}</th> <td mat-cell *matCellDef="let competition">{{ competition.location }}</td> </ng-container> <ng-container matColumnDef="country"> <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'competitions.country' | translate }}</th> <td mat-cell *matCellDef="let competition">{{ competition.country }}</td> </ng-container> ... <ng-container matColumnDef="action"> <th mat-header-cell *matHeaderCellDef>{{ 'competitions.action' | translate }}</th> <td mat-cell *matCellDef="let competition; let i = index;"> <button mat-raised-button color="accent" (click)="delete(i, competition)" *ngIf="isLoggedIn()"> {{ 'competitions.delete' | translate }}</button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns" (click)="onItemSelected(row)"></tr> </table>
- And the mobile HTML showed a Nativescript RadListView: https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/competitions/competitions.component.tns.html
<ListView [items]="competitions" class="list-group" height="800"> <ng-template let-item="item" let-i="index" let-odd="odd" let-even="even"> <GridLayout columns="*, *, *, *" (tap)="onItemSelected(item)" row="0"> <Label [text]='item?.name' class="list-group-item" col="0"></Label> <Label [text]="item?.country" col="1" class="list-group-item"></Label> <Label [text]="item?.category" col="2" class="list-group-item"></Label> <div *ngIf="isLoggedIn()"> <Button text="X" (tap)="deleteMobile(index, item)" class="list-group-item delete" col="3" style="background-color: white;"></Button> </div> </GridLayout> </ng-template> </ListView>
As demonstrated, both show the same information but wih the web or mobile concrete elements for the UI, so you can see how easy it is to extend an already existing web component to a mobile one with Nativescript, having the same logic in their .ts component class.
FORMS
In other cases like forms, I had to create a separate typescript .ts class for mobile, with the extension .tns.ts. Let me show you an example:
- https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/new-competition/new-competition.component.ts: in this class, I declare the Angular FormGroup and FormBuilder for the web.
- https://github.com/PatriciaJuane/MultiplatformApp/blob/master/tfm/src/app/new-competition/new-competition.component.tns.ts: and in the mobile class, I didn’t need them so I declared the fields to fill.
CONCLUSION
In conclusion, a lot of concepts have been explained here so take the time you need to look at them separately. I hope that, as I did, you find Nativescript really interesting and powerful for your Angular apps. You can check in the official Nativescript documentation (https://docs.nativescript.org/angular/core-concepts/technical-overview) concrete concepts and components and create elegant multiplatform and mobile applications. If you have any suggestions or doubts please don’t hesitate to contact me!
(Here you have the demo video of my multiplatform app: https://www.youtube.com/watch?v=Et-WEXv1o7s).
Thank you for taking the time to read my post! 🙂