Итак, вы создаете свое следующее большое приложение, и все работает отлично, кроме случаев, когда ваши пользователи отключены от сети, находятся в зоне с нестабильным приемом или просто при медленном соединении в целом. Внезапно их профиль, сообщения или любой другой контент в приложении, загружаемый из API вашего сервера, становится очень медленным, истекает время ожидания или полностью не загружается. Это ужасный пользовательский опыт, который делает ваше приложение совершенно бесполезным в автономном режиме.

В этом посте мы рассмотрим кеширование наблюдаемых данных вашего Ionic-приложения, чтобы уменьшить задержку, улучшить взаимодействие с пользователем и заставить ваше приложение работать в автономном режиме.

Если вы просто хотите попробовать, вы можете клонировать исходный код примера здесь и сразу же опробовать его с ionic serve: https://github.com/westphalen/ionic-offline-example

Установка

Начните с запуска нового ионного проекта. Если вы не делали этого раньше, обязательно зайдите на IonicFramework.com и прочтите, как начать работу.

ionic start myOfflineApp

Чтобы волшебство произошло, вам понадобятся два пакета: первый @ionic/storage, который нужен для хранения данных между сеансами приложения, а второй - ionic-cache-observable, пакет, который творит всю магию:

npm install --save @ionic/storage ionic-cache-observable

Теперь нам нужно зарегистрировать модули в нашем AppModule, например. src/app/app.module.ts:

//.........
@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
    // Add the cache module
    CacheModule, 
    // We will use the HttpClient for our API provider          
    HttpClientModule, 
    // Add the storage module
    IonicStorageModule.forRoot(),
    IonicModule.forRoot(MyApp),
    ...
  ],
//.........

Загрузить данные из API

Теперь, если у вас уже есть поставщик данных API, пропустите этот раздел. В противном случае создадим его с данными из JSONPlaceholder.

ionic g provider Placeholder

Это создаст вашего нового провайдера в src/providers/placeholder/placeholder.ts. Откройте его и давайте добавим данные API:

import { Injectable } from '@angular/common';
import { HttpClient } from '@angular/common/http';
// Remember to import RxJs dependencies.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/defer';
import 'rxjs/add/operator/delay';
// Interface for the data returned by the JSONPlaceholder API
export interface Placeholder {
  userId: number;
  id: number;
  title: string;
  body: string;
}
@Injectable()
export class PlaceholderProvider {
  constructor(private http: HttpClient) {}
  public random(): Observable<Placeholder> {
    // Observable.defer makes sure we generate a 
    // random number each time the Observable is triggered.
    return Observable.defer(() => {
      const randIndex = Math.ceil(Math.rand() * 10); // 0-10
      const url = 'https://jsonplaceholder.typicode.com/posts/' 
        + randIndex;
      return this.http.get<Placeholder>(url).delay(1000);
    });
  }
}

Хорошо, мы определили интерфейс, который описывает данные заполнителя, которые мы ожидаем от нашего API. Далее идет наша случайная функция, которая запрашивает случайные данные из API, имитируя данные, которые постоянно меняются в нашем приложении. Кроме того, мы применяем задержку 1000 ms, чтобы имитировать медленное соединение, чтобы лучше видеть, что происходит.

Кеширование данных в ваших представлениях

На страницу, на которой мы хотим отображать наши данные, например src/pages/home/home.ts:

