【问题标题】:Angular 2 @ViewChild annotation returns undefinedAngular 2 @ViewChild 注释返回未定义
【发布时间】:2016-04-29 01:54:53
【问题描述】:

我正在尝试学习 Angular 2。

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

这里有几行代码:

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

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

最后是模板(在 cmets 中建议):

BodyContent.html

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

FilterTiles.html

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

FilterTiles.html 模板已正确加载到 ico-filter-tiles 标记中(确实可以看到标题)。

注意:BodyContent 类使用DynamicComponetLoader: 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);
   } 
}

问题是,当我尝试将ft 写入控制台日志时,我得到undefined,当然,当我尝试在“tiles”数组中推送某些内容时,我会遇到异常:'没有“未定义”的属性图块

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

有什么建议吗?

【问题讨论】:

  • 看起来正确。也许与模板有关,但它不包含在您的问题中。
  • 同意君特。我用你的代码和简单的关联模板创建了一个 plunkr,它可以工作。请参阅此链接:plnkr.co/edit/KpHp5Dlmppzo1LXcutPV?p=preview。我们需要模板 ;-)
  • ft 不会在构造函数中设置,但在点击事件处理程序中已经设置。
  • 您正在使用loadAsRoot,它有一个known issue 与更改检测。只是为了确保尝试使用loadNextToLocationloadIntoLocation
  • 问题是loadAsRoot。一旦我替换为loadIntoLocation,问题就解决了。如果您将您的评论作为答案,我可以将其标记为已接受

标签: typescript angular


【解决方案1】:

我有一个类似的问题,并认为我会发布以防其他人犯同样的错误。首先,要考虑的一件事是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 below 所提到的,一个解决方案是使用@ViewChildren 而不是@ViewChild

【讨论】:

  • @kenecaswell 那么您是否找到了解决问题的更好方法。我也面临同样的问题。我有很多*ngIf,所以该元素毕竟是唯一的,但我需要元素引用。解决这个问题的任何方法>
  • 如果使用 ngIf,我发现子组件在 ngAfterViewInit() 中是“未定义的”。我尝试过长时间超时,但仍然没有效果。但是,子组件稍后可用(即响应点击事件等)。如果我不使用 ngIf 并且它在 ngAfterViewInit() 中按预期定义。这里有更多关于父/子通信的内容angular.io/docs/ts/latest/cookbook/…
  • 我使用引导程序 ngClass+hidden 类而不是 ngIf。那行得通。谢谢!
  • 这并不能解决问题,使用下面的解决方案使用@ViewChildren 获取对子控件的引用,一旦它变得可用
  • 这只是证明了“问题”,对吗?它没有发布解决方案。
【解决方案2】:

前面提到的问题是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 的子代。希望这会有所帮助。

