【问题标题】:Angular 2 @ViewChild returns undefinedAngular 2 @ViewChild 返回未定义
【发布时间】:2017-01-08 11:06:33
【问题描述】:

我查看了几个相关的帖子和​​文档,但似乎仍然无法从 @ViewChild 获得预期的行为。

最终,我正在尝试设置 div 的滚动位置。此元素不是组件,而是我的 HTML 中的普通 div。

为此,我尝试使用@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>

我的输出很简单:我的元素:未定义。

如您所见,我目前正在尝试通过 ID 访问元素,但也尝试过类名。谁能提供更多关于 ViewChild 选择器查询的详细信息?

我还看到了将哈希“#”用作@ViewChild 使用的选择器标识符的示例——但这会导致我使用#gallery-container 时出现模板解析错误。

我想不出还有什么可能是错误的。感谢所有帮助,谢谢!

此处提供完整代码:https://github.com/aconfee/KimbyArting/tree/master/client/KimbyArting/components/portfolio-page

【问题讨论】:

  • 你试过使用属性绑定吗?我不知道你想要哪个滚动属性,但你可以试试:&lt;div [someScrollProperty]="someComponentProperty" ...&gt;。然后只需在您的组件逻辑中更新this.someComponentProperty。另请参阅stackoverflow.com/a/36224837/215945
  • 感谢您添加此内容!实际上,我一直在研究这个作为解决我最初问题的一种方式——设置滚动值。我已经制作了一个自定义指令并将其传递给所需的滚动值。我还希望该指令监视父组件何时使用 ngOnChanges 更改此值。 ngOnChanges 是在组件和指令之间绑定属性的最佳方式吗?我注意到您发送的示例的做事方式有所不同..您能否详细说明或发送有关他们正在做什么的文档?我没有意识到组件有一个指令数组。那里存在什么默认绑定?
  • ngOnChanges 是一个好方法。我发送的示例只是向您展示一个实际的绑定示例(在这种情况下使用[inScrollHeight])。不要担心该帖子中的内容投影和“检查后已更改”错误。组件与其在directives 数组中指定的指令之间没有默认绑定。您必须在 HTML 模板中声明所需的任何绑定。
  • 明白了,谢谢。我仔细阅读了它,事情变得更有意义了。

标签: dom angular scroll viewchild


【解决方案1】:

尝试在模板中使用 ref:

<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 接口)。

引用名称不能包含破折号,否则这将不起作用

【讨论】:

  • 谢谢!我之前尝试过#gallery-container,导致解析错误。我没有意识到有一个约定。我见过其他人的工作示例没有这种 ref 标识符。这是@ViewChild 唯一的期望吗?
  • 得到您的编辑,谢谢!虽然,在我上面的代码中,你会看到我已经实现了它。我之前也尝试过使用 ref 语法......只是没有意识到它必须是驼峰式的。
  • 必须是驼峰式的吗?那么帕斯卡大小写(#GalleryContainer)无效?
  • 我认为只有 - 引起问题,而不是其他外壳
  • FWIW,接口(现在)命名为AfterViewInit,没有On 前缀。
【解决方案2】:

有时,如果在您访问组件时该组件尚未初始化,您会收到一条错误消息,指出子组件未定义。

但是,即使您在 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
    });
  }
}

【讨论】:

  • 这是关于 *ngIf 的非常重要的一点。如果您正在异步加载数据并隐藏部分 UI,那么它不仅不可见,而且由于它不存在而无法绑定!
  • 谢谢@Simon_Weaver。在这种情况下使用 *ngIf 给我带来了一些问题,我通过应用此解决方案最终解决了这些问题。
【解决方案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 使用 DOM 元素的 .ts 文件代码

      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('galleryContainer') galleryContainer: ElementRef;
      
          ngAfterViewInit(){
              console.log('My element: ' + this.galleryContainer);
          }
      }
      

      这是你的 .html 文件代码

          <div #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>
      

      希望对你有帮助!

      【讨论】:

      • 在为元素创建 id 时不要使用破折号或减号
      【解决方案6】:

      另一种可能的情况是,当您在 angular 或 ionic 框架中使用 typescript 并从具有类似 javascript 签名的函数中调用 ViewChild 元素时。请改用类似函数签名的打字稿(例如,使用粗箭头 =>)。 例如

      accessViewChild(){
          console.log(this.galleryContainer.nativeElement.innerHTML);
      }
      

      会显示打印未定义

      accessViewChild = ()=>{
          console.log(this.galleryContainer.nativeElement.innerHTML);
      }
      

      将打印内部 html。

      【讨论】:

      • 胖箭头不是 TypeScript 特有的。两者都可以在 TypeScript 中使用。
      猜你喜欢
      • 2019-07-28
      • 2016-04-29
      • 2019-10-19
      • 2020-03-25
      • 2017-03-11
      • 2023-03-21
      • 2019-10-03
      • 2018-05-14
      相关资源
      最近更新 更多