Maintain treeview behaviour when branch is pre-opened in Angular 2

Issue

I have a recursive treeview component which loads its data from a third-party API. It works well. What I’m seeking to do now though is open the tree at a particular branch when the tree is first rendered (long term this will hook into route params so the tree can be opened at a particular branch by calling a different URL, but baby steps first…).

Each of the branches in the tree has a unique id. I have hard-coded an id of one of the branches into the component and am checking in the template loop whether the value of the current branch in the loop matches, if it does, it opens the tree at that branch.

The component code is as follows:

import { Component, Input, OnInit } from '@angular/core';
import { Router, RouteSegment }     from '@angular/router';

import { ContentNode }              from './content-node';
import { ContentService }           from '../services/content.service';


@Component({
    selector: 'content-tree',
    directives: [ContentTreeComponent],
    template: `
        <ol class="tree">
            <li *ngFor="let contentNode of contentNodes" class="tree__branch" [ngClass]="{'tree__branch--has-children': contentNode.HasChildren}">
                <a *ngIf="contentNode.HasChildren" (click)="contentNode.toggle=!contentNode.toggle" class="tree__branch__toggle">
                    {{ !!contentNode.toggle || openNode == contentNode.Id ? '-' : '+' }}
                </a> 
                <a class="tree__branch__link" (click)="onSelect(contentNode)">{{ contentNode.Name }}</a>
                <content-tree *ngIf="contentNode.toggle || openNode == contentNode.Id" [startNodeId]="contentNode.Id"></content-tree>
            </li>
        </ol>
        <div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
    `
})
export class ContentTreeComponent implements OnInit {

  openNode = 1054;

    constructor(
        private _contentService: ContentService,
        private _router: Router,
        private _currSegment: RouteSegment
    ) { }

    errorMessage: string;

    @Input('startNodeId')
    private _startNodeId: number;

    contentNodes: ContentNode[];

    ngOnInit() { 
        this.getContentNodes();
    }

    onSelect(contentNode: ContentNode) {
        this._router.navigate([`./${contentNode.Id}`], this._currSegment);
    }

    getContentNodes() {
        this._contentService.getContentNodes(this._startNodeId)
            .subscribe(
                contentNodes => this.contentNodes = contentNodes,
                error =>  this.errorMessage = <any>error
            );
    }
}

Here’s a plunkr.

This works correctly… almost! Click ‘Content’ from the red menu on the left and you’ll see the treeview. Notice the first branch in the tree (Rugby League) is open, which is correct (this has an id of 1054 which I’m hard-coding in the component to ensure it opens) however this branch now seems to have lost its behaviour; it can no longer be closed by clicking the - link on its left side. If you remove || openNode == contentNode.Id from the template it opens/closes correctly. The other branches in the tree maintain their behaviour, just not this one.

Can anyone suggest how to maintain the behaviour of the tree even when a branch is programmatically opened?

Many thanks for any help with this!

Solution

This is the correct behavior because the second part of your *ngIf is always true.

Your “Rugby League” tree has two states: toggled & untoggled

1- untoggled: *ngIf="false || 1054 == 1054" > *ngIf="false || true" > *ngIf="true"

2- toggled: *ngIf="true || 1054 == 1054" > *ngIf="true || true" > *ngIf="true"

As you can see, the *ngIf expression will always evaluate to true.


How To fix it:

I removed || openNode == contentNode.Id and changed your getContentNodes() to set the node to toggled after it is fetched.

getContentNodes() {
    this._contentService.getContentNodes(this._startNodeId)
        .subscribe(
            contentNodes => {
              this.contentNodes = contentNodes;
              this.contentNodes.filter(n=>n.Id === this.openNode).forEach(n=>n.toggle = true);  // I added this line
            },
            error =>  this.errorMessage = <any>error
        );
}

Here is you plunk fixed

Answered By – Abdulrahman Alsoghayer

Answer Checked By – Robin (AngularFixing Admin)

Leave a Reply

Your email address will not be published.