0

I'am trying to find a good structure for an Angular 2 application which has something like interdependent components.

Here's the basic idea of the problem: There should be side navigation menu component and a content area which should contain the router-outlet. For some routes the side component should contain an additional component for navigation (e.g. a list of items for quick access where in the main content area is the "detail view").

Top level template structure:

<sidenav>
  <featureA-nav-fragment></featureA-nav-fragment>
</sidenav>
<router-outlet>
  <!-- featureA component -->
</router-outlet>

The menu should be a component which is indepentend from any other component or feature area and should not know about the additional navigation component but it should wrap this component.

One way to implement would be to define just the as top level component and in every screen which is defined by a route provide the side menu in the HTML-template. It seems to me that this is bad design cause parts of the HTML-code are duplicated.

An other alternative would be to create a switch on the active route in the menu component and include the correct navigation fragment there but this spreads the logic of the route (screen) into several components which seems to be an even worse design to me.

Using a named router-outlet for the navigation fragment isn't the right way to go neither since the components are not independently displayed from each other but always together. Modifying the route manually (clearing the nav fragment part) would break the design.

I didn't managed to find a good design for such a use case in the angular documentation yet. Am I missing something here?

1 Answers1

0

I finally found a working solution by myself.

If you ever run into problems where some component should inject parts of its template content into another component you might want to use structural directives with a custom router service.

The general idea of this pattern is to use a spectial structural directive in the template of the main view component (featureA component from above).

<div *embeddedIntoNav>some navigation related content</div>   
<div>
  Feature A view
<div>

The directive is implemented by forwarding the template reference to a custom router service.

@Directive({
  selector: '[embeddedIntoNav]'
})
export class EmbeddedIntoNavDirective {

  constructor(
    private customRouter: CustomRouter,
    private templateRef: TemplateRef<any>
  ) { }

  ngOnInit() {
    this.customRouter.navContent$.next(this.templateRef);
  }

  ngOnDestroy() {
    this.customRouter.navContent$.next(null);
  }
}

The part which is taged by with this sturctural directive is not appended to the children of feature A but only forwared to the router. The router implementation is merly a set of BehaviorObservables for each embedded view outlet.

Another directive is used to define the outlet for the embedded views:

@Directive({
  selector: 'nav-container'
})
export class NavContainerDirective implements OnInit {

  constructor(
    private viewContainer: ViewContainerRef,
    private customRouter: CustomRouter
  ) {
  }

  ngOnInit() {
    this.customRouter.navContent$.subscribe((template) => {
      this.viewContainer.clear();
      if (template) {
        this.viewContainer.createEmbeddedView(template);
      }
    });
  }
}

Using this pattern you can build router which inject into several views with only touching one single file and keep the logic all in one place.