【问题标题】:Template not binding to observable while using async pipe on initial load在初始加载时使用异步管道时模板未绑定到可观察对象
【发布时间】:2017-02-03 08:04:54
【问题描述】:

我正在使用contentful js SDK 来获取服务 中的数据。 SDK 提供了一种从 Promise 中检索条目并解析这些条目的方法。由于我想使用可观察对象,因此我将承诺作为可观察对象返回,然后从那里进行转换。

在我的 home 组件中,然后我调用 contentfulService OnInit 并使用 async 管道在模板中解开 observable。 p>

我的问题:
当 home 组件加载时,即使服务已成功获取数据,模板也不存在。现在,如果我与页面上的 DOM 交互(单击、悬停),模板将立即出现。为什么这不仅仅是在页面加载时异步加载?我该如何解决这个问题?

An example .gif showing the behavior.

contentful.service.ts

import { Injectable } from '@angular/core';    
import { Observable, Subject } from 'rxjs/Rx';    
import { Service } from '../models/service.model';
import * as contentful from 'contentful';


@Injectable()
export class ContentfulService {

  client: any;

  services: Service[];
  service: Service;    

  constructor() {    
    this.client = contentful.createClient({
      space: SPACE_ID,
      accessToken: API_KEY
    });
  }   


  loadServiceEntries(): Observable<Service[]> {

    let contentType = 'service';
    let selectParams = 'fields';

    return this.getEntriesByContentType(contentType, selectParams)
      .take(1)          
      .map(entries => {
        this.services = [];

        let parsedEntries = this.parseEntries(entries);

        parsedEntries.items.forEach(entry => {
          this.service = entry.fields;
          this.services.push(this.service);
        });

        this.sortAlpha(this.services, 'serviceTitle');
        return this.services;
      })          
      .publishReplay(1)
      .refCount();

  }


  parseEntries(data) {
    return this.client.parseEntries(data);
  }


  getEntriesByContentType(contentType, selectParam) {

    const subject = new Subject();

    this.client.getEntries({
      'content_type': contentType,
      'select': selectParam
    })
      .then(
      data => {
        subject.next(data);
        subject.complete();
      },
      err => {
        subject.error(err);
        subject.complete();
      }
      );

    return subject.asObservable();
  }


  sortAlpha(objArray: Array<any>, property: string) {
    objArray.sort(function (a, b) {
      let textA = a[property].toUpperCase();
      let textB = b[property].toUpperCase();

      return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
    });
  }


}

home.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { ContentfulService } from '../shared/services/contentful.service';
import { Service } from '../shared/models/service.model';    

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  service: Service;      
  services: Service[];
  services$: Observable<Service[]>;

  constructor(
    private contentfulService: ContentfulService,
  ) {

  }    

  ngOnInit() {       

    this.services$ = this.contentfulService.loadServiceEntries();

    this.services$.subscribe(
      () => console.log('services loaded'),
      console.error
    );    

  }; 


}

home.component.html

...
<section class="bg-faded">
  <div class="container">
    <div class="row">
      <div class="card-deck">
        <div class="col-md-4 mb-4" *ngFor="let service of services$ | async">
          <div class="card card-inverse text-center">
            <img class="card-img-top img-fluid" [src]="service?.serviceImage?.fields?.file?.url | safeUrl">
            <div class="card-block">
              <h4 class="card-title">{{service?.serviceTitle}}</h4>
              <ul class="list-group list-group-flush">
                <li class="list-group-item bg-brand-black"><i class="fa fa-wrench mr-2" aria-hidden="true"></i>Cras justo odio</li>
                <li class="list-group-item bg-brand-black"><i class="fa fa-wrench mr-2" aria-hidden="true"></i>Dapibus ac facilisis in</li>
                <li class="list-group-item bg-brand-black"><i class="fa fa-wrench mr-2" aria-hidden="true"></i>Vestibulum at eros</li>
              </ul>
            </div>
            <div class="card-footer">
              <a href="#" class="btn btn-brand-red">Learn More</a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>
...

【问题讨论】:

    标签: javascript angular asynchronous rxjs contentful


    【解决方案1】:

    听起来 Contentful 承诺正在 Angular 的区域之外解决。

    您可以通过将NgZone 注入您的服务来确保可观察对象的方法在区域内运行:

    import { NgZone } from '@angular/core';
    
    constructor(private zone: NgZone) {
      this.client = contentful.createClient({
        space: SPACE_ID,
        accessToken: API_KEY
      });
    }
    

    并且在调用主体的方法时使用注入区域的run 调用:

    getEntriesByContentType(contentType, selectParam) {
    
      const subject = new Subject();
    
      this.client.getEntries({
        'content_type': contentType,
        'select': selectParam
      })
      .then(
        data => {
          this.zone.run(() => {
            subject.next(data);
            subject.complete();
          });
        },
        err => {
          this.zone.run(() => {
            subject.error(err);
            subject.complete();
          });
        }
      );
    
      return subject.asObservable();
    }
    

    【讨论】:

    • 谢谢@cartant,这绝对是问题所在。我认为它最终会变得很简单......我不知道NgZone 的用例。
    猜你喜欢
    • 2018-11-19
    • 2019-06-27
    • 2020-08-29
    • 2018-07-24
    • 2017-01-10
    • 2011-07-16
    • 2019-03-15
    • 2017-04-16
    • 2020-07-20
    相关资源
    最近更新 更多