@LihatChild di * ngIf


215

Pertanyaan

Apa cara paling elegan untuk mendapatkan @ViewChildsetelah elemen yang sesuai dalam template ditampilkan?

Di bawah ini adalah contohnya. Juga Plunker tersedia.

Templat:

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

Komponen:

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

Saya memiliki komponen dengan isinya disembunyikan secara default. Ketika seseorang memanggil show()metode itu menjadi terlihat. Namun, sebelum deteksi perubahan Angular 2 selesai, saya tidak bisa merujuk viewContainerRef. Saya biasanya memasukkan semua tindakan yang diperlukan setTimeout(()=>{},1)seperti yang ditunjukkan di atas. Apakah ada cara yang lebih benar?

Saya tahu ada opsi dengan ngAfterViewChecked, tetapi itu menyebabkan terlalu banyak panggilan tidak berguna.

JAWABAN (Plunker)


3
apakah Anda mencoba menggunakan atribut [tersembunyi] alih-alih * ngIf? Itu bekerja untuk saya untuk situasi yang sama.
Shardul

Jawaban:


335

Gunakan setter untuk ViewChild:

 private contentPlaceholder: ElementRef;

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

Setter disebut dengan referensi elemen begitu *ngIfmenjadi true.

Catatan, untuk Angular 8 Anda harus memastikan untuk mengatur { static: false }, yang merupakan pengaturan default di versi Agnular lainnya:

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

Catatan: jika contentPlaceholder adalah komponen, Anda bisa mengubah ElementRef ke kelas komponen Anda:

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

27
perhatikan bahwa penyetel ini disebut awalnya dengan konten yang tidak ditentukan, jadi periksa null jika melakukan sesuatu dalam penyetel
Recep

1
Baik jawabannya, tapi contentPlaceholderini ElementReftidak ViewContainerRef.
developer033

6
Bagaimana Anda memanggil setter?
Leandro Cusack

2
@LeandroCusack dipanggil secara otomatis ketika Angular menemukan <div #contentPlaceholder></div>. Secara teknis Anda dapat memanggilnya secara manual seperti setter lainnya this.content = someElementReftetapi saya tidak mengerti mengapa Anda ingin melakukan itu.
parlemen

3
Hanya catatan bermanfaat untuk siapa saja yang menemukan ini sekarang - Anda harus memiliki @ViewChild ('myComponent', {static: false}) di mana bit kunci adalah static: false, yang memungkinkannya untuk mengambil input yang berbeda.
nospamthanks

108

Alternatif untuk mengatasi ini adalah menjalankan detektor perubahan secara manual.

Anda pertama-tama menyuntikkan ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}

Kemudian Anda menyebutnya setelah memperbarui variabel yang mengontrol * ngIf

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

1
Terima kasih! Saya menggunakan jawaban yang diterima tetapi masih menyebabkan kesalahan karena anak-anak masih tidak terdefinisi ketika saya mencoba menggunakannya beberapa waktu kemudian onInit(), jadi saya menambahkan detectChangessebelum memanggil fungsi anak dan memperbaikinya. (Saya menggunakan jawaban yang diterima dan jawaban ini)
Minyc510

Sangat membantu! Terima kasih!
AppDreamer

57

8 Sudut

Anda harus menambahkan { static: false }sebagai opsi kedua untuk @ViewChild. Ini menyebabkan hasil kueri diselesaikan setelah deteksi perubahan berjalan, memungkinkan Anda @ViewChilduntuk diperbarui setelah nilai berubah.

Contoh:

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

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


3
Terima kasih Sviatoslav. Mencoba semuanya di atas tetapi hanya solusi Anda yang bekerja.
Peter Drinnan

Ini juga bekerja untuk saya (seperti trik viewchildren). Yang ini lebih intuitif dan lebih mudah untuk sudut 8.
Alex

2
Bekerja seperti pesona :)
Sandeep K Nair

1
Ini harus menjadi jawaban yang diterima untuk versi terbaru.
Krishna Prashatt

Saya menggunakan <mat-horizontal-stepper *ngIf="viewMode === DialogModeEnum['New']" linear #stepper, @ViewChild('stepper', {static: true}) private stepper: MatStepper;dan this.changeDetector.detectChanges();dan itu masih tidak berhasil.
Paul Strupeikis

21

Jawaban di atas tidak bekerja untuk saya karena dalam proyek saya, ngIf ada pada elemen input. Saya membutuhkan akses ke atribut nativeElement untuk fokus pada input ketika ngIf benar. Tampaknya tidak ada atribut nativeElement di ViewContainerRef. Inilah yang saya lakukan (mengikuti dokumentasi @ViewChild ):

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

Saya menggunakan setTimeout sebelum fokus karena ViewChild membutuhkan beberapa detik untuk ditugaskan. Kalau tidak, itu tidak akan ditentukan.


2
SetTimeout () dari 0 bekerja untuk saya. Elemen saya disembunyikan oleh ngIf saya terikat dengan benar setelah setTimeout, tanpa perlu fungsi set assetInput () di tengah.
Will Shaver

Anda dapat mendeteksi Perubahan di showAsset () dan tidak harus menggunakan batas waktu.
WrksOnMyMachine

Bagaimana ini jawabannya? OP sudah disebutkan menggunakan setTimeout? I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?
Juan Mendes

12

