Website-Suche

typescript - Angular 2 @ViewChild Annotation gibt undefined zurück


Ich versuche, Angular 2 zu lernen.

Ich möchte von einer Elternkomponente aus auf eine Kindkomponente mithilfe der @ViewChild-Annotation zugreifen.

Hier einige Codezeilen:

In BodyContent.ts habe ich:

import { ViewChild, Component, Injectable } from 'angular2/core';
import { FilterTiles } from '../Components/FilterTiles/FilterTiles';

@Component({
    selector: 'ico-body-content',
    templateUrl: 'App/Pages/Filters/BodyContent/BodyContent.html',
    directives: [FilterTiles] 
})
export class BodyContent {
    @ViewChild(FilterTiles) ft: FilterTiles;

    public onClickSidebar(clickedElement: string) {
        console.log(this.ft);
        var startingFilter = {
            title: 'cognomi',
            values: [ 'griffin', 'simpson' ]
        }
        this.ft.tiles.push(startingFilter);
    } 
}

während in FilterTiles.ts:

 import { Component } from 'angular2/core';

 @Component({
     selector: 'ico-filter-tiles',
     templateUrl: 'App/Pages/Filters/Components/FilterTiles/FilterTiles.html'
 })
 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }

Endlich hier die Vorlagen (wie in den Kommentaren vorgeschlagen):

BodyContent.html :

<div (click)="onClickSidebar()" class="row" style="height:200px; background-color:red;">
    <ico-filter-tiles></ico-filter-tiles>
</div>

FilterTiles.html :

<h1>Tiles loaded</h1>
<div *ngFor="#tile of tiles" class="col-md-4">
     ... stuff ...
</div>

Die FilterTiles.html-Vorlage wird korrekt in das ico-filter-tiles-Tag geladen (tatsächlich kann ich die Kopfzeile sehen).

Hinweis: Die BodyContent-Klasse wird in eine andere Vorlage (Body) eingefügt, indem DynamicComponetLoader: dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector) verwendet wird:

import { ViewChild, Component, DynamicComponentLoader, Injector } from 'angular2/core';
import { Body } from '../../Layout/Dashboard/Body/Body';
import { BodyContent } from './BodyContent/BodyContent';

@Component({
    selector: 'filters',
    templateUrl: 'App/Pages/Filters/Filters.html',
    directives: [Body, Sidebar, Navbar]
})
export class Filters {

    constructor(dcl: DynamicComponentLoader, injector: Injector) {
       dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector);
       dcl.loadAsRoot(SidebarContent, '#ico-sidebarContent', injector);
   } 
}

Das Problem ist, dass ich, wenn ich versuche, ft in die Konsole zu schreiben, undefined erhalte, und natürlich erhalte ich eine Ausnahme, wenn ich versuche, etwas in das "tiles"-Array zu schieben: 'keine Eigenschaft tiles für "undefined"'.

Noch eine Sache: Die FilterTiles-Komponente scheint korrekt geladen zu werden, da ich in der Lage bin, die HTML-Vorlage dafür zu sehen.

Irgendwelche Vorschläge?

Antwort #1

Ich hatte ein ähnliches Problem und dachte, ich poste es, falls jemand denselben Fehler gemacht hat. Zuerst ist etwas zu beachten: AfterViewInit; man muss warten, bis die Ansicht initialisiert ist, bevor man auf sein @ViewChild zugreifen kann. Mein @ViewChild hat jedoch immer noch null zurückgegeben. Das Problem war mein *ngIf. Die *ngIf-Direktive hat meine Steuerungskomponente zerstört, sodass ich nicht darauf zugreifen konnte.

import { Component, ViewChild, OnInit, AfterViewInit } from 'angular2/core';
import { ControlsComponent } from './controls/controls.component';
import { SlideshowComponent } from './slideshow/slideshow.component';

@Component({
  selector: 'app',
  template: `
    <controls *ngIf="controlsOn"></controls>
    <slideshow (mousemove)="onMouseMove()"></slideshow>
  `,
  directives: [SlideshowComponent, ControlsComponent],
})
export class AppComponent {
  @ViewChild(ControlsComponent) controls: ControlsComponent;

  controlsOn: boolean = false;

  ngOnInit() {
    console.log('on init', this.controls);
    // this returns undefined
  }

  ngAfterViewInit() {
    console.log('on after view init', this.controls);
    // this returns null
  }

  onMouseMove(event) {
    this.controls.show();
    // throws an error because controls is null
  }
}

BEARBEITEN
Wie unten von @Ashg erwähnt, besteht eine Lösung darin, @ViewChildren anstelle von @ViewChild zu verwenden.

Antwort #2

Das zuvor erwähnte Problem ist das ngIf, das dazu führt, dass die Ansicht undefiniert ist. Die Lösung besteht darin, ViewChildren anstelle von ViewChild zu verwenden. Ich hatte ein ähnliches Problem, bei dem ich nicht wollte, dass ein Raster angezeigt wird, bis alle Referenzdaten geladen waren.

html:

   <section class="well" *ngIf="LookupData != null">
       <h4 class="ra-well-title">Results</h4>
       <kendo-grid #searchGrid> </kendo-grid>
   </section>

Komponentencode

import { Component, ViewChildren, OnInit, AfterViewInit, QueryList  } from '@angular/core';
import { GridComponent } from '@progress/kendo-angular-grid';

export class SearchComponent implements OnInit, AfterViewInit
{
    //other code emitted for clarity

    @ViewChildren("searchGrid")
    public Grids: QueryList<GridComponent>

    private SearchGrid: GridComponent

