@ NgIf içinde @ViewChild


215

Soru

@ViewChildŞablondaki ilgili öğenin gösterilmesinden sonra elde etmenin en zarif yolu nedir ?

Aşağıda bir örnek verilmiştir. Ayrıca Plunker mevcuttur.

Şablon:

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

Bileşen:

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

İçeriği varsayılan olarak gizli olan bir bileşenim var. Birisi show()yöntemi çağırdığında görünür hale gelir. Ancak, Açısal 2 değişiklik tespiti tamamlanmadan referans veremiyorum viewContainerRef. Genellikle tüm gerekli işlemleri setTimeout(()=>{},1)yukarıda gösterildiği gibi sararım. Daha doğru bir yol var mı?

İle bir seçenek olduğunu biliyorum ngAfterViewChecked, ama çok işe yaramaz çağrılara neden olur.

CEVAP (Dalgıç)


3
* ngIf yerine [gizli] özelliğini kullanmayı denediniz mi? Benzer bir durum için benim için çalıştı.
Shardul

Yanıtlar:


335

ViewChild için bir ayarlayıcı kullanın:

 private contentPlaceholder: ElementRef;

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

Bir öğe referans kez ile ayarlayıcı olarak adlandırılır *ngIfolur true.

Açısal 8 için { static: false }, diğer Agnular sürümlerinde varsayılan bir ayar olan ayarlardan emin olmanız gerekir :

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

Not: contentPlaceholder bir bileşense, ElementRef'i bileşen Sınıfınızla değiştirebilirsiniz:

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

27
Bu ayarlayıcının başlangıçta tanımlanmamış içerikle çağrıldığını unutmayın, bu nedenle ayarlayıcıda bir şey yapıyorsanız null olup olmadığını kontrol edin
Recep

1
Cevap İyi, ama contentPlaceholderbir ElementRefdeğil ViewContainerRef.
developer033

6
Setter'ı nasıl çağırırsınız?
Leandro Cusack

2
@LeandroCusack, Angular bulduğunda otomatik olarak çağrılır <div #contentPlaceholder></div>. Teknik olarak diğer ayarlayıcılar gibi manuel olarak da çağırabilirsiniz, this.content = someElementRefancak bunu neden yapmak isteyeceğinizi anlamıyorum.
parlamento

3
Şimdi karşılaşan herkes için yararlı bir not - anahtar bitinin statik: false olduğu @ViewChild'e ('myComponent', {static: false}) sahip olmanız gerekir, bu da farklı girişler almasını sağlar.
nospamthanks

107

Bunun üstesinden gelmek için bir alternatif, değişim dedektörünü manuel olarak çalıştırmaktır.

Önce aşağıdakileri enjekte edersiniz ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}

Sonra * ngIf denetleyen değişkeni güncelledikten sonra çağırırsınız

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

1
Teşekkürler! Kabul edilen cevabı kullanıyordum ama yine de hataya neden oluyordu çünkü çocuklar onları bir süre sonra kullanmaya çalıştığımda hala tanımsızdı onInit(), bu yüzden detectChangesherhangi bir çocuk işlevini çağırmadan önce ekledim ve düzeltildi. (Hem kabul edilen yanıtı hem de bu yanıtı kullandım)
Minyc510

Süper yararlı! Teşekkürler!
AppDreamer

55

Açısal 8+

İçin { static: false }ikinci bir seçenek olarak eklemelisiniz @ViewChild. Bu, değişiklik algılama çalıştırıldıktan sonra sorgu sonuçlarının çözümlenmesine neden olur @ViewChildve değer değiştikten sonra güncelleştirilmenizi sağlar.

Misal:

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 örneği: https://stackblitz.com/edit/angular-d8ezsn


3
Teşekkürler Sviatoslav. Yukarıdaki her şeyi denedim ama sadece çözümünüz işe yaradı.
Peter Drinnan

Bu da benim için çalıştı (tıpkı görüntü hile gibi). Bu, açısal 8 için daha sezgisel ve daha kolay.
Alex

2
Bir cazibe gibi çalıştı :)
Sandeep K Nair

1
Bu en son sürüm için kabul edilen cevap olmalıdır.
Krishna Prashatt

Ben kullanıyorum <mat-horizontal-stepper *ngIf="viewMode === DialogModeEnum['New']" linear #stepper, @ViewChild('stepper', {static: true}) private stepper: MatStepper;ve this.changeDetector.detectChanges();ve hala çalışmıyor.
Paul Strupeikis

21

Yukarıdaki cevaplar benim için işe yaramadı çünkü projemde, ngIf bir giriş elemanı üzerinde. NgIf doğru olduğunda girişe odaklanmak için nativeElement özniteliğine erişim gerekiyordu. ViewContainerRef üzerinde hiçbir nativeElement özniteliği yok gibi görünüyor. Yaptığım şey ( @ViewChild belgelerini takip ederek ):

<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'in atanması gereken bir saniye aldığı için odaklanmadan önce setTimeout'u kullandım. Aksi takdirde tanımsız olur.


2
0 için setTimeout () yöntemi benim için çalıştı. Benim ngIf tarafından gizlenmiş benim öğenin ortasında set assetInput () işlevine gerek kalmadan, bir setTimeout sonra doğru bağlandı.
Will Shaver

ShowAsset () öğesinde ChangeChanges'i algılayabilir ve zaman aşımını kullanmanız gerekmez.
WrksOnMyMachine

Bu nasıl bir cevap? OP zaten bir setTimeout? I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?
Juan Mendes

