Implementing a Settings Page
In this lesson, we are going to focus on allowing the user to configure their experience a little more. Right now we just fetch 100
posts from Reddit, and filter those to only include results that contain some form of GIF. By the end of this lesson, we are going to allow our user to set a specific limit on how many GIFs to return per page, and we will also allow them to configure whether these GIFs should be sorted by hot
or new
.
Create the Settings Service
The first thing we are going to do is create a new SettingsService
which will handle storing and retrieving our settings for us.
Create the following interface at
src/app/shared/interfaces/settings.ts
:
export interface Settings {
perPage: number;
sort: 'hot' | 'new';
}
Export this interface along with the other interfaces in the
index.ts
file
Create the following file at
src/app/shared/data-access/settings.service.ts
and add the following:
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage-angular';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { shareReplay, switchMap, take, tap } from 'rxjs/operators';
import { Settings } from '../interfaces';
@Injectable({
providedIn: 'root',
})
export class SettingsService {
#hasLoaded = false;
storage$ = from(this.ionicStorage.create()).pipe(shareReplay(1));
load$: Observable<Settings> = this.storage$.pipe(
switchMap((storage) => from(storage.get('settings'))),
tap(() => (this.#hasLoaded = true)),
shareReplay(1)
);
#settings$ = new BehaviorSubject<Settings>({
sort: 'hot',
perPage: 10,
});
settings$ = this.#settings$.asObservable();
constructor(private ionicStorage: Storage) {}
init() {
this.load$.pipe(take(1)).subscribe((settings) => {
if (settings) {
this.#settings$.next(settings);
}
});
}
save(settings: Settings) {
this.#settings$.next(settings);
if (this.#hasLoaded) {
this.storage$.pipe(take(1)).subscribe((storage) => {
storage.set('settings', settings);
});
}
}
}
NOTE: Remember that for the code above to work you will need to set up Ionic Storage in the root module, and if you want to configure native storage you will also need to install additional dependencies. If you get stuck here, you can refer to the final source code for this application, or the previous lessons for the Quicklists or Snapaday applications.
HINT: This is the configuration you will need in the root module:
import { Drivers } from '@ionic/storage';
import { IonicStorageModule } from '@ionic/storage-angular';
import * as CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
// ...snip
IonicStorageModule.forRoot({
driverOrder: [
// eslint-disable-next-line no-underscore-dangle
CordovaSQLiteDriver._driver,
Drivers.IndexedDB,
Drivers.LocalStorage,
],
}),
We have taken a slightly different approach for our SettingsService
. Typically, we have been creating a separate StorageService
whenever we want to access local storage. However, this time we are just accessing storage directly from within our SettingsService
. This changes what we need to do a little bit, but it is the same general idea. I wanted to show you this different approach, but in general I think the cleaner solution is to abstract the storage handling away into a separate StorageService
.
If we were to extend this application later such that we needed to access storage from multiple different services, we would be in a situation where are duplicating the storage code unnecessarily. On top of that, I think it also just makes the code nicer to user when the storage service is abstracted into its own service.
Create the Settings Component
Now that we have a way to save and retrieve settings, we can start making use of that.
Create a file at
src/app/settings/settings.component.ts
and add the following:
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { PopoverController } from '@ionic/angular';
import { SettingsService } from '../shared/data-access/settings.service';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Settings } from '../shared/interfaces';
@Component({
selector: 'app-settings',
template: `
<ion-header>
<ion-toolbar color="light">
<ion-buttons slot="end">
<ion-button (click)="popoverCtrl.dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding"> </ion-content>
`,
styles: [
`
:host {
height: 100%;
}
ion-segment {
--ion-background-color: #fff;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsComponent {
settingsForm = this.fb.nonNullable.group<Settings>({
sort: 'hot',
perPage: 10,
});
constructor(
private fb: FormBuilder,
public settingsService: SettingsService,
public popoverCtrl: PopoverController
) {}
handleSave() {
this.settingsService.save(this.settingsForm.getRawValue());
this.popoverCtrl.dismiss();
}
}
@NgModule({
imports: [CommonModule, ReactiveFormsModule, IonicModule],
declarations: [SettingsComponent],
exports: [SettingsComponent],
})
export class SettingsComponentModule {}