Как переопределить некоторые действия в магазине ngrx? - PullRequest
0 голосов
/ 14 мая 2018

У нас есть угловой проект в работе (разрабатывается, еще не начался).Это довольно сложно и имеет сложные потоки данных.Кроме того, у нас есть два вида пользователей в нашем приложении. Диспетчер и пользователи .Эти пользователи будут видеть похожие представления, но для каждого из них будет несколько различных пользовательских представлений. Менеджер будет иметь доступ к большему количеству функций, как вы могли бы себе представить.Чтобы управлять этим довольно большим и сложным приложением в масштабируемой форме, мы следуем шаблону NX .Т.е. несколько приложений в одном репо.В конце концов, в рамках одного репо у меня есть следующие приложения.

Apps

Большая часть разработки будет выполняться в приложении common и различных видах и настройкахбудет сделано как в mngr-app, так и в user-app соответственно.

Мы также думаем о применении ngrx в нашем приложении для управления состоянием.Я видел несколько примеров и учебных пособий о том, как это сделать.Пока все в порядке.Эта часть только для справочной информации.

Наша проблема начинается после этого.Наша бизнес-команда также хочет, чтобы мы разрабатывали приложения для iOS и Android с веб-представлениями, содержащими веб-приложение (я забыл упомянуть, что это адаптивное веб-приложение).Таким образом, все, что мы сделали, будет отправлено мобильным пользователям в рамках веб-просмотра.Однако бизнес-команда также хочет, чтобы мы разработали несколько собственных нативных представлений для мобильных приложений.

Давайте рассмотрим следующий пример: (это из примера ngrx )

Add book collection

Когдапользователь нажимает кнопку Add Book to Collection, в хранилище отправляется тип действия [Collection] Add Book, и эффект позаботится о нем следующим образом:

@Effect()
addBookToCollection$: Observable<Action> = this.actions$
  .ofType(collection.ADD_BOOK)
  .map((action: collection.AddBookAction) => action.payload)
  .switchMap(book =>
    this.db.insert('books', [ book ])
      .map(() => new collection.AddBookSuccessAction(book))
      .catch(() => of(new collection.AddBookFailAction(book)))
  );

Это обычный поток веб-приложения.

Наша бизнес-команда хочет, чтобы мы создали какую-то специальную логику для мобильных приложений, чтобы при переходе пользователей на эту страницу в мобильном приложении (iOS или Android) вместо добавления книги в ее коллекцию »Я открою родную страницу, и пользователь будет выполнять действия на этой родной странице.Под этим я подразумеваю, что они хотят, чтобы веб-приложение работало по-разному, когда оно присутствует в мобильном приложении.Я могу добиться этого с кучей if(window.nativeFlag === true) в веб-приложении.Тем не менее, это просто грязный взлом, который мы хотим избежать.Поскольку мы используем ngrx и rxjs, мы чувствуем, что это можно сделать с Observable с rxjs и Action с ngrx.

То, что мы пробовали до сих порсостоит в том, чтобы выставить объект store и actions в DOM, чтобы мы могли получить к нему доступ в мобильном приложении.

  constructor(private store: Store<fromRoot.State>, private actions$: Actions) {
    window['store'] = this.store;
    window['actions'] = this.actions$;
  }

Таким образом, мы можем подписаться на событие [Collection] Add Book следующим образом

actions().ofType('[Collection] Add Book').subscribe(data => {
    console.log(data)
})

и получать уведомления при добавлении книги в коллекцию.Тем не менее, веб-приложение по-прежнему делает то, что обычно, и мы не можем переопределить это поведение.

У меня вопрос, как подписаться на некоторые ngrx действия из мобильных приложений и отменить поведение веб-приложения?

Редактировать

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

1 Ответ

0 голосов
/ 18 мая 2018

Что я смог сделать до сих пор

Я написал следующее bridge в веб-приложении.

window['bridge'] = {
  dispatch: (event: string, payload = '') => {
    // I had to use zone.run, 
    // otherwise angular won't update the UI on 
    // an event triggered from mobile.
    this.zone.run(() => {
      this.store.dispatch({type: event, payload: payload});
    });
  },
  ofEvent: (event: string) => {
    // I register this event on some global variable 
    // so that I know which events mobile application listens to.
    window['nativeActions'].push(event);
    return this.actions.ofType(event);
  }
};

Из приложения Android я могу прослушать [Collection] Add Book следующим образом:

webView.loadUrl("
    javascript:bridge.ofEvent('[Collection] Add Book')
       .subscribe(function(book) {
           android.addBook(JSON.stringify(book.payload));
       });
");

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

@JavascriptInterface
public void addBook(String book) {
    Log.i("addBook", book);
    this.book = book;
}

Я также добавляю кнопку в приложение для Android, чтобы удалить эту книгу.

webView.evaluateJavascript("
      bridge.dispatch('[Collection] Remove Book', "+this.book+")
", null);

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

Также ранее я упоминал, что зарегистрировал это событие в глобальной переменной.

Я изменил свой @Effect на следующий

@Effect()
addBookToCollection$: Observable<Action> = this.actions$.pipe(
  ofType<AddBook>(CollectionActionTypes.AddBook),
  map(action => action.payload),
  switchMap(book => {
    if (window['nativeActions'].indexOf(CollectionActionTypes.AddBook) > -1) {
      return of();
    } else {
      return this.db
        .insert('books', [book])
        .pipe(
        map(() => new AddBookSuccess(book)),
        catchError(() => of(new AddBookFail(book)))
        );
    }
  })
);

С помощью if (window['nativeActions']..) я смог проверить, зарегистрировано ли мобильное приложение для этого конкретного события. Однако я не буду выполнять этот контроль на каждом @Effect, которое у меня есть. На данный момент я думаю написать какой-то пользовательский оператор RxJs, чтобы абстрагироваться от этой проверки из моего effects. Я чувствую, что близок к ответу, но буду признателен за любой вклад.

Пользовательский оператор RxJs

Я написал следующий пользовательский оператор и решил мою проблему.

export function nativeSwitch(callback) {
    return function nativeSwitchOperation(source) {
        return Observable.create((subscriber: Observer<any>) => {
            let subscription = source.subscribe(value => {
                try {
                    if (window['nativeActions'].indexOf(value.type) > -1) {
                        subscriber.complete();
                    } else {
                        subscriber.next(callback(value));
                    }
                } catch (err) {
                    subscriber.error(err);
                }
            },
            err => subscriber.error(err),
            () => subscriber.complete());

            return subscription;
        });
    };
}

Изменен мой эффект:

@Effect()
addBookToCollection$: Observable<Action> = this.actions$.pipe(
  ofType<AddBook>(CollectionActionTypes.AddBook),
  nativeSwitch(action => action.payload),
  switchMap((book: Book) =>
    this.db
      .insert('books', [book])
      .pipe(
      map(() => new AddBookSuccess(book)),
      catchError(() => of(new AddBookFail(book)))
      )
  )
);

Это именно то, что я хочу. Поскольку я вызываю subscriber.complete(), если данное событие зарегистрировано мобильным приложением, метод в пределах switchMap никогда не вызывается.

Надеюсь, это кому-нибудь поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...