【讨论】:

  • 在某些情况下,当您尝试更改时,我想补充一点。 this.SearchGrid 属性,您应该使用 setTimeout(()=&gt;{ ///your code here }, 1); 之类的语法来避免异常:检查后表达式已更改
  • 如果您想将#searchGrid 标签放在普通的 HTML 元素而不是 Angular2 元素上,该怎么做? (例如,
    这是在 *ngIf 块内?
  • 这是我用例的正确答案!谢谢,我需要通过 ngIf= 访问一个组件
  • 这适用于 ajax 响应,现在是 *ngIfworks,渲染后我们可以从动态组件中保存 ElementRef。
  • 也不要忘记将其分配给订阅,然后取消订阅
【解决方案3】:

你可以为@ViewChild()使用setter

@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
    console.log(tiles);
};

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

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

【讨论】:

  • 这对我不起作用——我得到第一个未定义的,但我没有得到第二个带有引用的调用。应用是 ng2...这是 ng4+ 功能吗?
  • @Jay 我相信这是因为你还没有向 Angular 注册组件,在本例中是 FilterTiles。由于这个原因,我以前遇到过这个问题。
  • 适用于 Angular 8,在 html 元素上使用#paginator 和 @ViewChild('paginator', {static: false}) 等注释
  • 这是 ViewChild 更改的回调吗?
  • 能否请您也提供 getter 的代码?
【解决方案4】:

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

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

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

【讨论】:

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

这对我有用。

例如,我的名为“my-component”的组件是使用 *ngIf="showMe" 显示的 像这样:

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

因此,当组件初始化时,组件在“showMe”为真之前不会显示。因此,我的 @ViewChild 引用都是未定义的。

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

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

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

希望这可以帮助某人节省一些时间和挫败感。

【讨论】:

  • 确实,您的解决方案似乎是迄今为止列出的最佳方法。注意我们必须记住,top 73 解决方案现在已弃用...因为指令:[...] 声明不再支持 Angular 4。IOW 它在 Angular 4 场景中不起作用
  • 别忘了取消订阅,或者使用.take(1).subscribe(),但是很好的答案,非常感谢!
  • 优秀的解决方案。我订阅了 ngAfterViewInit() 而不是 ngOnChanges() 中的 ref 更改。但我必须添加一个 setTimeout 来摆脱 ExpressionChangedAfterChecked 错误
  • 这应该被标记为实际解决方案。非常感谢!
【解决方案6】:

我对此的解决方案是将*ngIf 替换为[hidden]。缺点是所有子组件都存在于代码 DOM 中。但符合我的要求。

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • 有一个页面,根据 showList 变量的值,在表格中显示项目列表或显示编辑项目。通过使用 [style.display]="!showList" 和 *ngIf="!showList" 来消除恼人的控制台错误。
    【解决方案8】:

    就我而言,我有一个使用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;
    

    【讨论】:

    • 这是一个比所有 settimeout nonesense 更好的解决方案
    • 不错的解决方案,我认为这是解决这个问题的最“角度”的方法。
    【解决方案9】:

    它必须有效。

    但正如 Günter Zö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(){};
     }
    

    它就像一个魅力。请仔细检查您的标签和引用。

    谢谢...

    【讨论】:

    • 如果问题和我的一样,要复制你需要在 周围的模板中放一个 *ngIf。显然如果 ngIf 返回 false,ViewChild 不会t 连接并返回 null
    • 这并没有解决 OP 的问题,即需要 viewChild 引用的后期(r)初始化/可用组件/元素。
    【解决方案10】:

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

    【讨论】:

    • 但是,您是如何获得父级中的“可见”变量的?
    • 是的,这对我来说是最简单的解决方案。因此,您将 [visible]="yourVisibleVar" 添加到组件标记并将其绑定为 @Input visible:boolean;在您的组件中...然后在该组件的模板中,在最外层标签中有一个 *ngIf="visible",可能包含在父 div 中。对我来说是一个标签集,所以只需将 *ngIf 添加到那个
    【解决方案11】:

    这对我有用,请参见下面的示例。

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

    【讨论】:

      【解决方案12】:

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

      【讨论】:

        【解决方案13】:

        一种通用方法:

        您可以创建一个等待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
          })
        

        【讨论】:

          【解决方案14】:

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

          【讨论】:

            【解决方案15】:

            如果 *ngIf="show" 阻止了 ViewChild 的呈现,并且您在 show 变为 true 后立即需要 ViewChild,它可以帮助我在将 show 设置为 true 后立即触发 ChangeDetectorRef.detectChanges()。

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

            @ViewChild(MatSort) sort: MatSort;    
            
            constructor(private cdRef: ChangeDetectorRef) {}
            
            ngOnInit() {
              this.show = false;
              this.someObservable()
                .pipe(
                  tap(() => {
                    this.show = true;
                    this.cdRef.detectChanges();
                  })
                )
                .subscribe({
                  next: (data) => {
                    console.log(sort)
                    this.useResult(data);
                  }
                });
            }
            

            这很糟糕,还是为什么没有人提出?

            【讨论】:

              【解决方案16】:

              我修复它只是在设置可见组件后添加 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);
                }
              }
              

              【讨论】:

                【解决方案17】:

                就我而言,我知道子组件将始终存在,但想在子组件初始化之前更改状态以节省工作。

                我选择在子组件出现之前对其进行测试并立即进行更改,这为我节省了子组件的更改周期。

                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 有效地将函数抛出到待处理操作列表的底部。

                【讨论】:

                • 使用 setTimeout 会触发 Angular 的全局变化检测周期,这对于大型应用程序的性能来说是可怕的。可能你不想这样做。
                • SetTimeout 不会触发全局更改检测。它最终执行的工作是因为孩子被改变了,这正是 OP 试图完成的。这不是等待整个渲染完成然后进行更改,而是立即执行。如果孩子不应该知道父母,奴隶主关系是不可避免的。这样可以节省 Dom 渲染。
                • 确实如此,如果您不知道这一点,我建议您阅读macrotasks & zone.js。或者,如果您更喜欢博文而不是官方文档:read this
                • 仅供参考,这是在附近的某个地方完成的:github.com/angular/zone.js/blob/master/dist/zone-mix.js#L3118
                • 我明白你的困惑。您正在通过角度运行 setTimeout。在我的代码中,角度组件仅在 2 点运行: 1. 角度首次初始化组件时。这是“WhenReady”开始的地方。 2. 当“测试”函数解析为真并且组件更新时。
                【解决方案18】:

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

                @ViewChild('survey-form') slides:IonSlides;
                
                <div id="survey-form"></div>
                

                而不是这样:

                @ViewChild('surveyForm') slides:IonSlides;
                
                <div #surveyForm></div>
                

                【讨论】:

                  【解决方案19】:

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

                  【讨论】:

                    【解决方案20】:

                    对于 Angular: 在 HTML 中将 *ngIf 更改为显示样式 'block' 或 'none'。

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

                    【讨论】:

                      【解决方案21】:

                      使用 [hidden] 而不是 *ngif,因为 *ngif 在条件不满足时会终止您的代码。

                      <div [hidden]="YourVariable">
                         Show Something
                      </div>
                      

                      【讨论】:

                        【解决方案22】:

                        这里有一些对我有用的东西。

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

                        所以我基本上每秒设置一次检查,直到*ngIf 变为真,然后我做与​​ElementRef 相关的工作。

                        【讨论】:

                          【解决方案23】:

                          我遇到了类似的问题,其中 ViewChild 位于有条件 (*ngIf) 呈现的组件内。这将在 api 调用的响应中呈现。响应晚于 @ViewChild 装饰器执行时的响应,因此所需的组件引用保持未定义(空)。使用{static: false} 后,@ViewChild 装饰器不会再次触发,即使在一些(少量)时间后可以看到所需的组件。这违背了 Angular 的“承诺”?(如本帖其他答案所述)

                          原因是ChangeDetectionStrategy 设置为 OnPush?。将其更改为 ChangeDetectionStrategy.Default 时,一切都按预期工作。

                          结论:

                          1. ✅ 使用{ static: false } &
                          2. ChangeDetectionStrategy.Default

                          对于@ViewChild 有条件地 (*ngIf) 渲染的组件,以便在“稍后”(当它们被渲染时)获取它们的引用

                          【讨论】:

                          • 我不会推荐这种方法。 ChangeDetectionStrategy.OnPush 非常高效。如果这是使用的默认策略,那么在您之前编写代码的作者一定是经过深思熟虑的。 {static: false} 也是可用的默认选项。如果将其设置为true,则逻辑必须在oninit 内执行,因此这是必要的。这个问题可能已经通过手动触发更改检测来解决。关于变更检测的有用文章:mokkapps.de/blog/…
                          • 好点@AakashGoplani ?? 手动触发更改检测。尽管如果有大量异步流量正在进行,则可能会使组件的实现变得臃肿,每当某些数据到达或状态发生变化时,所有这些都必须触发更改检测。
                          【解决方案24】:

                          我通过更改检测以及延迟初始化视图容器引用解决了这个问题。

                          HTML 设置:

                          <ng-container *ngIf="renderMode === 'modal'" [ngTemplateOutlet]="renderModal">
                          </ng-container>
                          <ng-container *ngIf="renderMode === 'alert'" [ngTemplateOutlet]="renderAlert">
                          </ng-container>
                          
                          <ng-template #renderModal>
                            <div class="modal">
                              <ng-container appSelector></ng-container>
                            </div>
                          </ng-template>
                          
                          <ng-template #renderAlert>
                            <div class="alert">
                              <ng-container appSelector></ng-container>
                            </div>
                          </ng-template>
                          

                          组件:

                          @ViewChild(SelectorDirective, { static: true }) containerSelector!: SelectorDirective;
                          
                          constructor(private cdr: ChangeDetectorRef) { }
                          
                          ngOnInit(): void {
                            // step: 1
                            this.renderMode = someService.someMethod();
                            // step: 2
                            this.cdr.markForCheck();
                            // step: 3
                            const viewContainerRef = this.containerSelector?.viewContainerRef;
                            if (viewContainerRef) {
                              // logic...
                            }
                          }
                          
                          1. 修改了代码,使得 HTML 所依赖的条件 (*ngIf) 应该首先更新
                          2. 一旦条件更新,手动触发ChangeDetection
                          3. 在手动 cdr 触发后从 ViewChild 获取引用并继续进行逻辑处理。

                          【讨论】:

                            【解决方案25】:

                            对我有用的解决方案是在 app.module.ts 的 declarations 中添加指令

                            【讨论】:

                              猜你喜欢
                              • 2017-01-08
                              • 2019-07-28
                              • 2018-02-25
                              • 2019-10-19
                              • 2020-03-25
                              • 2017-03-11
                              • 2023-03-21
                              • 2019-10-03
                              相关资源
                              最近更新 更多