* ngIf의 @ViewChild


215

질문

@ViewChild템플릿의 해당 요소가 표시된 후 얻는 가장 우아한 방법은 무엇입니까 ?

아래는 예입니다. 또한 Plunker 가능합니다.

주형:

<div id="layout" *ngIf="display">
    <div #contentPlaceholder></div>
</div>

구성 요소:

export class AppComponent {

    display = false;
    @ViewChild('contentPlaceholder', {read: ViewContainerRef}) viewContainerRef;

    show() {
        this.display = true;
        console.log(this.viewContainerRef); // undefined
        setTimeout(()=> {
            console.log(this.viewContainerRef); // OK
        }, 1);
    }
}

내용이 기본적으로 숨겨져있는 구성 요소가 있습니다. 누군가 show()메서드를 호출 하면 표시됩니다. 그러나 Angular 2 변경 감지가 완료되기 전에를 참조 할 수 없습니다 viewContainerRef. 일반적으로 setTimeout(()=>{},1)위의 그림과 같이 필요한 모든 작업을 래핑합니다 . 더 정확한 방법이 있습니까?

에 옵션이 있다는 것을 알고 ngAfterViewChecked있지만 너무 많은 쓸모없는 호출이 발생합니다.

답변 (Plunker)


3
* ngIf 대신 [hidden] 속성을 사용해 보셨습니까? 비슷한 상황에서 나를 위해 일했습니다.
Shardul

답변:


335

ViewChild에 setter를 사용하십시오.

 private contentPlaceholder: ElementRef;

 @ViewChild('contentPlaceholder') set content(content: ElementRef) {
    if(content) { // initially setter gets called with undefined
        this.contentPlaceholder = content;
    }
 }

setter는 일단 요소 참조로 호출 *ngIf됩니다 true.

Angular 8의 경우 { static: false }다른 Agnular 버전의 기본 설정 인을 설정해야합니다.

 @ViewChild('contentPlaceholder', { static: false })

참고 : contentPlaceholder가 컴포넌트 인 경우 ElementRef를 컴포넌트 클래스로 변경할 수 있습니다.

  private contentPlaceholder: MyCustomComponent;
  @ViewChild('contentPlaceholder') set content(content: MyCustomComponent) {
     if(content) { // initially setter gets called with undefined
          this.contentPlaceholder = content;
     }
  }

27
이 세터는 처음에 정의되지 않은 내용으로 호출되므로 세터에서 무언가를 수행하는 경우 null을 확인하십시오.
Recep

1
대답은 좋은,하지만 contentPlaceholder입니다 ElementRef없습니다 ViewContainerRef.
developer033

6
세터를 어떻게 불러요?
Leandro Cusack

2
@LeandroCusack Angular가 발견하면 자동으로 호출됩니다 <div #contentPlaceholder></div>. 기술적으로 다른 세터처럼 수동으로 호출 할 수는 this.content = someElementRef있지만 왜 그렇게하고 싶은지는 모르겠습니다.
의회

3
지금이 문제를 겪고있는 사람에게는 유용한 참고 사항입니다. 키 비트가 static : false 인 경우 @ViewChild ( 'myComponent', {static : false})가 필요하므로 다른 입력을받을 수 있습니다.
nospamthanks

107

이를 극복하기위한 대안은 변경 감지기를 수동으로 실행하는 것입니다.

먼저 ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}

그런 다음 * ngIf를 제어하는 ​​변수를 업데이트 한 후 호출합니다.

show() {
        this.display = true;
        this.changeDetector.detectChanges();
    }

1
감사! 허용 된 답변을 사용하고 있었지만 이후 onInit()에 언젠가 그들을 사용하려고 할 때 자식이 정의되지 않았기 때문에 여전히 오류가 발생했기 때문에 detectChanges자식 함수를 호출하기 전에를 추가하고 수정했습니다. (허용 된 답변과이 답변을 모두 사용했습니다)
Minyc510

정말 도움이되었습니다! 감사!
AppDreamer

55

앵귤러 8+

