Para comprender completamente el problema y las posibles soluciones, debemos analizar la detección de cambios angulares, para tuberías y componentes.
Detección de cambio de tubería
Tubos sin estado / puros
De forma predeterminada, las canalizaciones son sin estado / puras. Las tuberías sin estado / puras simplemente transforman los datos de entrada en datos de salida. No recuerdan nada, por lo que no tienen propiedades, solo un transform()
método. Por lo tanto, Angular puede optimizar el tratamiento de tuberías sin estado / puras: si sus entradas no cambian, las tuberías no necesitan ejecutarse durante un ciclo de detección de cambios. Para una tubería como {{power | exponentialStrength: factor}}
, power
y factor
son entradas.
Para esta pregunta, "#student of students | sortByName:queryElem.value"
, students
y queryElem.value
son entradas, y el tubo sortByName
no tiene estado / puro. students
es una matriz (referencia).
- Cuando se agrega un estudiante, la referencia de la matriz no cambia,
students
no cambia, por lo tanto, la tubería sin estado / pura no se ejecuta.
- Cuando se escribe algo en la entrada del filtro,
queryElem.value
cambia, por lo tanto, se ejecuta la tubería sin estado / pura.
Una forma de solucionar el problema de la matriz es cambiar la referencia de la matriz cada vez que se agrega un estudiante, es decir, crear una nueva matriz cada vez que se agrega un estudiante. Podríamos hacer esto con concat()
:
this.students = this.students.concat([{name: studentName}]);
Aunque esto funciona, nuestro addNewStudent()
método no debería tener que implementarse de cierta manera solo porque estamos usando una tubería. Queremos usar push()
para agregar a nuestra matriz.
Tubos con estado
Las tuberías con estado tienen estado: normalmente tienen propiedades, no solo un transform()
método. Es posible que deban evaluarse incluso si sus entradas no han cambiado. Cuando especificamos que una tubería tiene estado / no es pura, pure: false
entonces siempre que el sistema de detección de cambios de Angular verifique un componente en busca de cambios y ese componente use una tubería con estado, verificará la salida de la tubería, ya sea que su entrada haya cambiado o no.
Esto suena como lo que queremos, aunque es menos eficiente, ya que queremos que la tubería se ejecute incluso si la students
referencia no ha cambiado. Si simplemente hacemos que la tubería tenga estado, obtenemos un error:
EXCEPTION: Expression 'students | sortByName:queryElem.value in HelloWorld@7:6'
has changed after it was checked. Previous value: '[object Object],[object Object]'.
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value
De acuerdo con la respuesta de @ drewmoore , "este error solo ocurre en el modo de desarrollo (que está habilitado de manera predeterminada a partir de la versión beta-0). Si llama enableProdMode()
al iniciar la aplicación, el error no se producirá". Los documentos para elApplicationRef.tick()
estado:
En el modo de desarrollo, tick () también realiza un segundo ciclo de detección de cambios para garantizar que no se detecten más cambios. Si se recogen cambios adicionales durante este segundo ciclo, las vinculaciones en la aplicación tienen efectos secundarios que no se pueden resolver en una sola pasada de detección de cambios. En este caso, Angular arroja un error, ya que una aplicación Angular solo puede tener una pasada de detección de cambios durante la cual deben completarse todas las detecciones de cambios.
En nuestro escenario, creo que el error es falso / engañoso. Tenemos una tubería con estado y la salida puede cambiar cada vez que se llama; puede tener efectos secundarios y eso está bien. NgFor se evalúa después de la tubería, por lo que debería funcionar bien.
Sin embargo, realmente no podemos desarrollar con este error, por lo que una solución es agregar una propiedad de matriz (es decir, estado) a la implementación de la tubería y siempre devolver esa matriz. Vea la respuesta de @ pixelbits para esta solución.
Sin embargo, podemos ser más eficientes y, como veremos, no necesitaremos la propiedad de matriz en la implementación de la tubería y no necesitaremos una solución para la detección de doble cambio.
Detección de cambio de componente
De forma predeterminada, en cada evento del navegador, la detección de cambios angulares pasa por cada componente para ver si cambió: las entradas y plantillas (¿y tal vez otras cosas?) Están marcadas.
Si sabemos que un componente solo depende de sus propiedades de entrada (y eventos de plantilla), y que las propiedades de entrada son inmutables, podemos usar la onPush
estrategia de detección de cambios mucho más eficiente . Con esta estrategia, en lugar de verificar cada evento del navegador, un componente se verifica solo cuando las entradas cambian y cuando se activan los eventos de la plantilla. Y, aparentemente, no obtenemos ese Expression ... has changed after it was checked
error con esta configuración. Esto se debe a que un onPush
componente no se vuelve a comprobar hasta que se vuelve a "marcar" ( ChangeDetectorRef.markForCheck()
). Por lo tanto, los enlaces de plantilla y las salidas de tubería con estado se ejecutan / evalúan solo una vez. Las canalizaciones sin estado / puras todavía no se ejecutan a menos que cambien sus entradas. Entonces todavía necesitamos una tubería con estado aquí.
Esta es la solución que sugirió @EricMartinez: tubería con estado con onPush
detección de cambios. Vea la respuesta de @caffinatedmonkey para esta solución.
Tenga en cuenta que con esta solución, el transform()
método no necesita devolver la misma matriz cada vez. Sin embargo, me parece un poco extraño: una tubería con estado sin estado. Pensando en ello un poco más ... la tubería con estado probablemente siempre debería devolver la misma matriz. De lo contrario, solo podría usarse con onPush
componentes en modo dev.
Entonces, después de todo eso, creo que me gusta una combinación de las respuestas de @ Eric y @ pixelbits: tubería con estado que devuelve la misma referencia de matriz, con onPush
detección de cambios si el componente lo permite. Dado que la tubería con estado devuelve la misma referencia de matriz, la tubería aún se puede usar con componentes que no están configurados con onPush
.
Plunker
Esto probablemente se convertirá en un modismo de Angular 2: si una matriz está alimentando una tubería, y la matriz puede cambiar (los elementos de la matriz, no la referencia de la matriz), necesitamos usar una tubería con estado.
pure:false
su tubería ychangeDetection: ChangeDetectionStrategy.OnPush
su componente.