Angular 2 @ViewChild注释返回undefined

And*_*nti 203 typescript angular

我正在努力学习Angular 2.

我想使用@ViewChild Annotation 从父组件访问子组件.

这里有一些代码行:

BodyContent.ts我有:

import {ViewChild, Component, Injectable} from 'angular2/core';
import {FilterTiles} from '../Components/FilterTiles/FilterTiles';


@Component({
selector: 'ico-body-content'
, templateUrl: 'App/Pages/Filters/BodyContent/BodyContent.html'
, directives: [FilterTiles] 
})


export class BodyContent {
    @ViewChild(FilterTiles) ft:FilterTiles;

    public onClickSidebar(clickedElement: string) {
        console.log(this.ft);
        var startingFilter = {
            title: 'cognomi',
            values: [
                'griffin'
                , 'simpson'
            ]}
        this.ft.tiles.push(startingFilter);
    } 
}
Run Code Online (Sandbox Code Playgroud)

而在FilterTiles.ts中:

 import {Component} from 'angular2/core';


 @Component({
     selector: 'ico-filter-tiles'
    ,templateUrl: 'App/Pages/Filters/Components/FilterTiles/FilterTiles.html'
 })


 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }
Run Code Online (Sandbox Code Playgroud)

最后这里是模板(如评论中所示):

BodyContent.html

<div (click)="onClickSidebar()" class="row" style="height:200px; background-color:red;">
        <ico-filter-tiles></ico-filter-tiles>
    </div>
Run Code Online (Sandbox Code Playgroud)

FilterTiles.html

<h1>Tiles loaded</h1>
<div *ngFor="#tile of tiles" class="col-md-4">
     ... stuff ...
</div>
Run Code Online (Sandbox Code Playgroud)

FilterTiles.html模板正确加载到 ico-filter-tiles标记中(实际上我能够看到标题).

注意:BodyContent类使用DynamicComponetLoader注入另一个模板(Body):dcl.loadAsRoot(BodyContent,'#ico-bodyContent',injector):

import {ViewChild, Component, DynamicComponentLoader, Injector} from 'angular2/core';
import {Body}                 from '../../Layout/Dashboard/Body/Body';
import {BodyContent}          from './BodyContent/BodyContent';

@Component({
    selector: 'filters'
    , templateUrl: 'App/Pages/Filters/Filters.html'
    , directives: [Body, Sidebar, Navbar]
})


export class Filters {

    constructor(dcl: DynamicComponentLoader, injector: Injector) {
       dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector);
       dcl.loadAsRoot(SidebarContent, '#ico-sidebarContent', injector);

   } 
}
Run Code Online (Sandbox Code Playgroud)

问题是,当我尝试写入ft控制台日志时,我得到了undefined,当我尝试在"tiles"数组中推送一些内容时,我当然会遇到异常:'没有属性区块为"undefined"'.

还有一件事:FilterTiles组件似乎正确加载,因为我能够看到它的html模板.

有什么建议吗?谢谢

ken*_*ell 312

我有一个类似的问题,并认为我发布以防其他人犯了同样的错误.首先,要考虑的一件事是AfterViewInit; 您需要等待视图初始化才能访问您的视图@ViewChild.但是,我@ViewChild仍然返回null.问题是我的*ngIf.该*ngIf指令正在杀死我的控件组件,所以我无法引用它.

import {Component, ViewChild, OnInit, AfterViewInit} from 'angular2/core';
import {ControlsComponent} from './controls/controls.component';
import {SlideshowComponent} from './slideshow/slideshow.component';

@Component({
    selector: 'app',
    template:  `
        <controls *ngIf="controlsOn"></controls>
        <slideshow (mousemove)="onMouseMove()"></slideshow>
    `,
    directives: [SlideshowComponent, ControlsComponent]
})

export class AppComponent {
    @ViewChild(ControlsComponent) controls:ControlsComponent;

    controlsOn:boolean = false;

    ngOnInit() {
        console.log('on init', this.controls);
        // this returns undefined
    }

    ngAfterViewInit() {
        console.log('on after view init', this.controls);
        // this returns null
    }

    onMouseMove(event) {
         this.controls.show();
         // throws an error because controls is null
    }
}
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.

编辑
正如下面@Ashg所提到的,解决方案是使用@ViewChildren而不是@ViewChild.

  • 这只是证明了"问题",对吧?它没有发布解决方案. (13认同)
  • @kenecaswell所以你找到了解决问题的更好方法.我也面临同样的问题.我有很多*ngIf,所以元素将在唯一的真实之后,但我需要元素参考.任何解决这个问题的方法> (9认同)
  • 这并没有解决问题,使用下面的解决方案使用@ViewChildren获取对子控件一旦可用后的引用 (8认同)
  • 如果使用ngIf,我发现在ngAfterViewInit()中子组件是'undefined'.我尝试了很长的超时但仍然没有效果.但是,子组件稍后可用(即响应点击事件等).如果我不使用ngIf,它在ngAfterViewInit()中被定义为预期.家长/孩子的沟通还有更多内容https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child (3认同)
  • 我不明白为什么选择这个答案。它没有提供任何解决方案... (3认同)
  • 我使用bootstrap`ngClass` +`hidden`类而不是`ngIf`.那很有效.谢谢! (2认同)
  • 这如何解决问题? (2认同)
  • 由 ngIf 搞砸的问题可以通过使用 viewchildren 来解决。这篇文章有一个对我有用的解决方案。https://expertcodeblog.wordpress.com/2018/01/12/angular-resolve-error-viewchild-annotation-returns-undefined/ (2认同)