{ static: false }대한 두 번째 옵션으로 추가해야합니다 @ViewChild. 이로 인해 변경 탐지가 실행 된 쿼리 결과가 해결 @ViewChild되어 값이 변경된 후 업데이트 될 수 있습니다 .

예:

export class AppComponent {
    @ViewChild('contentPlaceholder', { static: false }) contentPlaceholder: ElementRef;

    display = false;

    constructor(private changeDetectorRef: ChangeDetectorRef) {
    }

    show() {
        this.display = true;
        this.changeDetectorRef.detectChanges(); // not required
        console.log(this.contentPlaceholder);
    }
}

Stackblitz 예 : https://stackblitz.com/edit/angular-d8ezsn


3
스 비아 토 슬라브 감사합니다. 위의 모든 것을 시도했지만 솔루션 만 작동했습니다.
피터 Drinnan 1

이것은 viewchildren 트릭과 마찬가지로 나를 위해 일했습니다. 이것은 각도 8에 대해보다 직관적이고 쉽습니다.
Alex

2
매력처럼 일했다 :)
Sandeep K Nair

1
이것은 최신 버전에 허용되는 답변이어야합니다.
Krishna Prashatt

내가 사용하고 <mat-horizontal-stepper *ngIf="viewMode === DialogModeEnum['New']" linear #stepper, @ViewChild('stepper', {static: true}) private stepper: MatStepper;그리고 this.changeDetector.detectChanges();그것은 여전히 작동하지 않습니다.
Paul Strupeikis

21

내 프로젝트에서 ngIf가 입력 요소에 있기 때문에 위의 답변이 효과가 없었습니다. ngIf가 true 일 때 입력에 집중하기 위해 nativeElement 속성에 액세스해야했습니다. ViewContainerRef에 nativeElement 속성이없는 것 같습니다. 여기 내가 한 일이 있습니다 ( @ViewChild documentation 따라 ) :

<button (click)='showAsset()'>Add Asset</button>
<div *ngIf='showAssetInput'>
    <input #assetInput />
</div>

...

private assetInputElRef:ElementRef;
@ViewChild('assetInput') set assetInput(elRef: ElementRef) {
    this.assetInputElRef = elRef;
}

...

showAsset() {
    this.showAssetInput = true;
    setTimeout(() => { this.assetInputElRef.nativeElement.focus(); });
}

ViewChild가 할당되기까지 초가 걸리기 때문에 초점을 맞추기 전에 setTimeout을 사용했습니다. 그렇지 않으면 정의되지 않습니다.


2
0의 setTimeout ()이 나를 위해 일했습니다. 내 ngIf에 의해 숨겨진 내 요소는 setTimeout 후에 올바르게 바인딩되어 중간에 set assetInput () 함수가 필요하지 않습니다.
윌 면도기

showAsset ()에서 변경 사항을 감지 할 수 있으며 시간 종료를 사용할 필요는 없습니다.
WrksOnMyMachine 2016 년

이것은 어떻게 대답합니까? OP는 이미 setTimeout? I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?
Juan Mendes

12

다른 사람들이 언급했듯이, 가장 빠르고 빠른 해결책은 * ngIf 대신 [hidden]을 사용하는 것입니다.이 방법으로 구성 요소가 생성되지만 보이지 않지만 액세스 할 수는 있지만 가장 효율적이지 않을 수도 있습니다 방법.


1
요소가 "display : block"이 아닌 경우 "[hidden]"을 사용하면 작동하지 않을 수 있습니다. 더 나은 사용 [style.display] = "condition? '': 'none'"
Félix Brunet

10

이것은 작동 할 수 있지만 귀하의 경우에 편리한 지 모르겠습니다.

@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;

ngAfterViewInit() {
 this.viewContainerRefs.changes.subscribe(item => {
   if(this.viewContainerRefs.toArray().length) {
     // shown
   }
 })
}

1
ngAfterViewInit()대신 사용해보십시오 ngOnInit(). viewContainerRefs이미 초기화되었지만 아직 항목이 포함되어 있지 않다고 가정했습니다 . 내가 이것을 잘못 기억 한 것 같습니다.
Günter Zöchbauer

