等待订阅在Angular中完成

时间:2019-08-17 12:23:37

标签: angular

我正在使用服务在多个组件之间进行数据共享。 在这里,在服务中,我有一个吸气剂,正在初始化基础数据,并有一个主题,通知其他组件任何更改。

@Injectable({
  providedIn: "root"
})
export class ExpenseHandlerService {
  expenseDataChanged = new Subject<ExpenseDetails>();

  private expenseData: Array<ExpenseDetails> = [];
  constructor(private http: HttpClient) {}
  /**
   * Fetched expense data and stored in the this.expenseData
   */
  getExpenseData() {
    console.log("Starting...");
    this.http
      .get("assets/model/data.json")
      .pipe(map(result => result))
      .subscribe(data => {
        this.expenseData = data as Array<ExpenseDetails>;
        console.log(this.expenseData);
      });
    return this.expenseData.slice();
  }

  /**
   * Notifies other components of any change in the data
   *
   * @param data
   */
  addExpenseData(data: ExpenseDetails) {
    //console.log(this.expenseData.length);
    //this.expenseData.push(data);
    this.expenseDataChanged.next(data);
    //console.log(this.expenseData);
  }
}

我已经声明了一个使用getExpenseData方法初始化现有数据的组件。

该组件的init方法如下:

ngOnInit() {
  this.expenseData = this.expenseHandlerService.getExpenseData();
  this.expenseHandlerService.expenseDataChanged.subscribe(res => {
    this.receiveExpenseData(res);
  });
}

但是在执行该应用程序时,我只会得到我定义的空数组,并且在执行ngOnInit之后,服务中的订阅者将完成并记录实际数据。

由于我试图将服务作为几个组件之间的中央通信链接,因此我希望在服务本身中具有api调用。

1 个答案:

答案 0 :(得分:1)

有多种方法可以解决此问题(即解析器/防护),但我将根据您当前的代码提出解决方案。您应该在ExpenseHandlerService.getExpenseData的构造函数中调用ExpenseHandlerService。另外,将您的expenseDataChanged类型更改为BehaviorSubject。话虽如此,让我们像这样更改ExpenseHandlerService [请参见代码注释以获取说明]:

@Injectable(
  {
    providedIn: 'root'
  }
)
export class ExpenseHandlerService {

  //I think BehaviorSubject type should be Array<ExpenseDetails> instead of ExpenseDetails
  //As your question was not clear so I am assuming as Aray<ExpenseDetails>; If you want type as ExpenseDetails
  //then you will have to adjust the code accordinlgy
  //LETS KEEP THIS VARIABLE AS PRIVATE SO THAT NOONE FROM OUTSIDE CAN EMIT NEW VALUE
  //AND EXPOSE AN OBSERVABLE as public property [see below]
  private expenseDataChanged = new BehaviorSubject<Array<ExpenseDetails>>([]);

  //This should be subscribed by the consumers i.e. Components
  get expenseDataObs() {
     return this.expenseDataChanged.asObservable();
  }

  //NO NEED TO MAINTAIN THIS ARRAY AS BehaviorSubject.value will give us the last emitted array
  //private expenseData: Array<ExpenseDetails> = [];
  constructor( private http: HttpClient) {

    this.getExpenseData();

  }
/**
 * Fetched expense data and stored in the this.expenseData
 */
  getExpenseData() {
    console.log('Starting...');
    this.http.get("assets/model/data.json")
      .pipe(map(result => result)).subscribe(data => {

      //I AM NOT SURE IF YOU API CAN RETURN NULL OR NOT
      //STILL IF API RETURNS NULL THEN EMIT EMPTY ARRAY
      //ADJUST THIS CODE AS PER YOUR APP LOGIC
      if(!data) {
       data = [];
      }
      const arr = data as Array<ExpenseDetails>;

      //I AM NOT USING SLICE (with no argument will return same array) AS IT WAS NOT DOING ANYTHING;
      //IF YOU WANT TO USE THEN USE AS PER YOUR LOGIC
      this.expenseDataChanged.next(arr);
    });

  }

/**
 * Notifies other components of any change in the data
 *
 * @param data
 */
  addExpenseData(data: ExpenseDetails) {

    //Get the last emited array from behaviorsubject
    const lastEmittedArr = this.expenseDataChanged.value;

    //push the data in to newArray
    //NOTICE I AM CREATING NEW ARRAY - This is to make sure that array is rendered; again it is NOT mandatory
    //but it depends on kind of changedetection stretegy is used.
    const newArrayToEmit = [...lastEmittedArr, data];
    this.expenseDataChanged.next(newArrayToEmit);
  }
}

现在在您的组件中,像这样订阅expenseDataObs

ngOnInit() {
    this.expenseHandlerService.expenseDataObs
        .pipe(

          //As soon as you subscribe the initial value could be an null because the Behavior subject was initialized with null
          //THIS IS BECAUSE OF THE NATURE OF BehaviorSubject; It may happen because by the time you subscribe
          //response of HTTP API might not have recieved; but as soon as it is received
          //Using the filter will ensure to avoid null in callback
          filter(arr => !!arr)
        )
        .subscribe((res) => {

      //Process the array recieved;
      this.receiveExpenseData(res);
    });
  }

注意:在以上方法中,可能存在这样一种可能性,即在初始化时间组件之前,将不会呈现任何数据,直到HTTP响应返回为止。因此用户可能会看到空白页。为避免出现这种情况,您可以显示一个加载程序,也可以使用resolver / guard加载数据。

[我个人使用某种存储区,即NGRX / NGXS / AKITA进行状态管理,而不是管理服务中的状态。

希望有帮助。