Qual é a diferença entre markForCheck () e detectChanges ()

174

Qual é a diferença entre ChangeDetectorRef.markForCheck()e ChangeDetectorRef.detectChanges()?

Eu só encontrei informações sobre SO quanto à diferença entre NgZone.run(), mas não entre essas duas funções.

Para respostas com apenas uma referência ao documento, ilustre alguns cenários práticos para escolher um sobre o outro.

parlamento
fonte
@Milad Como você sabe que ele diminuiu o voto? Muitas pessoas pesquisam este site.
Adeus StackExchange
2
@FrankerZ, porque estava escrevendo e vi o voto negativo e, um segundo depois, a pergunta foi atualizada, dizendo que "Para respostas com apenas uma referência ao documento, ilustre alguns cenários práticos para escolher um sobre o outro? Isso ajudará a esclarecer em minha mente".
Milad
3
O voto negativo foi para incentivá-lo a completar a resposta original que foi apenas copiada e colada dos documentos que eu já vi. E funcionou! Agora a resposta tem muita clareza e é a resposta aceita, obrigado
:)
3
que plano tortuoso @parl Parliament!
HankCa

Respostas:

234

Dos documentos:

detectChanges (): void

Verifica o detector de alterações e seus filhos.

Isso significa que, se houver um caso em que alguma coisa dentro do seu modelo (sua classe) tenha mudado, mas não tenha refletido a exibição, talvez seja necessário notificar o Angular para detectar essas alterações (detectar alterações locais) e atualizar a exibição.

Os cenários possíveis podem ser:

1- O detector de alterações é desconectado da visualização (consulte desanexação )

2- Uma atualização aconteceu, mas não está dentro da Zona Angular; portanto, a Angular não sabe disso.

Como quando uma função de terceiros atualiza seu modelo e você deseja atualizar a visualização depois disso.

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

Como esse código está fora da zona do Angular (provavelmente), você provavelmente precisará verificar as alterações e atualizar a exibição, assim:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

NOTA :

Existem outras maneiras de fazer o trabalho acima, em outras palavras, existem outras maneiras de trazer essa mudança para dentro do ciclo de mudança angular.

** Você pode agrupar essa função de terceiros dentro de um zone.run:

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** Você pode agrupar a função dentro de um setTimeout:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3- Existem também casos em que você atualiza o modelo após o change detection cycletérmino, e nesses casos você recebe esse erro temido:

"A expressão mudou depois de ter sido verificada";

Isso geralmente significa (da linguagem Angular2):

Vi uma mudança no seu modelo causada por uma das minhas maneiras aceitas (eventos, solicitações XHR, setTimeout e ...) e, em seguida, executei minha detecção de alterações para atualizar sua visualização e a terminei, mas havia outra. no código que atualizou o modelo novamente e não quero executar minha detecção de alterações novamente porque não há mais verificação suja como o AngularJS: D e devemos usar o fluxo de dados de uma maneira!

Você definitivamente encontrará esse erro: P.

Duas maneiras de corrigi-lo:

1- Maneira adequada : verifique se a atualização está dentro do ciclo de detecção de alterações (as atualizações do Angular2 são um fluxo de sentido único que ocorre uma vez, não atualize o modelo depois disso e mova seu código para um local / hora melhor).

2- Maneira preguiçosa : execute detectChanges () após essa atualização para tornar angular2 feliz, essa definitivamente não é a melhor maneira, mas como você perguntou quais são os cenários possíveis, esse é um deles.

Dessa forma, você está dizendo: Sinceramente, sei que você executou a detecção de alterações, mas quero que você faça isso novamente porque tive que atualizar algo rapidamente depois que você terminou a verificação.

3- Coloque o código dentro de a setTimeout, porque setTimeouté corrigido pela zona e será executado detectChangesdepois que terminar.


Dos documentos

markForCheck() : void

Marca todos os ancestrais ChangeDetectionStrategy como a serem verificados.

