angular - example - ngzone para que sirve
Detección de cambio angular 2 y ChangeDetectionStrategy.OnPush (4)
De acuerdo, ya que esto me tomó toda una noche para comprender, hice un currículum para resolver todo lo que estaba en mi cabeza y podría ayudar a los futuros lectores. Así que vamos a empezar por aclarar algunas cosas:
Los cambios provienen de eventos
Un componente puede tener campos. Esos campos solo cambian después de algún tipo de evento, y solo después de eso.
Podemos definir un evento como un clic del ratón, una solicitud ajax, setTimeout ...
Los datos fluyen de arriba a abajo
El flujo de datos angular es una calle de sentido único. Eso significa que los datos no pasan de los niños a los padres. Solo de padres a hijos, por ejemplo, a través de la etiqueta @Input
. La única manera de que un componente superior sea consciente de algún cambio en un niño es a través de un evento . Lo que nos lleva a:
Detección de cambio de activación de evento
Cuando ocurre un evento, el marco angular revisa cada componente de arriba a abajo para ver si han cambiado. Si alguno ha cambiado, se actualiza la vista en consecuencia.
Angular comprueba todos los componentes después de un evento ha sido disparado. Digamos que tiene un evento de clic en un componente que es el componente en el nivel más bajo, lo que significa que tiene padres pero no hijos. Ese clic podría desencadenar un cambio en un componente principal a través de un emisor de eventos, un servicio, etc. Angular no sabe si los padres cambiarán o no. Es por eso que Angular comprueba todos los componentes después de que un evento se haya activado de forma predeterminada.
Para ver si han cambiado angular usa la clase ChangeDetector
.
Detector de cambio
Cada componente tiene un detector de cambios de clase adjunto. Se utiliza para verificar si un componente ha cambiado de estado después de algún evento y para ver si la vista debe actualizarse. Cuando ocurre un evento (clic del mouse, etc.), este proceso de detección de cambios ocurre para todos los componentes, de manera predeterminada.
Por ejemplo si tenemos un ParentComponent:
@Component({
selector: ''comp-parent'',
template:''<comp-child [name]="name"></comp-child>''
})
class ParentComponent{
name:string;
}
Tendremos un detector de cambios conectado al ParentComponent
que se ParentComponent
así:
class ParentComponentChangeDetector{
oldName:string; // saves the old state of the component.
isChanged(newName){
if(this.oldName !== newName)
return true;
else
return false;
}
}
Cambio de propiedades del objeto
Como puede haber notado, el método isChanged devolverá false si cambia una propiedad de objeto. En efecto
let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true
Dado que cuando una propiedad de un objeto puede cambiar sin devolver verdadero en el changeDetector
isChanged()
, angular asumirá que todos los componentes siguientes también podrían haber cambiado. Así que simplemente comprobará la detección de cambios en todos los componentes.
Ejemplo: aquí tenemos un componente con un subcomponente. Si bien la detección de cambios devolverá el valor falso para el componente principal, la vista del elemento secundario debería actualizarse muy bien.
@Component({
selector: ''parent-comp'',
template: `
<div class="orange" (click)="person.name=''frank''">
<sub-comp [person]="person"></sub-comp>
</div>
`
})
export class ParentComponent {
person:Person = { name: "thierry" };
}
// sub component
@Component({
selector: ''sub-comp'',
template: `
<div>
{{person.name}}
</div>
})
export class SubComponent{
@Input("person")
person:Person;
}
Es por eso que el comportamiento por defecto es comprobar todos los componentes. Porque aunque un subcomponente no puede cambiar si su entrada no ha cambiado, angular no sabe con certeza que su entrada no haya cambiado realmente . El objeto pasado a él podría ser el mismo pero podría tener diferentes propiedades.
Estrategia onpush
Cuando un componente está marcado con changeDetection: ChangeDetectionStrategy.OnPush
, angular asumirá que el objeto de entrada no cambió si la referencia del objeto no cambió. Lo que significa que cambiar una propiedad no activará la detección de cambios. Por lo tanto, la vista no estará sincronizada con el modelo.
Ejemplo
Este ejemplo es genial porque muestra esto en acción. Tiene un componente principal que al hacer clic en las propiedades de nombre de objeto de entrada se cambia. Si marca el método click()
dentro del componente primario, notará que genera la propiedad del componente secundario en la consola. Esa propiedad ha cambiado ... Pero no puedes verla visualmente. Eso es porque la vista no ha sido actualizada. Debido a la estrategia de OnPush, el proceso de detección de cambios no ocurrió porque el objeto ref no cambió.
@Component({
selector: ''my-app'',
template: `
<div class="orange" (click)="click()">
<sub-comp [person]="person" #sub></sub-comp>
</div>
`
})
export class App {
person:Person = { name: "thierry" };
@ViewChild("sub") sub;
click(){
this.person.name = "Jean";
console.log(this.sub.person);
}
}
// sub component
@Component({
selector: ''sub-comp'',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div>
{{person.name}}
</div>
`
})
export class SubComponent{
@Input("person")
person:Person;
}
export interface Person{
name:string,
}
Después de hacer clic, el nombre sigue estando en la vista pero no en el componente en sí.
Un evento disparado dentro de un componente activará la detección de cambios.
Aquí llegamos a lo que me confundió en mi pregunta original. El componente a continuación está marcado con la estrategia OnPush, pero la vista se actualiza cuando cambia.
@Component({
selector: ''my-app'',
template: `
<div class="orange" >
<sub-comp ></sub-comp>
</div>
`,
styles:[`
.orange{ background:orange; width:250px; height:250px;}
`]
})
export class App {
person:Person = { name: "thierry" };
click(){
this.person.name = "Jean";
console.log(this.sub.person);
}
}
// sub component
@Component({
selector: ''sub-comp'',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="grey" (click)="click()">
{{person.name}}
</div>
`,
styles:[`
.grey{ background:#ccc; width:100px; height:100px;}
`]
})
export class SubComponent{
@Input()
person:Person = { name:"jhon" };
click(){
this.person.name = "mich";
}
}
Entonces, aquí vemos que la entrada del objeto no ha cambiado la referencia y estamos usando la estrategia OnPush. Lo que podría llevarnos a creer que no será actualizado. De hecho se actualiza.
Como dijo Gunter en su respuesta, eso se debe a que, con la estrategia OnPush, la detección de cambios ocurre para un componente si:
- se recibe un evento enlazado (clic) en el propio componente.
- se actualizó un @Input () (como en la referencia obj cambiado)
- | async pipe recibió un evento
- la detección de cambios se invocó "manualmente"
Independientemente de la estrategia.
Campo de golf
- https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
- http://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
- https://angular-2-training-book.rangle.io/handout/change-detection/change_detector_classes.html
- https://www.youtube.com/watch?v=X0DLP_rktsc
Estoy tratando de entender el mecanismo ChangeDetectionStrategy.OnPush
.
Lo que obtengo de mis lecturas es que una detección de cambio funciona al comparar el valor antiguo con el valor nuevo. Esa comparación devolverá false si la referencia del objeto no ha cambiado.
Sin embargo, parece que hay ciertos escenarios en los que esa "regla" se pasa por alto. ¿Podría explicar cómo funciona todo?
En angular utilizamos altamente la estructura padre - hijo. Allí pasamos el formulario de datos de padre a hijo usando @Inputs .
Allí, si se produce un cambio en cualquier antepasado del hijo, la detección del cambio se producirá en el árbol de componentes de ese antepasado.
Pero en la mayoría de las situaciones, necesitaremos actualizar la vista del niño (llamada Detección de cambio) solo cuando cambien las entradas. Para lograr esto, podemos usar OnPush ChangeDetectionStrategy y cambiar las entradas (usando inmutables) según sea necesario. LINK
Para evitar que Application.tick
intente separar changeDetector:
constructor(private cd: ChangeDetectorRef) {
ngAfterViewInit() {
this.cd.detach();
}
*ngFor
hace su propia detección de cambio. Cada vez que se ejecuta la detección de cambios, NgFor
recibe su método ngDoCheck()
y NgFor
verifica si el contenido de la matriz ha cambiado.
En su caso, no hay cambios, porque el constructor se ejecuta antes de que Angular comience a renderizar la vista.
Si por ejemplo añades un botón como
<button (click)="persons.push({name: ''dynamically added'', id: persons.length})">add</button>
entonces un clic realmente causaría un cambio que ngFor
tiene que reconocer.
Con ChangeDetectionStrategy.OnPush
detección de cambios en su componente porque con OnPush
se ejecuta la detección de cambios cuando
- se recibe un evento enlazado
(click)
- un
@Input()
fue actualizado por la detección de cambios -
| async
| async
pipe recibió un evento - la detección de cambios se invocó "manualmente"