*RxJs* 如何将多个 ajax 组合成一个不需要序列的可观察对象

*RxJs* How to combine multiple ajax to one observable that doesn't require sequence

我有两个API在Angular5上获取菜单数据,但我不知道我是否使用嵌套订阅getSecondMenu函数是RxJs的正确方式。有没有人知道,下面是预期结果和示例代码:

console.log: 1. get firstMenuList 2. all done 3. get secondMenu * 3

open on jsFiddle

getFirstMenu$()
  .do(firstMenuList => {
    console.log('get firstMenuList');
  })
  .do(firstMenuList => {
    firstMenuList.forEach(firstMenu => {
      // nested subscribe : bind to child property, let Angular auto-generate second Menu in HTML
      getSecondMenu$(firstMenu.ID).subscribe((secondMenu) => {
        firstMenu.child = secondMenu;
        console.log('get secondMenu');
      });
    })
  })
  .subscribe((menuList) => {
    console.log('all done' );
  });


function getFirstMenu$() {
  return Rx.Observable.of([{
    ID: 'menu1',
    child: null
  }, {
    ID: 'menu2',
    child: null
  }, {
    ID: 'menu3',
    child: null
  }, ]);
}

function getSecondMenu$(menuID) {
  let source = null;
  switch (menuID) {
    case 'menu1':
      source = [{
        ID: 'subMenu1-1',
      }];
      break;
    case 'menu2':
      source = [{
        ID: 'subMenu2-1',
      }];
      break;
    default:
      source = [];
      break;
  }
  const delayTime = ((Math.random() * 20) + 5) * 120;
  return Rx.Observable.of(source).delay(delayTime);
}
<script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.8/dist/global/Rx.umd.js"></script>


结论

谢谢,@Fan Cheung 谈了很多关于如何组合多个 observable 的内容,总结一下你有两种方法可以做到这一点

使用回调

// componnet
class myComponent {
  getMenu() {
    new sharedService().getMenu({
      getFirstMenuList: () => {
        console.log('render first menu to template...')
      }
    })
  }
}

// shared service
class sharedService {
  getMenu(callback) {
    this.getFirstMenu$()
      .mergeMap(firstMenuList => {
        console.log('get first menu');
        //
        if (callback.getFirstMenuList) {
          callback.getFirstMenuList(firstMenuList);
        }

        return Rx.Observable.from(firstMenuList);
      })
      .mergeMap(firstMenu => this.getSecondMenu$(firstMenu.ID), (firstMenu, secondMenu) => {
        //
        console.log('get second menu');
        //
        firstMenu.child = secondMenu
        return firstMenu;
      })
      .reduce((acc, curr) => acc.concat(curr), [])
      .subscribe((menuList) => {
        console.log('all done');
      });
  }

  getFirstMenu$() {
    return Rx.Observable.of([{
      ID: 'menu1',
      child: null
    }, {
      ID: 'menu2',
      child: null
    }, {
      ID: 'menu3',
      child: null
    }, ]);
  }

  getSecondMenu$(menuID) {
    let source = null;
    switch (menuID) {
      case 'menu1':
        source = [{
          ID: 'subMenu1-1',
        }];
        break;
      case 'menu2':
        source = [{
          ID: 'subMenu2-1',
        }];
        break;
      default:
        source = [];
        break;
    }
    const delayTime = ((Math.random() * 20) + 5) * 120;
    return Rx.Observable.of(source).delay(delayTime);
  }
}

// getTemplate
new myComponent().getMenu();
<script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.8/dist/global/Rx.umd.js"></script>

return 其他订阅功能

// componnet
class myComponent {
  getMenu() {
    new sharedService().getMenu().subscribe(() => {
        console.log('render first menu to template...');      
    })
  }
}

// shared service
class sharedService {
  getMenu() {
    return this.getFirstMenu$()
      .mergeMap(firstMenuList => {
        console.log('get first menu');
        return Rx.Observable.from(firstMenuList);
      })
      .mergeMap(firstMenu => this.getSecondMenu$(firstMenu.ID), (firstMenu, secondMenu) => {
        //
        console.log('get second menu');
        //
        firstMenu.child = secondMenu
        return firstMenu;
      })
      .reduce((acc, curr) => acc.concat(curr), [])
      .do((menuList) => {
        console.log('all done');
      });
  }

  getFirstMenu$() {
    return Rx.Observable.of([{
      ID: 'menu1',
      child: null
    }, {
      ID: 'menu2',
      child: null
    }, {
      ID: 'menu3',
      child: null
    }, ]);
  }

  getSecondMenu$(menuID) {
    let source = null;
    switch (menuID) {
      case 'menu1':
        source = [{
          ID: 'subMenu1-1',
        }];
        break;
      case 'menu2':
        source = [{
          ID: 'subMenu2-1',
        }];
        break;
      default:
        source = [];
        break;
    }
    const delayTime = ((Math.random() * 20) + 5) * 120;
    return Rx.Observable.of(source).delay(delayTime);
  }
}

// getTemplate
new myComponent().getMenu();
<script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.8/dist/global/Rx.umd.js"></script>

试试这个

getFirstMenu$()
.map(firstMenuList => Observable.from(firstMenuList))
.flatMap(firstMenu=> getSecondMenu$(firstMenu.ID))
.do(secondMenu => firstMenu.child = secondMenu)
.subcribe()

编辑1

getFirstMenu$()
.mergeMap(firstMenuList => Rx.Observable.from(firstMenuList))
.mergeMap(firstMenu=> getSecondMenu$(firstMenu.ID)
,(firstMenu, secondMenu) => {
 firstMenu.child=secondMenu
 return firstMenu
 })
.reduce((acc,curr)=>  acc.concat(curr) ,[])
.subscribe((menuList) => {
 console.log(menuList)
 });