Isso é necessário principalmente quando o ChangeDetectionStrategy do seu componente é OnPush .

OnPush propriamente dito significa, somente execute a detecção de alterações se alguma dessas coisas aconteceu:

1 - Um dos @inputs do componente foi completamente substituído por um novo valor, ou simplesmente, se a referência da propriedade @Input mudou completamente.

Portanto, se ChangeDetectionStrategy do seu componente for OnPush , você terá:

   var obj = {
     name:'Milad'
   };

E então você atualiza / modifica como:

  obj.name = "a new name";

Isso não atualizará a referência obj , portanto, a detecção de alterações não será executada, portanto a exibição não está refletindo a atualização / mutação.

Nesse caso, você precisa informar manualmente o Angular para verificar e atualizar a exibição (markForCheck);

Então, se você fez isso:

  obj.name = "a new name";

Você precisa fazer isso:

  this.cd.markForCheck();

Em vez disso, abaixo faria com que uma detecção de alterações fosse executada:

    obj = {
      name:"a new name"
    };

O que substituiu completamente o objeto anterior por um novo {};

2- Um evento foi acionado, como um clique ou algo assim ou qualquer componente filho emitiu um evento.

Eventos como:

  • Clique
  • Keyup
  • Eventos de assinatura
  • etc.

Então, resumindo:

  • Use detectChanges()quando você atualizou o modelo após a execução do angular, é a detecção de alterações ou se a atualização não está no mundo angular.

  • Use markForCheck()se você estiver usando o OnPush e estiver ignorando ChangeDetectionStrategyalguns dados ou tiver atualizado o modelo dentro de um setTimeout ;

Milad
fonte
6
Portanto, se você alterar esse objetivo, a visualização não será atualizada e, mesmo que você execute o detectChanges, não funcionará porque não houve nenhuma alteração - isso não é verdade. detectChangesatualizações. Veja esta explicação detalhada .
precisa saber é o seguinte
Quanto ao markForCheck na conclusão, também não é preciso. Aqui está modificado exemplo de esta questão , ele não detecta alterações de objetos com OnPush e markForCheck. Mas o mesmo exemplo funcionará se não houver estratégia OnPush.
Estus Flask
@ Maximus, sobre o seu primeiro comentário, eu li o seu post, obrigado por isso foi bom. Mas, na sua explicação, você está dizendo se a estratégia é OnPush, significa que this.cdMode === ChangeDetectorStatus.Checked, se não atualizar a exibição, é por isso que você usaria o markForCheck.
Milad
E Quanto às suas ligações com Plunker, ambos os exemplos estão funcionando bem para mim, eu não sei o que dizer
Milad
@ Milad, esses comentários vieram de @estus :). O meu era sobre detectChanges. E não há cdModeno Angular 4.x.x. Eu escrevo sobre isso no meu artigo. Que bom que você gostou. Não esqueça que você pode recomendar-lo no meio ou siga-me :)
Max Koretskyi
99

A maior diferença entre os dois é que, detectChanges()na verdade, aciona a detecção de alterações, enquanto markForCheck()não aciona a detecção de alterações.

detectChanges

Este é usado para executar a detecção de alterações na árvore de componentes começando com o componente no qual você dispara detectChanges(). Portanto, a detecção de alterações será executada para o componente atual e todos os seus filhos. O Angular mantém referências à árvore de componentes raiz na ApplicationRefe quando qualquer operação assíncrona ocorre, ela dispara a detecção de alterações nesse componente raiz através de um método de wrapper tick():

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

viewaqui está a visão do componente raiz. Pode haver muitos componentes raiz, como descrevi em Quais são as implicações da inicialização de vários componentes .

O @milad descreveu os motivos pelos quais você pode precisar ativar a detecção de alterações manualmente.

markForCheck

Como eu disse, esse cara não aciona a detecção de alterações. Ele simplesmente sobe do componente atual para o componente raiz e atualiza seu estado de exibição para ChecksEnabled. Aqui está o código fonte:

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  <-----------------
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

