Angular 2 @ViewChildアノテーションが未定義を返す


241

Angular 2を学習しようとしています。

@ViewChildアノテーションを使用して、親コンポーネントから子コンポーネントにアクセスしたいと思います。

ここにいくつかのコード行:

BodyContent.ts私が持っています:

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

いる間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(){};
 }

最後にここにテンプレートがあります(コメントで提案されています):

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テンプレートがico-filter-tilesタグに正しく読み込まれ ます(実際にヘッダーを確認できます)。

注:BodyContentクラスは、DynamicComponetLoaderを使用して別のテンプレート(Body)内に挿入されます:dcl.loadAsRoot(BodyContent、 '#ico-bodyContent'、インジェクター):

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

   } 
}

問題はft、コンソールログに書き込もうとするundefinedと、「タイル」配列内に何かをプッシュしようとすると例外が発生することです。「「未定義」のプロパティタイルはありません」

もう1つ:FilterTilesコンポーネントは正しく読み込まれているようです。HTMLテンプレートを確認できるからです。

なにか提案を?ありがとう


正しいようです。テンプレートに何かあるかもしれませんが、それはあなたの質問には含まれていません。
ギュンターZöchbauer

1
ギュンターに同意する。私はあなたのコードと簡単な関連付けられたテンプレートでplunkrを作成し、それは動作します。このリンクを参照してください:plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview。テンプレートが必要です;-)
ティエリーテンプリエ2016年

1
ftコンストラクタでは設定されませんが、クリックイベントハンドラではすでに設定されています。
ギュンターZöchbauer

5
を使用していますがloadAsRoot、これには変更の検出に関する既知の問題があります。確認するには、loadNextToLocationまたはを使用してくださいloadIntoLocation
Eric Martinez

1
問題はでしたloadAsRoot。交換したらloadIntoLocation問題は解決しました。あなたは答えとしてあなたのコメントをした場合受け入れたように私はそれをマークすることができます
アンドレアIalenti

回答:


372

同様の問題があり、誰かが同じ間違いをした場合に備えて投稿しようと思いました。最初に、考慮すべき1つはAfterViewInitです。にアクセスするには、ビューが初期化されるのを待つ必要があります@ViewChild。しかし、私@ViewChildはまだnullを返していました。問題は私のことでした*ngIf*ngIf私はそれを参照することができませんでしたので、ディレクティブは、私のコントロールコンポーネントを殺しました。

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

お役に立てば幸いです。

編集以下の@Ashgで
言及されているように、解決策はの@ViewChildren代わりに使用することです@ViewChild


9
@kenecaswellそれであなたは問題を解決するより良い方法を見つけましたか?私も同じ問題に直面しています。私は多くの* ngIfを持っているので、その要素は結局trueになりますが、要素参照が必要です。これを解決する方法>
モニカ2016

4
ngIfを使用している場合、ngAfterViewInit()で子コンポーネントが「未定義」であることがわかりました。長いタイムアウトを設定しようとしましたが、それでも効果はありません。ただし、子コンポーネントは後で使用できます(クリックイベントへの応答など)。ngIfを使用せず、ngAfterViewInit()で期待どおりに定義されている場合。親/子のコミュニケーションについては、こちらをご覧
マシューヘ

3
の代わりにブートストラップngClass+ hiddenクラスを使用しました ngIf。うまくいきました。ありがとう!
Rahmathullah M 2017

9
これは問題を解決しません。@ ViewChildrenを使用して以下のソリューションを使用し、子コントロールが利用可能になったら参照を取得します
Ashg

20
これは「問題」を証明するだけですよね?解決策は投稿しません。
ミゲルリベイロ

144

前述の問題ngIfは、ビューが未定義になる原因です。答えはのViewChildren代わりに使用することですViewChild。すべての参照データが読み込まれるまでグリッドを表示したくないという同様の問題がありました。

html:

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

コンポーネントコード

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


    }
}

ここではViewChildren、変更をリッスンできるを使用しています。この場合、参照を持つすべての子#searchGrid。お役に立てれば。


