首页 文章

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

提问于
浏览
2

嗨我'm pretty new to Observables and I'm寻找一种用递归可观察调用加载我的导航树的方法 . 应基于目录和子目录中的所有 index.json 文件动态构建导航 .

只有第一个调用的url是静态的:/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;

}

那么如何构建一个可观察的“链”呢?去做这个?

new info 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

    暂且不说“为什么?”因为我对递归的可观察结构感兴趣...你不能通过嵌套订阅的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([]);
        });
    }
    
  • 2

    我现在明白了为什么@ bryan60开始回答......

    暂且不说“为什么?” . . .

    Observables递归非常复杂 . 我对可观察的东西如此固定,以至于我错过了一个非常好的Typescript特征 . . . async/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;
        }
      }
    

相关问题