(Routable Component) Data tree with adjacent detail pane

253 Views Asked by At

We're building an Angular 2 application with a navigable tree (on the left), and an adjacent detail pane (on the right).

I want to render about 20-30 different components in the same detail area, depending on what node on the tree is selected.

To send many components to the same RouterOutlet it seems I have to load all those components in the same parent component where the outlet is defined; I'd prefer not to have a monolithic top-level component like that.

In case that isn't clear, the rest of this describes in more detail my scenario, and then restates the same question at the end.

The tree can only have one node selected at a time. When a node is selected, all other non-parent nodes are closed, and the list of children for the selected node are loaded over the network and display beneath the selected node. The detail information related to the selected node is also loaded over the network, and rendered in the shared right pane area, replacing any other information that was previously displayed there.

Different nodes represent different application features and contain different entity types. Therefore, various nodes of the tree require both:

  • different child templates/components to expand the child tree (some tree nodes enumerate contact names, others contain appointments, etc.)
  • different templates/components loaded in the detail pane

Here is an example:

  • appointments
    • Joe 11am
    • Sue 12am
  • people
    • Joe Smith
    • Sue Li (selected)
      • 3 days ago
      • 5 days ago

The detail panel would show information about Sue Li.

We are currently using our Primary Routes to operate the tree. We call CanActivate to wait for the child node's data to be resolved before rendering its Routable Component. Then we manipulate the DOM to graft the node on the tree immediately after it is rendered. The tree works by itself. I like that the application's main URL navigation is driven as I'd expect by the route tree; although I'd welcome alternate suggestions.

We are attempting to use Aux Routes to display the correct detail pane template. The relevant tree nodes understand how to "initiate" the correct detail pane using a simple Aux Route. However, we have two problems:

  1. Aux Routes don't support CanActivate, so until the @Resolve decorator is available in Angular 2, we can't easily delay render for Aux Components until data is resolved. This is a low-priority issue, since it will be fixed with the Angular release.

  2. All the detail Aux Components must be loaded in one parent component, so they can each overwrite the same parent Aux RouterOutlet. Is there another way to do this that doesn't require such a massive top-level component?

1

There are 1 best solutions below

0
On

This sounds like a similar approach as used in Angular 2 dynamic tabs with user-click chosen components would help here as well.

By using a wrapper element around ViewContainerRef.createComponent() (former DynamicComponentLoader.loadXxx() you can add one main component by the router which renders a list of components depending on data it gets passed (from a shared service for example)

@Component({
  selector: 'dcl-wrapper',
  template: `<div #target></div>`
})
export class DclWrapper {
  @ViewChild('target', {read: ViewContainerRef}) target;
  @Input() type;
  cmpRef:ComponentRef;
  private isViewInitialized:boolean = false;

  constructor(private resolver: ComponentResolver) {}

  updateComponent() {
    if(!this.isViewInitialized) {
      return;
    }
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
   this.resolver.resolveComponent(this.type).then((factory:ComponentFactory<any>) => {
      this.cmpRef = this.target.createComponent(factory)
    });
  }

  ngOnChanges() {
    this.updateComponent();
  }

  ngAfterViewInit() {
    this.isViewInitialized = true;
    this.updateComponent();  
  }

  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }    
  }
}

to build the list of child components like

@Component({
  selector: 'inserted-by-router',
  directives: [DclWrapper],
  template: `<dcl-wrapper *ngFor="let type in sharedService.types" [type]="type"></dcl-wrapper>`})
export class InsertedByRouter {
  constructor(private sharedService.types) {}
}