미안해, 내가 틀렸어. AfterViewInit실제로 작동합니다. 사람들을 혼동하지 않기 위해 모든 의견을 삭제했습니다. 작동하는 플 런커는 다음과 같습니다. plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview
sinedsem

1
이것은 실제로 좋은 대답입니다. 작동하며 지금 사용하고 있습니다. 감사!
Konstantin

1
이것은 각도 7에서 8로 업그레이드 한 후에 저에게 효과적이었습니다. 어떤 이유로 업그레이드하면 구성 요소가 ngIf에 래핑되었을 때 새 ViewChild 구문에 대해 static : false를 사용하더라도 afterViewInit에서 구성 요소가 정의되지 않았습니다. 또한 QueryList에는 이제이 QueryList <YourComponentType>과 같은 유형이 필요합니다.
Alex

받는 사람 관련 변경 될 수 const의 매개 변수ViewChild
귄터 Zöchbauer

9

또 다른 빠른 "속임수"(쉬운 해결책) 는 * ngIf 대신 [hidden] 태그를 사용하는 것입니다.이 경우 Angular가 객체를 빌드하고 클래스 아래에 페인트한다는 것을 아는 것이 중요합니다. 숨겨진 이유는 ViewChild가 문제없이 작동하는 이유입니다 . 따라서 성능 문제를 일으킬 수있는 무겁거나 비싼 품목에는 숨겨서는 안된다는 점을 명심해야합니다.

  <div class="addTable" [hidden]="CONDITION">

숨겨진 그 다른에 내부의 경우, 많은 일을 변경해야하는 경우
비 카스 콜리에게

6

내 목표는 무엇인가를 생각 어떤 해키 방법 (예에서는 setTimeout) 피할 수 있었고, 난 약간의와 허용 솔루션을 구현 결국 RxJS의 상단에 풍미를 :

  private ngUnsubscribe = new Subject();
  private tabSetInitialized = new Subject();
  public tabSet: TabsetComponent;
  @ViewChild('tabSet') set setTabSet(tabset: TabsetComponent) {
    if (!!tabSet) {
      this.tabSet = tabSet;
      this.tabSetInitialized.next();
    }
  }

  ngOnInit() {
    combineLatest(
      this.route.queryParams,
      this.tabSetInitialized
    ).pipe(
      takeUntil(this.ngUnsubscribe)
    ).subscribe(([queryParams, isTabSetInitialized]) => {
      let tab = [undefined, 'translate', 'versions'].indexOf(queryParams['view']);
      this.tabSet.tabs[tab > -1 ? tab : 0].active = true;
    });
  }

내 시나리오 :@ViewChild 라우터에 따라 요소에 대한 작업을 시작하고 싶었습니다 queryParams. *ngIfHTTP 요청이 데이터를 리턴 할 때까지 랩핑 이 false이므로 @ViewChild요소 초기화가 지연됩니다.

작동 방식 : combineLatest 순간 combineLatest이 구독 된 이후 제공된 Observable 각각이 첫 번째 값을 방출 할 때만 처음으로 값을 내 보냅니다 . 요소를 설정할 tabSetInitialized때 내 주제가 값을 내 보냅니다 @ViewChild. 그것으로, 나는 턴이 긍정적이되고 초기화 subscribe될 때까지 코드 실행을 지연시킵니다 .*ngIf@ViewChild

물론 ngOnDestroy를 구독 취소하는 것을 잊지 마십시오. ngUnsubscribeSubject를 사용하여 수행합니다 .

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

고맙게도 tabSet & ngIf와 같은 문제가 발생하여 메서드가 많은 시간과 두통을 절약했습니다. 건배 m8;)
Exitl0l

3

단순화 된 버전으로 Google Maps JS SDK를 사용할 때 이와 비슷한 문제가 발생했습니다.

내 솔루션은을 추출했다 divViewChild/를 사용하여 표시가 HID 될 수 상위 구성 요소에 사용 된 자신의 아이 컴퍼넌트이었다 것으로 *ngIf.

전에

HomePageComponent 주형

<div *ngIf="showMap">
  <div #map id="map" class="map-container"></div>
</div>

HomePageComponent 구성 요소

