显示模板中相应元素后获取@ViewChild
的最优雅方法是什么?
以下是一个例子。另外Plunker可用。
模板:
<div id="layout" *ngIf="display">
<div #contentPlaceholder></div>
</div>
组件:
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
有一个选项,但它会导致太多无用的通话。
答案 0 :(得分:224)
使用QueryList接受的答案对我不起作用。然而,有什么工作是使用ViewChild的setter:
private contentPlaceholder: ElementRef;
@ViewChild('contentPlaceholder') set content(content: ElementRef) {
this.contentPlaceholder = content;
}
一旦* ngIf变为真,就会调用setter。
答案 1 :(得分:84)
另一种解决方法是手动运行更换检测器。
首先注入ChangeDetectorRef
:
constructor(private changeDetector : ChangeDetectorRef) {}
然后在更新控制* ngIf
的变量后调用它show() {
this.display = true;
this.changeDetector.detectChanges();
}
答案 2 :(得分:21)
角度8
您应将{ static: false }
添加为@ViewChild
的第二个选项
示例:
export class AppComponent {
@ViewChild('contentPlaceholder', { static: false }) contentPlaceholder: ElementRef;
display = false;
constructor(private changeDetectorRef: ChangeDetectorRef) {
}
show() {
this.display = true;
this.changeDetectorRef.detectChanges();
console.log(this.contentPlaceholder);
}
}
Stackblitz示例:https://stackblitz.com/edit/angular-d8ezsn
答案 3 :(得分:18)
上面的答案对我不起作用,因为在我的项目中,ngIf在输入元素上。我需要访问nativeElement属性,以便在ngIf为true时关注输入。 ViewContainerRef上似乎没有nativeElement属性。这是我做的(跟@ViewChild documentation之后):
<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需要一秒钟来分配。否则它将是未定义的。
答案 4 :(得分:9)
正如其他人所提到的,最快和最快的解决方案是使用[hidden]而不是* ngIf,这样组件将被创建但不可见,因为你可以访问它,尽管它可能不是最有效的方式。
答案 5 :(得分:6)
这可行,但我不知道你的情况是否方便:
@ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs: QueryList;
ngAfterViewInit() {
this.viewContainerRefs.changes.subscribe(item => {
if(this.viewContainerRefs.toArray().length) {
// shown
}
})
}
答案 6 :(得分:3)
我的目标是避免任何假设某事的hacky方法(例如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
为假,因此@ViewChild
元素的初始化会延迟。
它是如何工作的: combineLatest
仅在每个提供的Observable从订阅combineLatest
的那一刻起发出第一个值时才首次发出值。我的主题tabSetInitialized
在设置@ViewChild
元素时会发出一个值。因此,我延迟了subscribe
下的代码执行,直到*ngIf
变为正,@ViewChild
被初始化。
当然不要忘记取消订阅ngOnDestroy,我使用ngUnsubscribe
主题:
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
答案 7 :(得分:2)
另一个快速的“技巧”(简单的解决方案)只是使用[hidden]标签而不是* ngIf,重要的是要知道在这种情况下Angular会构建对象并将其绘制在类下:隐藏的原因是ViewChild可以正常工作的原因。 因此请务必记住,不要在可能导致性能问题的笨重或昂贵物品上使用隐藏物品
<div class="addTable" [hidden]="CONDITION">
答案 8 :(得分:1)
一个简化的版本,在使用Google Maps JS SDK时遇到类似的问题。
我的解决方案是将div
和ViewChild
提取到其自己的子组件中,当在父组件中使用该子组件时,可以使用*ngIf
隐藏/显示它。
之前
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;
}
之后
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;
}
答案 9 :(得分:1)
如果我在Angular 9中使用ChangeDetectorRef,这对我有用
<form method="POST" data-url="{% url 'home:post-create' %}" class="post-create-form" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-header text-center">
<h5 class="modal-title col-12 text-center">Create a Post
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</h5>
</div>
<div class="modal-body" style="height:400px;overflow-y: auto; margin:0;">
{{ form|crispy }}
<div class="pt-3" style="margin-bottom: -100px !important;">
<input type="file" class="filepond" accept="image/*">
</div>
</div>
<div class="modal-footer col-12" style="margin-top: -20px;">
<button type="submit" class="btn btn-primary" style="border-radius: 20px; width: 100%;">Post</button>
</div>
</form>
<script>
$.fn.filepond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageEdit,
FilePondPluginImageEdit,
FilePondPluginFileValidateType,
);
$(function () {
var csrf_token="{{ csrf_token }}";
$('.filepond').filepond({
allowMultiple: true,
className: "d-flex",
maxFiles: 4,
allowFileTypeValidation: true,
acceptedFileType:['image/png', 'image/jpeg'],
dropOnPage: true,
server: {
process: {
headers: {"X-CSRFToken":csrf_token,},
url: '/home/post/create/',
method: 'POST',
},
revert: {
headers: {
"X-CSRFToken":csrf_token,
},
url: '/home/post/create/',
method: 'DELETE',
},
fetch: null,
load: null,
},
labelIdle: '<span class="filepond--label-action">Add Photos</span>',
imagePreviewHeight: 250,
});
})
</script>
答案 10 :(得分:0)
如果 setter 似乎无法与 @ViewChild
一起工作(根本没有被调用),请尝试使用 @ContentChild
。
答案 11 :(得分:0)
我自己也遇到了同样的问题,使用 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;
答案 12 :(得分:0)
Angular 8 -空检查和#include <gtkmm.h>
int main( int argc, char **argv)
{
Glib::RefPtr< Gtk::Application > app = Gtk::Application::create( "App1" );
Gtk::Window window;
Gtk::MenuButton menuButton;
menuButton.set_label("menu button");
Gtk::Menu menu;
Gtk::Label label1("label1");
Gtk::Label label2("label2");
Gtk::MenuItem item1(label1);
Gtk::MenuItem item2(label2);
menu.append(item1);
menu.append(item2);
menu.show_all();
menuButton.set_popup(menu);
window.add(menuButton);
window.show_all();
return app->run(window);
}
@ViewChild
黑客的混合物
用于等待异步数据的分页控件
static: false
答案 13 :(得分:0)
使用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();
});
这也使我避免使用任何其他库或导入。
答案 14 :(得分:0)
我认为使用defer from lodash非常有意义,尤其是在我的@ViewChild()
位于async
管道内的情况下
答案 15 :(得分:0)
就我而言,我仅在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>
答案 16 :(得分:-1)
只需确保将 static 选项设置为false
@ViewChild('contentPlaceholder', {static: false}) contentPlaceholder: ElementRef;
答案 17 :(得分:-3)
这对我有用。在绑定数据后形成一些延迟。
List<SelectListItem> Contracts = (from a in
db.v_xxx_yyyy_aaaa_contracts.Where(m => m.GroupID.ToString() ==
@group).Distinct().OrderBy(a => a.ContractID).ToList()
select new SelectListItem()
{
Value = a.ContractID,
Text = a.ContractID
}).ToList();
ViewBag.ContractID = Contracts;