Angular 2 @ViewChild注释返回undefined

时间:2016-01-22 12:50:08

标签: typescript angular

我正在努力学习Angular 2。

我想使用 @ViewChild 注释从父组件访问子组件。

这里有一些代码行:

BodyContent.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(){};
 }

FilterTiles.ts

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

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

BodyContent.html

<h1>Tiles loaded</h1>
<div *ngFor="#tile of tiles" class="col-md-4">
     ... stuff ...
</div>

FilterTiles.html

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

   } 
}

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

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

ft

问题在于,当我尝试将undefined写入控制台日志时,我得到**I would need a simple script that will zip a file using vb script ''basically my requirement is like below ,当我尝试在“tiles”数组中推送内容时,当然会出现异常: '没有属性区块为“undefined”'

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

有什么建议吗?感谢

21 个答案:

答案 0 :(得分:251)

我有一个类似的问题,并认为我发布以防其他人犯了同样的错误。首先,要考虑的一件事是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
    }
}

希望有所帮助。

修改
如下面的@Ashg所述,解决方案是使用@ViewChildren代替@ViewChild

答案 1 :(得分:93)

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

<强> HTML:

   <section class="well" *ngIf="LookupData != null">
       <h4 class="ra-well-title">Results</h4>
       <kendo-grid #searchGrid> </kendo-grid>
   </section>

组件代码

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


    }
}

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

答案 2 :(得分:18)

这对我有用。

我的组件名为&#39; my-component&#39;,例如,使用* ngIf =&#34; showMe&#34; 像这样:

<my-component [showMe]="showMe" *ngIf="showMe"></my-component>

因此,当组件初始化时,组件尚未显示,直到&#34; showMe&#34;是真的。因此,我的@ViewChild引用都是未定义的。

这是我使用@ViewChildren和它返回的QueryList的地方。见angular article on QueryList and a @ViewChildren usage demo

您可以使用@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

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

答案 3 :(得分:6)

我的解决方法是使用[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";
    }
  }
....

答案 4 :(得分:3)

这适用于我,请参阅下面的示例。

import {Component, ViewChild, ElementRef} from 'angular2/core';

@Component({
    selector: 'app',
    template:  `
        <a (click)="toggle($event)">Toggle</a>
        <div *ngIf="visible">
          <input #control name="value" [(ngModel)]="value" type="text" />
        </div>
    `,
})

export class AppComponent {

    private elementRef: ElementRef;
    @ViewChild('control') set controlElRef(elementRef: ElementRef) {
      this.elementRef = elementRef;
    }

    visible:boolean;

    toggle($event: Event) {
      this.visible = !this.visible;
      if(this.visible) {
        setTimeout(() => { this.elementRef.nativeElement.focus(); });
      }
    }

}

答案 5 :(得分:3)

我的解决方法是用[hidden]替换* ngIf。下行是代码DOM中存在的所有子组件。但是按照我的要求工作。

答案 6 :(得分:3)

必须有效。

但正如 GünterZöchbauer 所说,模板中一定存在其他一些问题。我创造了有点Relevant-Plunkr-Answer。请检查浏览器的控制台。

<强> boot.ts

@Component({
selector: 'my-app'
, template: `<div> <h1> BodyContent </h1></div>

      <filter></filter>

      <button (click)="onClickSidebar()">Click Me</button>
  `
, directives: [FilterTiles] 
})


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

    public onClickSidebar() {
        console.log(this.ft);

        this.ft.tiles.push("entered");
    } 
}

<强> filterTiles.ts

@Component({
     selector: 'filter',
    template: '<div> <h4>Filter tiles </h4></div>'
 })


 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }

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

...谢谢

答案 7 :(得分:2)

我的解决方法是将ngIf从子组件外部移动到包含整个html部分的div的子组件内部。这样它在需要时仍然被隐藏,但能够加载组件,我可以在父级中引用它。

答案 8 :(得分:2)

我遇到了类似的问题,其中ViewChild位于switch子句内部,该子句在引用之前未加载viewChild元素。我以一种半hacky方式解决了它,但将ViewChild引用包含在立即执行的setTimeout中(即0ms)

答案 9 :(得分:2)

我修复了它,只是在设置可见组件后添加SetTimeout

我的HTML:

<input #txtBus *ngIf[show]>

我的组件JS

@Component({
  selector: "app-topbar",
  templateUrl: "./topbar.component.html",
  styleUrls: ["./topbar.component.scss"]
})
export class TopbarComponent implements OnInit {

  public show:boolean=false;

  @ViewChild("txtBus") private inputBusRef: ElementRef;

  constructor() {

  }

  ngOnInit() {}

  ngOnDestroy(): void {

  }


