Dilip Raj Baral
Dilip Raj Baral

Reputation: 3070

Parent DOM manipulation by child?

I have a setup something like the following:

<nav>
    <li>
        <a someClassToggleDirective>Menu 1</a> <!-- This anchor element toggle determines if ul.sub-menu should be expanded (displayed) -->
        <ul class="sub-menu">   <!-- This is displayed when it's first sibling 'a' has 'open' class -->
            <li routerLinkActive="active"><a routerLink="childComp1">Child Component 1</a></li>
            <li routerLinkActive="active"><a routerLink="childComp2">Child Component 2</a></li>
            ...
        </ul>
    </li>
    <li>
        <a someClassToggleDirective>...</a>
        <ul class="sub-menu">
            ...
        </ul>
    </li>
    ...
</nav>
<router-outlet></router-outlet>

To load any component, a li item is clicked which then expands it's sub menu ul. Clicking on one of the sub-menu items loads the corresponding component in <router-outlet></outlet> and set the sub-menu item as active. The route would be then [root]/childComp1 for example. But if I directly navigate to [root]/childComp1 using browser's address bar, the component will be loaded but the parent ul of the active child component is not expanded.

So, how do I get it expanded? For a sub menu (ul) to expand, I need to add a class open to its sibling a. How do I do this? A way that comes to my mind is using this.router.url to determine the component and based on the route name expand the corresponding sub-menu. But I feel this is not the best way to do this. Is there any better way? I want to avoid any third-party libraries like jQuery.

Upvotes: 1

Views: 412

Answers (1)

taras-d
taras-d

Reputation: 1827

You can use @ContentChildren decorator to get all instances of RouterLinkActive inside submenu. And detect whether there is active links.

For example, you can create directive, which keeps list of links. And on every route change toggle expand prop.

Directive

import {Directive, HostListener, HostBinding, ContentChildren, QueryList} from '@angular/core';
import {Router, RouterLinkActive, NavigationEnd} from '@angular/router';
import 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';

@Directive({ selector: '[listToggle]' })
export class ListToggleDirective {

  // Collect RouterLinkActive instances
  @ContentChildren(RouterLinkActive) links: QueryList<RouterLinkActive>;

  // If expand=true - show submenu
  @HostBinding('class.expand')
  expand: boolean;

  routerSub: Subscription;

  constructor(private router: Router) {

  }


  ngAfterContentInit(): void {
    // Detect if there is active links
    this.routerSub = this.router.events
      .filter(e => e instanceof NavigationEnd)
      .subscribe(() => this.detectActiveLink());
  }

  ngOnDestroy(): void {
    if (this.routerSub) {
      this.routerSub.unsubscribe();
    }
  }

  detectActiveLink(): void {
    setTimeout(() => {
      const hasActive = this.links.some(link => link.isActive);
      this.expand = hasActive;
    });
  }

}

Usage

<ul>
    <li listToggle>
        <a>Open menu 1</a>
        <ul class="sub-menu">
            <a routerLink="/cmp1" routerLinkActive="active">cmp1</a>
            <a routerLink="/cmp2" routerLinkActive="active">cmp2</a>
        </ul>
    </li>
    <li listToggle>
        <a>Open menu 2</a>
        <ul class="sub-menu">
            <a routerLink="/cmp3" routerLinkActive="active">cmp3</a>
        </ul>
    </li>
</ul>

Styles

li[listtoggle] {
    &.expand .sub-menu {
        display: block;
    }
    .sub-menu {
        display: none;
    }
}

Hope this helps

Upvotes: 1

Related Questions