Угловой 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.