Pytanie
Jaki jest najbardziej elegancki sposób uzyskania @ViewChild
po pokazaniu odpowiedniego elementu w szablonie?
Poniżej znajduje się przykład. Dostępny również Plunker .
Szablon:
<div id="layout" *ngIf="display">
<div #contentPlaceholder></div>
</div>
Składnik:
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);
}
}
Mam komponent z domyślnie ukrytą zawartością. Kiedy ktoś wywoła show()
metodę, staje się widoczna. Jednak zanim zakończy się wykrywanie zmian w Angular 2, nie mogę się do niego odwoływać viewContainerRef
. Zwykle zawijam wszystkie wymagane działania, setTimeout(()=>{},1)
jak pokazano powyżej. Czy jest bardziej poprawny sposób?
Wiem, że istnieje opcja ngAfterViewChecked
, ale powoduje zbyt wiele bezużytecznych połączeń.
ODPOWIEDŹ (Plunker)
angular
angular2-changedetection
sinedsem
źródło
źródło
Odpowiedzi:
Użyj narzędzia do ustawiania ViewChild:
Seter jest wywoływany z referencją elementu, gdy
*ngIf
staje siętrue
.Uwaga: w przypadku Angulara 8 musisz się upewnić, że
{ static: false }
jest to ustawienie domyślne w innych wersjach Agnulara:Uwaga: jeśli contentPlaceholder jest składnikiem, możesz zmienić ElementRef na klasę składnika:
źródło
contentPlaceholder
jest .ElementRef
ViewContainerRef
<div #contentPlaceholder></div>
. Technicznie możesz to nazwać ręcznie, jak każdy inny setter,this.content = someElementRef
ale nie rozumiem, dlaczego chcesz to zrobić.Alternatywą do rozwiązania tego problemu jest ręczne uruchomienie detektora zmian.
Najpierw wstrzykujesz
ChangeDetectorRef
:Następnie wywołujesz go po aktualizacji zmiennej kontrolującej * ngIf
źródło
onInit()
, więc dodałemdetectChanges
przed wywołaniem dowolnej funkcji potomnej i naprawiłem ją. (Użyłem zarówno zaakceptowanej odpowiedzi, jak i tej odpowiedzi)Angular 8+
Powinieneś dodać
{ static: false }
jako drugą opcję dla@ViewChild
. Powoduje to, że wyniki zapytania zostaną rozwiązane po uruchomieniu wykrywania zmian, umożliwiając@ViewChild
aktualizację po zmianie wartości.Przykład:
Przykład Stackblitz: https://stackblitz.com/edit/angular-d8ezsn
źródło
<mat-horizontal-stepper *ngIf="viewMode === DialogModeEnum['New']" linear #stepper
,@ViewChild('stepper', {static: true}) private stepper: MatStepper;
athis.changeDetector.detectChanges();
i nadal nie działa.Powyższe odpowiedzi nie działały dla mnie, ponieważ w moim projekcie ngIf znajduje się na elemencie wejściowym. Potrzebowałem dostępu do atrybutu nativeElement, aby skupić się na danych wejściowych, gdy ngIf jest prawdą. Wygląda na to, że nie ma atrybutu nativeElement na ViewContainerRef. Oto co zrobiłem (zgodnie z dokumentacją @ViewChild ):
Użyłem setTimeout przed ustawieniem ostrości, ponieważ ViewChild zajmuje sekundę. W przeciwnym razie byłoby niezdefiniowane.
źródło
setTimeout
?I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?
Jak wspomniano wcześniej, najszybszym i najszybszym rozwiązaniem jest użycie [ukryty] zamiast * ng Jeśli w ten sposób komponent zostanie utworzony, ale niewidoczny, możesz mieć do niego dostęp, chociaż może nie być to najbardziej wydajny sposób.
źródło
Może to działać, ale nie wiem, czy jest to wygodne w twoim przypadku:
źródło
ngAfterViewInit()
zamiastngOnInit()
. Zakładam, żeviewContainerRefs
jest już zainicjowany, ale jeszcze nie zawiera elementów. Wygląda na to, że źle to zapamiętałem.AfterViewInit
faktycznie działa. Usunąłem wszystkie moje komentarze, aby nie mylić ludzi. Oto działający Plunker: plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=previewconst
parametremViewChild
Kolejną szybką „sztuczką” (łatwym rozwiązaniem) jest po prostu użycie tagu [ukryty] zamiast * ngIf, po prostu ważne jest, aby wiedzieć, że w tym przypadku Angular zbuduje obiekt i pomaluje go pod klasą: ukryty dlatego ViewChild działa bez problemu . Dlatego ważne jest, aby pamiętać, że nie należy używać ukrytych przedmiotów ciężkich lub drogich, które mogą powodować problemy z wydajnością
źródło
Moim celem było uniknięcie jakichkolwiek podejrzanych metod, które zakładają coś (np. SetTimeout) i ostatecznie wdrożyłem zaakceptowane rozwiązanie z odrobiną smaku RxJS na górze:
Mój scenariusz: chciałem uruchomić akcję na
@ViewChild
elemencie w zależności od routeraqueryParams
. Ponieważ zawijanie*ngIf
jest fałszywe, dopóki żądanie HTTP nie zwróci danych, inicjalizacja@ViewChild
elementu następuje z opóźnieniem.Jak to działa:
combineLatest
emituje wartość po raz pierwszy tylko wtedy, gdy każdy z dostarczonych Obserwatorów emituje pierwszą wartość od momentucombineLatest
subskrypcji. Mój przedmiottabSetInitialized
emituje wartość, gdy@ViewChild
element jest ustawiany. W związku z tym opóźniam wykonanie kodu,subscribe
dopóki nie*ngIf
zmieni się na dodatnią i nie@ViewChild
zostanie zainicjowana.Oczywiście nie zapomnij zrezygnować z subskrypcji ngOnDestroy, robię to za pomocą
ngUnsubscribe
tematu:źródło
Uproszczona wersja, miałem podobny problem do tego podczas korzystania z Google Maps JS SDK.
Moim rozwiązaniem było wyodrębnienie
div
iViewChild
umieszczenie go we własnym komponencie podrzędnym, który po zastosowaniu w komponencie nadrzędnym mógł zostać ukryty / wyświetlony przy użyciu*ngIf
.Przed
HomePageComponent
SzablonHomePageComponent
SkładnikPo
MapComponent
SzablonMapComponent
SkładnikHomePageComponent
SzablonHomePageComponent
Składnikźródło
W moim przypadku musiałem załadować cały moduł tylko wtedy, gdy div istniał w szablonie, co oznacza, że wylot znajdował się w ngif. W ten sposób za każdym razem, gdy kątowy wykrywał element #geolocalizationOutlet, tworzył w nim komponent. Moduł ładuje się tylko raz.
źródło
Myślę, że użycie odroczenia z lodash ma sens, szczególnie w moim przypadku, gdy
@ViewChild()
byłem wasync
rurzeźródło
Praca na Angular 8 Nie trzeba importować ChangeDector
ngIf pozwala nie ładować elementu i unikać dodawania dodatkowych obciążeń do aplikacji. Oto jak działam bez ChangeDetectora
Następnie, gdy zmienię wartość ngIf na prawdę, użyję setTimeout w ten sposób, aby poczekał tylko na następny cykl zmian:
Pozwoliło mi to również uniknąć korzystania z jakichkolwiek dodatkowych bibliotek lub importów.
źródło
dla Angular 8 - połączenie sprawdzania zera i
@ViewChild
static: false
hakowaniadla kontrolki stronicowania oczekującej na dane asynchroniczne
źródło
Działa dla mnie, jeśli używam ChangeDetectorRef w Angular 9
źródło