3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 8

Implementing a Settings Page

Configure the options used to fetch GIFs

STANDARD

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 {}
STANDARD
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).