    public ngAfterViewInit(): void
    {
        
        this.Grids.changes.subscribe((comps: QueryList <GridComponent>) =>
        {
            this.SearchGrid = comps.first;
        });

       
    }
}

Hier verwenden wir ViewChildren, auf denen Sie auf Änderungen hören können. In diesem Fall alle Kinder mit der Referenz #searchGrid.

Antwort #3

Sie könnten einen Setter für @ViewChild() verwenden

@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
    console.log(tiles);
};

Wenn Sie einen ngIf-Wrapper haben, wird der Setter mit undefined aufgerufen und dann erneut mit einer Referenz, sobald ngIf das Rendern zulässt.

Mein Problem war allerdings etwas anderes. Ich hatte das Modul, das mein "FilterTiles" enthält, nicht in meinen app.modules eingeschlossen. Die Vorlage warf keinen Fehler aus, aber die Referenz war immer undefiniert.

Antwort #4

Was mein Problem gelöst hat, war sicherzustellen, dass static auf false gesetzt ist.

@ViewChild(ClrForm, {static: false}) clrForm;

Mit deaktiviertem static wird die @ViewChild-Referenz von Angular aktualisiert, wenn sich die *ngIf-Direktive ändert.

Antwort #5

Das hat bei mir funktioniert.

Meine Komponente namens 'my-component' wurde zum Beispiel mit *ngIf="showMe" wie folgt angezeigt:

<my-component [showMe]="showMe" *ngIf="showMe"></my-component>

Also, wenn die Komponente initialisiert wird, wird die Komponente noch nicht angezeigt, bis „showMe“ wahr ist. Daher waren alle meine @ViewChild-Referenzen undefiniert.

Hier habe ich @ViewChildren und die von ihm zurückgegebene QueryList verwendet. Siehe Angular-Artikel über QueryList und ein @ViewChildren-Anwendungsbeispiel.

Sie können die QueryList verwenden, die @ViewChildren zurückgibt, und auf Änderungen der referenzierten Elemente mit rxjs reagieren, wie unten gezeigt. @ViewChild hat diese Fähigkeit nicht.

import { Component, ViewChildren, ElementRef, OnChanges, QueryList, Input } from '@angular/core';
import 'rxjs/Rx';

@Component({
    selector: 'my-component',
    templateUrl: './my-component.component.html',
    styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnChanges {

  @ViewChildren('ref') ref: QueryList<any>; // this reference is just pointing to a template reference variable in the component html file (i.e. <div #ref></div> )
  @Input() showMe; // this is passed into my component from the parent as a    

  ngOnChanges () { // ngOnChanges is a component LifeCycle Hook that should run the following code when there is a change to the components view (like when the child elements appear in the DOM for example)
    if(showMe) // this if statement checks to see if the component has appeared becuase ngOnChanges may fire for other reasons
      this.ref.changes.subscribe( // subscribe to any changes to the ref which should change from undefined to an actual value once showMe is switched to true (which triggers *ngIf to show the component)
        (result) => {
          // console.log(result.first['_results'][0].nativeElement);                                         
          console.log(result.first.nativeElement);                                          

          // Do Stuff with referenced element here...   
        } 
      ); // end subscribe
    } // end if
  } // end onChanges 
} // end Class

Hoffe, dies hilft jemandem, etwas Zeit und Frustration zu sparen.

Antwort #6

Meine Lösung dafür war, *ngIf durch [hidden] zu ersetzen. Nachteil war, dass alle untergeordneten Komponenten im Code-DOM vorhanden waren. Aber es funktionierte für meine Anforderungen.

Antwort #7

In meinem Fall hatte ich einen Eingabevariable-Setter, der ViewChild verwendete, und das ViewChild befand sich innerhalb einer *ngIf-Direktive, sodass der Setter versuchte, darauf zuzugreifen, bevor das *ngIf gerendert wurde (es würde ohne das *ngIf einwandfrei funktionieren, aber nicht, wenn es immer auf true gesetzt war mit *ngIf="true").

Um das Problem zu lösen, habe ich Rxjs verwendet, um sicherzustellen, dass jede Referenz auf das ViewChild wartet, bis die Ansicht initialisiert wurde. Erstellen Sie zuerst ein Subject, das nach der View-Init abgeschlossen wird.

export class MyComponent implements AfterViewInit {
  private _viewInitWaiter$ = new Subject();

  ngAfterViewInit(): void {
    this._viewInitWaiter$.complete();
  }
}

Erstellen Sie dann eine Funktion, die ein Lambda übernimmt und ausführt, nachdem das Subjekt abgeschlossen ist.

private _executeAfterViewInit(func: () => any): any {
  this._viewInitWaiter$.subscribe(null, null, () => {
    return func();
  })
}

Stellen Sie schließlich sicher, dass Verweise auf das ViewChild diese Funktion verwenden.

@Input()
set myInput(val: any) {
    this._executeAfterViewInit(() => {
        const viewChildProperty = this.viewChild.someProperty;
        ...
    });
}

@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;

Antwort #8

Meine Umgehungslösung war die Verwendung von [style.display]="getControlsOnStyleDisplay()" anstelle von *ngIf="controlsOn". Der Block ist vorhanden, wird aber nicht angezeigt.

@Component({
selector: 'app',
template:  `
    <controls [style.display]="getControlsOnStyleDisplay()"></controls>
...

export class AppComponent {
  @ViewChild(ControlsComponent) controls:ControlsComponent;

  controlsOn:boolean = false;

  getControlsOnStyleDisplay() {
    if(this.controlsOn) {
      return "block";
    } else {
      return "none";
    }
  }
....

Verwandte Artikel