A concept you will quickly come to terms with in Ionic is the concept of a page. A page is just a normal Angular component that we use to display a particular page in our application. We might have a page for displaying a list of users, and so we have a component called UserListPage
that we use for that task – easy enough to understand.
What might not be so obvious is the role of Providers, Services, or Injectables in Ionic. The first thing to clear up is that Providers, Services, and Injectables all reference the same thing, they are classes in our applications that are decorated with the @Injectable decorator:
import { Injectable } from '@angular/core';
@Injectable()
export class SomeProvider {
constructor() {}
someFunction() {
console.log('I do something useful!');
}
}
set up as a provider in the app.module.ts file:
@NgModule({
declarations: [MyApp],
imports: [BrowserModule, IonicModule.forRoot(MyApp)],
bootstrap: [IonicApp],
entryComponents: [MyApp],
providers: [
SomeProvider, // <-- List providers here
],
})
export class AppModule {}
and injected into classes that want to use them:
import { Component } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { SomeProvider } from '../../providers/some-provider/some-provider';
@IonicPage()
@Component({
selector: 'page-something',
templateUrl: 'something.html',
})
export class SomePage {
constructor(public myService: SomeService) {
}
ionViewDidLoad() {
this.myService.someFunction(); // This will log "I do something useful!"
}
}
The various names for these classes make sense if you think of them as classes that are injected into other classes to provide a service. In life you might use services to get things done for you – pizza delivery, dry cleaning, renovations. It’s similar in Ionic, we use services to perform some kind of work for us.
One particularly common example of this is a data service. We might create a data service that handles saving and retrieving data for us. There could be quite a bit of “work” involved in saving or retrieving data – maybe we need to make a call to some server, or maybe we need to store that data in some specific way – so in the same way that we can make a call to a pizza place and request pizza, we could make a call to a service we have created to fetch some data for us without having to worry about what is happening behind the scenes.
We still do need to write the code for whatever it is this service does, but the benefit of creating a separate service to do the work, rather than adding it directly to the code for a particular page, is that by using a service or multiple services we can keep the code for organised and we can more easily reuse that same service in other places.
It can be hard, in the beginning, to identify when you should create a service to do something, but in general just ask yourself “is this task I am performing strictly related to this page and this page only?” if the answer is yes, then you can probably just add the code directly to the page. If the answer is no, then you should probably create a service. If you are setting up click handlers for buttons in your template, then you aren’t going to need a service. If you are fetching data from a server to display on the page, then you should probably be using a service.
Let’s consider an example. Take a look at the following page:
import { Component, Http, Headers } from '@angular/core';
import { NavController, LoadingController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
users: any = []
loading: any;
username: string;
password: string;
constructor(public navCtrl: NavController, public http: Http, public loadingCtrl: LoadingController) {
}
ionViewDidLoad(){
this.http.get('https://someserver.com/api/users').map(res => res.json()).subscribe((users) => {
this.users = users;
});
}
addUser(){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let credentials = {
username: this.username,
password: this.password
};
this.http.post('https://someserver.com/api/users', JSON.stringify(credentials), {headers: headers})
.subscribe((res) => {
this.loading.dismiss();
});
}
presentLoading() {
this.loading = this.loadingCtrl.create({
content: 'Please wait...'
});
this.loading.present();
}
}
This page will work just fine. We are fetching a list of users, and we have some functions for… There is quite a bit of code here, it looks a little messy, but that’s fine we can get over that. The bigger issue here is what if we also want to display these users on another page in our application? Well, we could just copy and paste from this page onto the other page. But now we’re repeating ourselves and duplicating code. What if we want to change the way data is fetched from the server? Now we have to update the same code on two separate pages. What if we had copied that code over onto 7 different pages?
If we separate the logic related to fetching and managing users into its own Users
service we are going to have a much easier time:
src/providers/users/users.ts
import { Injectable, Http, Headers } from '@angular/core';
@Injectable()
export class UserProvider {
constructor(public http: Http) {
}
getUsers(){
return this.http.get('https://someserver.com/api/users').map(res => res.json());
}
addUser(username, password){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let credentials = {
username: username,
password: password
};
return this.http.post('https://someserver.com/api/users', JSON.stringify(credentials), {headers: headers});
}
}
src/pages/home/home.ts
import { Component } from '@angular/core';
import { NavController, LoadingController } from 'ionic-angular';
import { UsersProvider } from '../../providers/users/users';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
users: any = []
loading: any;
username: string;
password: string;
constructor(public navCtrl: NavController, public loadingCtrl: LoadingController, public usersService: UsersProvider) {
}
ionViewDidLoad(){
this.usersService.getUsers().subscribe((users) => {
this.users = users;
});
}
addUser(){
this.presentLoading();
this.usersService.addUser(this.username, this.password).subscribe((res) => {
this.loading.dismiss();
});
}
presentLoading() {
this.loading = this.loadingCtrl.create({
content: 'Please wait...'
});
this.loading.present();
}
}
Now not only does the code look a bit cleaner, but we can easily use the UsersProvider
service in any class we want. If we ever need to update the way that the data is retrieved, we only need to update it in one place. Now our page code can focus on code that directly affects the page itself, rather than worrying about backend implementation details.
Summary
The code used in this example was reasonably simple, but imagine if we were doing more than just working with user data. As well as users, maybe this page also displays the current weather at each user’s location among other data, if we were trying to handle all of the logic for that inside of the page, instead of in individual services, the page would quickly become a monstrosity.
Hopefully, this tutorial highlights the benefit of identifying logic that can be separated out into its own service.