Açısal 2 @ViewChild ek açıklaması tanımsız döndürüyor


242

Açısal 2'yi öğrenmeye çalışıyorum.

@ViewChild Ek Açıklama'yı kullanarak bir üst bileşenden bir alt bileşene erişmek istiyorum .

İşte bazı kod satırları:

Gelen BodyContent.ts ben:

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);
    } 
}

ise 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(){};
 }

Son olarak, burada şablonlar (yorumlarda önerildiği gibi):

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>

FilterTiles.html şablonu ico-filter-tiles etiketine doğru bir şekilde yüklendi (gerçekten de başlığı görebiliyorum).

Not: BodyContent sınıfı, DynamicComponetLoader kullanılarak başka bir şablonun (Body) içine enjekte edilir: dcl.loadAsRoot (BodyContent, '# ico-bodyContent', enjektör):

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);

   } 
}

Sorun şu ki ft, konsol günlüğüne yazmaya çalıştığımda , alıyorum undefinedve "fayans" dizisi içinde bir şey itmeye çalıştığımda elbette bir istisna alıyorum: "tanımsız" için hiçbir özellik döşemeleri yok .

Bir şey daha var: Bunun için html şablonunu görebildiğim için FilterTiles bileşeni doğru yüklenmiş gibi görünüyor.

Herhangi bir öneri? Teşekkürler


Doğru görünüyor. Şablonla ilgili bir şey olabilir, ancak sorunuza dahil edilmez.
Günter Zöchbauer

1
Günter ile aynı fikirde. Kodunuz ve basit ilişkili şablonlarla bir plunkr oluşturdum ve işe yarıyor. Bu bağlantıya bakın: plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview . Şablonlara ihtiyacımız var ;-)
Thierry Templier

1
ftyapıcıda ayarlanmaz, ancak bir tıklama olayı işleyicisinde zaten ayarlanmış olur.
Günter Zöchbauer

5
Değişiklik tespiti ile ilgili bilinen bir sorunuloadAsRoot olan kullanıyorsunuz . Sadece veya kullanmayı deneyin . loadNextToLocationloadIntoLocation
Eric Martinez

1
Sorun şuydu loadAsRoot. Bir kez ben loadIntoLocationsorunu ile değiştirildi çözüldü. Yorumunuzu cevap olarak
yaparsanız

Yanıtlar:


374

Benzer bir sorun yaşadım ve başka birinin aynı hatayı yapması durumunda yayınlayacağımı düşündüm. Birincisi, dikkate alınması gereken bir şey AfterViewInit; erişebilmeniz için görünümün başlatılmasını beklemeniz gerekir @ViewChild. Ancak, benim @ViewChildhala null dönüyordu. Sorun benim *ngIf. *ngIfBunu başvuramaz böylece direktif benim kontrolleri bileşeni öldürüyordu.

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
    }
}

Umarım yardımcı olur.

DÜZENLEME
olarak belirttiği aşağıdaki @Ashg , bir çözelti kullanmaktır @ViewChildrenyerine @ViewChild.


9
@kenecaswell Sorunu çözmenin daha iyi bir yolunu buldunuz. Ben de aynı sorunla karşı karşıyayım. Böylece öğe sadece sonuçta doğru olacak birçok * ngIf var, ama öğe başvurusu gerekir. Bunu çözmek için herhangi bir yolu>
monica

4
NgIf kullanılıyorsa ngAfterViewInit () alt bileşeni 'undefined' buldum. Uzun zaman aşımları yapmaya çalıştım ama yine de bir etkisi olmadı. Ancak, alt bileşen daha sonra kullanılabilir (yani tıklama etkinliklerine yanıt olarak vb.). Ben ngIf kullanmıyorsanız ve ngAfterViewInit () 'de beklendiği gibi tanımlanırsa. Ebeveyn / Çocuk iletişimi hakkında daha fazla bilgi burada açısal.io
Matthew Hegarty

3
Bunun yerine bootstrap ngClass+ hiddensınıfını kullandım ngIf. İşe yaradı. Teşekkürler!
Rahmathullah M

9
Bu sorunu çözmez, @ViewChildren'i kullanarak aşağıdaki çözümü kullanın, kullanılabilir olduğunda çocuk kontrolüne bir referans alın
Ashg

20
Bu sadece "sorunu" kanıtlıyor, değil mi? Bir çözüm göndermez.
Miguel Ribeiro

146

Daha önce de belirtildiği gibi ngIf, görünümün tanımlanmamasına neden olan konu budur. Cevap kullanmak ViewChildrenyerineViewChild . Tüm referans verileri yüklenene kadar bir kılavuzun gösterilmesini istemediğim benzer bir sorun yaşadım.

