Асинхронная инициализация службы Angular 2

У меня есть служба Angular 2, которая должна выполнять асинхронную работу при инициализации, и ее нельзя использовать до завершения этой инициализации.

@Injectable()
export class Api {
    private user;
    private storage;

    constructor(private http: Http) {
        this.storage = LocalStorage;
        this.storage.get('user').then(json => {
            if (json !== "") {
                this.user = JSON.parse(json);
            }
        });        
    }

    // one of many methods
    public getSomethingFromServer() {
        // make a http request that depends on this.user
    }
}

В настоящее время эта служба инициализируется и немедленно возвращается к любому компоненту, который ее использует. Затем этот компонент вызывает getSomethingFromServer() в своем ngOnInit, но в этот момент Api.user не инициализируется, и поэтому отправляется неверный запрос.

Хуки жизненного цикла (OnInit, OnActivate и т. д.) не работают с сервисами, только с компонентами и директивами, поэтому я не могу их использовать.

Для сохранения промиса из вызова get() потребуются все различные методы, которые зависят от пользователя, чтобы дождаться его, что приведет к большому дублированию кода.

Каков рекомендуемый способ асинхронной инициализации сервисов в Angular 2?


person Vegard Larsen    schedule 03.04.2016    source источник
comment
использование APP_INITIALIZER может быть подходом в некоторых сценариях, например. если вы хотите, чтобы инициализация вызывалась только один раз, см. пример здесь stackoverflow.com/questions/49686406/ Я понимаю, что на ваш вопрос уже дан ответ, но это может быть полезно для дальнейшего использования   -  person wal    schedule 05.06.2019


Ответы (2)


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

@Injectable()
export class Api {
  private userPromise: Promise<User>;

  constructor(private http: Http) {
    this.userPromise = LocalStorage.get('user').then(json => {
      if (json !== "") {
        return JSON.parse(json);
      }
      return null;
    });        
  }

  public getSomethingFromServer() {
      return Observable.fromPromise(this.userPromise).flatMap((user) => {
        return this.http.get(...).map(...);
      });
    }
  }
}

Это гарантирует, что функция flatMap получает пользователя каждый раз, когда она вызывается, а не только в первый раз, как в ответе Тьерри.

person Vegard Larsen    schedule 06.04.2016
comment
Что делать, если пользователь существует в LocalStorage? Разве вы не собираетесь получать его с http каждый раз? - person ; 19.04.2016
comment
@MadHollander ОП просто хотел дождаться загрузки службы перед выполнением http-вызова - http-вызов не получит пользователя, он просто получает «что-то с сервера», что требует предварительного разрешения пользователя - person dannrob; 17.02.2017

Вы можете использовать наблюдаемую, чтобы сделать это с помощью оператора flatMap. Если пользователя нет, вы можете дождаться его, а затем связать целевой запрос.

Вот пример:

@Injectable()
export class Api {
  private user;
  private storage;
  private userInitialized = new Subject();

  constructor(private http: Http) {
    this.storage = LocalStorage;
    this.storage.get('user').then(json => {
      if (json !== "") {
        this.user = JSON.parse(json);
        this.userInitialized.next(this.user);
      }
    });        
  }

  // one of many methods
  public getSomethingFromServer(): Observable<...> {
    // make a http request that depends on this.user
    if (this.user) {
      return this.http.get(...).map(...);
    } else {
      return this.userInitialized.flatMap((user) => {
        return this.http.get(...).map(...);
      });
    }
  }
}
person Thierry Templier    schedule 03.04.2016
comment
Мне пришла в голову мысль. Однако у меня много таких getSomethingFromServer, и для этого потребуется, чтобы все они исполнили этот маленький танец. Есть ли способ иметь для этого что-то вроде шаблона асинхронной фабрики? - person Vegard Larsen; 03.04.2016
comment
Этот подход может вас заинтересовать: stackoverflow.com/questions/36365134/ и заголовок stackoverflow.com/questions/36180108/ - person Thierry Templier; 03.04.2016
comment
Чем больше я думаю об этом, тем лучше звучит ваше решение. Я пытаюсь реализовать это, но у меня есть небольшие проблемы. Откуда Subject? Это Rx.Subject? Кажется, у него нет метода emit. Вообще, какой импорт мне для этого нужен? - person Vegard Larsen; 04.04.2016
comment
Да, ты прав! Есть опечатка: next вместо emit (emit при использовании класса EventEmitter). Subject взят из rxjs и может быть импортирован следующим образом: import {Subject} from 'rxjs/Subject'; или import {Subject} from 'rxjs/Rx';. - person Thierry Templier; 04.04.2016