Nesse cenário, estou exibindo uma lista de alunos (matriz) para a visualização com ngFor
:
<li *ngFor="#student of students">{{student.name}}</li>
É maravilhoso que ele seja atualizado sempre que adiciono outro aluno à lista.
No entanto, quando atribuo pipe
a filter
pelo nome do aluno,
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
Ele não atualiza a lista até que eu digite algo no campo de filtro de nome do aluno.
Aqui está um link para 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
em seu Pipe echangeDetection: ChangeDetectionStrategy.OnPush
em seu Componente..test()
em sua função de filtro. É porque, se o usuário inserir uma string que inclui caracteres de significado especial como:*
ou+
etc., seu código será interrompido. Acho que você deve usar.includes()
ou escapar da string de consulta com uma função personalizada.pure:false
e tornar seu canal com estado corrigirá o problema. Modificar ChangeDetectionStrategy não é necessário.Respostas:
Para compreender totalmente o problema e as possíveis soluções, precisamos discutir a detecção de alteração angular - para tubos e componentes.
Detecção de mudança de tubulação
Tubos sem estado / puros
Por padrão, os canais são sem estado / puros. Pipes sem estado / puros simplesmente transformam dados de entrada em dados de saída. Eles não se lembram de nada, então eles não têm nenhuma propriedade - apenas um
transform()
método. O Angular pode, portanto, otimizar o tratamento de tubos sem estado / puros: se suas entradas não mudarem, os tubos não precisam ser executados durante um ciclo de detecção de mudança. Para um tubo como{{power | exponentialStrength: factor}}
,power
efactor
são entradas.Para esta pergunta,
"#student of students | sortByName:queryElem.value"
,students
equeryElem.value
são entradas e tubosortByName
é apátrida / puro.students
é uma matriz (referência).students
não muda - portanto, o pipe sem estado / puro não é executado.queryElem.value
muda, portanto, o pipe sem estado / puro é executado.Uma maneira de corrigir o problema do array é alterar a referência do array sempre que um aluno é adicionado - ou seja, criar um novo array cada vez que um aluno é adicionado. Podemos fazer isso com
concat()
:Embora isso funcione, nosso
addNewStudent()
método não deve ser implementado de uma determinada maneira apenas porque estamos usando um pipe. Queremos usarpush()
para adicionar ao nosso array.Stateful Pipes
Pipes com estado têm estado - eles normalmente têm propriedades, não apenas um
transform()
método. Eles podem precisar ser avaliados mesmo que suas entradas não tenham mudado. Quando especificamos que um canal tem estado / não puro -pure: false
então, sempre que o sistema de detecção de alterações do Angular verifica se há alterações em um componente e esse componente usa um canal com estado, ele verifica a saída do canal, quer sua entrada tenha mudado ou não.Parece o que queremos, embora seja menos eficiente, pois queremos que o pipe seja executado mesmo que a
students
referência não tenha mudado. Se simplesmente tornarmos o canal com estado, obteremos um erro:De acordo com a resposta de @drewmoore , "este erro só acontece no modo dev (que é ativado por padrão a partir de beta-0). Se você chamar
enableProdMode()
ao inicializar o aplicativo, o erro não será gerado." Os documentos para oApplicationRef.tick()
estado:Em nosso cenário, acredito que o erro é falso / enganoso. Temos um pipe com estado e a saída pode mudar cada vez que é chamado - pode ter efeitos colaterais e tudo bem. NgFor é avaliado após o tubo, então deve funcionar bem.
No entanto, não podemos realmente desenvolver com esse erro sendo lançado, então uma solução alternativa é adicionar uma propriedade de matriz (ou seja, estado) à implementação do tubo e sempre retornar essa matriz. Veja a resposta de @ pixelbits para esta solução.
No entanto, podemos ser mais eficientes e, como veremos, não precisaremos da propriedade de array na implementação do tubo e não precisaremos de uma solução alternativa para a detecção de dupla alteração.
Detecção de alteração de componente
Por padrão, em cada evento do navegador, a detecção de mudança angular passa por cada componente para ver se ele mudou - entradas e modelos (e talvez outras coisas?) São verificados.
Se soubermos que um componente depende apenas de suas propriedades de entrada (e eventos de modelo), e que as propriedades de entrada são imutáveis, podemos usar a
onPush
estratégia de detecção de mudança muito mais eficiente . Com essa estratégia, em vez de verificar todos os eventos do navegador, um componente é verificado apenas quando as entradas mudam e quando os eventos do modelo são acionados. E, aparentemente, não recebemos esseExpression ... has changed after it was checked
erro com essa configuração. Isso ocorre porque umonPush
componente não é verificado novamente até que seja "marcado" (ChangeDetectorRef.markForCheck()
) novamente. Portanto, as ligações de modelo e as saídas de pipe com estado são executadas / avaliadas apenas uma vez. Pipes sem estado / puros ainda não são executados, a menos que suas entradas sejam alteradas. Portanto, ainda precisamos de um tubo com estado aqui.Esta é a solução sugerida por @EricMartinez: pipe stateful com
onPush
detecção de mudanças. Consulte a resposta de @caffinatedmonkey para essa solução.Observe que, com esta solução, o
transform()
método não precisa retornar o mesmo array todas as vezes. Eu acho isso um pouco estranho: um tubo com estado sem estado. Pensando nisso um pouco mais ... o pipe stateful provavelmente sempre deve retornar o mesmo array. Caso contrário, ele só poderia ser usado comonPush
componentes no modo de desenvolvimento.Depois de tudo isso, acho que gosto de uma combinação das respostas de @Eric e @ pixelbits: tubo com estado que retorna a mesma referência de array, com
onPush
detecção de mudança se o componente permitir. Uma vez que o canal com estado retorna a mesma referência de matriz, o tubo ainda pode ser usado com componentes que não estão configurados comonPush
.Plunker
Isso provavelmente se tornará um idioma Angular 2: se um array está alimentando um pipe e o array pode mudar (os itens do array, não a referência do array), precisamos usar um pipe stateful.
fonte
onPush
detecção de alteração plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=previewComo Eric Martinez apontou nos comentários, adicionar
pure: false
ao seuPipe
decorador echangeDetection: ChangeDetectionStrategy.OnPush
ao seuComponent
decorador resolverá seu problema. Aqui está um plunkr de trabalho. Mudar paraChangeDetectionStrategy.Always
também funciona. Aqui está o porquê.De acordo com o guia angular2 em tubos :
Quanto ao
ChangeDetectionStrategy
, por padrão, todas as ligações são verificadas a cada ciclo. Quando umpure: false
pipe é adicionado, acredito que o método de detecção de alterações muda deCheckAlways
paraCheckOnce
por motivos de desempenho. ComOnPush
, as ligações para o Componente são verificadas apenas quando uma propriedade de entrada muda ou quando um evento é disparado. Para obter mais informações sobre detectores de mudança, uma parte importante doangular2
, confira os seguintes links:fonte
pure: false
pipe é adicionado, acredito que o método de detecção de alterações muda de CheckAlways para CheckOnce" - isso não concorda com o que você citou nos documentos. Meu entendimento é o seguinte: as entradas de um tubo sem estado são verificadas a cada ciclo por padrão. Se houver uma alteração, otransform()
método do pipe é chamado para (re) gerar a saída. Otransform()
método de um pipe com estado é chamado de cada ciclo e sua saída é verificada quanto a alterações. Consulte também stackoverflow.com/a/34477111/215945 .OnPush
(ou seja, o componente é marcado como "imutável"), a saída do tubo com estado não precisa "estabilizar" - ou seja, parece que otransform()
método é executado apenas uma vez (provavelmente todos a detecção de alterações para o componente é executada apenas uma vez). Compare isso com a resposta de @ pixelbits, onde otransform()
método é chamado várias vezes e precisa se estabilizar (de acordo com a outra resposta do pixelbits ), daí a necessidade de usar a mesma referência de array.transform
só é chamado quando novos dados são enviados em vez de várias vezes até que a saída se estabilize, certo?Demo Plunkr
Você não precisa alterar ChangeDetectionStrategy. Implementar um Pipe stateful é suficiente para fazer tudo funcionar.
Este é um canal com estado (nenhuma outra alteração foi feita):
fonte
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]
Da documentação angular
Canos puros e impuros
Existem duas categorias de tubos: puro e impuro. Pipes são puros por padrão. Cada cachimbo que você viu até agora é puro. Você torna um cano impuro definindo seu sinalizador puro como falso. Você poderia tornar o FlyingHeroesPipe impuro assim:
@Pipe({ name: 'flyingHeroesImpure', pure: false })
Antes de fazer isso, entenda a diferença entre puro e impuro, começando com um tubo puro.
Canais puros O Angular executa um cano puro apenas quando detecta uma mudança pura no valor de entrada. Uma mudança pura é uma mudança em um valor de entrada primitivo (String, Number, Boolean, Symbol) ou uma referência de objeto alterada (Date, Array, Function, Object).
Angular ignora alterações em objetos (compostos). Ele não chamará um pipe puro se você alterar um mês de entrada, adicionar a uma matriz de entrada ou atualizar uma propriedade de objeto de entrada.
Isso pode parecer restritivo, mas também é rápido. Uma verificação de referência de objeto é rápida - muito mais rápida do que uma verificação profunda de diferenças - então o Angular pode determinar rapidamente se pode pular a execução do tubo e uma atualização da vista.
Por esse motivo, um pipe puro é preferível quando você pode conviver com a estratégia de detecção de alterações. Quando você não pode, você pode usar o cano impuro.
fonte
Em vez de fazer puro: falso. Você pode copiar em profundidade e substituir o valor no componente por this.students = Object.assign ([], NEW_ARRAY); onde NEW_ARRAY é a matriz modificada.
Ele funciona para o angular 6 e deve funcionar para outras versões do angular também.
fonte
Uma solução alternativa: importar manualmente o tubo no construtor e chamar o método de transformação usando este tubo
Na verdade você nem precisa de um cachimbo
fonte
Adicione o parâmetro extra ao pipe e altere-o logo após a mudança da matriz, e mesmo com o pipe puro, a lista será atualizada
deixar item de itens | tubo: param
fonte
Neste caso de uso, usei meu Pipe no arquivo ts para filtragem de dados. É muito melhor para desempenho do que usar tubos puros. Use em ts como este:
fonte
para criar tubos impuros são caros no desempenho. portanto, não crie tubos impuros, em vez disso, altere a referência da variável de dados criando uma cópia dos dados quando alterar os dados e reatribua a referência de cópia na variável de dados original.
fonte