각도 2 @ViewChild 주석은 정의되지 않은 값을 반환


242

Angular 2를 배우려고합니다.

@ViewChild Annotation을 사용하여 부모 구성 요소에서 자식 구성 요소에 액세스하고 싶습니다 .

다음은 몇 줄의 코드입니다.

에서 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, 나는이 "타일"배열 안에 무언가를 추진하려고 할 때, 물론 나는 예외를 얻을 : ' "정의되지 않은"에 대한 속성 타일' .

한 가지 더 : FilterTiles 구성 요소가 html 템플릿을 볼 수 있으므로 올바르게로드 된 것 같습니다.

어떠한 제안? 감사


맞습니다. 템플릿에 문제가있을 수 있지만 질문에 포함되어 있지 않습니다.
Günter Zöchbauer

1
귄터와 동의 함. 코드와 간단한 관련 템플릿으로 플런저를 만들었으며 작동합니다. plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview 링크를 참조하십시오 . 우리는 템플릿이 필요합니다 ;-)
Thierry Templier

1
ft생성자에는 설정되지 않지만 click 이벤트 핸들러에서는 이미 설정되어 있습니다.
Günter Zöchbauer

5
변경 감지와 관련 loadAsRoot하여 알려진 문제 가 있는를 사용 하고 있습니다. loadNextToLocation또는을 사용해보십시오 loadIntoLocation.
Eric Martinez

1
문제는이었다 loadAsRoot. loadIntoLocation문제가 해결 되면 해결되었습니다. 귀하의 의견을 답변으로 작성하면 승인 된 것으로 표시 할 수 있습니다.
Andrea Ialenti

답변:


374

비슷한 문제가 있었고 다른 사람이 같은 실수를 한 경우 게시 할 것이라고 생각했습니다. 우선 고려해야 할 한 가지입니다 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를 가지고 있기 때문에 그 요소는 결국 진실에 불과하지만 요소 참조가 필요합니다. 이 문제를 해결하는 방법>
monica

4
ngIf를 사용하는 경우 자식 구성 요소가 ngAfterViewInit ()에서 '정의되지 않음'으로 나타났습니다. 긴 시간 초과를 시도했지만 여전히 효과가 없습니다. 그러나 하위 구성 요소는 나중에 사용할 수 있습니다 (예 : 클릭 이벤트 등에 대한 응답으로). ngIf를 사용하지 않고 ngAfterViewInit ()에 예상대로 정의 된 경우. 여기에 더 부모 / 자식 통신에있다 angular.io/docs/ts/latest/cookbook/...
마태 복음 Hegarty

3
대신 부트 스트랩 ngClass+ hidden클래스를 사용했습니다 ngIf. 효과가있었습니다. 감사!
Rahmathullah M

9
그것은 사용할 수있게되면이 자식 컨트롤에 대한 참조를 가져 가서 @ViewChildren를 사용하여 아래의 솔루션을 사용하여 문제를 해결하지 않습니다
Ashg

20
이것은 단지 "문제"를 증명합니다. 솔루션을 게시하지 않습니다.
Miguel Ribeiro

146

앞에서 언급 한 문제 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

3
#searchGrid 태그를 Angular2 요소 대신 일반 HTML 요소에 배치하려면 어떻게해야합니까? (예를 들어, <div #searchGrid> </ div>이고 이것은 * ngIf 블록 안에 있습니까?
Vern Jensen

1
이것은 내 유스 케이스에 대한 정답입니다! 감사합니다. ngIf =를 통해 제공되는 구성 요소에 액세스해야합니다.
Frozen_byte

1
이것은 아약스 응답, 이제는 작업에 완벽하게 *ngIf작동하며 렌더링 후 동적 구성 요소에서 ElementRef를 저장할 수 있습니다.
elporfirio

4
또한 구독에 할당 한 다음 구독을 취소하는 것을 잊지 마십시오
tam.teixeira

64

