@ViewChild在*ngIf

问题

在模板中显示相应的元素后,最优雅的方法是什么?

下面是一个例子。还有砰砰作响可用。

Component.template.html:

<div id="layout" *ngIf="display">
<div #contentPlaceholder></div>
</div>

Component.component.ts:

export class AppComponent {


display = false;
@ViewChild('contentPlaceholder', { read: ViewContainerRef }) viewContainerRef;


show() {
this.display = true;
console.log(this.viewContainerRef); // undefined
setTimeout(() => {
console.log(this.viewContainerRef); // OK
}, 1);
}
}

我有一个默认隐藏其内容的组件。当有人调用show()方法时,它变得可见。然而,在Angular 2变更检测完成之前,我无法引用viewContainerRef。如上所示,我通常将所有必需的操作包装到setTimeout(()=>{},1)中。有没有更正确的方法?

我知道ngAfterViewChecked有一个选项,但它会导致太多无用的调用。

< a href = " http://plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview" rel="noreferrer">ANSWER(柱塞) . p=preview" rel="noreferrer">

170731 次浏览

这可能有用,但我不知道这对你的情况是否方便:

@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;


ngAfterViewInit() {
this.viewContainerRefs.changes.subscribe(item => {
if(this.viewContainerRefs.toArray().length) {
// shown
}
})
}

为ViewChild使用setter:

 private contentPlaceholder: ElementRef;


@ViewChild('contentPlaceholder') set content(content: ElementRef) {
if(content) { // initially setter gets called with undefined
this.contentPlaceholder = content;
}
}

一旦*ngIf变成true, setter就会被元素引用调用。

注意,对于Angular 8,你必须确保设置{ static: false },这是其他Angular版本的默认设置:

 @ViewChild('contentPlaceholder', { static: false })

注意:如果contentPlaceholder是一个组件,你可以改变ElementRef到你的组件类:

  private contentPlaceholder: MyCustomComponent;


@ViewChild('contentPlaceholder') set content(content: MyCustomComponent) {
if(content) { // initially setter gets called with undefined
this.contentPlaceholder = content;
}
}

正如其他人提到的,最快的解决方案是使用[hidden]而不是*ngIf。采用这种方法,组件将被创建但不可见,因此您可以访问它。这可能不是最有效的方法。

上面的答案并不适用于我,因为在我的项目中,ngIf是在一个输入元素上。我需要访问nativeElement属性,以便在ngIf为真时专注于输入。在ViewContainerRef上似乎没有nativeElement属性。以下是我所做的(遵循@ViewChild文档):

<button (click)='showAsset()'>Add Asset</button>
<div *ngIf='showAssetInput'>
<input #assetInput />
</div>


...


private assetInputElRef:ElementRef;
@ViewChild('assetInput') set assetInput(elRef: ElementRef) {
this.assetInputElRef = elRef;
}


...


showAsset() {
this.showAssetInput = true;
setTimeout(() => { this.assetInputElRef.nativeElement.focus(); });
}

我在聚焦之前使用了setTimeout,因为ViewChild需要一秒来分配。否则就没有定义了。

解决这个问题的另一种方法是手动运行更改检测器。

首先注入ChangeDetectorRef:

constructor(private changeDetector : ChangeDetectorRef) {}

然后在更新控制*ngIf的变量后调用它

show() {
this.display = true;
this.changeDetector.detectChanges();
}

我的目标是避免任何假定的方法(例如setTimeout),我最终实现了接受的解决方案,上面有一点RxJS的味道:

  private ngUnsubscribe = new Subject();
private tabSetInitialized = new Subject();
public tabSet: TabsetComponent;
@ViewChild('tabSet') set setTabSet(tabset: TabsetComponent) {
if (!!tabSet) {
this.tabSet = tabSet;
this.tabSetInitialized.next();
}
}