4
あなたが変更しようとするとき、私はいくつかのケースでそれを追加したいと思います例えば。 例外を回避するために次のthis.SearchGridような構文を使用する必要があるプロパティsetTimeout(()=>{ ///your code here }, 1); :確認後に式が変更されました
rafalkasa '12

3
#searchGridタグをAngular2要素ではなく通常のHTML要素に配置する場合は、どうすればよいですか?(たとえば、<div #searchGrid> </ div>であり、これは* ngIfブロックの内部にありますか?
Vern Jensen

1
これは私のユースケースの正解です!ngIf =を介して利用できるようになり、コンポーネントにアクセスする必要があります。ありがとう
Frozen_byte

1
これはajax応答で完璧に機能し、今では*ngIf機能します。レンダリング後に、動的コンポーネントからElementRefを保存できます。
elporfirio 2017年

4
また、サブスクリプションに割り当ててからサブスクライブを解除することを忘れないでください
tam.teixeira

63

あなたはセッターを使うことができます @ViewChild()

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

ngIfラッパーがある場合、セッターはundefinedで呼び出され、ngIfがレンダリングを許可すると、参照で再度呼び出されます。

私の問題は別のものでした。app.modulesに「FilterTiles」を含むモジュールを含めていませんでした。テンプレートはエラーをスローしませんでしたが、参照は常に未定義でした。


3
これは私にとっては機能しません-最初の未定義を取得しますが、参照付きの2番目の呼び出しを取得しません。アプリはng2です...これはng4 +機能ですか?
ジェイカミンズ

@Jay私は、この場合、Angularにコンポーネントを登録していないためと考えていますFilterTiles。その理由で以前にその問題に遭遇したことがあります。
議会

1
以下のようなHTML要素と注釈に#paginatorを使用して角度の8のための作品@ViewChild('paginator', {static: false})
Qiteq

1
これはViewChildの変更に対するコールバックですか?
Yasser Nascimento

24

これでうまくいきました。

たとえば、「my-component」という名前のコンポーネントは、* ngIf = "showMe"を使用して次のように表示されました。

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

したがって、コンポーネントが初期化されると、「showMe」がtrueになるまでコンポーネントはまだ表示されません。したがって、私の@ViewChild参照はすべて未定義でした。

ここで、@ ViewChildrenとそれが返すQueryListを使用しました。QueryListに関する角度のある記事と@ViewChildrenの使用法のデモをご覧ください。

以下に示すように、@ ViewChildrenが返すQueryListを使用し、rxjsを使用して参照項目への変更をサブスクライブできます。@ViewChildにはこの機能はありません。

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

これが誰かが時間とフラストレーションを節約するのに役立つことを願っています。


3
確かにあなたの解決策はこれまでにリストされた最善のアプローチのようです。注:ディレクティブ:[...]宣言はAngular 4ではサポートされなくなったため、上位73のソリューションは廃止されました
。Angular4の

5
購読を解除するか、を使用することを忘れないでください.take(1).subscribe()
ブレアコノリー

2
優れたソリューション。ngOnChanges()ではなくngAfterViewInit()の参照変更をサブスクライブしました。しかし、ExpressionChangedAfterCheckedエラーを取り除くためにsetTimeoutを追加する必要がありました
Josf

これは、実際のソリューションとしてマークする必要があります。どうもありがとう!
platzhersh

10

私の回避策はの[style.display]="getControlsOnStyleDisplay()"代わりにを使用することでした*ngIf="controlsOn"。ブロックはありますが、表示されません。

@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変数の値に基づいて、アイテムのリストがテーブルに表示されるページ、または編集アイテムが表示されるページを用意します。* ngIf = "!showList"と組み合わせて[style.display] = "!showList"を使用することで、迷惑なコンソールエラーを解消しました。
razvanone

9

私の問題を解決したのは、staticがに設定されていることを確認することでしたfalse

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

staticオフ、@ViewChild際の基準は、角度によって更新される*ngIfディレクティブは変化します。


1
これはほぼ完璧なanswserであり、指摘するだけでもnull可能値をチェックするのに適した方法であるため、次のような結果になります。@ ViewChild(ClrForm、{static:false})set clrForm(clrForm:ClrForm){if (clrForm){this.clrForm = clrForm; }};
クラウスクライン

私はたくさんのことを試みましたが、最終的にこのことが原因であることがわかりました。
マニッシュシャルマ

8

これに対する私の解決策は、に置き換える*ngIf こと[hidden]でした。欠点は、すべての子コンポーネントがコードDOMに存在していたことです。しかし、私の要件のために働いた。


5

私の場合、を使用する入力変数セッターがViewChildあり、ViewChild*ngIfディレクティブの内部にあったため、セッターは*ngIfレンダリングの前にアクセスしようとしました(*ngIfがなければ正常に機能しますが、常にに設定されていると機能しません)で真*ngIf="true")。

解決するために、Rxjsを使用してViewChild、ビューが開始されるまで待機するすべての参照を確認しました。最初に、ビューの初期化後に完了するサブジェクトを作成します。

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

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

次に、サブジェクトの完了後にラムダを取得して実行する関数を作成します。

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

最後に、ViewChildへの参照がこの関数を使用していることを確認してください。

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

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

1
これは、すべてのsettimeout nonesense
Liam

4

うまくいくはずです。

しかし、ギュンターツェッヒバウアーが言ったように、テンプレートには他にも問題があるはずです。ちょっとRelevant-Plunkr-Answerを作成しました。ブラウザのコンソールを確認してください。

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

それは魅力のように働きます。タグと参照を再確認してください。

ありがとう...


1
問題は私と同じである場合、あなたは<フィルター> </フィルタ> .. ngIfがfalseを返した場合どうやら、ViewChildは有線とリターンのヌルされません周りのテンプレートに* ngIfを配置する必要があります複製する
ダンチェイス

2

これは私にとってはうまくいきます、以下の例を見てください。

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

参照される前にviewChild要素をロードしなかっViewChildswitch句の内部にがあったという同様の問題がありました。私はそれをセミハッキーな方法で解決しましたが、すぐに実行されたもの(つまり0ms)でViewChild参照をラップしますsetTimeout


1

これに対する私の解決策は、ngIfを子コンポーネントの外側から、htmlのセクション全体をラップするdiv上の子コンポーネントの内側に移動することでした。そうすることで、必要なときに非表示になりましたが、コンポーネントをロードでき、親で参照できました。


しかし、そのためには、親にある「可視」変数にどのようにして行きましたか?
Dan Chase、

1

コンポーネントを表示するように設定した後にSetTimeoutを追加するだけで修正します

私のHTML:

<input #txtBus *ngIf[show]>

私のコンポーネント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

私の場合、子コンポーネントが常に存在することはわかっていましたが、子を初期化して作業を保存する前に状態を変更したいと考えました。

私は、子が表示されるまでテストしてすぐに変更を加えることを選択しました。これにより、子コンポーネントの変更サイクルを節約できました。

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

注意深く、最大試行回数を追加したり、数ミリ秒の遅延をに追加したりできsetTimeoutます。setTimeout保留中の操作のリストの一番下に関数を効果的にスローします。


0

一種の一般的なアプローチ:

ViewChild準備ができるまで待機するメソッドを作成できます

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

使用法:

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

0

私にとって問題は、要素のIDを参照していたことでした。

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

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

このようにする代わりに:

@ViewChild('surveyForm') slides:IonSlides;

<div #surveyForm></div>

0

Ionicを使用している場合は、ionViewDidEnter()ライフサイクルフックを使用する必要があります。Ionicはいくつかの追加の要素(主にアニメーション関連)を実行します。これにより、通常、このような予期しないエラーが発生します。そのため、、などの ngOnInitに実行するものが必要ですngAfterContentInit


-1

ここに私のために働いたものがあります。

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

したがって、私は基本的に*ngIfがtrue になるまで毎秒チェックを設定し、次にに関連するものを行いElementRefます。


-3

私のために働いた解決策は、app.module.tsの宣言にディレクティブを追加することでした

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.