递归可观察调用中的Angular 4加载树结构

时间:2017-11-30 21:27:15

标签: angular async-await rxjs observable

您好我是Observables的新手,我正在寻找一种通过递归可观察调用加载导航树的方法。 应根据目录和子目录中的所有index.json文件动态构建导航。

  

只有第一个电话的网址是静态的:/public/index.json

这是目录结构。每个目录可能包含index.json, 提供有关其内容的信息,并可通过loadChildrenFromUrl属性引用其他索引文件。

|-public
   |- subdir1
       |- index.json
       |- test.html
   |- subdir2  
       |- index.json
       |- test.html
       |- subdir2.1  
           |- index.json
           |- . . .
   |- index.json

导航文件index.json

[
  // static entry with static children
  {
    "state": "module1",
    "name": "Modul 1",
    "type": "sub",
    "icon": "dashboard",
    "children": [
       {"state": "", "name": "Index", "icon": "https" },
       {"state": "test1", "name": "Test1", "icon": "live_help"}
    ]
 },
 {
   // dynamic entry children needs to be load from url
   "state": "test",
   "name": "Test loaded from url",
   "type": "sub",
   "icon": "info_outline",
   "loadChildrenFromUrl": "subdir2/index.json"
   "children": [] // should be loaded via url
  },
  . . .
]

结果应该是描述整个导航树的一个大对象。 所以孩子可能包含可能包含孩子的孩子...... Router-Guard(CanActivate returning Observable)会小心等待,直到加载树完成。

我的代码正在运行,但函数在加载整个树之前返回。 我知道整个事情都是异步的所以这是设计但我不知道如何正确解决它。看起来我使用flatMap?

NavigationService.ts

loadNavigation(): Observable<Menu[]> {
    if (this.navigationLoaded) {
      return Observable.of(this.navigationTree);
    } else {
      this.navigationTree = new Array();
      return this.loadNavigationByUrl('public', this.navigationTree);

    }
}

loadNavigationByUrl(url: string, navArray: Menu[]): Observable<Menu[]> {

    console.log(`Loading ${url}/index.json`);

    const result = this.http.get<Menu[]>(`${url}/index.json`, { responseType: 'json' });
    result.catch((err) => this.handleError(err));
    result.subscribe(data => {

      // console.log(data);
      if (data) {

        data.forEach((item: Menu, index: number, array: Menu[]) => {

          // add to navigationTree
          navArray.push(item);

          if (item.loadChildrenFromUrl && item.loadChildrenFromUrl !== '') {
            item.children = new Array();
            this.loadNavigationByUrl(`${url}/${item.loadChildrenFromUrl}`, item.children);
          }

          // console.log(this.navigationTree);
        });

        // this.navigationTree = data;
        console.log('navigation loaded');
        this.navigationLoaded = true;
      }
    },
    err => {

    },
    () => {
      console.log(`Loading ${url}/index.json completed`);
    }
    );

    return result;

}

那么如何构建一个可观察的链?&#34;这样做?

新信息2017-12-01

最后,我需要在Route Guard中使用此功能,以便在路由激活之前加载导航结构。

NavigationGuard.ts

@Injectable()
export class NavigationGuard implements CanActivate, CanActivateChild  {

  constructor(private svc: NavigationService, private router: Router) { }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    // console.log('canActivate');
    return this.svc.loadNavigation()
      .mapTo(true) // I'm not interested in the result
      .catch((error: any) => {
        console.log(error);
        return Observable.of(false);
      });

  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):  Observable<boolean> | Promise<boolean> | boolean {
    return this.canActivate(route, state);
  }
}

2 个答案:

答案 0 :(得分:2)

暂且不说“为什么?”因为我对递归的可观察结构感兴趣...你不能通过嵌套订阅的observable递归。您需要使用更高阶的可观察量,而您根本不应该订阅。关键是调用者需要订阅,否则它永远不会工作。

loadNavigation(): Observable<Menu[]> {
    if (this.navigationLoaded) {
      return Observable.of(this.navigationTree);
    } else {
      let navigationTree = new Array();
      return this.loadNavigationByUrl('public', this.navigationTree)
                 .do(data => {
                   // console.log(data);
                   if (data) {

                     this.navigationTree = data;
                     console.log('navigation loaded');
                     this.navigationLoaded = true;
                   }
                  }); // could subscribe here instead if you really want.
    }
}

loadNavigationByUrl(url: string, navArray: Menu[]): Observable<Menu[]> {

  console.log(`Loading ${url}/index.json`);

  return this.http.get<Menu[]>(`${url}/index.json`, { responseType: 'json' })
    .catch((err) => this.handleError(err))
    .switchMap(data => {
      if (!data) 
        return Observable.of(null);

      let children$ = [];
      data.forEach((item: Menu, index: number, array: Menu[]) => {
        // add to navigationTree
        navArray.push(item);

        if (item.loadChildrenFromUrl) { // FYI empty string is "false" in JS
          item.children = new Array();
          children$.push(this.loadNavigationByUrl(`${url}/${item.loadChildrenFromUrl}`, item.children));
        }
      });
      return (children$.length) ? Observable.forkJoin(children$) : Observable.of([]);
    });
}

答案 1 :(得分:0)

我现在明白为什么@ bryan60正在以...开始他的答案。

  

撇开&#34;为什么?&#34; 。 。

Observables递归非常复杂。我对可观察的东西如此固定,以至于我错过了一个非常好的Typescript特征。 。 。的异步/ AWAIT

代码不是那么优雅,但更容易阅读。以防有人像我一样愚蠢。

  loadNavigation(): Promise<boolean>  {

    if (this.navigationLoaded) {

      return new Promise<boolean>(resolve => {
          resolve(true);
      });

    } else {

      const navigationTreeCache = new Array();

      return new Promise<boolean>(resolve => {
        this.loadNavigationPromise('public', navigationTreeCache).then((value: boolean) => {
          this.navigationTree = navigationTreeCache;
          this.navigationLoaded = true;
          console.log('navigation loaded');
          resolve(true);
        });
      });

    }
  }

  async loadNavigationPromise(url: string, navArray: Menu[]): Promise<boolean> {

    console.log(`Loading ${url}/index.json`);

    try {

      // debug wait a second on each function call
      // await new Promise<number>(resolve => { setTimeout(() => { resolve(); }, 1000); });

      const data = await this.http.get<Menu[]>(`${url}/index.json`, { responseType: 'json' }).first().toPromise();

      if (data) {
        for (let i = 0; i < data.length ; i++) {
          const item = data[i];
          navArray.push(item);

          if (item.loadChildrenFromUrl) {
            item.children = new Array();
            const loadSucessfully = await this.loadNavigationPromise(`${url}/${item.loadChildrenFromUrl}`, item.children);
            if (!loadSucessfully) {
              return false;
            }
          }
        }
      } else {
        console.error(`got no data from url ${url}`);
      }

      return true;

    } catch (error) {
        console.error(error);
        return false;
    }
  }