Aurelia Peters
Aurelia Peters

Reputation: 2209

navigate to a different URL based on path contents

I have an Angular application that browses a virtual filesystem. Paths are accessed by (e.g.) /my/endpoint/path/to/file.ext. I'd like to be able to correctly handle paths containing '..' so that (e.g.) path/to/some/directory/../../file.ext resolves to path/to/file.ext. I can handle resolving the former path to the latter, but I don't know how to navigate there. I've put what I've got so far (various items omitted for clarity) below. Am I on the right track by calling navigate in iif()? What should I be returning there to make sure switchMap() gets the string it expects?

The goal is that, when the URL http://myapp.app/my/endpoint/path/to/some/directory/../../blah is accessed, the component lists the contents of path/to/blah and the browser URL is http://myapp.app/my/endpoint/path/to/blah.

class MyComponent implements OnInit {
  constructor(
    private router: Router,
    private log: LoggingService,
    private backend: BackendService,
    private route: ActivatedRoute,
    private fileSaverService: FileSaverService
  ) {}

  ngOnInit(): void {
    this.log.debug('URL: ', this.url);
    this.path$
      .pipe(
        mergeMap((path) => {
          return iif(
            () => path.split('/').includes('..'),
            (path) => {
              const newPath = resolvePath(path.split('/'));
              // Not sure what to return here so that switchMap() gets a path
              return from(this.router.navigate(newPath));
            },
            of(path)
          );
        }),
        switchMap((path) => this.listFiles(path)),
        catchError((err) => {
          this.log.error('Error listing files', err);
          return throwError(() => err);
        }),
        mergeMap((result) => {
          const found = result.entries.find(
            ({ type, path }) => type === 'file' && path === result.path
          );
          return iif(
            () => !!found,
            this.getFile(found),
            of(result.entries)
          );
        })
      )
      .subscribe((result) => {
        if (result) {
          this.contents = result;
        } else {
          this.log.debug('Got file', { result });
        }
      });
  }
}

const resolvePath = (segments: string[]): string[] => {
  const index = segments.indexOf('..');
  if (index > -1) {
    const left = segments.slice(0, index - 1);
    const right = resolvePath(segments.slice(index + 1));
    return [...left, ...right];
  } else {
    return segments;
  }
}

EDIT: If I'm understanding @Tortilla correctly, here's what it should look like.

    const navigate$ = this.path$.pipe(
      map((path) => path.split('/')),
      filter((segments) => segments.includes('..')),
      map((segments) => {
        const newpath = resolvePath(segments);
        this.log.debug(`NAVIGATE TO ${newpath}`);
        // await this.router.navigate([path])
        return newpath.join('/');
      })
    );
    const noNavigate$ = this.path$.pipe(
      filter((path) => !path.split('/').includes('..'))
    );
    const result$ = merge(
      navigate$, noNavigate$
    )
    result$.pipe(...) // as above

Upvotes: 0

Views: 194

Answers (1)

Tortila
Tortila

Reputation: 568

First of all I think you are not using iif correctly. It is not the same as simple if. Here is a great explanation along with the comment about defer.

And as for your issue itself, Router.navigate doesn't return path. So just go with this:

this.router.navigate(newPath);
return of(newPath);

But again take a closer look at iif. I would suggest to use simple if or ternary operator instead.

Upvotes: 1

Related Questions