@ दृश्य में * ngIf


215

सवाल

@ViewChildटेम्प्लेट में संबंधित तत्व दिखाए जाने के बाद प्राप्त करने का सबसे सुरुचिपूर्ण तरीका क्या है ?

नीचे एक उदाहरण है। इसके अलावा प्लंकर उपलब्ध है।

टेम्पलेट:

<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()विधि कहता है तो वह दृश्यमान हो जाता है। हालांकि, कोणीय 2 परिवर्तन का पता लगाने से पहले, मैं संदर्भ नहीं दे सकता viewContainerRef। मैं आमतौर पर setTimeout(()=>{},1)ऊपर दिखाए गए अनुसार सभी आवश्यक कार्यों को लपेटता हूं । क्या अधिक सही तरीका है?

मुझे पता है कि इसके साथ एक विकल्प है ngAfterViewChecked, लेकिन यह बहुत बेकार कॉल का कारण बनता है।

उत्तर (प्लंकर)


3
क्या आपने * ngIf के बजाय [छिपे हुए] विशेषता का उपयोग करने की कोशिश की? इसने एक समान स्थिति के लिए मेरे लिए काम किया।
शारदूल

जवाबों:


335

ViewChild के लिए एक सेटर का उपयोग करें:

 private contentPlaceholder: ElementRef;

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

एक बार *ngIfहो जाने पर सेटर को एक तत्व संदर्भ के साथ बुलाया जाता है true

ध्यान दें, कोणीय 8 के लिए आपको यह सुनिश्चित करना होगा कि { static: false }कौन सा अन्य Agnular संस्करणों में एक डिफ़ॉल्ट सेटिंग है:

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

ध्यान दें: यदि सामग्रीधारक एक घटक है, तो आप ElementRef को अपने घटक वर्ग में बदल सकते हैं:

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

27
ध्यान दें कि इस सेटर अपरिभाषित सामग्री के साथ शुरू में कहा जाता है, अगर सेटर में कुछ ऐसा करने से अशक्त के लिए जाँच
रेसेप

1
अच्छा जवाब है, लेकिन contentPlaceholderहै ElementRefनहीं ViewContainerRef
डेवलपर

6
आप सेटर को कैसे बुलाते हैं?
लिएंड्रो क्यूसैक

2
@LeandroCusack को यह स्वतः प्राप्त हो जाता है जब कोणीय मिलता है <div #contentPlaceholder></div>। तकनीकी रूप से आप इसे किसी भी अन्य सेटर की तरह मैन्युअल रूप से कॉल कर सकते हैं, this.content = someElementRefलेकिन मैं यह नहीं देखता कि आप ऐसा क्यों करना चाहते हैं।
संसद

3
अभी जो भी इस पर आता है, उसके लिए एक उपयोगी नोट - आपको @ViewChild ('myComponent', {static: false}) की आवश्यकता है जहां कुंजी बिट स्थिर: गलत है, जो इसे अलग-अलग इनपुट लेने की अनुमति देता है।
nospamthanks

107

इसे दूर करने के लिए एक विकल्प मैन्युअल रूप से परिवर्तन डिटेक्टर चला रहा है।

आप पहले इंजेक्ट करें ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}

फिर आप इसे * ngIf को नियंत्रित करने वाले चर को अपडेट करने के बाद कहते हैं

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

1
धन्यवाद! मैं स्वीकृत उत्तर का उपयोग कर रहा था लेकिन यह अभी भी एक त्रुटि पैदा कर रहा था क्योंकि बच्चे तब भी अपरिभाषित थे जब मैंने कुछ समय बाद उनका उपयोग करने की कोशिश की थी onInit(), इसलिए मैंने detectChangesकिसी भी बाल फ़ंक्शन को कॉल करने से पहले जोड़ा और इसे ठीक कर दिया। (मैंने स्वीकार किए गए उत्तर और इस उत्तर दोनों का उपयोग किया)
मिनिक 510

सुपर सहायक! धन्यवाद!
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
शुक्रिया Satatoslav। ऊपर सब कुछ करने की कोशिश की लेकिन केवल आपके समाधान ने काम किया।
पीटर ड्रिनानन

