Adding Ionic to an Angular Application
We’ve spent a little while working with a standard Angular application generated using the Angular CLI. We’ve also already created an Ionic application using the Ionic CLI. This is great because it sets up everything for us out of the box, and we can just start building!
But… it does muddy the waters a bit. If our application already includes Ionic by default then it is kind of hard to tell what is “Ionic” and what is “Angular”. We’ve let the Ionic CLI handle the jump from an “Angular” application to an “Ionic/Angular” application for us.
This is most certainly what you should do when building Ionic applications: use the Ionic CLI to generate the application for you. However, it is useful as a learning exercise to start off with a standard Angular application generated with the Angular CLI and add Ionic to it manually. Not only will this show us what Ionic changes about a standard Angular application, it is also going to give us some experience setting up and configuring a third party library in Angular.
It is also useful to demonstrate that all of the Angular knowledge you are gaining by learning Ionic is also applicable more broadly to Angular applications in general.
So, that is exactly what we are going to do in this lesson!
Generate an application with the Angular CLI
First, let’s generate a new Angular application with the Angular CLI:
Run the following command
ng new angular-to-ionic
You can use the following configurations:
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS
Install @ionic/angular
Ionic comes with an ng add
schematic for Angular. This means that we can just run:
ng add @ionic/angular
And the schematic will take care of everything for us… but it seems like this kind of defeats the purpose of what we are doing in this lesson. We may as well just use the Ionic CLI.
We are going to do this anyway, as we will be able to inspect specifically what this schematic is adding. We will see the files that have been modified output to the terminal, and we can also use git
to check what specifically has changed.
Run the following command inside of your new project:
ng add @ionic/angular
You should see an output something like this:
> ng add @ionic/angular
i Using package manager: npm
✔ Found compatible package version: @ionic/[email protected].
✔ Package information loaded.
The package @ionic/[email protected] will be installed and executed.
Would you like to proceed? Yes
✔ Packages successfully installed.
CREATE src/theme/variables.css (288 bytes)
UPDATE package.json (1118 bytes)
UPDATE src/app/app.module.ts (466 bytes)
UPDATE angular.json (5501 bytes)
✔ Packages installed successfully.
Understanding the Changes
We can see that the Ionic schematic has created the following file:
src/theme/variables.css
and it has modified the following files:
package.json
src/app/app.module.ts
angular.json
To more closely inspect what has changed we can use git diff
inside of our project in the terminal. If you are using something like VS Code that highlights changes, you can also go to the files marked with an M
and then in the gutter of the editor (where the code line numbers are) the sections that have been modified will have a line next to the numbers. I will save you the hassle of doing this if you aren’t comfortable with it, and I will just paste the changes below.
Let’s start out with the src/theme/variables.css
file. I don’t need to tell you what has changed here because the entire file is new.
variables.css
/* Ionic Variables and Theming. */
/* This is just a placeholder file For more info, please see: */
/* https://ionicframework.com/docs/theming/basics */
/* To quickly generate your own theme, check out the color generator */
/* https://ionicframework.com/docs/theming/color-generator */
There isn’t actually anything in this file yet, but this is where we will define the CSS variables that will configure what the Ionic components look like. Often this means just specifying what colours certain components should use, but we can get more in-depth as well. We will be discussing theming and styling in another lesson.
package.json
"dependencies": {
"@angular/animations": "^14.1.0",
"@angular/common": "^14.1.0",
"@angular/compiler": "^14.1.0",
"@angular/core": "^14.1.0",
"@angular/forms": "^14.1.0",
"@angular/platform-browser": "^14.1.0",
"@angular/platform-browser-dynamic": "^14.1.0",
"@angular/router": "^14.1.0",
"@ionic/angular": "^6.2.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.1.0",
"@angular/cli": "~14.1.0",
"@angular/compiler-cli": "^14.1.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2",
"@ionic/angular-toolkit": "latest"
}
The schematic has added and installed the @ionic/angular
and @ionic/angular-toolkit
dependencies. The @ionic/angular
package is what provides most of what Ionic does including all of the Ionic web components (technically, the web components come from @ionic/core
but that is included within @ionic/angular
). The @ionic/angular-toolkit
package provides additional schematics for Ionic.
src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IonicModule } from '@ionic/angular';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
IonicModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
The ng add
command has also made a modification to our root module file. See if you can pick out what it is.
Click here to reveal solution
Solution
We are now importing the IonicModule
:
IonicModule.forRoot()
Just like how we would import things like CommonModule
to allow us to use things like *ngIf
and *ngFor
, we import the IonicModule
to allow us to use Ionic’s components. We do this once in the root module and call the forRoot()
static method. A static method is a method on a class that can be called without actually needing to create an instance of that class. The important part here is that we call forRoot
in the root module, and we can optionally supply forRoot
with a configuration object for Ionic.
We use forRoot
in the root module, but we will also need to import the IonicModule
inside of any other module we want to use it in (e.g. our HomeModule
). If we don’t, the compiler will complain that Ionic’s components don’t exist. Inside of any other module, we just import it like this:
imports: [
IonicModule
],
angular.json
I won’t paste the changes to angular.json
because it is quite long, so I would encourage you to go have a look for yourself. In general, Ionic adds some additional resources to be added to builds of the application, including all of its CSS files (it’s internal ones, but our variables.css
that was created as well). It also adds some build configurations related to Cordova, but that isn’t relevant to us.
That’s not everything…
The changes above will allow us to use Ionic components in an Angular application, but it isn’t quite everything we get when we generate a new application with the Ionic CLI. Likely the reason Ionic doesn’t add everything is because some changes are more destructive, and if you are adding Ionic to an existing Angular application you might not want the ng add
schematic making sweeping changes to your application.
Let’s manually add in a few more things.
Modify
src/app/app.component.html
to reflect the following:
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
This is an important one, and there are two things going on here. One is that we are using the <ion-router-outlet>
instead of the default Angular <router-outlet>
. By default, Angular will apply mobile style transition animations when switching between views, and using Ionic’s version of the router outlet is what will enable this. We also surround our entire application in the <ion-app>
component which is important for styling.
Modify
src/styles.css
to reflect the following:
/* You can add global styles to this file, and also import other style files */
/* Core CSS required for Ionic components to work properly */
@import "~@ionic/angular/css/core.css";
/* Basic CSS for apps built with Ionic */
@import "~@ionic/angular/css/normalize.css";
@import "~@ionic/angular/css/structure.css";
@import "~@ionic/angular/css/typography.css";
@import '~@ionic/angular/css/display.css';
/* Optional CSS utils that can be commented out */
@import "~@ionic/angular/css/padding.css";
@import "~@ionic/angular/css/float-elements.css";
@import "~@ionic/angular/css/text-alignment.css";
@import "~@ionic/angular/css/text-transformation.css";
@import "~@ionic/angular/css/flex-utils.css";
This includes the CSS files that were referenced in the angular.json
file. Typically, Ionic actually creates a global.scss
file instead of a styles.css
file, but it is fine for us to just use the styles.css
file if we want. We will just be using the Ionic CLI anyway so it doesn’t really matter right now.
Modify
src/app/app-routing.module.ts
to reflect the following:
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'home',
loadChildren: () =>
import('./home/home.component').then((m) => m.HomeComponentModule),
},
{
path: '',
redirectTo: 'home',
pathMatch: 'full',
},
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
NOTE: I’ve set up an example route here that links to a HomeComponent
, if you are following along, see if you can set that component up yourself.
The important part is this:
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
Ionic CLI will also generate an application with a specific preloadingStrategy
to be used. The preloadingStrategy
defines how Angular should preload modules in the application. We talked about this before when discussing lazy loading and setting up the pages in our application with modules. By using modules and supplying it to loadChildren
in the route configuration, it doesn’t need to be immediately loaded when the application is booting up.
By default, modules will not be loaded until the path for that module is loaded (e.g. the HomeModule
won’t load until we go to the /home
path). However, with the PreloadAllModules
strategy, all of the modules will be loaded in the background as quickly as possible after the application has finished booting. This means our application can still load faster initially, but we also aren’t going to get a delay when trying to access routes that haven’t loaded yet.
Modify
src/app/app.module.ts
to reflect the following:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { RouteReuseStrategy } from '@angular/router';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, IonicModule.forRoot()],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent],
})
export class AppModule {}
Ionic already added the IonicModule
for us, but a new Ionic application also includes a RouteReuseStrategy
. This is beside the point, but this is a good example of utilising the more advanced features of tokens and dependency injection that we discussed before. Take a look at this:
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
We are saying we want to provide the RouteReuseStrategy
token, but we want to use the class IonicRouteStrategy
for that token. That means that wherever the RouteReuseStrategy
token is used, it will actually use an instance of IonicRouteStrategy
.
Route reuse strategies are a much more advanced topic, and you don’t really ever need to consider it too much. The key idea is that, by default, whenever we navigate between components in Angular, Angular will destroy the component that was just navigated away from. If we later come back to that route the component will be recreated again. This might not always be what you want, and Ionic defines some more specific rules around when components should be created and destroyed.
This is where Ionic’s lifecycle hooks like ionViewWillEnter
and ionViewWillLeave
come into play. These are like Angular’s ngOnInit
lifecycle hook, except that they run at times related to the Ionic Page Lifecycle. We will not often use Ionic’s specific hooks, but consider a situation where you are running some logic using Angular’s ngOnInit
hook. This only runs when the component is first created. If, according to Ionic’s route reuse strategy, some component is not destroyed when we navigate away from it, then the ngOnInit
logic will not run when we navigate back to it. It is rarer, but in situations like this we might need to explicitly run some logic inside of a hook like ionViewWillEnter
which will run every time we navigate to that page, regardless of whether it has already been created or not.
Recap
That’s it! We have now converted a standard Angular application into an Ionic application that is almost identical to one created by the Ionic CLI.
What does importing IonicModule allow us to do?
Correct!
Incorrect
Incorrect
Why do we need to use ion-router-outlet instead of router-outlet?
Incorrect
Incorrect
Correct!