En este escenario, estoy mostrando una lista de estudiantes (matriz) en la vista con ngFor
:
<li *ngFor="#student of students">{{student.name}}</li>
Es maravilloso que se actualice cada vez que agrego a otro estudiante a la lista.
Sin embargo, cuando le doy una pipe
a filter
por el nombre del estudiante,
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
No actualiza la lista hasta que escribo algo en el campo de filtrado del nombre del estudiante.
Aquí hay un enlace a plnkr .
Hello_world.html
<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>
sort_by_name_pipe.ts
import {Pipe} from 'angular2/core';
@Pipe({
name: 'sortByName'
})
export class SortByNamePipe {
transform(value, [queryString]) {
// console.log(value, queryString);
return value.filter((student) => new RegExp(queryString).test(student.name))
// return value;
}
}
pure:false
su tubería ychangeDetection: ChangeDetectionStrategy.OnPush
su componente..test()
en su función de filtro. Es porque, si el usuario ingresa una cadena que incluye caracteres de significado especial como:*
o+
etc., su código se romperá. Creo que debería usar.includes()
o escapar de la cadena de consulta con una función personalizada.pure:false
y hacer que su tubería tenga estado solucionará el problema. No es necesario modificar ChangeDetectionStrategy.Respuestas:
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
yfactor
son entradas.Para esta pregunta,
"#student of students | sortByName:queryElem.value"
,students
yqueryElem.value
son entradas, y el tubosortByName
no tiene estado / puro.students
es una matriz (referencia).students
no cambia, por lo tanto, la tubería sin estado / pura no se ejecuta.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()
:Aunque esto funciona, nuestro
addNewStudent()
método no debería tener que implementarse de cierta manera solo porque estamos usando una tubería. Queremos usarpush()
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: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 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 eseExpression ... has changed after it was checked
error con esta configuración. Esto se debe a que unonPush
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 cononPush
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 cononPush
.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.
fuente
onPush
detección de cambios plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=previewComo señaló Eric Martinez en los comentarios, agregar
pure: false
a suPipe
decorador ychangeDetection: ChangeDetectionStrategy.OnPush
a suComponent
decorador solucionará su problema. Aquí hay un plunkr que funciona. Cambiar aChangeDetectionStrategy.Always
, también funciona. Este es el por qué.Según la guía angular2 sobre tuberías :
En cuanto a
ChangeDetectionStrategy
, por defecto, todos los enlaces se comprueban en cada ciclo. Cuandopure: false
se agrega una tubería, creo que el método de detección de cambios cambia deCheckAlways
aCheckOnce
por razones de rendimiento. ConOnPush
, los enlaces para el componente solo se comprueban cuando cambia una propiedad de entrada o cuando se desencadena un evento. Para obtener más información sobre los detectores de cambios, una parte importante deangular2
, consulte los siguientes enlaces:fuente
pure: false
se agrega una tubería, creo que el método de detección de cambios cambia de CheckAlways a CheckOnce", eso no concuerda con lo que citó de los documentos. Mi comprensión es la siguiente: las entradas de una tubería sin estado se verifican en cada ciclo de forma predeterminada. Si hay un cambio,transform()
se llama al método de la tubería para (re) generar la salida. Eltransform()
método de una tubería con estado se llama en cada ciclo y se comprueba su salida para ver si hay cambios. Consulte también stackoverflow.com/a/34477111/215945 .OnPush
(es decir, el componente está marcado como "inmutable"), la salida de tubería con estado no tiene que "estabilizarse", es decir, parece que eltransform()
método se ejecuta solo una vez (probablemente todos la detección de cambios para el componente se ejecuta solo una vez). Contraste eso con la respuesta de @ pixelbits, donde eltransform()
método se llama varias veces y tiene que estabilizarse (de acuerdo con la otra respuesta de pixelbits ), de ahí la necesidad de usar la misma referencia de matriz.transform
solo se llama cuando se envían nuevos datos en lugar de varias veces hasta que la salida se estabiliza, ¿verdad?Demo Plunkr
No es necesario cambiar ChangeDetectionStrategy. Implementar una tubería con estado es suficiente para que todo funcione.
Esta es una tubería con estado (no se realizaron otros cambios):
fuente
Expression 'students | sortByName:queryElem.value in HelloWorld@7:6' has changed after it was checked. Previous value: '[object Object],[object Object],[object Object]'. Current value: '[object Object],[object Object],[object Object]' in [students | sortByName:queryElem.value in HelloWorld@7:6]
De la documentación angular
Pipas puras e impuras
Hay dos categorías de pipas: puras e impuras. Las tuberías son puras por defecto. Cada pipa que has visto hasta ahora ha sido pura. Usted convierte una tubería en impura estableciendo su bandera pura en falso. Podrías hacer que FlyingHeroesPipe sea impuro así:
@Pipe({ name: 'flyingHeroesImpure', pure: false })
Antes de hacer eso, comprenda la diferencia entre puro e impuro, comenzando con una pipa pura.
Tubos puros Angular ejecuta un tubo puro solo cuando detecta un cambio puro en el valor de entrada. Un cambio puro es un cambio en un valor de entrada primitivo (Cadena, Número, Booleano, Símbolo) o una referencia de objeto modificada (Fecha, Matriz, Función, Objeto).
Angular ignora los cambios dentro de los objetos (compuestos). No llamará a una tubería pura si cambia un mes de entrada, lo agrega a una matriz de entrada o actualiza una propiedad de objeto de entrada.
Esto puede parecer restrictivo, pero también es rápido. Una verificación de referencia de objeto es rápida, mucho más rápida que una verificación profunda de diferencias, por lo que Angular puede determinar rápidamente si puede omitir tanto la ejecución de la tubería como la actualización de la vista.
Por esta razón, es preferible una tubería pura cuando puede vivir con la estrategia de detección de cambios. Cuando no pueda, puede usar la pipa impura.
fuente
En lugar de hacer puro: falso. Puede copiar en profundidad y reemplazar el valor en el componente por this.students = Object.assign ([], NEW_ARRAY); donde NEW_ARRAY es la matriz modificada.
Funciona para angular 6 y debería funcionar también para otras versiones angulares.
fuente
Una solución alternativa: importe manualmente la tubería en el constructor y llame al método de transformación utilizando esta tubería
En realidad ni siquiera necesitas una pipa
fuente
Agregue un parámetro adicional a la tubería y cámbielo justo después del cambio de matriz, e incluso con tubería pura, la lista se actualizará
dejar artículo de artículos | tubería: param
fuente
En este caso de uso, utilicé mi archivo Pipe in ts para el filtrado de datos. Es mucho mejor para el rendimiento que usar tubos puros. Úselo en ts como este:
fuente
crear tuberías impuras tiene un alto rendimiento. así que no cree tuberías impuras, más bien cambie la referencia de la variable de datos creando una copia de los datos cuando cambie en los datos y reasigne la referencia de la copia en la variable de datos original.
fuente