Implementing a recursive structure using angular2 components (without forwardRef)?
Asked Answered
T

1

8

I have a recursive structure that's composed of two components:

  • OptionsMenuComponent (A menu)
  • MenuItemComponent (The menu items)

OptionsMenuComponent: (Template + Component)

Template:
    <menu-item *ngFor="let menuItem of menu.menuItems" [menuItem]="menuItem"> </menu-item>

Component:
    import { Component, Input } from '@angular/core';
    import { Menu } from '../models/menu.model';
    import { MenuItemComponent } from '../menu-item/menu-item.component';

    @Component({
        moduleId: __moduleName,
        selector: 'options-menu',
        templateUrl: 'options-menu.component.html',
        styleUrls: ['options-menu.component.css'],
        directives: [MenuItemComponent],
    })
    export class OptionsMenuComponent {
        @Input() menu: Menu;

        constructor() { }
    }

Menu Item: (Template + Component)

Template:
    <span class="menu-item-title">{{menuItem.title}}</span>

    <options-menu *ngIf="menuItem.subMenu != null" [menu]="menuItem.subMenu" class="sub-menu"> </options-menu>

Component:
    import { Component, Input, forwardRef } from '@angular/core';
    import { MenuItem } from '../models/menu-item.model';
    import { OptionsMenuComponent } from '../options-menu/options-menu.component';

    @Component({
        moduleId: __moduleName,
        selector: 'menu-item',
        directives: [forwardRef(() => OptionsMenuComponent)],
        templateUrl: 'menu-item.component.html',
        styleUrls: ['menu-item.component.css'],
    })
    export class MenuItemComponent {
        @Input() menuItem: MenuItem;

        constructor() { }

    }

Note: I removed almost all logic and design to simplify as it's not relevant

As you can see the OptionsMenuComponent knows the MenuItemComponent and the other way around.
In addition you might have noticed that in MenuItemComponent I used forwardRef inside the directive. Without that when I load the page I get the following error:

Error: Uncaught (in promise): Unexpected directive value 'undefined' on the View of component 'MenuItemComponent'

Currently the way it's written (with forwardRef) it works great but TSLint is unhappy with it and outputs the following:

[gulp-tslint] error (no-forward-ref) menu-item.component.ts[9, 18]: Avoid using forwardRef in class "MenuItemComponent"

I looked around the web for alternative solutions and came up empty handed. All I could find is two suggestions:

  • My solution with the forwardRef
  • "You have a cyclic reference, you're doing something wrong so decouple them" (not exact words)

The first suggestion works but my question is if it's possible to avoid this because it doesn't sound like good practice (And everywhere people say to avoid it)

The second suggestion is correct and easy to implement for most cases but how do I do that in a recursive structure (using angular2 components)?

Thanks ahead,

p.s - before this I had all the functionality in one component and I didn't have any problem with that single component referencing itself (no need for forwardRef) so please don't recommend doing so because it's not the case here (trying to keep the components small and simple)

TL;DR:
Two components referencing each other (recursive structure was required). Without using forwardRef I get error, Is there an alternative solution that doesn't involve using forwardRef?

Tarazi answered 27/7, 2016 at 11:59 Comment(3)
This is an old question, So I figure it's solved by having ngModules?Clarendon
@SanderElias what problems did ngModules solve exactly?Galliot
Problems like this one. It enables you to bundle code into a module, so it doesn't require you to include everything in every component.Clarendon
M
0

here is an example of a recursive nav i wrote, stripped down to an ordered list. in short, you're defining a template to recurse with, then iterate over the root children (collection of pages) context:{ $implicit: root.children }, and at each child, iterating over root child children by changing the context context:{ $implicit: page.children }.

<ng-template #nav let-pages>
  <ol>
    <ng-container *ngFor="let page of pages">
      <ng-container *ngIf="page.public && page.visible">
        <li>
          <a routerLink="/{{page.urlname}}">{{page.name}}</a>
          <ng-container *ngTemplateOutlet="nav; context:{ $implicit: page.children }"></ng-container>
        </li>
      </ng-container>
    </ng-container>
  </ol>
</ng-template>
<ng-container *ngTemplateOutlet="nav; context:{ $implicit: root.children }"></ng-container>
Medicaid answered 22/1, 2018 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.