Ash*_*shg 115

前面提到的问题是导致视图未定义的ngIf.答案是使用ViewChildren而不是ViewChild.我有类似的问题,我不希望在加载所有参考数据之前显示网格.

HTML:

   <section class="well" *ngIf="LookupData != null">
       <h4 class="ra-well-title">Results</h4>
       <kendo-grid #searchGrid> </kendo-grid>
   </section>
Run Code Online (Sandbox Code Playgroud)

组件代码

import { Component, ViewChildren, OnInit, AfterViewInit, QueryList  } from '@angular/core';
import { GridComponent } from '@progress/kendo-angular-grid';

export class SearchComponent implements OnInit, AfterViewInit
{
    //other code emitted for clarity

    @ViewChildren("searchGrid")
    public Grids: QueryList<GridComponent>

    private SearchGrid: GridComponent

    public ngAfterViewInit(): void
    {

        this.Grids.changes.subscribe((comps: QueryList <GridComponent>) =>
        {
            this.SearchGrid = comps.first;
        });


    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用ViewChildren来监听更改.在这种情况下,任何带有#searchGrid引用的子代.希望这可以帮助.

  • 在某些情况下,我想补充一点,例如。您必须使用`this.SearchGrid`属性的语法,如`setTimeout(()=&gt; {///此处是您的代码},1); 避免异常:检查表达式后,表达式已更改 (3认同)
  • 另外,不要忘记将其分配给订阅,然后取消订阅 (3认同)
  • 如果要将#searchGrid标记放在普通HTML元素而不是Angular2元素上,该怎么做?(例如,<div #searchGrid> </ div>,这是在*ngIf块内? (2认同)

par*_*ent 59

你可以使用一个setter @ViewChild()

@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
    console.log(tiles);
};
Run Code Online (Sandbox Code Playgroud)

如果你有一个ngIf包装器,将使用undefined调用setter,然后在ngIf允许它渲染时再使用引用.

我的问题是其他问题.我没有在我的app.modules中包含包含我的"FilterTiles"的模块.模板没有抛出错误,但引用始终未定义.

  • 适用于 Angular 8,在 html 元素和注释上使用 #paginator,如 `@ViewChild('paginator', {static: false})` (3认同)
  • 这对我不起作用-我得到第一个未定义的消息,但没有得到带有引用的第二个调用。应用程式是ng2 ...这是ng4 +功能吗? (2认同)

Pau*_*eau 44

解决我的问题的是确保static设置为false.

@ViewChild(ClrForm, {static: false}) clrForm;
Run Code Online (Sandbox Code Playgroud)

随着static关闭,@ViewChild参考得到由角时更新的*ngIf指令改变。

  • 这几乎是一个完美的答案,只是指出检查可空值是一个很好的做法,所以我们最终得到这样的结果: @ViewChild(ClrForm, { static: false }) set clrForm(clrForm: ClrForm) { if (clrForm) { this.clrForm = clrForm; } }; (5认同)
  • `{static:false}` 是默认值(从 Angular9 iirc 开始) (5认同)
  • 我尝试了很多事情,最后发现罪魁祸首就是这个东西。 (4认同)
  • 恕我直言,这是解决此问题的最简单、最快的解决方案。 (2认同)

Jos*_*yck 23

这对我有用.

例如,名为"my-component"的组件使用*ngIf ="showMe"显示,如下所示:

<my-component [showMe]="showMe" *ngIf="showMe"></my-component>
Run Code Online (Sandbox Code Playgroud)

因此,当组件初始化时,组件尚未显示,直到"showMe"为真.因此,我的@ViewChild引用都是未定义的.

这是我使用@ViewChildren和它返回的QueryList的地方.请参阅QueryList上的角度文章和@ViewChildren用法演示.

您可以使用@ViewChildren返回的QueryList,并使用rxjs订阅对引用项的任何更改,如下所示.@ViewChid没有这种能力.

import { Component, ViewChildren, ElementRef, OnChanges, QueryList, Input } from '@angular/core';
import 'rxjs/Rx';

@Component({
    selector: 'my-component',
    templateUrl: './my-component.component.html',
    styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnChanges {

  @ViewChildren('ref') ref: QueryList<any>; // this reference is just pointing to a template reference variable in the component html file (i.e. <div #ref></div> )
  @Input() showMe; // this is passed into my component from the parent as a    

  ngOnChanges () { // ngOnChanges is a component LifeCycle Hook that should run the following code when there is a change to the components view (like when the child elements appear in the DOM for example)
    if(showMe) // this if statement checks to see if the component has appeared becuase ngOnChanges may fire for other reasons
      this.ref.changes.subscribe( // subscribe to any changes to the ref which should change from undefined to an actual value once showMe is switched to true (which triggers *ngIf to show the component)
        (result) => {
          // console.log(result.first['_results'][0].nativeElement);                                         
          console.log(result.first.nativeElement);                                          

          // Do Stuff with referenced element here...   
        } 
      ); // end subscribe
    } // end if
  } // end onChanges 
} // end Class
Run Code Online (Sandbox Code Playgroud)

希望这有助于节省一些时间和挫折.

  • 不要忘记取消订阅,或使用`.take(1).subscribe()`,但是答案非常好,非常感谢你! (4认同)
  • 事实上,您的解决方案似乎是迄今为止列出的最佳方法.注意我们必须记住,前73个解决方案现已被弃用...因为指令:[...]声明在Angular 4中不再受支持.但它在Angular 4场景中不起作用 (3认同)
  • 优秀的解决方案。我订阅了 ngAfterViewInit() 而不是 ngOnChanges() 中的 ref 更改。但我不得不添加一个 setTimeout 来摆脱 ExpressionChangedAfterChecked 错误 (2认同)

小智 8

我的解决方法是使用[style.display] ="getControlsOnStyleDisplay()"而不是*ngIf ="controlsOn".块在那里,但不显示.

@Component({
selector: 'app',
template:  `
    <controls [style.display]="getControlsOnStyleDisplay()"></controls>
...

export class AppComponent {
  @ViewChild(ControlsComponent) controls:ControlsComponent;

  controlsOn:boolean = false;

  getControlsOnStyleDisplay() {
    if(this.controlsOn) {
      return "block";
    } else {
      return "none";
    }
  }
....
Run Code Online (Sandbox Code Playgroud)


nik*_*apa 8

在我的例子中,我有一个使用ViewChild,的输入变量 setter ,并且在指令ViewChild内部*ngIf,所以 setter 试图在*ngIf渲染之前访问它(没有*ngIf,它可以正常工作,但如果它总是设置为真*ngIf="true")。

为了解决这个问题,我使用 Rxjs 来确保ViewChild在视图启动之前对等待的任何引用。首先,创建一个在 view init 之后完成的主题。

export class MyComponent implements AfterViewInit {
  private _viewInitWaiter$ = new Subject();

  ngAfterViewInit(): void {
    this._viewInitWaiter$.complete();
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,创建一个在主题完成后接受并执行 lambda 的函数。

private _executeAfterViewInit(func: () => any): any {
  this._viewInitWaiter$.subscribe(null, null, () => {
    return func();
  })
}
Run Code Online (Sandbox Code Playgroud)

最后,确保对 ViewChild 的引用使用此函数。

@Input()
set myInput(val: any) {
    this._executeAfterViewInit(() => {
        const viewChildProperty = this.viewChild.someProperty;
        ...
    });
}

@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;
Run Code Online (Sandbox Code Playgroud)

  • 这是一个比所有设置超时废话更好的解决方案 (3认同)

suz*_*nne 7

我对这个解决办法是更换*ngIf[hidden]。缺点是所有子组件都存在于代码DOM中。但是满足了我的要求。


mic*_*yks 5

它必须有效。

\n\n

但正如G\xc3\xbcnter Z\xc3\xb6chbauer所说,模板中肯定还有其他问题。我创建了一种Relevant-Plunkr-Answer。请检查浏览器的控制台。

\n\n

启动.ts

\n\n
@Component({\nselector: \'my-app\'\n, template: `<div> <h1> BodyContent </h1></div>\n\n      <filter></filter>\n\n      <button (click)="onClickSidebar()">Click Me</button>\n  `\n, directives: [FilterTiles] \n})\n\n\nexport class BodyContent {\n    @ViewChild(FilterTiles) ft:FilterTiles;\n\n    public onClickSidebar() {\n        console.log(this.ft);\n\n        this.ft.tiles.push("entered");\n    } \n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

过滤器Tiles.ts

\n\n
@Component({\n     selector: \'filter\',\n    template: \'<div> <h4>Filter tiles </h4></div>\'\n })\n\n\n export class FilterTiles {\n     public tiles = [];\n\n     public constructor(){};\n }\n
Run Code Online (Sandbox Code Playgroud)\n\n

它就像一个魅力。请仔细检查您的标签和参考文献。

\n\n

谢谢...

\n

  • 如果问题与我的相同,要复制,您需要在模板中 &lt;filter&gt;&lt;/filter&gt; 周围放置一个 *ngIf 。显然,如果 ngIf 返回 false,ViewChild 不会连接并返回 null (3认同)

yay*_*aya 5

对我来说,使用ngAfterViewInit而不是ngOnInit解决问题:

export class AppComponent implements OnInit {
  @ViewChild('video') video;
  ngOnInit(){
    // <-- in here video is undefined
  }
  public ngAfterViewInit()
  {
    console.log(this.video.nativeElement) // <-- you can access it here
  }
}
Run Code Online (Sandbox Code Playgroud)