Pour résoudre votre problème, vous simplement besoin d'obtenir et stocker la taille de l' div
dans une propriété d'un composant après chaque événement de redimensionnement, et d'utiliser cette propriété dans le modèle. De cette façon, la valeur va rester constante lors de la 2e manche de la détection de changement s'exécute en dev mode.
Je recommande également à l'aide de @HostListener
plutôt que d'ajouter (window:resize)
de votre modèle. Nous allons utiliser @ViewChild
pour obtenir une référence à l' div
. Et nous allons utiliser le cycle de vie de crochet ngAfterViewInit()
pour définir la valeur initiale.
import {Component, ViewChild, HostListener} from '@angular/core';
@Component({
selector: 'my-app',
template: `<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="divWidth > 350">Value2</td>
<td *ngIf="divWidth > 700">Value3</td>
</tr>
</table>`,
})
export class AppComponent {
divWidth = 0;
@ViewChild('widgetParentDiv') parentDiv:ElementRef;
@HostListener('window:resize') onResize() {
// guard against resize before view is rendered
if(this.parentDiv) {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
ngAfterViewInit() {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
Dommage que cela ne fonctionne pas. Nous obtenons
L'Expression a changé après qu'il ait été vérifié. Valeur précédente: 'false'. Valeur actuelle: 'true'.
L'erreur est de se plaindre de notre - NgIf
expressions: la première fois qu'il s'exécute, divWidth
est égal à 0, ngAfterViewInit()
pistes et les changements de la valeur à quelque chose d'autre que 0, alors le 2ème tour de la détection de changement de pistes (en dev mode). Heureusement, il est facile de/solution connue, et c'est une seule fois question, non pas d'un problème récurrent comme dans l'OP:
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
Notez que cette technique, de l'attente d'une tique est documenté ici: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-de-vue-enfant
Souvent, en ngAfterViewInit()
et ngAfterViewChecked()
, nous allons avoir besoin d'employer l' setTimeout()
tour parce que ces méthodes sont appelées après que le composant est d'avis est composé.
Voici un travail plunker.
Nous pouvons faire mieux. Je pense que nous devrions augmenter la vitesse de la redimensionner des événements tels que Angulaires de la détection de changement ne s'exécute que, disons, 100-250ms, plutôt que chaque fois qu'un événement de redimensionnement se produit. Cela devrait permettre d'éviter l'application d'arriver là où l'utilisateur est le redimensionnement de la fenêtre, parce qu'en ce moment, chaque événement de redimensionnement des causes de la détection de changement à exécuter (deux fois en dev mode). Vous pouvez vérifier cela, ajoutez la méthode suivante à la précédente plunker:
ngDoCheck() {
console.log('change detection');
}
Observables peuvent facilement gaz d'événements, donc au lieu d'utiliser @HostListener
de lier l'événement resize, nous allons créer un observables:
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
Cela fonctionne, mais... tout en expérimentant avec qui, j'ai découvert quelque chose de très intéressant... même si nous étouffer l'événement de redimensionnement, angle de détection de changement de toujours s'exécute à chaque fois il y a un événement de redimensionnement. I. e., la limitation n'a pas d'incidence sur la fréquence de la détection de changement s'exécute. (Tobias Bosch confirmé ce:
https://github.com/angular/angular/issues/1773#issuecomment-102078250.)
Je veux seulement la détection de changement à exécuter si l'événement passe la manette des gaz du temps. Et je n'ai besoin que de la détection de changement de courir sur cette composante. La solution est de créer les observables de l'extérieur de la zone Angulaire, puis manuellement appel à la détection de changement à l'intérieur de l'abonnement rappel:
constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {}
ngAfterViewInit() {
// set initial value, but wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
this.ngzone.runOutsideAngular( () =>
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
this.cdref.detectChanges();
})
);
}
Voici un travail plunker.
Dans le plunker j'ai ajouté un counter
que je incrémenter chaque détection de changement de cycle, cycle de vie du crochet ngDoCheck()
. Vous pouvez voir que cette méthode n'est pas appelée – la valeur du compteur ne change pas sur les événements de redimensionnement.
detectChanges()
sera exécuté à la détection de changement sur ce composant et de ses enfants. Si vous préférez exécuter la détection de changement à partir de la racine du composant (c'est à dire, de fonctionner à plein détection de changement de contrôle), puis utiliser ApplicationRef.tick()
à la place (ce est commenté dans la plunker). Notez que tick()
entraînera ngDoCheck()
d'être appelé.
C'est une grande question. J'ai passé beaucoup de temps à essayer différentes solutions et j'ai beaucoup appris. Merci de poster cette question.