Программное использование AsyncPipe — Angular 2

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

Пока все хорошо, единственная проблема заключается в том, что один из этих каналов является асинхронным каналом...

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

Я предполагаю, что причина, по которой он вызывается несколько раз в шаблоне, заключается в том, что это нечистый канал, но как я могу справиться с этим программно?

Вот plunker, демонстрирующий то, что я только что сказал:

@Injectable()
export class AsyncProvider {
  constructor() {}
  
  getById(id: number) {
    // Just some mock data
    return Observable.of({id, times_five: id*5}).delay(1000);
  }
}

@Component({
  selector: 'my-app',
  providers: [AsyncPipe, AsyncProvider]
  template: `
    <div>
      <p>Template async pipe</p>
      <p>{{ asyncObj | async | json }}</p>
      <hr>
      <p>Programmatically async pipe</p>
      <p>{{ asyncObjPiped | json }}</p>
    </div>
  `,
  directives: []
})
export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe
  ) {
    this.asyncObj = this._provider.getById(123);
    this.asyncObjPiped = this._async.transform(this.asyncObj);
  }
}

EDIT: Потому что AsyncPipe выполняет markForCheck() для ChangeDetectorRef при получении новых значений, я также пробовал следующее:

export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe,
    private _ref: ChangeDetectorRef,
  ) {
    this.asyncObj = this._provider.getById(123);
    this.asyncObjPiped = this._async.transform(this.asyncObj);
    
    setInterval(() => {
      this._ref.detectChanges();
    }, 1000);
  }
}

Без особого успеха :(


person Ignasi    schedule 25.07.2017    source источник
comment
Что вы имеете в виду под обработать это программно, что вы хотите сделать?   -  person AJT82    schedule 25.07.2017
comment
@ AJT_82 Я ожидаю, что он будет вести себя так же, как если бы я использовал канал в шаблоне (подробности см. В plunker), потому что работает asyncPipe (код src доступен здесь: github.com/angular/angular/blob/master/packages/common/src/ ) канал должен вызываться более одного раза для получения правильного результата. Кажется, это причина, по которой asyncpipe не является чистым, но я не могу понять, как программно эмулировать это нечистое поведение на контроллере, и я понятия не имею, где он находится в угловом коде.   -  person Ignasi    schedule 25.07.2017


Ответы (1)


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

Первый способ: plunker

export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe,
  ) {
    
    this.asyncObj = this._provider.getById(123)
    
    let processPipe = () => {
      this.asyncObjPiped = this._async.transform(new_asyncObj);
    }
    let new_asyncObj = this.asyncObj.finally(processPipe).repeat(2);
    processPipe();
  }
}

Обратите внимание, что необходима новая переменная (new_asyncObj), поскольку, похоже, что finally возвращает новый объект, а не изменяет существующий. И повторите (2) после, наконец, чтобы развернуть обещание.


Второй метод: plunker

export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe,
  ) {
    
    this.asyncObj = this._provider.getById(123)
    
    setInterval(() => {
      this.asyncObjPiped = this._async.transform(this.asyncObj);
    }, 500);
    
  }
}

Пересчитывать пайп каждые 500 мс, просто и эффективно, хотя я ожидал чего-то лучшего.


Окончательный метод: plunker

export class App {
  constructor(
    private _provider: AsyncProvider,
    private _async: AsyncPipe,
    private _ref: ChangeDetectorRef,
    private zone: NgZone,
  ) {
    
    this.asyncObj = this._provider.getById(123)
      
    this.zone.onMicrotaskEmpty
      .subscribe(() => {
        this.asyncObjPiped = this._async.transform(this.asyncObj);
        this.asyncObjPiped = this._async.transform(this.asyncObj);
        this._ref.detectChanges();
      });
  }
}

Использование NgZone и ChangeDetectorRef, кажется, работает также хорошо, даже несмотря на то, что какой-то уродливый взлом, например, вызов AsyncPipe дважды, чтобы развернуть значение.


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

Любое предложение или лучший ответ по-прежнему приветствуются!

person Ignasi    schedule 26.07.2017
comment
Мне нравится идея использования асинхронного канала, потому что он запускает обнаружение изменений OnPush (там, где ручная подписка не работает). И часто вам нужна наблюдаемая переменная в методе. Дайте вам пятерку за то, чтобы заставить это работать вообще. При этом ... было бы неплохо, если бы был более чистый и лучше документированный способ сделать это. - person Paul Evans; 21.02.2018