Angular 2 - 使用异步 http 请求进行插值和绑定

Angular 2 - Interpolation and binding with async http request

我是 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();
        }     
    }
}

呈现模板时出现错误,因为尚未设置 "model"。

我用这个非常丑陋的 hack 解决了这个问题:

@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 调用完成,或者如何在不绑定到另一个组件的情况下直接在模板中插入 "model" 值?

谢谢!

这个讨论列出了一些策略https://github.com/angular/angular/issues/6674#issuecomment-174699245

The question has many implications. In some cases, the observables should be managed outside of the framework.

A. You can scan all observables before you bootstrap

B. Bootstrap then scan all observables before passing the object to the top level component.

C. You can also change the changeDetection within the component to either trigger when inputs changes or manually trigger the change detector them.

D. If you're using |async then it should only be used at the top level if you don't feel like using .subscribe but you really should just use .subscribe.

E. If you're using |async everywhere then what you're really doing is inverting control over rendering to the observables which means we're back in the Angular1 days of cascading changes so you either need to do C, D, B, or A

ChangeDetectionStrategy doesn't seem to be working at the moment. You would simple set the component as Detached.

We can also use ngOnInit lifecycle hook to remove the component from the change detection tree. You would need to run this.ref.detach(); where ref is injected via 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

You can also not include zone.js and manually control all changes. You can also inject NgZone to run an operation outside of zone.js so it won't tell angular to trigger chanes. For example,

// 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
  }
}

如果要从服务器返回对象,可以在模板中使用 safe navigation (previously "Elvis") operator (?.):

@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()); 
    }
}

请参阅 了解工作中的 plunker。