इसने मेरे लिए भी काम किया (जैसा कि व्यूचिल्रेन ट्रिक है)। यह एक कोणीय 8 के लिए अधिक सहज और आसान है।
एलेक्स

2
एक आकर्षण की तरह काम किया :)
संदीप के नायर

1
यह नवीनतम संस्करण के लिए स्वीकृत उत्तर होना चाहिए।
कृष्ण प्रसट

मैं उपयोग कर रहा हूँ <mat-horizontal-stepper *ngIf="viewMode === DialogModeEnum['New']" linear #stepper, @ViewChild('stepper', {static: true}) private stepper: MatStepper;और this.changeDetector.detectChanges();यह अभी भी काम नहीं करता है।
पॉल स्ट्रूपेकिस

21

ऊपर दिए गए उत्तर मेरे काम नहीं आए क्योंकि मेरी परियोजना में, ngIf एक इनपुट तत्व पर है। मुझे इनपुट पर ध्यान केंद्रित करने के लिए नेटइमेंट विशेषता तक पहुंच की आवश्यकता है जब एनजीआईएफ सच है। ऐसा लगता है कि ViewContainerRef पर कोई मूल विशेषता नहीं है। यहाँ मैंने जो किया है ( @ दृश्य-प्रक्रिया प्रलेखन के बाद ):

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

मैंने फोकस करने से पहले setTimeout का उपयोग किया क्योंकि ViewChild को असाइन होने में एक सेकंड लगता है। अन्यथा यह अपरिभाषित होगा।


2
मेरे लिए 0 का एक सेटटाइमआउट () काम किया। मेरे एनजीआईएफ द्वारा छिपाया गया मेरा तत्व बीच में सेट एसेट इनपुट () फ़ंक्शन की आवश्यकता के बिना एक सेटटाइमआउट के बाद सही ढंग से बाध्य था।
विल शेवर

आप शॉर्सेटसेट में पता लगा सकते हैं () और टाइमआउट का उपयोग नहीं करना है।
राइट्सऑन मायचिन

यह कैसा उत्तर है? ओपी पहले से ही एक का उपयोग कर उल्लेख किया है setTimeout? I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?
जुआन मेंडेस

12

जैसा कि अन्य द्वारा उल्लेख किया गया था, सबसे तेज़ और सबसे तेज़ समाधान * एनजीआईएफ * के बजाय [छिपी] का उपयोग करना है, इस तरह से घटक बनाया जाएगा, लेकिन दिखाई नहीं देगा, आपके लिए वहां तक ​​पहुंच हो सकती है, हालांकि यह सबसे कुशल नहीं हो सकता है मार्ग।


1
आपको यह ध्यान रखना होगा कि यदि "प्रदर्शन" ब्लॉक का नहीं है तो "[छिपा हुआ]" का उपयोग करना काम नहीं कर सकता है। बेहतर उपयोग [style.display] = "स्थिति? '': 'कोई नहीं"
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पहले से ही आरंभिक है, लेकिन अभी तक आइटम शामिल नहीं हैं। लगता है मुझे यह गलत याद है।
गुंटर ज़ोचबॉएर

मुझे माफ कर दो मैं गलत था। AfterViewInitवास्तव में काम करता है। मैंने लोगों को भ्रमित न करने के लिए अपनी सभी टिप्पणियां हटा दी हैं। यहाँ एक कार्यशील प्लंकर है: plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview
sinedsem

1
यह वास्तव में एक अच्छा जवाब है। यह काम करता है और मैं अब इसका उपयोग कर रहा हूं। धन्यवाद!
कोंस्टेंटिन

1
यह मेरे लिए कोणीय 7 से 8. के ​​उन्नयन के बाद काम करता है। किसी कारण से, अपग्रेड ने घटक को बाद में दृश्य में अपरिभाषित होने का कारण बना दिया, यहां तक ​​कि स्थैतिक का उपयोग करने के साथ: नए व्यूचाइल्ड सिंटैक्स के प्रति झूठ जब घटक एक एनजीआईएफ में लपेटा गया था। यह भी ध्यान दें कि QueryList को अब इस QueryList की तरह एक प्रकार की आवश्यकता है <YourComponentType>;
एलेक्स

