Подтвердить что ты не робот

Angular 2 - Интерполяция и привязка с асинхронным HTTP-запросом

Я новичок в Angular 2, и мне грозит проблема с асинхронным HTTP-запросом и привязкой к интерполяции.

Здесь мой компонент:

@Component({
  selector: 'info',
  template: `<h1>{{model.Name}}</h1>`
})
export class InfoComponent implements OnInit {

    model: any;

    constructor(
        private _service: BackendService
    ) { }

    ngOnInit() {
         if (this.model == null) {
            this._service.observableModel$.subscribe(m => this.model = m);
            this._service.get();
        }     
    }
}

Когда шаблон визуализируется, я получаю сообщение об ошибке, потому что "модель" еще не установлена.

Я решил проблему с этим очень уродливым взломом:

@Component({
    selector: 'info',
    template: `
  <template ngFor #model="$implicit" [ngForOf]="models | async">
  <h1>{{model.Name}}</h1>
  </template>
  `
})
export class NeadInfoComponent implements OnInit {

    models: Observable<any>;

    constructor(
        private _service: BackendService
    ) { }

    ngOnInit() {
         if (this.models == null) {
            this._service.observableModel$.subscribe(m => this.models = Observable.of([m]));
            this._service.get();
        }     
    }
}

Мой вопрос: как отложить отрисовку шаблона до завершения моего HTTP-вызова или как интерполировать значения "модели" непосредственно в шаблоне без привязки к другому компоненту?

Спасибо!

4b9b3361

Ответ 1

Если вы возвращаете объект со своего сервера, вы можете использовать безопасную навигацию (?). в вашем шаблоне:

@Component({
  selector: 'info',
  template: `<h1>{{model?.Name}}</h1>`
})
export class InfoComponent implements OnInit {
    model: any;
    constructor(private _service: BackendService) { }

    ngOnInit() {
       this._service.getData().subscribe(m => this.model = m);
       // getData() looks like the following:
       //    return this._http.get('....')  // gets JSON document
       //       .map(data => data.json()); 
    }
}

См. этот ответ для рабочего плунжера.

Ответ 2

В этом обсуждении перечислены несколько стратегий https://github.com/angular/angular/issues/6674#issuecomment-174699245

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

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

В. Затем Bootstrap сканирует все наблюдаемые объекты перед тем, как передать объект компоненту верхнего уровня.

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

Д. Если вы используете | async, то его следует использовать только на верхнем уровне, если вам не нравится использовать .subscribe, но вы действительно должны просто использовать .subscribe.

Е. Если вы используете | async везде, то то, что вы действительно делаете, - это инвертирование контроля над рендерингом на наблюдаемые, что означает, что мы вернулись в Angular1 дней каскадных изменений, так что вам либо нужно делать C, D, B или A

ChangeDetectionStrategy, похоже, не работает в данный момент. Вы бы просто установили компонент как отдельный.

Мы также можем использовать привязку жизненного цикла ngOnInit для удаления компонента из дерева обнаружения изменений. Вам нужно будет запустить this.ref.detach(); где ref вводится через ChangeDetectorRef

  ngOnInit() {
    this.ref.detach();
  }
  makeYourChanges() {
    this.ref.reattach(); // attach back to change detector tree

    this.data.value = Math.random() + ''; // make changes

    this.ref.detectChanges(); // check as dirty

    this.ref.detach(); // remove from tree
    // zone.js triggers changes
  }

ChangeDetectorRef

Вы также не можете включать zone.js и вручную управлять всеми изменениями. Вы также можете ввести NgZone для запуска операции за пределами zone.js, чтобы он не сообщил angular о запуске шайб. Например,

// this example might need a refactor to work with rxjs 5
export class Timeflies {
  pos   = 'absolute';
  color = 'red';
  letters: LetterConfig[];
  constructor(
    private service: Message,
    private el: ElementRef,
    private zone: NgZone) {

  }
  ngOnInit() {
    // initial mapping (before mouse moves)
    this.letters = this.service.message.map(
      (val, idx) => ({
        text: val,
        top: 100,
        left: (idx * 20 + 50),
        index: idx
      })
    );
    this.zone.runOutsideAngular(() => {
      Observable
        .fromEvent(this.el.nativeElement, 'mousemove')
        .map((e: MouseEvent) => {
          //var offset = getOffset(this.el);

          // subtract offset of the element
          var o = this.el.nativeElement.getBoundingClientRect();

          return {
            offsetX: e.clientX - o.left,
            offsetY: e.clientY - o.top
          };
        })
        .flatMap(delta => {
          return Observable
            .fromArray(this.letters
              .map((val, index) => ({
                letter: val.text,
                delta,
                index
              })));
        })
        .flatMap(letterConfig => {
          return Observable
            .timer( (letterConfig.index + 1) * 100)
            .map(() => ({
              text:  letterConfig.letter,
              top:   letterConfig.delta.offsetY,
              left:  letterConfig.delta.offsetX + letterConfig.index * 20 + 20,
              index: letterConfig.index
            }));
        })
        .subscribe(letterConfig => {
          // to render the letters, put them back into app zone
          this.zone.run(() => this.letters[letterConfig.index] = letterConfig);
        });

    });//zone
  }
}