0

I am currently trying to incorporate a category structure into a menu for an application I am building. I have a service which calls the categories from an API box and this is all working fine and I have had the results returning and showing correctly before implementing this new functionality. Nothing is showing for me at the moment and there is no error in the console.

Here is my app.component.ts

import { Component, OnInit }       from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { ProductComponent } from './components/product.component'

// Services
import { ProductService }  from './services/product.service';
import { CategoryService } from './services/category.service';

// Classes
import { Product } from './classes/Product';
import { Category } from './classes/Category';
import {serializeXmb} from "angular2/i18n";

// Directives
import { CategoryTree } from './directives/category-tree';

@Component({
    selector: 'product-display',

    templateUrl:'./app/views/category-view.html',

    directives: [
        ROUTER_DIRECTIVES,
        ProductComponent,
        CategoryTree
    ],
    providers: [
        ROUTER_PROVIDERS,
        ProductService,
        CategoryService
    ]
})

@RouteConfig([
    {
        path : '/dashboard',
        name : 'Product',
        component : ProductComponent,
        useAsDefault : true
    }
])

export class AppComponent {
    constructor(private _productService: ProductService, private _categoryService: CategoryService) {}

    title = 'Product Viewer';
    contentLoaded = false;
    categories = [];
    selectedCategory = null;
    selectedProduct = Product;
    products = [];
    APIError = [];

    getProducts() {
        this._productService.getProducts()
            .subscribe(
                products => {this.products = products},
                error    => {this.APIError = error},
                ()       => this.contentLoaded = true
            );
    }
    getCategories() {
        this._categoryService.getCategories({
            order           : 'asc',
            order_by        : 'name',
            sub_cats        : true,
            group_by_parent : true
        })
            .subscribe(
                categories => {this.categories = categories},
                error      => {this.APIError = error},
                ()         => this.contentLoaded = true
            );
    }
    selectCategory(category: Category) {
        this.selectedCategory = category
    }
    ngOnInit() {
        this.getProducts();
        this.getCategories();
    }
}

As you can see, I am calling a function in the CategoryService service which returns all the categories I want to display on the page. As this is an aSync call I use the getCategories function to subscribe the Observer when it is returned.

Here is the category-view-html which is called by the component and is used to display the component:

<div id='left-menu-wrapper'>
    <div id='left-menu'>
        <h1>{{title}}</h1>
        <h2>Categories</h2>
        <category-tree [categories]="categories"></category-tree>
        <div *ngIf="selectedCategory">
            {{selectedCategory.name}}
        </div>
    </div>
    <div *ngIf="!contentLoaded" class='spinner'></div>
</div>
<product-view [product]="selectedProduct"></product-view>

Within here you can see the directive I am attempting to call 'category-tree'. I have also included this in the app.component.ts directives property which is called CategoryTree. I pass the categories I have returned from the service using the [categories]="categories" line.

Here is the CategoryTree directive I am importing into app.component.ts:

import { Component, Input } from 'angular2/core';

@Component({
    selector: 'category-tree',
    templateUrl: './app/views/category-tree.html',
    directives: [CategoryTree]
})

export class CategoryTree {
    @Input categories;
}

As you can see I define the selector and include a template to be used. I then export the directive an instruct the categories property to be an accepted input from app.component.ts. Here is the template (not yet finished).

<ul class="categories">
    <li *ngFor="#category of categories">
        <span (click)="selectCategory(category)" [class.selected]="category === selectedCategory">{{category.name}}</span>
        <ul *ngIf="category.sub_categories.length" class='sub-category' data-cat="category.id">
            <li *ngFor="#sub_category of category.sub_categories">
                <span (click)="selectCategory(sub_category)" [class.selected]="sub_category === selectedCategory">{{sub_category.name}}</span>
            </li>
        </ul>
    </li>
</ul>

For some reason the categories don't seem to be showing and the lack of an error in the console leads me to believe that this may be because the directive is being included before the categories are returned from the service? I thought this wouldn't be an issue as the directive was subscribed to the Observable and would update itself accordingly when the Observable changed?

Can anyone see where I may be going wrong here?

Thanks

James
  • 2,800
  • 7
  • 45
  • 81

2 Answers2

0

update

With the introduction of @NgModule and the migration of directives to @NgModule forwardRef shouldn't be necessary anymore.

original

From the @Component() decorator it looks like you are using CategoryTree inside Category tree, but it's not obvious what the template of this component is.

If you want to use the component in this recursive way, you need to use forwardRef

import {forwardRef} from 'angular2/core'; 
// or @angular/core in newer Angular versions

and use it like:

directives: [forwardRef(() => CategoryTree)]
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Hi. Yes I forgot to add that this is going to be a recursive component. I modified my code to what you suggested but I cannot see the categories still that are supposed to be passed to it. – James Jun 14 '16 at 10:29
  • Did you check the `CategoryTree` component gets instantiated? I'd add a `constructor() { console.log('CategoryTree'); }` to check. Also an an `ngOnChanges() { console.log('categories changed', this.categories);}`. Please add to your question what this prints to the console. – Günter Zöchbauer Jun 14 '16 at 10:35
  • A Plunker that allows to reproduce would be helpful. See also http://stackoverflow.com/questions/37746516/use-component-in-itself/37747022#37747022 for a similar example. – Günter Zöchbauer Jun 14 '16 at 10:37
0

I solved it. It was simply a pair of parenthesis missing from @Input in category-tree:

export class CategoryTree {
    @Input categories;
    constructor() {
        console.log("i am category tree");
    }
}

It is supposed to be

I solved it. It was simply a pair of parenthesis missing from @Input in category-tree:

export class CategoryTree {
    @Input categories;
    constructor() {
        console.log("i am category tree");
    }
}

It is supposed to be:

  export class CategoryTree {
            @Input() categories;
            constructor() {
                console.log("i am category tree");
            }
        }
James
  • 2,800
  • 7
  • 45
  • 81