@ViewChild('map') public mapElement: ElementRef; 

public ionViewDidLoad() {
    this.loadMap();
});

private loadMap() {

  const latLng = new google.maps.LatLng(-1234, 4567);
  const mapOptions = {
    center: latLng,
    zoom: 15,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
  };
   this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}

public toggleMap() {
  this.showMap = !this.showMap;
 }

MapComponent 주형

 <div>
  <div #map id="map" class="map-container"></div>
</div>

MapComponent 구성 요소

@ViewChild('map') public mapElement: ElementRef; 

public ngOnInit() {
    this.loadMap();
});

private loadMap() {

  const latLng = new google.maps.LatLng(-1234, 4567);
  const mapOptions = {
    center: latLng,
    zoom: 15,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
  };
   this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}

HomePageComponent 주형

<map *ngIf="showMap"></map>

HomePageComponent 구성 요소

public toggleMap() {
  this.showMap = !this.showMap;
 }

1

필자의 경우 div가 템플릿에 존재할 때만 전체 모듈을로드해야했습니다. 즉, 콘센트가 ngif 안에 있음을 의미합니다. 이런 식으로 angular가 요소 #geolocalisationOutlet을 감지 할 때마다 내부에 컴포넌트를 생성했습니다. 모듈은 한 번만로드됩니다.

constructor(
    public wlService: WhitelabelService,
    public lmService: LeftMenuService,
    private loader: NgModuleFactoryLoader,
    private injector: Injector
) {
}

@ViewChild('geolocalisationOutlet', {read: ViewContainerRef}) set geolocalisation(geolocalisationOutlet: ViewContainerRef) {
    const path = 'src/app/components/engine/sections/geolocalisation/geolocalisation.module#GeolocalisationModule';
    this.loader.load(path).then((moduleFactory: NgModuleFactory<any>) => {
        const moduleRef = moduleFactory.create(this.injector);
        const compFactory = moduleRef.componentFactoryResolver
            .resolveComponentFactory(GeolocalisationComponent);
        if (geolocalisationOutlet && geolocalisationOutlet.length === 0) {
            geolocalisationOutlet.createComponent(compFactory);
        }
    });
}

<div *ngIf="section === 'geolocalisation'" id="geolocalisation">
     <div #geolocalisationOutlet></div>
</div>


0

Angular 8 작업 ChangeDector를 가져올 필요가 없습니다.

ngIf를 사용하면 요소를로드하지 않고 응용 프로그램에 스트레스를 더하지 않아도됩니다. ChangeDetector없이 실행하는 방법은 다음과 같습니다.

elem: ElementRef;

@ViewChild('elemOnHTML', {static: false}) set elemOnHTML(elemOnHTML: ElementRef) {
    if (!!elemOnHTML) {
      this.elem = elemOnHTML;
    }
}

그런 다음 ngIf 값을 진실로 변경하면 다음 변경주기 만 기다릴 수 있도록 다음과 같이 setTimeout을 사용합니다.

  this.showElem = true;
  console.log(this.elem); // undefined here
  setTimeout(() => {
    console.log(this.elem); // back here through ViewChild set
    this.elem.do();
  });

또한 추가 라이브러리 나 가져 오기를 사용하지 않아도됩니다.


0

위한 8 각도 널 검사 및 혼합 - @ViewChild static: false해커

비동기 데이터를 기다리는 페이징 제어

@ViewChild(MatPaginator, { static: false }) set paginator(paginator: MatPaginator) {
  if(!paginator) return;
  paginator.page.pipe(untilDestroyed(this)).subscribe(pageEvent => {
    const updated: TSearchRequest = {
      pageRef: pageEvent.pageIndex,
      pageSize: pageEvent.pageSize
    } as any;
    this.dataGridStateService.alterSearchRequest(updated);
  });
}

0

Angular 9에서 ChangeDetectorRef를 사용하면 효과가 있습니다.

@ViewChild('search', {static: false})
public searchElementRef: ElementRef;

constructor(private changeDetector: ChangeDetectorRef) {}

//then call this when this.display = true;
show() {
   this.display = true;
   this.changeDetector.detectChanges();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.