ngOnInit() {
combineLatest(
this.route.queryParams,
this.tabSetInitialized
).pipe(
takeUntil(this.ngUnsubscribe)
).subscribe(([queryParams, isTabSetInitialized]) => {
let tab = [undefined, 'translate', 'versions'].indexOf(queryParams['view']);
this.tabSet.tabs[tab > -1 ? tab : 0].active = true;
});
}

我想在一个@ViewChild元素上触发一个动作,这取决于路由器queryParams。由于在HTTP请求返回数据之前包装*ngIf为false,因此@ViewChild元素的初始化发生延迟。

它是如何工作的: combineLatest只有在每个提供的可观察对象自订阅combineLatest以来第一次发出一个值时才会第一次发出一个值。我的Subject tabSetInitialized在设置@ViewChild元素时发出一个值。因此,我延迟执行subscribe下的代码,直到*ngIf为正,并且@ViewChild初始化。

当然,不要忘记取消订阅ngOnDestroy,我使用ngUnsubscribe Subject:

  ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

简化版,我在使用谷歌Maps JS SDK时遇到了类似的问题。

我的解决方案是将__abc0和ViewChild提取到它自己的子组件中,当在父组件中使用时,可以使用*ngIf隐藏/显示。

< em > < / em >之前

HomePageComponent模板

<div *ngIf="showMap">
<div #map id="map" class="map-container"></div>
</div>

HomePageComponent组件

@ViewChild('map') public mapElement: ElementRef;


public ionViewDidLoad() {
this.loadMap();
});


private loadMap() {


const latLng = new google.maps.LatLng(-1234, 4567);
const mapOptions = {
center: latLng,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}


public toggleMap() {
this.showMap = !this.showMap;
}

< em > < / em >后

MapComponent模板

 <div>
<div #map id="map" class="map-container"></div>
</div>

MapComponent组件

@ViewChild('map') public mapElement: ElementRef;


public ngOnInit() {
this.loadMap();
});


