2016-06-07 1 views
4

Я работаю над компонентом автозаполнения TypeScript (для Angular 2, хотя это не является ключевым вопросом), и я бы хотел управлять большинством (или всеми) его через наблюдаемые в RxJs (5,0,0-бета8). У меня возникают проблемы с отслеживанием позиции текущего элемента в списке предложений при действии с помощью клавиш со стрелками вверх/вниз: индекс остается застрявшим на 0.Отслеживать текущее положение в списке типов с наблюдаемыми

Вся следующая логика отложена в отдельном class, который получает входные наблюдаемые и производит выходные наблюдаемые для подписки на них (поэтому не имеет строгого отношения к Angular 2). Клиентский код правильно подписывается на выходные наблюдаемые данные.

Вот код:

// Component responsible for managing only the list of suggestions 
// It receives inputs from text field and it produces outputs 
// as current index in list, when to hide list, etc. 
class AutocompleteListDriver { 
    currentIndex$: Observable<number>; 
    doClose$: Observable<void>; 
    // ... 

    constructor(... 
    matches$: Observable<string[]>, // list of suggestions matching text in field 
    keyUp$: Observable<KeyboardEvent>, // keyup events from text field 
    keyDown$: Observable<KeyboardEvent>, // keydown events from text field 
    ...) { 

    const safeMatches$ = matches$ 
     .startWith([]); // start with a clear, known state internally 

    // when list is empty, component is hidden at rest: 
    // detect keys only when component is visible 
    const isActive$ = safeMatches$ 
     .map(matches => matches.length !== 0); 

    const activeKeyUp$ = keyUp$ 
     .withLatestFrom(isActive$) 
     .filter(tuple => tuple[1]) // -> isActive 
     .map(tuple => tuple[0]); // -> keyboardEvent 

    this.currentIndex$ = safeMatches$ 
     .switchMap(matches => { 
     const length = matches.length; 
     console.log('length: ' + length); 

     const initialIndex = 0; 

     const arrowUpIndexChange$ = activeKeyUp$ 
      .filter(isArrowUpKey) 
      .map(_ => -1); 

     const arrowDownIndexChange$ = activeKeyUp$ 
      .filter(isArrowDownKey) 
      .map(_ => +1); 

     const arrowKeyIndexChange$ = Observable 
      .merge(arrowUpIndexChange$, arrowDownIndexChange$) 
      .do(value => console.log('arrow change: ' + value)); 

     const arrowKeyIndex$ = arrowKeyIndexChange$ 
      .scan((acc, change) => { 
      // always bound result between 0 and length - 1 
      const index = limitPositive(acc + change, length); 

      return index; 

      }, initialIndex) 
      .do(value => console.log('arrow key index: ' + value)) 
      .startWith(0); 

     return arrowKeyIndex$; 
     }) 
     .do(value => console.log('index: ' + value)) 
     .share(); 
    } 
} 

Идея заключается в том, что каждый раз, когда новый список ссылок (предложений) испускается, текущий индекс в списке должен начать новую «последовательность», так сказать. Каждая из этих последовательностей начинается с 0, прислушивается к приращениям/сокращениям из-за клавиш со стрелкой вниз/вверх, накапливает их, стараясь не выходить за пределы нижнего/верхнего предела.

Чтобы начать новую последовательность, это переводит на switchMap. Но с таким кодом, только консоль показывает:

length: 5 
index: 0 

и не стрелка клавиши вверх/вниз не обнаружены на всех (пытался вставляя другие журналы на arrowDownIndexChange$), так больше журналов и не оказывает влияния на конечного компонента. Это похоже на то, что их наблюдаемые не являются , а подписали, но, насколько я знаю, switchMap должен подписаться на последнюю сгенерированную последовательность и отказаться от подписки на все предыдущие.

Просто, чтобы попробовать, я использовал mergeMap: в этом случае обнаружены клавиши со стрелками, но, конечно, проблема в том, что все последовательности (из-за предыдущих моментов, когда совпадают с установленными) объединяются вместе, а их значения перекрываются друг с другом. Кроме того, в любом случае это неверно, время от времени список совпадений будет пустым, поэтому всегда есть точка, где текущая индексная последовательность всегда остается равной 0. Эта последовательность сливается и перекрывается со всеми остальными, давая чистый результат индекса, застрявшего в 0.

Что я делаю неправильно?

+0

В любом случае вы можете сделать это в plunkr? Это облегчит отслеживание проблемы. – paulpdaniels

+0

компонент вверх [на github] (https://github.com/BrainCrumbz/ng2-autocomplete-words-example/) с примером приложения. [Это] (https://github.com/BrainCrumbz/ng2-autocomplete-words-example/blob/ec42b926f04abea6a722673a7389363196fa36a5/src/client/autocomplete/acw-list-driver.ts#L82) вызов 'switch()' , Я понимаю, что это не то же самое, что плункер, но, возможно, в качестве первого шага это может помочь сыграть на нем? – superjos

ответ

3

Хорошо. По-видимому, мои прежние находки через rxjs docs где-то не так (и, конечно, я получил это после, записывая вопрос, а затем искал больше информации).

Похоже, что switchMap - неправильный инструмент для футляра. Вместо этого я должен использовать switch(). И это не должно удивлять, исходя из фона .Net Reactive Extensions ... но я не знаю, почему изменения имен между языками часто ломают меня неправильно.

Рабочий раствор должен быть вместо этого:

this.currentIndex$ = safeMatches$ 
    // use map to generate an "observable of observables" 
    // (so called higher-order observable) 
    .map(matches => { 
    const length = matches.length; 
    // ... 
    }) 
    // "unwrap" higher order observable, by always 
    // keeping subscribed to the latest inner observable 
    .switch() 
    .do(value => console.log('index: ' + value)) 

EDIT
Это было не достаточно. Мне также пришлось отказаться от activeKeyUp$ в пользу сырья keyUp$.

Если у кого есть другие предложения (или пояснения о текущем поведении, например, activeKeyUp/keyUp), не стесняйтесь отвечать.

EDIT # 2
Благодаря @paulpdaniels комментарий: получается, что единственная реальная проблема в том, что activeKeyUp$ Vs keyUp$ ошибка. Замена map().switch() на switchMap() никак не повредила функциональность.

+1

'switchMap()' является (должно быть) семантически идентичным 'map()' + 'switch()'. Поэтому, если у вас разные поведения между ними, это может быть ошибкой. – paulpdaniels

+0

@paulpdaniels не стесняйтесь публиковать это сообщение как ответ, чтобы я мог его принять. Это реальный ответ будущим читателям – superjos