html:

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

Bileşen Kodu

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;
        });


    }
}

Burada ViewChildrendeğişiklikleri dinlemek için kullanıyoruz . Bu durumda referansı olan herhangi bir çocuk #searchGrid. Bu yardımcı olur umarım.


4
Bazı durumlarda örneğin değişiklik denediğinizde bunu eklemek istiyorum. this.SearchGridsözdizimi setTimeout(()=>{ ///your code here }, 1); önlemek için kullanmanız gereken özellikler İstisna: İfade kontrol edildikten sonra değişti
rafalkasa

3
#SearchGrid etiketinizi bir Angular2 öğesi yerine normal bir HTML öğesine yerleştirmek istiyorsanız bunu nasıl yaparsınız? (Örneğin, <div #searchGrid> </div> ve bu bir * ngIf bloğunun içinde mi?
Vern Jensen

1
bu benim kullanım durumum için doğru cevap! Teşekkürler Ben ngIf =
Frozen_byte

1
Bu ajax yanıtları, şimdi işler üzerinde mükemmel *ngIfçalışır ve render işleminden sonra bir ElementRef'i dinamik bileşenlerden kaydedebiliriz.
elporfirio

4
Ayrıca bir aboneliğe atamayı ve ardından aboneliği iptal etmeyi unutmayın
tam.teixeira

64

İçin bir ayarlayıcı kullanabilirsiniz @ViewChild()

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

Bir ngIf paketleyiciniz varsa, ayarlayıcı tanımsız olarak çağrılır ve ardından ngIf'in oluşturulmasına izin verdikten sonra yine bir referans ile çağrılır.

Benim sorunum başka bir şeydi. Benim app.modules benim "FilterTiles" içeren modülü dahil etmemişti. Şablon bir hata atmadı, ancak başvuru her zaman tanımsızdı.


3
Bu benim için işe yaramıyor - ilk tanımsızı alıyorum, ancak referansla ikinci çağrıyı alamıyorum. Uygulama bir ng2 ... bu ng4 + özelliği mi?
Jay Cummins

@Jay inanıyorum, bu durumda bileşeni Angular'a kaydetmediniz FilterTiles. Bu sorunla daha önce karşılaşmıştım.
parlamento

1
Html öğesi üzerinde #paginator ve Annotation gibi Angular 8 için çalışıyor@ViewChild('paginator', {static: false})
Qiteq

1
Bu ViewChild değişiklikleri için bir geri arama mı?
Yasser Nascimento

24

Bu benim için çalıştı.

Örneğin, 'bileşenim' adlı bileşenim şu şekilde * ngIf = "showMe" kullanılarak görüntülendi:

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

Bu nedenle, bileşen başlatıldığında, bileşen "showMe" doğru olana kadar henüz görüntülenmez. Böylece, @ViewChild referanslarımın hepsi tanımsızdı.

Burada @ViewChildren ve döndürdüğü QueryList kullandım. QueryList ve @ViewChildren kullanım demosu hakkındaki açısal makaleye bakın .

@ViewChildren'in döndürdüğü ve aşağıda görüldüğü gibi rxjs kullanarak başvurulan öğelerde yapılan değişikliklere abone olan QueryList'i kullanabilirsiniz. @ViewChild bu yeteneğe sahip değildir.

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

Umarım bu, birinin biraz zaman ve hayal kırıklığına uğramasına yardımcı olur.


3
Aslında çözüm şimdiye kadar listelenen en iyi yaklaşım gibi görünüyor. NOT İlk 73 çözümün artık DEPRECATED olduğunu aklımızda tutmalıyız ... yönerge: [...] beyanı artık Açısal 4'te desteklenmemektedir - IOW Açısal 4 senaryosunda
ÇALIŞMAYACAK

5
Abonelikten çıkmayı veya kullanmayı unutmayın .take(1).subscribe(), ancak mükemmel yanıt, çok teşekkür ederim!
Blair Connolly

2
Mükemmel çözüm. Ben ngOnChanges () yerine ngAfterViewInit () ref değişikliklere abone oldum. Ama ExpressionChangedAfterChecked hatadan kurtulmak için bir setTimeout eklemek zorunda kaldım
Josf

Bu asıl çözüm olarak işaretlenmelidir. Çok teşekkürler!
platzhersh

10

Benim geçici çözüm [style.display]="getControlsOnStyleDisplay()"yerine kullanmak oldu *ngIf="controlsOn". Blok orada ama görüntülenmiyor.

@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";
    }
  }