private loadMap() {


const latLng = new google.maps.LatLng(-1234, 4567);
const mapOptions = {
center: latLng,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}

HomePageComponent模板

<map *ngIf="showMap"></map>

HomePageComponent组件

public toggleMap() {
this.showMap = !this.showMap;
}

另一个快速“技巧”(简单的解决方案)只是使用[hidden]标签而不是*ngIf,重要的是要知道在这种情况下,Angular会构建对象并在class:hidden下绘制它,这就是ViewChild工作没有问题的原因。 所以重要的是要记住,你不应该在沉重或昂贵的物品上使用隐藏,这会导致性能问题

  <div class="addTable" [hidden]="CONDITION">

在我的例子中,我只需要在模板中存在div时加载整个模块,这意味着出口在ngif中。这样,每当angular检测到#geolocalisationOutlet元素时,它就会在其中创建一个组件。该模块也只加载一次。

constructor(
public wlService: WhitelabelService,
public lmService: LeftMenuService,
private loader: NgModuleFactoryLoader,
private injector: Injector
) {
}


@ViewChild('geolocalisationOutlet', {read: ViewContainerRef}) set geolocalisation(geolocalisationOutlet: ViewContainerRef) {
const path = 'src/app/components/engine/sections/geolocalisation/geolocalisation.module#GeolocalisationModule';
this.loader.load(path).then((moduleFactory: NgModuleFactory<any>) => {
const moduleRef = moduleFactory.create(this.injector);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(GeolocalisationComponent);
if (geolocalisationOutlet && geolocalisationOutlet.length === 0) {
geolocalisationOutlet.createComponent(compFactory);
}
});
}


<div *ngIf="section === 'geolocalisation'" id="geolocalisation">
<div #geolocalisationOutlet></div>
</div>

我认为使用从lodash延迟非常有意义,特别是在我的情况下,我的@ViewChild()async管道内

角8 +

你应该添加{ static: false }作为@ViewChild的第二个选项。这会导致查询结果被解析变化检测运行,允许你的@ViewChild在值更改后被更新。

例子:

export class AppComponent {
@ViewChild('contentPlaceholder', { static: false }) contentPlaceholder: ElementRef;


display = false;


constructor(private changeDetectorRef: ChangeDetectorRef) {
}


show() {
this.display = true;


// Required to access this.contentPlaceholder below,
// otherwise contentPlaceholder will be undefined
this.changeDetectorRef.detectChanges();


console.log(this.contentPlaceholder);
}
}

Stackblitz示例:https://stackblitz.com/edit/angular-d8ezsn

在Angular 8上工作,不需要导入ChangeDector

ngIf允许你不加载元素,避免给你的应用程序增加更多的压力。以下是我如何在没有ChangeDetector的情况下运行它

elem: ElementRef;


@ViewChild('elemOnHTML', {static: false}) set elemOnHTML(elemOnHTML: ElementRef) {
if (!!elemOnHTML) {
this.elem = elemOnHTML;
}
}

然后当我将我的ngIf值更改为true时,我将像这样使用setTimeout让它只等待下一个更改周期:

  this.showElem = true;
console.log(this.elem); // undefined here
setTimeout(() => {
console.log(this.elem); // back here through ViewChild set
this.elem.do();
});

这也允许我避免使用任何额外的库或导入。

for 角8 - null检查和@ViewChild static: false黑客的混合物

用于等待异步数据的分页控件

@ViewChild(MatPaginator, { static: false }) set paginator(paginator: MatPaginator) {
if(!paginator) return;
paginator.page.pipe(untilDestroyed(this)).subscribe(pageEvent => {
const updated: TSearchRequest = {
pageRef: pageEvent.pageIndex,
pageSize: pageEvent.pageSize
} as any;
this.dataGridStateService.alterSearchRequest(updated);
});
}

如果我在Angular 9中使用ChangeDetectorRef,它就可以工作

@ViewChild('search', {static: false})
public searchElementRef: ElementRef;


constructor(private changeDetector: ChangeDetectorRef) {}


//then call this when this.display = true;
show() {
this.display = true;
this.changeDetector.detectChanges();
}

只要确保静态选项被设置为false

  @ViewChild('contentPlaceholder', {static: false}) contentPlaceholder: ElementRef;

我自己在使用Angular 10时也遇到了同样的问题。

如果我尝试使用[hidden]*ngIf,则@ViewChild变量总是未定义的

<p-calendar #calendar *ngIf="bShowCalendar" >
</p-calendar>

我通过从网页中删除它来修复它。
我使用[ngClass]使控件具有opacity:0,并将其完全移开

<style>
.notVisible {
opacity: 0;
left: -1000px;
position: absolute !important;
}
</style>


<p-calendar #calendar [ngClass]="{'notVisible': bShowCalendar }" >
</p-calendar>

我知道,这很蠢很丑,但它解决了问题。

我还必须使控件静态。我不明白为什么…但是,再一次,它拒绝在没有改变的情况下工作:

export class DatePickerCellRenderer {
@ViewChild('calendar', {static: true }) calendar: Calendar;

如果setter在@ViewChild中似乎没有工作(根本没有被调用),则尝试@ContentChild

We had a situation to set tabindex on ngif

html:

<div #countryConditional1 *ngIf="country=='USA'">
<input id="streetNumber"  [(ngModel)]="streetNumber" pInputText>
</div>
             

        

ts:

@ViewChild('countryConditional1') set countryConditional1(element){
if (element){
const container2 = document.querySelector("#someElement");
container2.querySelector("span > input").setAttribute("tabindex", "18");}

阅读并尝试

Make sure passing the param { static: false } to @ViewChild resolve the problem.


**template.html code**


<div *ngIf="showFirtChild">
<first-child #firstchildComponent ></first-child>
</div>


**in .ts file**


export class Parent implements
{
private firstChild: FirstchildComponent;


@ViewChild('firstchildComponent', { static: false }) set content(content:
FirstchildComponent) {
if(content) {
this.firstchildComponent = content;
}
}


constructor(){}


ShowChild(){
this.showFirtChild = true;
if(this.firstchildComponent){
this.firstchildComponent.YourMethod()
}
}


}