Seperti yang disebutkan oleh yang lain, solusi tercepat dan tercepat adalah dengan menggunakan [disembunyikan] daripada * ngIf, dengan cara ini komponen akan dibuat tetapi tidak terlihat, karena Anda dapat memiliki akses ke sana, meskipun itu mungkin bukan yang paling efisien cara.


1
Anda harus perhatikan bahwa menggunakan "[tersembunyi]" mungkin tidak berfungsi jika elemennya bukan dari "display: block". lebih baik gunakan [style.display] = "condition? '': 'none'"
Félix Brunet

10

Ini bisa berhasil, tetapi saya tidak tahu apakah ini cocok untuk kasus Anda:

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

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

1
Bisakah Anda mencoba ngAfterViewInit()alih-alih ngOnInit(). Saya berasumsi bahwa viewContainerRefsini sudah diinisialisasi tetapi belum mengandung item. Sepertinya saya ingat ini salah.
Günter Zöchbauer

Maaf aku salah. AfterViewInitsebenarnya bekerja. Saya telah menghapus semua komentar saya agar tidak membingungkan orang. Ini adalah Plunker yang berfungsi: plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview
sinedsem

1
Ini sebenarnya jawaban yang bagus. Ini berfungsi dan saya menggunakan ini sekarang. Terima kasih!
Konstantin

1
Ini bekerja untuk saya setelah upgrade dari sudut 7 ke 8. Untuk beberapa alasan, upgrade menyebabkan komponen tidak terdefinisi di afterViewInit bahkan dengan menggunakan static: false per sintaks ViewChild baru ketika komponen dibungkus dengan ngIf. Perhatikan juga bahwa QueryList memerlukan jenis sekarang seperti QueryList ini <YourComponentType>;
Alex

Mungkin perubahan yang terkait dengan constparameterViewChild
Günter Zöchbauer

9

"Trik" cepat lainnya (solusi mudah) adalah hanya menggunakan tag [tersembunyi] alih-alih * ngIf, hanya penting untuk mengetahui bahwa dalam kasus itu Angular membangun objek dan melukisnya di bawah kelas: disembunyikan inilah sebabnya ViewChild bekerja tanpa masalah . Jadi penting untuk diingat bahwa Anda tidak boleh menggunakan tersembunyi pada barang-barang berat atau mahal yang dapat menyebabkan masalah kinerja

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

Jika yang tersembunyi ada di dalam yang lain jika kemudian perlu mengubah banyak hal
VIKAS KOHLI

6

Tujuan saya adalah untuk menghindari metode peretasan yang mengasumsikan sesuatu (misalnya setTimeout) dan saya akhirnya menerapkan solusi yang diterima dengan sedikit rasa RxJS di atas:

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

Skenario saya: Saya ingin menjalankan aksi pada @ViewChildelemen tergantung pada router queryParams. Karena pembungkus *ngIfmenjadi salah sampai permintaan HTTP mengembalikan data, inisialisasi @ViewChildelemen terjadi dengan penundaan.

Bagaimana cara kerjanya: combineLatest memancarkan nilai untuk pertama kalinya hanya ketika masing-masing Observables yang disediakan memancarkan nilai pertama sejak saat combineLatestitu berlangganan. Subjek saya tabSetInitializedmemancarkan nilai saat @ViewChildelemen disetel. Dengan itu, saya menunda eksekusi kode di bawah subscribesampai *ngIfternyata positif dan @ViewChilddiinisialisasi.

Tentu saja jangan lupa berhenti berlangganan di ngOnDestroy, saya melakukannya menggunakan ngUnsubscribeSubjek:

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

terima kasih banyak saya sudah memiliki masalah yang sama, dengan tabSet & ngIf, metode Anda menghemat banyak waktu dan sakit kepala. Cheers m8;)
Exitl0l

3

Versi yang disederhanakan, saya memiliki masalah yang serupa dengan ini ketika menggunakan Google Maps JS SDK.

Solusi saya adalah untuk mengekstrak divdan ViewChildke dalam komponen anak itu sendiri yang ketika digunakan dalam komponen induk dapat disembunyikan / ditampilkan menggunakan *ngIf.

Sebelum

HomePageComponent Templat

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

HomePageComponent Komponen

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

Setelah

MapComponent Templat

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

MapComponent Komponen

@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 Templat

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

HomePageComponent Komponen

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

1

Dalam kasus saya, saya perlu memuat seluruh modul hanya ketika div ada dalam template, artinya outlet berada di dalam sebuah ngif. Dengan cara ini, setiap sudut mendeteksi elemen #geolocalisationOutlet, ia membuat komponen di dalamnya. Modul ini hanya memuat sekali juga.

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

Bekerja pada Angular 8 Tidak perlu mengimpor ChangeDector

ngIf memungkinkan Anda untuk tidak memuat elemen dan menghindari menambahkan lebih banyak tekanan ke aplikasi Anda. Inilah cara saya menjalankannya tanpa ChangeDetector

elem: ElementRef;

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

Kemudian ketika saya mengubah nilai ngIf saya menjadi benar, saya akan menggunakan setTimeout seperti ini untuk menunggu hanya untuk siklus perubahan berikutnya:

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

Ini juga memungkinkan saya untuk menghindari menggunakan perpustakaan atau impor tambahan.


0

untuk Angular 8 - campuran pemeriksaan nol dan @ViewChild static: falseperetasan

untuk kontrol paging menunggu data async

@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

Ini berfungsi untuk saya jika saya menggunakan ChangeDetectorRef di Angular 9

@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();
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.