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);
}