Angular 2 @ViewChild возвращает undefined

Я просмотрел несколько связанных записей и документации, но по-прежнему не может ожидать ожидаемого поведения от @ViewChild.

В конечном счете, я пытаюсь установить положение прокрутки div. Этот элемент не является компонентом, а нормальным div в моем HTML.

Чтобы выполнить это, я пытаюсь использовать @ViewChild для получения элемента DOM, который мне нужен, и установить его значение прокрутки. (В стороне, если вы знаете лучший способ выполнить это без @ViewChild (или jQuery), ответы будут очень оценены!)

В настоящий момент @ViewChild возвращает только undefined. Пройдя какие-то фиктивные проверки:  - Я обращаюсь к своему элементу в AfterViewInit  - У меня нет других директив, таких как * ngIf или * ngFor для этого элемента.

Здесь контроллер:

import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';

@Component({
    selector: 'portfolio-page',
    templateUrl: './portfolio-page.component.html',
    styleUrls: ['./portfolio-page.component.scss']
})

export class PortfolioPageComponent implements AfterViewInit{
    @ViewChild('gallery-container') galleryContainer: ElementRef;

    ngAfterViewInit(){
        console.log('My element: ' + this.galleryContainer);
    }
}

И шаблон:

<div id='gallery-container' class='gallery-image-container'>
    <div class='gallery-padding'></div>
    <img class='gallery-image' src='{{ coverPhotoVm }}' />
    <img class='gallery-image' src='{{ imagepath }}' *ngFor='let imagepath of imagesVm' />
</div>

Мой вывод прост: My element: undefined.

Как вы можете видеть, в настоящее время я пытаюсь получить доступ к элементу по идентификатору, но попробовал имя класса. Может ли кто-нибудь предоставить более подробную информацию о том, что ожидает запрос селектора ViewChild?

Я также видел примеры, где хеш "#" используется как идентификатор выбора, который использует @ViewChild -, но это вызывает ошибку анализа шаблона для меня С# gallery-container.

Я не могу думать ни о чем другом, что может быть ошибочным здесь. Вся помощь приветствуется, спасибо!

Полный код доступен здесь: https://github.com/aconfee/KimbyArting/tree/master/client/KimbyArting/components/portfolio-page

Ответ 1

Попробуйте использовать ссылку в шаблоне вместо этого:

<div id='gallery-container' #galleryContainer class='gallery-image-container'>
    <div class='gallery-padding'></div>
    <img class='gallery-image' src='{{ coverPhotoVm }}' />
    <img class='gallery-image' src='{{ imagepath }}' *ngFor='let imagepath of imagesVm' />
</div>

И используйте имя ref в качестве аргумента:

@ViewChild('galleryContainer') galleryContainer: ElementRef;

ИЗМЕНИТЬ

Забыл упомянуть, что любой объявленный таким образом ребенок доступен только после инициализации представления. В первый раз это происходит в ngAfterViewInit (импорт и реализация интерфейса AfterViewInit).

Имя ссылки не должно содержать тире или это не будет работать

Ответ 2

Иногда, если компонент еще не инициализирован при доступе к нему, вы получаете сообщение об ошибке, указывающее, что дочерний компонент undefined.

Однако, даже если вы обращаетесь к дочернему компоненту в AfterViewInit, иногда @ViewChild все еще возвращал значение null. Проблема может быть вызвана * ngIf или другой директивой.

Решением является использование @ViewChildren вместо @ViewChild и подписка подписки на изменения, которая выполняется, когда компонент готов.

Например, если в родительском компоненте ParentComponent вы хотите получить доступ к дочернему компоненту MyComponent.

import { Component, ViewChildren, AfterViewInit, QueryList } from '@angular/core';
import { MyComponent } from './mycomponent.component';

export class ParentComponent implements AfterViewInit
{
  //other code emitted for clarity

  @ViewChildren(MyComponent) childrenComponent: QueryList<MyComponent>;

  public ngAfterViewInit(): void
  {
    this.childrenComponent.changes.subscribe((comps: QueryList<MyComponent>) =>
    {
      // Now you can access the child component
    });
  }
}

Ответ 3

Подписка на изменения на

@ViewChildren(MyComponent) childrenComponent: QueryList<MyComponent>

подтверждена работа в сочетании с setTimeout() и notifyOnChanges() и тщательная проверка null.

Любой другой подход создает ненадежные результаты и его трудно проверить.

Ответ 4

У меня была похожая проблема. В отличие от вас, мой @ViewChild вернул действительный ElementRef, но когда я попытался получить доступ к его nativeElement, он не был undefined.

Я решил это, установив id как #content, а не как #content = "ngModel".

<textarea type = "text"
    id = "content"
    name = "content"
    #content
    [(ngModel)] = "article.content"
    required></textarea>

Ответ 5

Некий общий подход:

Вы можете создать метод, который будет ждать, пока ViewChild будет готов

function waitWhileViewChildIsReady(parent: any, viewChildName: string, refreshRateSec: number = 50, maxWaitTime: number = 3000): Observable<any> {
  return interval(refreshRateSec)
    .pipe(
      takeWhile(() => !isDefined(parent[viewChildName])),
      filter(x => x === undefined),
      takeUntil(timer(maxWaitTime)),
      endWith(parent[viewChildName]),
      flatMap(v => {
        if (!parent[viewChildName]) throw new Error('ViewChild "${viewChildName}" is never ready');
        return of(!parent[viewChildName]);
      })
    );
}


function isDefined<T>(value: T | undefined | null): value is T {
  return <T>value !== undefined && <T>value !== null;
}

Использование:

  // Now you can do it in any place of your code
  waitWhileViewChildIsReady(this, 'yourViewChildName').subscribe(() =>{
      // your logic here
  })

Ответ 6

У меня была такая же проблема сегодня. Странно с помощью setTimeout у меня сработало:

ngAfterViewInit(){
    setTimeout(_ => console.log('My element: ' + this.galleryContainer), 0);
}