strategy onpush force change angular angular2-changedetection

angular - onpush - @ViewChild en*ngIf



on push angular (11)

Como se mencionó por otro, la solución más rápida y rápida es usar [oculto] en lugar de * ng Si, de esta forma, el componente se creará pero no será visible, para que pueda tener acceso a él, aunque podría no ser el más eficiente camino.

Pregunta

¿Cuál es la forma más elegante de obtener @ViewChild después de @ViewChild elemento correspondiente en la plantilla?

A continuación se muestra un ejemplo. También Plunker disponible.

Modelo:

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

Componente:

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

Tengo un componente con su contenido oculto por defecto. Cuando alguien llama al método show() , se vuelve visible. Sin embargo, antes de que se complete la detección de cambio de Angular 2, no puedo hacer referencia a viewContainerRef . Usualmente setTimeout(()=>{},1) todas las acciones requeridas en setTimeout(()=>{},1) como se muestra arriba. ¿Hay una forma más correcta?

Sé que hay una opción con ngAfterViewChecked , pero causa demasiadas llamadas inútiles.

RESPUESTA (Plunker)


Creo que usar diferir de lodash tiene mucho sentido, especialmente en mi caso donde mi @ViewChild() estaba dentro de una tubería async


En mi caso, necesitaba cargar un módulo completo solo cuando el div existía en la plantilla, lo que significa que la salida estaba dentro de un ngif. De esta manera, cada vez que angular detecta el elemento #geolocalisationOutlet, crea el componente dentro de él. El módulo solo se carga una vez también.

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>


Esto me estaba funcionando. Simplemente aplique algún retraso después de vincular los datos al formulario.

setTimeout(() => { this.changeDetector.detectChanges(); }, 500);


Esto podría funcionar, pero no sé si es conveniente para su caso:

@ViewChildren(''contentPlaceholder'', {read: ViewContainerRef}) viewContainerRefs: QueryList; ngAfterViewInit() { this.viewContainerRefs.changes.subscribe(item => { if(this.viewContainerRefs.toArray().length) { // shown } }) }


La respuesta aceptada usando una Lista de consultas no funcionó para mí. Sin embargo, lo que sí funcionó fue usar un setter para ViewChild:

private contentPlaceholder: ElementRef; @ViewChild(''contentPlaceholder'') set content(content: ElementRef) { this.contentPlaceholder = content; }

El setter se llama una vez * ngIf se convierte en verdadero.


Las respuestas anteriores no me funcionaron porque en mi proyecto, el ngIf está en un elemento de entrada. Necesitaba acceso al atributo nativeElement para centrarme en la entrada cuando ngIf es verdadero. Parece que no hay un atributo nativeElement en ViewContainerRef. Esto es lo que hice (siguiendo la documentación de @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(); }); }

Usé setTimeout antes de enfocar porque ViewChild tarda un segundo en asignarse. De lo contrario, sería indefinido.


Mi objetivo era evitar cualquier método hacky que suponga algo (por ejemplo, setTimeout) y terminé implementando la solución aceptada con un poco de sabor RxJS en la parte superior:

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

Mi escenario: quería @ViewChild una acción en un elemento @ViewChild dependiendo del router queryParams . Debido a una envoltura *ngIf es falsa hasta que la solicitud HTTP devuelve los datos, la inicialización del elemento @ViewChild ocurre con un retraso.

Cómo funciona: combineLatest emite un valor por primera vez solo cuando cada uno de los Observables proporcionados emite el primer valor desde el momento en que se suscribió combineLatest . Mi Asunto tabSetInitialized emite un valor cuando se establece el elemento @ViewChild . Con esto, demoro la ejecución del código bajo subscribe hasta que *ngIf vuelve positivo y el @ViewChild se inicializa.

Por supuesto, no olvide darse de baja en ngOnDestroy, lo hago usando el Asunto ngUnsubscribe :

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


Una alternativa para superar esto es ejecutar el detector de cambios manualmente.

Primero se inyecta ChangeDetectorRef :

constructor(private changeDetector : ChangeDetectorRef) {}

Luego lo llama después de actualizar la variable que controla el * ngIf

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


Una versión simplificada, tuve un problema similar al usar el SDK de Google Maps JS.

Mi solución fue extraer el div y ViewChild en su propio componente hijo que, cuando se usaba en el componente padre, podía *ngIf / mostrarse usando un *ngIf .

antes de

HomePageComponent Template

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

HomePageComponent Component

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

Después

Plantilla MapComponent

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

Componente 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 Template

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

HomePageComponent Component

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


Otro "truco" rápido (solución fácil) es usar la etiqueta [oculta] en lugar de * ngIf, solo es importante saber que en ese caso Angular construye el objeto y lo pinta en clase: oculto es por eso que ViewChild funciona sin problemas . Por lo tanto, es importante tener en cuenta que no debe usar elementos ocultos o pesados ​​que puedan causar problemas de rendimiento

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