12

Başkaları tarafından da belirtildiği gibi, en hızlı ve en hızlı çözüm * ngIf yerine [gizli] kullanmaktır, bu şekilde bileşen oluşturulacak, ancak görünmeyecektir, orada sizin için erişebilirsiniz, ancak en verimli olmayabilir yol.


1
öğe "display: block" değilse "[hidden]" kullanmanın işe yaramayabileceğini unutmayın. daha iyi kullan [style.display] = "condition? '': 'none'"
Félix Brunet

10

Bu işe yarayabilir, ancak davanız için uygun olup olmadığını bilmiyorum:

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

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

1
ngAfterViewInit()Bunun yerine lütfen deneyebilir misiniz ngOnInit()? viewContainerRefsZaten başlatıldığını, ancak henüz öğe içermediğini varsaydım . Bunu yanlış hatırladım.
Günter Zöchbauer

Üzgünüm yanılmışım. AfterViewInitaslında işe yarıyor. İnsanları karıştırmamak için tüm yorumlarımı kaldırdım. İşte çalışan bir Plunker: plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview
sinedsem

1
Bu aslında iyi bir cevap. Çalışıyor ve şimdi kullanıyorum. Teşekkürler!
Konstantin

1
Bu, açısal 7'den 8'e yükseltmeden sonra benim için çalıştı. Bazı nedenlerden dolayı, yükseltme, bileşen bir ngIf'e sarıldığında yeni ViewChild sözdizimi başına static: false kullanarak bile afterViewInit'te bileşenin tanımsız olmasına neden oldu. Ayrıca, QueryList'in şimdi bu QueryList <YourComponentType> gibi bir tür gerektirdiğine dikkat edin;
Alex

constViewChild
Günter Zöchbauer

9

Başka bir hızlı "hile" (kolay çözüm) sadece * ngIf yerine [gizli] etiketini kullanmaktır, sadece bu durumda Angular'ın nesneyi oluşturup sınıf altında boyadığını bilmek önemlidir: gizli ViewChild'in sorunsuz çalışmasının nedeni budur . Bu nedenle, performans sorununa neden olabilecek ağır veya pahalı öğelerde gizli kullanmamanız gerektiğini unutmayın.

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

Eğer bu gizli başka bir şeyin içinde ise, o zaman birçok şeyi değiştirmeniz gerekiyor
VIKAS KOHLI

6

Amacım bir şey varsayalım (örneğin setTimeout) herhangi bir hacky yöntemlerden kaçınmak oldu ve ben kabul edilen çözümü biraz RxJS lezzet üstüne uygulamak ile sona erdi :

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

Senaryom:@ViewChild Yönlendiriciye bağlı olarak bir öğe üzerinde bir eylem başlatmak istedim queryParams. *ngIfHTTP isteği verileri döndürene kadar bir sarma yanlış @ViewChildolduğundan , öğenin başlatılması geciktirilir.

O nasıl çalışır: combineLatest yalnızca sağlanan Gözlemlenebilir öğelerin her biri combineLatestabone olduğu andan itibaren ilk değeri verdiğinde ilk kez bir değer yayar . Benim Konusu tabSetInitializedbir değeri yayar @ViewChildeleman kuruluyor. Bundan subscribesonra *ngIf, pozitif dönüşler ve@ViewChild başlangıçlar başlayana .

Tabii ki ngOnDestroy aboneliği iptal etmeyi unutmayın, bunu ngUnsubscribeKonu kullanarak yapmak :

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

çok teşekkürler aynı sorunu yaşadım, tabSet & ngIf ile, yöntem bana çok zaman ve baş ağrısı kurtardı. Şerefe m8;)
Exitl0l

3

Basitleştirilmiş bir sürüm olarak, Google Maps JS SDK'sını kullanırken buna benzer bir sorun yaşadım.

Benim çözümüm ayıklamak oldu divve ViewChild/ bir yardımıyla gösterilen o hid olmak mümkün ebeveyn bileşeninde kullanılan kendi çocuk bileşeni oldu sahiptir içine *ngIf.

Önce

HomePageComponent şablon

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

HomePageComponent Bileşen

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

Sonra

MapComponent şablon

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

MapComponent Bileşen

@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 şablon

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

HomePageComponent Bileşen

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

1

Benim durumumda, yalnızca şablonda div bulunduğunda tüm modülü yüklemem gerekiyordu, yani çıkış bir ngif içindeydi. Bu şekilde açısal her zaman #geolocalisationOutlet öğesini içinde bileşeni yarattığını tespit etti. Modül sadece bir kez yüklenir.

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

Açısal 8 üzerinde çalışma ChangeDector'u içe aktarmaya gerek yok

ngIf öğeyi yüklememenize ve uygulamanıza daha fazla stres eklemekten kaçınmanıza izin verir. ChangeDetector olmadan çalıştırmayı nasıl başardım

elem: ElementRef;

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

Sonra benim ngIf değeri doğruluk olarak değiştirdiğimde sadece bir sonraki değişiklik döngüsü için beklemek için böyle setTimeout kullanırdım:

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

Bu ayrıca herhangi bir ek kitaplık veya içe aktarma kullanmaktan kaçınmamı sağladı.


0

için 8 açısal boş kontrol ve bir karışım - @ViewChild static: falsehackery

zaman uyumsuz verileri bekleyen bir sayfalama kontrolü için

@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

Açısal 9'da ChangeDetectorRef kullanırsam benim için çalışır

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