//....................
export class HomePage {
  public data$: Observable<Placeholder>;
  constructor(placeholderProvider: PlaceholderProvider,
              cacheService: CacheService) {
    // The data we want to display on the page. 
    // This could be a user's profile or his list of todo items.
    const dataObservable = placeholderProvider.random();
    // We register a cache instance, using a unique name 
    // so the CacheService will know what data to display next time.
    cacheService.register('home', dataObservable)
      .subscribe((cache) => {
        this.data = cache.get$;
      });
  }
// ......................

В наш компонент страницы мы вставляем PlaceholderProvider для наших данных API и CacheService из пакета, который мы установили ранее. Затем мы регистрируем наблюдаемые данные API с CacheService, используя идентификатор 'home', чтобы иметь возможность распознавать данные между сеансами. Наблюдаемый регистр предоставляет нам объект cache, который отныне представляет наши Placeholder данные. В этом простом сценарии мы просто назначим get$ кеша переменной data$ страницы. Кеш будет автоматически извлекать данные из предоставленного нами dataObservable, которые мы можем отобразить в home.html:

<ion-content>
  <div *ngIf="data$ | async as data">
    <h1>{{ data.title }}</h1>
    <p>{{ data.body }}</p>
  </div>
</ion-content>

Канал async автоматически обновит представление при обновлении data$.

Теперь запустите приложение с помощью ionic serve или запустите его на своем устройстве. Когда вы впервые открываете страницу, вы должны заметить задержку в несколько секунд, прежде чем случайный заголовок и тело появятся из нашего API. Теперь откройте страницу, обновите или снова откройте приложение и посмотрите, как происходит волшебство. Когда вы перейдете на страницу во второй раз, данные появятся более или менее мгновенно, и даже несмотря на то, что данные должны быть случайными, мы показываем точно такой же контент, как и раньше. API никогда не вызывается, поэтому эти данные могут отображаться в автономном режиме! Но, конечно, мы не хотим постоянно показывать устаревшие данные. Давайте посмотрим, как обновить кеш.

Обновление кешированных данных

Давайте сделаем простую функцию обновления с помощью классного компонента Ionic Refresher. Вернитесь к компоненту страницы, например. home.ts:

//....................
export class HomePage {
  public data$: Observable<Placeholder>;
  private cache: Cache<Placeholder>;
  constructor(placeholderProvider: PlaceholderProvider,
              cacheService: CacheService) {
    const dataObservable = placeholderProvider.random();
    cacheService.register('home', dataObservable)
      .subscribe((cache) => {
        // Save the cache object.
        this.cache = cache;
        this.data = this.cache.get$;
      });
  }
  onRefresh(refresher: Refresher): void {
    // Check if the cache has registered.
    if (this.cache) {
      this.cache.refresh().subscribe(() => {
        // Refresh completed, complete the refresher animation.
        refresher.complete();
      }, (err) => {
        // Something went wrong! 
        // Log the error and cancel refresher animation.
        console.error('Refresh failed!', err);
        refresher.cancel();
      });
    } else {
      // Cache is not registered yet, so cancel refresher animation.
      refresher.cancel();
    }
  }
// ......................

Здесь изменились две вещи. В конструкторе мы теперь устанавливаем объект cache в нашем классе страницы, поэтому мы можем получить к нему доступ из второго изменения, нашей новой функции onRefresh, которая принимает аргумент Refresher от компонента Ionic Refresher. В этой функции мы просто вызываем обновление объекта кеша, а затем соответствующим образом обновляем пользовательский интерфейс через refresher. Вам не нужно беспокоиться об обновленных данных, поскольку они автоматически отправляются на data$.

Внедрите компонент Ionic Refresher в свой шаблон страницы, например. home.html:

<ion-content>
  <ion-refresher (ionRefresh)="onRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>
  <div *ngIf="data$ | async as data">
    <h1>{{ data.title }}</h1>
    <p>{{ data.body }}</p>
  </div>
</ion-content>

Все, что мы здесь сделали, это добавили ion-refresher, который вызывает нашу onRefresh функцию.

Теперь запустите ваше приложение. Вы все равно должны увидеть кешированные данные, которые были ранее. Выполните обновление по запросу и подождите примерно секунду, и вы должны увидеть изменение данных! Объект кеша вызывает ваш зарегистрированный наблюдаемый объект в фоновом режиме и отправляет новые данные, как только они поступают. Обновите приложение, и последнее содержимое будет загружаться до тех пор, пока вы снова не потянете для обновления. Хорошо, но пользователям не нужно постоянно тянуть, чтобы обновить. Давайте посмотрим на автоматическое обновление содержания.

Обновлять автоматически

Вы можете сохранить полезную функцию обновления по запросу и при этом автоматически предоставлять обновленные данные. Нам просто нужно изменить несколько вещей в нашем компоненте страницы, например home.ts:

//....................
constructor(private placeholderProvider: PlaceholderProvider,
            private cacheService: CacheService) {
  // I have moved to ionViewWillEnter.
}
  ionViewWillEnter() {
    const dataObservable = placeholderProvider.random();
    cacheService.register('home', dataObservable)
      .subscribe((cache) => {
        this.cache = cache;
        this.data = this.cache.get$;
        // Refresh the cache immediately.
        this.cache.refresh().subscribe();
      });
  }
// .....................

Мы переместили регистрацию кеша из конструктора в метод ionViewWillEnter, который вызывается каждый раз, когда пользователь хочет открыть страницу. Это включает в себя навигацию между представлениями, поэтому даже без закрытия или перезагрузки приложения пользователи будут видеть обновленные данные при каждом открытии представления. Как только метод ionViewWillEnter извлекает объект cache из cacheService.register, мы сразу же вызываем cache.refresh(). Нет необходимости обрабатывать подписку, так как мы просто хотим обновить данные без уведомления.

Попробуйте! Каждый раз, когда вы открываете страницу, вы мгновенно видите старые данные, но примерно через секунду обновленные / случайные данные заменяют кэшированные данные. И вы все еще можете потянуть для обновления! Не бойтесь вызывать cache.refresh() несколько раз, так как наблюдаемое обновление является общим. Таким образом, ваш пользователь может безопасно потянуть для обновления, даже если ваша страница уже обновляет данные в фоновом режиме.

Спасибо за прочтение!

Если это вам помогло, пожалуйста, хлопните в ладоши! Кроме того, если у вас есть какие-либо вопросы или отзывы, не стесняйтесь оставлять комментарии.

Подробнее о пакете ionic-cache-observable и его возможностях читайте на GitHub: https://github.com/westphalen/ionic-cache-observable

Не стесняйтесь предлагать новые функции или отправлять запросы на добавление улучшений.