Угловой 2 @ViewChild in * ngIf
вопрос
каков самый элегантный способ получить @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()
метод становится видимым. Однако, прежде чем обнаружение угловых 2 изменений завершится, я не могу ссылаться на viewContainerRef
. Я обычно оборачивают все необходимые действия в setTimeout(()=>{},1)
как показано выше. Есть ли более правильный способ?
Я знаю, что есть вариант с ngAfterViewChecked
, но это вызывает слишком много бесполезных звонков.
ответ (Plunker)
8 ответов:
принятый ответ с помощью QueryList не работает для меня. Однако то, что действительно работало, использовало сеттер для ViewChild:
private contentPlaceholder: ElementRef; @ViewChild('contentPlaceholder') set content(content: ElementRef) { this.contentPlaceholder = content; }
сеттер вызывается один раз *ngIf становится true.
альтернативой для преодоления этого является запуск детектора изменений вручную.
вы сначала вводите
ChangeDetectorRef
:constructor(private changeDetector : ChangeDetectorRef) {}
затем вы вызываете его после обновления переменной, которая управляет *ngIf
show() { this.display = true; this.changeDetector.detectChanges(); }
ответы выше не работают для меня, потому что в моем проекте ngIf находится на входном элементе. Мне нужен был доступ к атрибуту nativeElement, чтобы сосредоточиться на входе, когда ngIf истинен. Там, кажется, нет nativeelement атрибут на ViewContainerRef. Вот что я сделал (после @ 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(); }); }
я использовал setTimeout перед фокусировкой, потому что ViewChild занимает секунду, чтобы быть назначенным. В противном случае он был бы неопределенным.
Как уже упоминалось другими, самым быстрым и быстрым решением является использование [hidden] вместо *ngIf, таким образом, компонент будет создан, но не виден, там для вас может быть доступ к нему, хотя это может быть не самый эффективный способ.
Это может сработать, но я не знаю, удобно ли это для вашего случая:
@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList; ngAfterViewInit() { this.viewContainerRefs.changes.subscribe(item => { if(this.viewContainerRefs.toArray().length) { // shown } }) }
моя цель состояла в том, чтобы избежать каких-либо хакерских методов, которые предполагают что-то (например, 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
. Из-за обертывания*ngIf
значение false до тех пор, пока HTTP-запрос не вернет данные, инициализация@ViewChild
элемент, случается с задержка.как работает:
combineLatest
выдает значение в первый раз только тогда, когда каждый из предоставленных наблюдаемых испускает первое значение с моментаcombineLatest
был подписан. Моя ТемаtabSetInitialized
выдает значение, когда@ViewChild
элемент создается. При этом я задерживаю выполнение кода подsubscribe
до*ngIf
получается положительно и то@ViewChild
инициализируется.конечно не забудьте отписаться на ngOnDestroy, я это делаю используя
ngUnsubscribe
тема:ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); }
у меня была аналогичная проблема. Я исправил это с помощью
hidden
такой:вместо:
<div id="layout" *ngIf="display"> <div #contentPlaceholder></div> </div>
Я:
<div id="layout" [hidden]="!display"> <div #contentPlaceholder></div> </div>
*ngIf
удаляет элемент из DOM, в то время как[hidden]
отображение свойства CSS.