....

ShowList değişkeninin değerine bağlı olarak, bir öğe listesinin tabloda gösterildiği veya düzenleme öğesinin gösterildiği bir sayfanız olsun. * NgIf = "! ShowList" ile birlikte [style.display] = "! ShowList" kullanarak can sıkıcı konsol hatalarından kurtuldum.
razvanone

9

Ne sorun çözüldü emin olmak staticoldu false.

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

İle statickapatılmış, @ViewChildzaman referans açısal tarafından güncellenir *ngIfdirektifi değiştirir.


1
Bu neredeyse mükemmel bir cevaplayıcı, sadece işaret etmek için null değerlerin de kontrol edilmesi için iyi bir uygulama, bu yüzden böyle bir şeyle sonuçlanıyoruz: @ViewChild (ClrForm, {static: false}) set clrForm (clrForm: ClrForm) {if (clrForm) {this.clrForm = clrForm; }};
Klaus Klein

Çok şey denedim ve sonunda bu şeyi suçlu olarak buldum.
Manish Sharma

8

Bu benim çözüm ile değiştirmek *ngIf oldu [hidden]. Olumsuz tüm çocuk bileşenleri DOM kodunda mevcuttu. Ama benim gereksinimlerim için çalıştı.


5

Benim durumumda, giriş değişken ayarlayıcısını kullanarak ViewChild ve ViewChildbir *ngIfdirektifin içindeydi , bu yüzden ayarlayıcı işlenmeden önce ona erişmeye çalışıyordu *ngIf(bu olmadan iyi çalışacaktı *ngIf, ancak her zaman ayarlanmışsa işe yaramayacaktı ile doğru *ngIf="true").

Çözmek için ViewChild, görüş başlatılıncaya kadar bekletilen herhangi bir referanstan emin olmak için Rxjs kullandım . İlk olarak, görünüm başladıktan sonra tamamlanan bir Konu oluşturun.

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

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

Ardından, konu tamamlandıktan sonra lambda alan ve yürüten bir işlev oluşturun.

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

Son olarak, ViewChild referanslarının bu işlevi kullandığından emin olun.

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

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

1
Bu, tüm settimeout nonesense'den çok daha iyi bir çözüm
Liam

4

Çalışmalı.

Ancak Günter Zöchbauer'ın dediği gibi şablonda başka bir sorun olmalı. Biraz Relevant-Plunkr-Answer oluşturdum . Lütfen tarayıcı konsolunu kontrol edin.

boot.ts

@Component({
selector: 'my-app'
, template: `<div> <h1> BodyContent </h1></div>

      <filter></filter>

      <button (click)="onClickSidebar()">Click Me</button>
  `
, directives: [FilterTiles] 
})


export class BodyContent {
    @ViewChild(FilterTiles) ft:FilterTiles;

    public onClickSidebar() {
        console.log(this.ft);

        this.ft.tiles.push("entered");
    } 
}

filterTiles.ts

@Component({
     selector: 'filter',
    template: '<div> <h4>Filter tiles </h4></div>'
 })


 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }

Mucizevi şekilde çalışır. Lütfen etiketlerinizi ve referanslarınızı iki kez kontrol edin.

Teşekkürler...


1
Sorun benimkiyle aynı ise, <filtre> </ filtre> .. ngIf yanlış dönerse Görünüşe göre, ViewChild kablolu ve iadeler boş almaz etrafında şablonunda bir * ngIf koymak gerekir çoğaltmak için
Dan Chase

2

Bu benim için işe yarıyor, aşağıdaki örneğe bakın.

import {Component, ViewChild, ElementRef} from 'angular2/core';

@Component({
    selector: 'app',
    template:  `
        <a (click)="toggle($event)">Toggle</a>
        <div *ngIf="visible">
          <input #control name="value" [(ngModel)]="value" type="text" />
        </div>
    `,
})

export class AppComponent {

    private elementRef: ElementRef;
    @ViewChild('control') set controlElRef(elementRef: ElementRef) {
      this.elementRef = elementRef;
    }

    visible:boolean;

    toggle($event: Event) {
      this.visible = !this.visible;
      if(this.visible) {
        setTimeout(() => { this.elementRef.nativeElement.focus(); });
      }
    }

}


2

Benzer bir sorun vardı, burada başvurulan önce viewChild öğesini yükleme değildi ViewChildbir switchcümle içinde . Ben yarı-hacky bir şekilde çözdüm ama ViewChildreferans setTimeouthemen yürütülen bir sarma (yani 0 ms)


1

