Search code examples
angularangularjs-directiveselectorangular-componentsviewchild

Angular @ViewChild Directive stays undefined


[EDIT: SOLUTION]

I did not import the right Directive in my app.module.ts After including like the following, it just works and does not return undefined. :)

import { PlaceholderDirective } from './converter-submodule/utilities/placeholder-directive';
[...other imports]

@NgModule({
  declarations: [
    PlaceholderDirective,
    InfoComponent,
    ConverterViewComponent,
    [... other declarations]
  ],
  imports: [
    [... other imports]
  ],
  providers: [
    PlaceholderDirective,
    [... other providers]
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

[END OF EDIT]

i'm working on a rather complex angular-project and i am struggling to give a good description of my problem because the error can be everywhere. I'll just describe the part where one can see what is the effect of the error.

I marked my problem in the code of the following component as comments.

converter-view.component.ts

import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { PlaceholderDirective } from '../utilities/placeholder-directive';
[...]

@Component({
  selector: 'app-converter-view',
  templateUrl: './converter-view.component.html',
  styleUrls: ['./converter-view.component.scss']
})
export class ConverterViewComponent implements OnInit, AfterViewInit {
  @ViewChild(PlaceholderDirective)
  placeholder! : PlaceholderDirective;

  @ViewChild("test")
  test! : ElementRef<HTMLElement>;

  @ViewChildren(PlaceholderDirective)
  testList! : QueryList<PlaceholderDirective>;

  [...]

  ngOnInit(): void {
  }

  ngAfterViewInit(): void {

    console.log(this.placeholder); // returns undefined
    console.log(this.test); // returns an ElementRef as it should!!
    console.log(this.testList); // returns an empty List
    
    this.testList.changes.subscribe((newList : QueryList<PlaceholderDirective>)=>{
      console.log(newList); // this will never happen
      console.log("Test");
      
    })
    
  }
}

And that's the html of the component:

converter-view.component.html

<div class="wrap-generated-content">
    <div #test app-placeholder>

    </div>
</div>

I use the Directive only to get a viewContainerRef so that i can create components inside:

placeholder-directive.ts

import { Directive, ViewContainerRef } from "@angular/core";

@Directive({
    selector: '[app-placeholder]'
})
export class PlaceholderDirective{
    constructor(public viewContainerRef: ViewContainerRef){

    }
}

The ConverterViewComponent is wrapped in a lot of other components that i cannot explain in details, but i can say that multiple ConverterViewComponents are getting created in just the same way i try inside this component. The way i created the components looks like the following:

some other componnent where the above component gets created:

public createView(placeholder: PlaceholderDirective, docID : string) : void{
    console.log(placeholder); // here the placeholder is defined and everything is working well. It is just inside the ConverterViewComponent where the same method doesn't work...
    const view = placeholder.viewContainerRef.createComponent(ConverterViewComponent).instance;
    [... attributes etc]
    this.views.push(view);
  }

Maybe it is a problem that i create multiple Components in one single viewContainerRef?... But the components are getting created, that's not the problem. It is just the directive in the html that stays undefined.

Again simply to have an overview what i have tried:

First i tried to go with @ViewChild(PlaceholderDirective) to select the Directive because everywhere else in my other components this has worked perfectly. But here it always returns undefined.

As i listed above, i tried to go with the ViewChildren(PlaceholderDirective) and then .changes.subscribe() to see if it happens to get created later but nothing happened.

Also i tried to select the HTMLElement with @ViewChild("test") and as expected it returned the ElementRef, but i guess that doesn't help me, because i need the PlaceholderDirective to get its viewContainerRef.

Oh and i tried using @ViewChild(PlaceholderDirective) with a setter, but it only gets called one time with undefined like the others and then it doesnt get called again, so no...

And i tried @ContentChild(PlaceholderDirective), but it returns undefined aswell.

i want to say that the problem probably does not lie in the visible code but maybe someone has an idea what the state of the component is that leads to this error:)


Solution

  • If you have created your Directives from hand so you need to declare it inside your app.module.ts like this:

    import { AppComponent, PlaceholderDirective } from './app.component';
    
    @NgModule({
      imports:      [ BrowserModule, FormsModule ],
      declarations: [ AppComponent, PlaceholderDirective ],
      bootstrap:    [ AppComponent ]
    })
    export class AppModule { }
    

    After this your code should be work:

    enter image description here

    If you use the Angular CLI so you can simple call ng g d placehoder and Angular will do the magic for you.