세터를 사용할 수 있습니다. @ViewChild()

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

ngIf 래퍼가 있으면 setter가 정의되지 않은 상태로 호출 된 다음 ngIf가 렌더링을 허용하면 한 번 참조로 다시 호출됩니다.

내 문제는 다른 것이 었습니다. app.modules에 "FilterTiles"가 포함 된 모듈을 포함시키지 않았습니다. 템플릿에서 오류가 발생하지 않았지만 참조는 항상 정의되지 않았습니다.


3
이것은 나를 위해 작동하지 않습니다-첫 번째는 정의되지 않았지만 참조가있는 두 번째 호출은받지 않습니다. 앱이 ng2입니다.이 ng4 + 기능입니까?
Jay Cummins

@Jay 나는이 경우 Angular에 구성 요소를 등록하지 않았기 때문이라고 생각합니다 FilterTiles. 나는 그 이유 때문에 그 문제를 전에 직면했습니다.
의회

1
html 요소에 #paginator를 사용하여 Angular 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에서 지원되지 않으므로 Top 73 솔루션은 이제 더 이상 사용되지 않습니다. IOW it Angular 4 시나리오에서 작동하지 않음
PeteZaria

5
구독을 취소하거나 사용하는 것을 잊지 말고 .take(1).subscribe()훌륭한 답변 을 보내 주셔서 감사합니다.
블레어 코놀리

2
탁월한 솔루션. ngOnChanges () 대신 ngAfterViewInit ()의 ref 변경 사항을 구독했습니다. 그러나 나는에서는 setTimeout를 추가했다 ExpressionChangedAfterChecked 오류 없애
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
이것은 거의 완벽한 답이다. 요점은 nullable 값도 확인하는 좋은 연습이다. 따라서 @ViewChild (ClrForm, {static : false}) set clrForm (clrForm : ClrForm) {if (clrForm) {this.clrForm = clrForm; }};
클라우스 클라인

나는 많은 것을 시도했고 마침내이 일이 범인이라는 것을 알았습니다.
Manish Sharma

8

이것에 대한 나의 해결책은로 대체 *ngIf 하는 것이 었 습니다 [hidden]. 단점은 모든 하위 구성 요소가 코드 DOM에 존재한다는 것입니다. 그러나 내 요구 사항을 위해 일했습니다.


5

내 경우, 나는를 사용하여 입력 변수 세터를했다 ViewChild, 그리고는 ViewChild의 내부했다 *ngIf세터가 전에 액세스를 시도 그래서, 지시문을 *ngIf포함하지 않는 렌더링 (작동 것 잘 *ngIf하지만 작업은 항상로 설정하지 않을 경우 )에 해당합니다 *ngIf="true".

해결하기 위해 Rxjs를 사용 ViewChild하여 뷰가 시작될 때까지 대기 한 참조를 확인 했습니다. 먼저 view init 후에 완료되는 주제를 작성하십시오.

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

작동해야합니다.

그러나 Günter Zöchbauer 는 템플릿에 다른 문제가 있다고 말했다. 나는 일종의 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
문제가 내 것과 같은 경우 복제하려면 <filter> </ filter> 주위의 템플릿에 * ngIf를 넣어야합니다. ngIf가 false를 반환하면 ViewChild가 연결되지 않고 null을 반환합니다.
Dan

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 요소를로드하지 않은 절 ViewChild내부에 switch있었습니다. 나는 그것을 반 해키 방식으로 해결했지만 즉시 실행되는 ViewChild참조로 래핑합니다 setTimeout(예 : 0ms)


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

놀랍게도 최대 시도 횟수를 추가하거나에 몇 ms 지연을 추가 할 수 있습니다 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()수명주기 후크 를 사용해야합니다 . 이온은 일반적으로 다음과 같은 예기치 않은 오류가 발생 추가적인 물건 (주로 애니메이션 관련), 그 실행 무언가 따라서 필요 실행 한 후 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.