Bu benim çözümüm html tüm bölümünü sarılmış bir div ngIf alt bileşen dış alt bileşen içine taşımak oldu. Bu şekilde olması gerektiğinde hala gizleniyordu, ancak bileşeni yükleyebiliyordu ve üst öğeye başvurabiliyordum.


Fakat bunun için, üst öğedeki "görünür" değişkeninize nasıl ulaştınız?
Dan Chase

1

Bileşeni görünür hale getirdikten sonra SetTimeout ekleyerek düzeltirim

HTML kodum:

<input #txtBus *ngIf[show]>

Bileşenim JS

@Component({
  selector: "app-topbar",
  templateUrl: "./topbar.component.html",
  styleUrls: ["./topbar.component.scss"]
})
export class TopbarComponent implements OnInit {

  public show:boolean=false;

  @ViewChild("txtBus") private inputBusRef: ElementRef;

  constructor() {

  }

  ngOnInit() {}

  ngOnDestroy(): void {

  }


  showInput() {
    this.show = true;
    setTimeout(()=>{
      this.inputBusRef.nativeElement.focus();
    },500);
  }
}

1

Benim durumumda, çocuk bileşeninin her zaman mevcut olacağını biliyordum, ancak işi kurtarmaya başlamadan önce çocuğun durumunu değiştirmek istedim.

Çocuğu görünene kadar test etmeyi ve hemen değişiklik yapmayı seçiyorum, bu da bana alt bileşende bir değişiklik döngüsü kaydetti.

export class GroupResultsReportComponent implements OnInit {

    @ViewChild(ChildComponent) childComp: ChildComponent;

    ngOnInit(): void {
        this.WhenReady(() => this.childComp, () => { this.childComp.showBar = true; });
    }

    /**
     * Executes the work, once the test returns truthy
     * @param test a function that will return truthy once the work function is able to execute 
     * @param work a function that will execute after the test function returns truthy
     */
    private WhenReady(test: Function, work: Function) {
        if (test()) work();
        else setTimeout(this.WhenReady.bind(window, test, work));
    }
}

Dikkatle, maksimum sayıda deneme ekleyebilir veya öğesine birkaç ms gecikme ekleyebilirsiniz setTimeout. setTimeoutişlevi etkin bir şekilde bekleyen işlemler listesinin en altına atar.


0

Bir tür genel yaklaşım:

ViewChildHazır olana kadar bekleyecek bir yöntem oluşturabilirsiniz

function waitWhileViewChildIsReady(parent: any, viewChildName: string, refreshRateSec: number = 50, maxWaitTime: number = 3000): Observable<any> {
  return interval(refreshRateSec)
    .pipe(
      takeWhile(() => !isDefined(parent[viewChildName])),
      filter(x => x === undefined),
      takeUntil(timer(maxWaitTime)),
      endWith(parent[viewChildName]),
      flatMap(v => {
        if (!parent[viewChildName]) throw new Error(`ViewChild "${viewChildName}" is never ready`);
        return of(!parent[viewChildName]);
      })
    );
}


function isDefined<T>(value: T | undefined | null): value is T {
  return <T>value !== undefined && <T>value !== null;
}

Kullanımı:

  // Now you can do it in any place of your code
  waitWhileViewChildIsReady(this, 'yourViewChildName').subscribe(() =>{
      // your logic here
  })

0

Benim için sorun, elemanın kimliğine atıfta bulunmaktı.

@ViewChild('survey-form') slides:IonSlides;

<div id="survey-form"></div>

Bunun yerine:

@ViewChild('surveyForm') slides:IonSlides;

<div #surveyForm></div>

0

Ionic kullanıyorsanız, ionViewDidEnter()yaşam döngüsü kancasını kullanmanız gerekir . İyonik tipik böyle beklenmedik hatalara neden bazı ek malzeme (özellikle animasyon-ilişkili), o çalışır şey dolayısıyla ihtiyaç çalışır sonra ngOnInit , ngAfterContentInitvb.


-1

İşte benim için işe yarayan bir şey.

@ViewChild('mapSearch', { read: ElementRef }) mapInput: ElementRef;

ngAfterViewInit() {
  interval(1000).pipe(
        switchMap(() => of(this.mapInput)),
        filter(response => response instanceof ElementRef),
        take(1))
        .subscribe((input: ElementRef) => {
          //do stuff
        });
}

Bu yüzden temelde gerçekleşene kadar her saniye bir kontrol ayarladım *ngIfve sonra ile ilgili şeylerimi yapıyorum ElementRef.


-3

Benim için çalışan çözüm , app.module.ts dosyasındaki bildirimlerde yönergeyi eklemekti.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.