Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Router named outlets that are activated once

Is it possible to have router named outlets that are activated once and then never destroyed, no matter what route is navigated in primary outlet?

The intention is to have components that persist on page (for instance, sidebar) but get the benefits of routing on their initial load - such as guards (resolvers) and lazy loading.

The requirement is that named outlets shouldn't affect UX in any negative way, for example by introducing garbage suffixes to SPA URL, e.g. (outletName:routeName), they also shouldn't be accidentally deactivated. If there's a way to detach them from router after initial activation, it would be appropriate.

skipLocationChange option cannot be used for this purpose. In this example /login(popup:compose) URL appears when Contact and Login routes are sequentially navigated.

like image 724
Estus Flask Avatar asked Feb 16 '18 23:02

Estus Flask


2 Answers

skipLocationChange navigation option works only for a router it was supplied for, then named outlet appears in the url, like /login(foo:bar).

It's possible to have permanent foo router outlet by overriding UrlSerializer, as was suggested by @kemsky:

import {
  UrlSerializer, DefaultUrlSerializer, UrlSegmentGroup, UrlTree
} from '@angular/router';

export class FooUrlSerializer extends DefaultUrlSerializer {
  serialize(tree) {
    const { foo, ...noFooChildren } = tree.root.children;
    const root = new UrlSegmentGroup(tree.root.segments, noFooChildren);
    const noFooTree = Object.assign(new UrlTree(), tree, { root });

    return super.serialize(noFooTree);
  }
}

...
providers: [{ provide: UrlSerializer, useClass: FooUrlSerializer }, ...]
...
like image 74
Estus Flask Avatar answered Sep 18 '22 00:09

Estus Flask


We encountered the same (crucial) UX requirement in our project and came up with a semi-clean but so far fully functional solution.

Implement a custom LocationStrategy, we simply extend the default PathLocationStrategy class and preprocess the URL (that will be presented to the user / browser):

@Injectable()
export class OnlyPrimaryLocationStrategy extends PathLocationStrategy implements LocationStrategy {
  static readonly AUX_ROUTE_SEPERATOR = '//';

  replaceState(state: any, title: string, url: string, queryParams: string): void {
    super.replaceState(state, title, this.preprocessUrl(url), queryParams);
  }

  pushState(state: any, title: string, url: string, queryParams: string): void {
    super.pushState(state, title, this.preprocessUrl(url), queryParams);
  }

  preprocessUrl(url: string): string {
    if (url.includes(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR)) {
      if (url.split(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR).length > 2) {
        throw new Error(
          'Usage for more than one auxiliary route on the same level detected - please recheck imlementation'
        );
      }
      return url.split(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR)[0].replace('(', '');
    } else {
      return url;
    }
  }
}

Do not forget to provide it in your module:

providers: [
    {
     // ...
      provide: LocationStrategy,
      useClass: OnlyPrimaryLocationStrategy,
    },
  ],

String processing obviously is not a 100% clean, but it gets the job done for us - maybe it helps you. Be aware that your URL is now not fully capable of reconstructing your router state (obviously).

like image 26
mitschmidt Avatar answered Sep 19 '22 00:09

mitschmidt