  showInput() {
    this.show = true;
    setTimeout(()=>{
      this.inputBusRef.nativeElement.focus();
    },500);
  }
}

答案 10 :(得分:1)

对于我来说,我知道子组件始终存在,但是想在子初始化之前更改状态以保存工作。

我选择测试子组件直到它出现并立即进行更改,这为我节省了子组件的更改周期。

export class GroupResultsReportComponent implements OnInit {

    @ViewChild(ChildComponent) childComp: ChildComponent;

    ngOnInit(): void {
        this.WhenReady(() => this.childComp, () => { this.childComp.showBar = true; });
    }

    /**
     * Executes the work, once the test returns truthy
     * @param test a function that will return truthy once the work function is able to execute 
     * @param work a function that will execute after the test function returns truthy
     */
    private WhenReady(test: Function, work: Function) {
        if (test()) work();
        else setTimeout(this.WhenReady.bind(window, test, work));
    }
}

或者,您可以为setTimeout添加最大尝试次数或添加几毫秒的延迟。 setTimeout有效地将该函数置于待处理操作列表的底部。

答案 11 :(得分:1)

在我的情况下,我有一个使用ViewChild的输入变量设置器,并且ViewChild在* ngIf指令内,因此该设置器尝试在* ngIf呈现之前对其进行访问(如果没有* ngIf,它可以正常工作,但如果始终通过* ngIf =“ true”)将其设置为true,则无法使用。

为了解决这个问题,我使用Rxjs来确保对ViewChild的任何引用都一直等到视图启动为止。首先,创建一个在视图初始化后完成的主题。

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

  ngAfterViewInit(): void {
    this._viewInitWaiter$.complete();
  }
}

然后,创建一个函数,该函数在主题完成后执行lambda。

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

最后,确保对ViewChild的引用使用此功能。

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

@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;

答案 12 :(得分:0)

一种通用方法:

您可以创建一个方法,直到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
  })

答案 13 :(得分:0)

对我来说,问题是我在元素上引用了ID。

@ViewChild('survey-form') slides:IonSlides;

<div id="survey-form"></div>

而不是这样:

@ViewChild('surveyForm') slides:IonSlides;

<div #surveyForm></div>

答案 14 :(得分:0)

解决我问题的方法是确保将static设置为false

@ViewChild(ClrForm, {static: false}) clrForm;

关闭static后,@ViewChild指令更改时,Angular会更新*ngIf引用。

答案 15 :(得分:0)

如果您使用的是Ionic,则需要使用ionViewDidEnter()生命周期挂钩。 Ionic运行了一些其他内容(主要与动画相关),这些内容通常会导致此类意外错误,因此需要在之后 ngOnInitngAfterContentInit等运行的东西。

答案 16 :(得分:0)

对我来说,使用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
  }
}

答案 17 :(得分:0)

对于Angular: 更改* ngIf为HTML中的显示样式“阻止”或“无”。

selector: 'app',
template:  `
    <controls [style.display]="controlsOn ? 'block' : 'none'"></controls>
    <slideshow (mousemove)="onMouseMove()"></slideshow>
`,
directives: [SlideshowComponent, ControlsComponent]

答案 18 :(得分:0)

如果 *ngIf="show" 阻止渲染 ViewChild 并且您在 show 变为真后需要 ViewChild,它帮助我在设置 {{1 }} 对。

之后 *ngIf 创建组件并渲染 ViewChild,s.t.你可以在之后使用它。刚刚输入了一个快速示例代码。

show

这是不是很糟糕,或者为什么没有人提出它?

答案 19 :(得分:-1)

最适合我的解决方案是在app.module.ts

声明中添加指令

答案 20 :(得分:-1)

这对我有用。

foreach ($_FILES as $key => $file) {
    if ($file['size'] > 0) {
        $time = strtotime('today');
        if (!file_exists('./files/' . $time)) {
            mkdir('./files/' . $time);
            chmod('./files/' . $time, 0777);
        }
        $tmp_name = str_replace('/Applications/MAMP/tmp/php', '', $file['tmp_name']);
        move_uploaded_file($file['tmp_name'], './files/' . $time . $tmp_name);
        $_FILES[$key]['tmp_name'] = './files/' . $time . $tmp_name;
    }
}

因此,我基本上每秒进行一次检查,直到@ViewChild('mapSearch', { read: ElementRef }) mapInput: ElementRef; ngAfterViewInit() { interval(1000).pipe( switchMap(() => of(this.mapInput)), filter(response => response instanceof ElementRef), take(1)) .subscribe((input: ElementRef) => { //do stuff }); } 变为true,然后再处理与*ngIf相关的事情。