से संबंधित परिवर्तन हो सकता है constकी पैरामीटरViewChild
गुंटर Zöchbauer

9

एक और त्वरित "ट्रिक" (आसान समाधान) * एनजीआईएफ के बजाय [हिडन] टैग का उपयोग करना है, बस यह जानना महत्वपूर्ण है कि उस स्थिति में कोणीय वस्तु का निर्माण करें और इसे कक्षा के तहत पेंट करें: छिपा हुआ है यही कारण है कि ViewChild एक समस्या के बिना काम करता है । इसलिए यह ध्यान रखना महत्वपूर्ण है कि आपको भारी या महंगी वस्तुओं पर छिपी हुई चीजों का उपयोग नहीं करना चाहिए जो प्रदर्शन के मुद्दे का कारण बन सकती हैं

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

अगर वह छिपा हुआ दूसरे के अंदर है तो कई चीजों को बदलने की जरूरत है
VIKAS KOHLI

6

मेरा लक्ष्य किसी भी हैक करने वाले तरीकों से बचना था जो कुछ मान लेते हैं (जैसे सेटटाइमआउट) और मैंने शीर्ष पर 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*ngIfजब तक HTTP अनुरोध डेटा को वापस नहीं करता है, तब तक एक रैपिंग गलत होने के कारण , @ViewChildतत्व का प्रारंभ एक देरी से होता है।

यह कैसे काम करता है: combineLatest पहली बार केवल तभी मान का उत्सर्जन करता है जब प्रदान की गई वेधशालाओं में से प्रत्येक उस क्षण के combineLatestलिए सदस्यता लेने के बाद से पहले मूल्य का उत्सर्जन करता है। tabSetInitializedजब @ViewChildतत्व सेट किया जा रहा है तो मेरा विषय एक मूल्य का उत्सर्जन करता है । इसके अलावा, मैं subscribeतब तक कोड के निष्पादन में देरी करता हूं जब तक कि *ngIfसकारात्मक नहीं @ViewChildहो जाता है और प्रारंभिक हो जाता है।

बेशक ngOnDestroy पर सदस्यता समाप्त करने के लिए मत भूलना, मैं इसे ngUnsubscribeविषय का उपयोग कर रहा हूं :

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

बहुत बहुत धन्यवाद मैं एक ही मुद्दा था, tabSet और ngIf के साथ, आपकी विधि ने मुझे बहुत समय और सिरदर्द से बचाया। चीयर्स m8;)
एक्सिट्लल एल

3

एक सरल संस्करण, मेरे पास Google मैप्स जेएस एसडीके का उपयोग करते समय एक समान मुद्दा था।

मेरा समाधान निकालने के लिए था divऔर ViewChildयह स्वयं का बच्चा घटक है जो मूल घटक में उपयोग किए जाने पर एक का उपयोग करके छुपा / प्रदर्शित करने में सक्षम था *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 टेम्पलेट में मौजूद था, जिसका अर्थ है कि आउटलेट एक एनजीएफ के अंदर था। इस तरह से हर बार कोणीय ने तत्व #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>

1

मुझे लगता है कि लॉश से डेफर का उपयोग करना विशेष रूप से मेरे मामले में बहुत मायने रखता है जहां मेरा पाइप के @ViewChild()अंदर थाasync


0

कोणीय 8 पर कार्य करना ChangeDector को आयात करने की आवश्यकता नहीं है

ngIf आपको तत्व को लोड नहीं करने देता है और आपके आवेदन में अधिक तनाव जोड़ने से बचता है। यहां बताया गया है कि कैसे मैंने इसे चेंजडेक्टर के बिना चलाया

elem: ElementRef;

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

फिर जब मैं अपने एनजीआई मान को बदलकर सत्य मानूंगा तो मैं इसके लिए सेटटाइमआउट का उपयोग करूंगा, इसके लिए केवल अगले परिवर्तन चक्र की प्रतीक्षा करनी चाहिए:

  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: falsehackery

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

यह मेरे लिए काम करता है अगर मैं 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();
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.