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

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.

это работало на меня. Просто примените некоторую задержку после привязки данных к форме.

 setTimeout(() => { this.changeDetector.detectChanges(); }, 500);