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}}, powere factorsão entradas.
Para esta pergunta, "#student of students | sortByName:queryElem.value", studentse queryElem.valuesão entradas e tubo sortByNameé apátrida / puro. studentsé uma matriz (referência).
- Quando um aluno é adicionado, a referência do array não muda -
studentsnão muda - portanto, o pipe sem estado / puro não é executado.
- Quando algo é digitado na entrada do filtro,
queryElem.valuemuda, 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():
this.students = this.students.concat([{name: studentName}]);
Embora isso funcione, nosso addNewStudent()método não deve ser implementado de uma determinada maneira apenas porque estamos usando um pipe. Queremos usar push()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: falseentã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 studentsreferência não tenha mudado. Se simplesmente tornarmos o canal com estado, obteremos um erro:
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 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:
No modo de desenvolvimento, tick () também executa um segundo ciclo de detecção de alterações para garantir que nenhuma alteração adicional seja detectada. Se mudanças adicionais forem detectadas durante este segundo ciclo, as ligações no aplicativo têm efeitos colaterais que não podem ser resolvidos em uma única passagem de detecção de mudança. Nesse caso, o Angular gera um erro, pois um aplicativo Angular só pode ter uma passagem de detecção de alteração durante a qual todas as detecções de alteração devem ser concluídas.
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 onPushestraté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 esse Expression ... has changed after it was checkederro com essa configuração. Isso ocorre porque um onPushcomponente 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 onPushdetecçã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 com onPushcomponentes 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 onPushdetecçã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 com onPush.
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.
pure:falseem seu Pipe echangeDetection: ChangeDetectionStrategy.OnPushem seu Componente.