A detecção de alterações real para o componente não está agendada, mas quando ocorrerá no futuro (como parte do ciclo atual ou do próximo CD), as visualizações do componente pai serão verificadas mesmo que eles tenham desconectado os detectores de alterações. Os detectores de alterações podem ser desconectados usando cd.detach()ou especificando a OnPushestratégia de detecção de alterações. Todos os manipuladores de eventos nativos marcam todas as visualizações do componente pai para verificação.

Essa abordagem é frequentemente usada no ngDoCheckgancho do ciclo de vida. Você pode ler mais em Se você acha que ngDoCheckseu componente está sendo verificado - leia este artigo .

Consulte também Tudo o que você precisa saber sobre detecção de alterações no Angular para obter mais detalhes.

Max Koretskyi
fonte
1
Por que detectChanges funciona no componente e em seus filhos, enquanto markForCheck no componente e nos ancestrais?
Pablo
@ Pablo, isso é por design. Eu realmente não estou familiarizado com o raciocínio
Max Koretskyi
@ AngularInDepth.com a detecção de alterações bloqueia a interface do usuário se houver um processamento muito intenso?
alt255
1
@jerry, a abordagem recomendada é usar o canal assíncrono, que rastreia internamente a assinatura e a cada novo valor acionado markForCheck. Portanto, se você não estiver usando pipe assíncrono, provavelmente é isso que você deve usar. No entanto, lembre-se de que a atualização da loja deve ocorrer como resultado de algum evento assíncrono para iniciar a detecção de alterações. Esse é sempre o caso. Mas há exceções blog.angularindepth.com/…
Max Koretskyi
1
@MaxKoretskyiakaWizard obrigado pela resposta. Sim, a atualização da loja é principalmente o resultado de uma busca ou a configuração éFetching antes. e depois da busca .. mas nem sempre podemos usar, async pipepois dentro da assinatura geralmente temos algumas coisas para fazer call setFromValues do some comparison.. e se asyncele chama markForCheckqual é o problema, se o chamarmos nós mesmos? mas, novamente, normalmente temos de 2 a 3 ou mais seletores para ngOnInitobter dados diferentes ... e chamamos markForChecktodos eles .. está tudo bem?
jerry
0

cd.detectChanges() executará a detecção de alterações imediatamente do componente atual até os descendentes.

cd.markForCheck()não executará a detecção de alterações, mas marcará seus ancestrais como precisando executar a detecção de alterações. Na próxima vez que a detecção de alterações for executada em qualquer lugar, será executada também para os componentes que foram marcados.

  • Se você deseja reduzir o número de vezes que a detecção de alterações é chamada de uso cd.markForCheck(). Freqüentemente, as mudanças afetam vários componentes e em algum lugar a detecção de alterações será chamada. Você está basicamente dizendo: vamos apenas garantir que esse componente também seja atualizado quando isso acontecer. (A visualização é atualizada imediatamente em todos os projetos que escrevi, mas não em todos os testes de unidade).
  • Se você não tiver certeza de que cd.detectChanges()não está executando a detecção de alterações no momento, use cd.markForCheck(). detectChanges()irá erro nesse caso. Isso provavelmente significa que você está tentando editar o estado de um componente ancestral, que está trabalhando contra as suposições para as quais a detecção de alterações do Angular é projetada.
  • Se for crítico que a exibição seja atualizada de forma síncrona antes de outra ação, use detectChanges(). markForCheck()pode não atualizar sua visualização a tempo. O teste de unidade de algo afeta sua visualização, por exemplo, pode exigir que você ligue manualmente fixture.detectChanges()quando isso não for necessário no próprio aplicativo.
  • Se você estiver alterando o estado em um componente com mais ancestrais que descendentes, poderá obter um aumento no desempenho, detectChanges()pois não está executando desnecessariamente a detecção de alterações nos ancestrais do componente.
Kevin Beal
fonte