Angular: ExpressionChangedAfterItHasBeenCheckedError: a expressão foi alterada após ter sido verificada em ng 4

Criado em 16 jun. 2017  ·  201Comentários  ·  Fonte: angular/angular

Estou enviando um ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Comportamento atual


Depois de usar um objeto observável em meu modelo usando async, estou recebendo:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: a expressão mudou depois de ser verificada. Valor anterior: ''. Valor atual: 'algo'

Comportamento esperado

Conte-nos sobre o seu ambiente

<br i="15"/>
Angular version: 4.2.2

<p i="16">Browser:</p>

<ul i="17">
<li i="18">[X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)</li>
</ul>

<p i="19">For Tooling issues:</p>

<ul i="20">
<li i="21">Node version: v6.10.3</li>
<li i="22">Platform: Mac</li>
</ul>
core regression bufix

Comentários muito úteis

Eu tenho o mesmo problema depois de atualizar de 4.1.3 para 4.2.3. Usar setTimeout corrige o problema.

de:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

para:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

Todos 201 comentários

Tive o mesmo problema com o uso de redux. Aqui está um link para o problema que postei em seu repositório. Quando no angular 2 não há erros, mas no angular 4.2.2 eu consigo alterar a expressão depois de verificar os erros.
https://github.com/angular-redux/store/issues/428

O problema aparece para mim em meus componentes ao passar de 4.1.3 para 4.2.x. Reverter para a versão 4.1.3 corrige o problema para mim, mas isso não é algo que eu queira manter para sempre.

Eu poderia reproduzir em um projeto mínimo, ainda estou tentando descobrir como corrigir essa (talvez) regressão. Mas parece que a verificação suja foi aplicada no dev. modo de 4.2.x. Mas o changelog não é claro sobre isso. Qualquer dica bem-vinda.

Eu tenho o mesmo problema depois de atualizar de 4.1.3 para 4.2.3. Usar setTimeout corrige o problema.

de:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data);

para:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data, 0));

@jingglang, você poderia tentar reproduzir o problema em http://plnkr.co/edit/tpl : AvJOMERrnz94ekVua0u5 com o mínimo de código possível?

Estou tendo o mesmo problema - atualizado de 2.4 para 4.2, e parte da minha lógica de ocultar / mostrar agora está quebrada. É uma lógica tipo sanfona, onde a visibilidade depende da comparação do painel atual com um observável que contém qual painel deve ser visível (entre outras coisas). O componente usa @select para obter o observável e está vinculado a | assíncrono no modelo - funcionou perfeitamente no 2.4, agora obtenha o ExpressionChangedAfterItHasBeenCheckedError e os painéis não se escondem e mostram no primeiro clique, apenas no segundo.

Tenho o mesmo problema quando atualizo 4.1.3 para 4.2.2.

Mas, eu encontrei uma solução alternativa.
Injeção de ChangeDetectionRef e chamar detectionChanges() função no ponto de erro.

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

Acho que esse é um problema com a alteração a seguir feita em 4.2.

https://github.com/angular/angular/pull/16592

para resolver esse problema: https://github.com/angular/angular/issues/14321

Você tem algum conteúdo dentro de uma tag ng-content ?

Tenho o mesmo problema com um formulário dentro de uma tag ng-content que agora apresenta o erro ContentChangedAfter.

Tenho o mesmo problema quando atualizo 4.1.3 para 4.2.0 ou superior

Anexei um pequeno projeto para reproduzir o bug

app-error.zip

Em seguida, fiz o downgrade para 4.1.3 (conforme recomendado acima) e o erro desaparece, então, seja qual for o conflito, ele é específico para 4.2. Não tive tempo de montar um plunkr nesta semana (ou na próxima), mas parece estar relacionado a uma única ação atualizando dois observáveis ​​separados na loja, cada um dos quais tem um impacto na IU. Uma das atualizações da IU acontece, a outra causa a exceção.

Hmmm,
Definitivamente, a reversão para a versão 4.1.3 resolve o problema e também aplica o tempo limite, conforme mostrado por @jingglang.

oi @tytskyi. Fiz um plunkr http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT para esse problema. espero que ajude.

a solução alternativa fornecida por @jingglang funciona (para mim) em 4.2.0 ou superior
OU também alterar o evento de emissão do filho em seu construtor não levanta mais o erro

@umens Você atualiza o componente pai após ele ter sido verificado, portanto, você não mantém o fluxo de dados unidirecionado

@alexzuza como posso conseguir isso? Sempre fiz assim sem errar. Por que colocar o SetTimout não levanta mais o erro. (Eu atualizei o plunkr) Duvido que esteja interferindo no ciclo de vida ou não?

oi @umens, obrigado pelo seu tempo. O problema é o que @alexzuza diz: você atualiza o campo do componente pai depois que ele é verificado. Seu código é aproximadamente equivalente a este: http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm?p=preview (observe que removi o serviço, para reduzir o código).
Por que funcionou? Provavelmente por acidente a versão antiga do angular ou Rxjs teve um bug aqui. Você poderia dizer quais versões foram usadas? Ou mesmo colocado no êmbolo de trabalho?

Por que colocar o SetTimout não levanta mais o erro.

Porque você altera o campo de forma assíncrona, o que respeita o fluxo de dados unilateral.

Vamos considerar do contrário: Por que o erro é gerado? A primeira verificação de detecção de mudança vai de cima para baixo. Ele verifica o app.toolsConfig vinculado ao modelo. Em seguida, ele renderiza <child-cmp> que atualiza app.toolsConfig síncrona . A renderização está concluída. Agora, ele executa a segunda verificação (somente modo de desenvolvimento) da detecção de alterações para garantir que o estado do aplicativo seja estável. Mas app.toolsConfig antes de renderizar o filho não é igual a app.toolsConfig depois. Tudo isso acontece durante um único "turno", "ciclo" de Detecção de Mudanças.

Está bem. obrigado pela resposta detalhada. Não consegui encontrar informações sobre quando o ciclo de vida do componente filho acontece.
para a versão anterior "funcional" que usei:
@ angular / *: 4.1.1 (exceto compiler-cli -> 4.1.0)
rxjs: 5.3.1

@umens aqui é um erro reproduzido com as versões antigas que você mencionou: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview. Ele joga, então provavelmente alguma dependência de terceiros afetou o comportamento ou não completou as instruções de reprodução?

não posso dizer.
aqui está meu yarn.lock se você estiver interessado em ir mais longe: https://pastebin.com/msARLta1
mas eu não sei o que pode ser diferente

Estou vendo erros semelhantes "ExpressionChanged ..." após alternar de 4.1.2 para 4.2.3 .

No meu caso, tenho dois componentes que interagem por meio de um serviço. O serviço é fornecido por meio do meu módulo principal e ComponentA é criado antes de ComponentB .

Em 4.1.2 , a seguinte sintaxe funcionou perfeitamente sem erros:

Modelo do Componente A:

<ul *ngIf="myService.showList">
...

Classe ComponentB:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

Em 4.2.3 , tenho que modificar o modelo de ComponentA da seguinte maneira para evitar o erro "ExpressionChanged ...":

Modelo do Componente A:

<ul [hidden]="!myService.showList">
...

Ainda estou tentando descobrir por que isso acaba de se tornar um problema e por que a mudança de *ngIf para [hidden] funciona.

Eu tenho o mesmo problema, e isso acontece principalmente quando estou usando um pipe assíncrono como este:
<div>{{ (obs$ |async).someProperty }}</div>
Se eu usar assim:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

Então o erro desaparece, então não tenho tanta certeza de que é apenas porque muitas pessoas cometeram algum erro que não foi detectado antes: Acho que um bug foi introduzido em 4.2…

O mesmo problema para mim. O problema parece vir de @ angular / router

Também cheguei à conclusão de que está relacionado ao roteador. Tentei postar tanto ontem, mas parece que a postagem falhou. Investiguei mais e simplifiquei o código para ver se conseguia rastrear o erro. O código a seguir funciona perfeitamente quando a fonte final da ação é um clique no cabeçalho do painel, mas falha com o erro ExpressionChanged quando acionado por meio de roteamento.

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

Isso me ajudou a resolver o problema! Você tem que acionar explicitamente a mudança no pai.

Como @acidghost , eu poderia resolver esse problema executando ChangeDetectorRef#detectChanges em AfterViewInit do meu componente pai.

Se você tem o plunkr repro, estou disposto a verificar porque tivemos algumas reclamações semelhantes no gitter e sempre acabou sendo um problema do usuário.

Não posso ter certeza se um bug foi introduzido em 4.2 ou se o bug foi a falta de erro em <4.2 e foi resolvido em 4.2.

Erro: ExpressionChangedAfterItHasBeenCheckedError
solução:
componentRef.changeDetectorRef.detectChanges ();

Abaixo está um trecho do meu código (componentes gerados dinamicamente):
componentRef = viewContainerRef.createComponent (componentFactory); // componente foi criado
(componentRef.instance) .data = input_data; // alterando manualmente os dados do componente aqui
componentRef.changeDetectorRef.detectChanges (); // resultará na detecção de alterações

Para componentes simples, basta pesquisar no Google como acessar "componentRef" e executar
componentRef.changeDetectorRef.detectChanges ();
após configurar / alterar os dados / modelo do componente.

Solução 2:
Eu estava recebendo este erro novamente ao abrir a caixa de diálogo "this.dialog.open (DialogComponent)".
portanto, colocar esse código de diálogo aberto dentro de uma função e aplicar setTimeout () nessa função resolveu o problema.
ex:
openDialog () {
let dialogRef = this.dialog.open (DialogComponent);
}
ngOnInit (): void {
setTimeout (() => this.openDialog (), 0);
}
embora pareça hack .. funciona !!!

Desde 4.2 eu tenho o mesmo problema e nenhuma solução ou solução alternativa funcionou para mim :( eu crio um componente dinamicamente com uma fábrica de componentes em um método ngAfterViewInit. Este componente usa uma diretiva que tem um campo @Input() . Eu vinculo isso para uma string estática. Parece que o campo @Input() da diretiva é indefinido até que a visualização do componente seja construída e verificada e só então inicializada com a string estática.

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd?p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

test.directive.ts

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

dynamic.component.ts

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: a expressão mudou depois de ser verificada. Valor anterior: 'undefined'. Valor atual: 'teste'. Parece que a exibição foi criada depois que seu pai e seus filhos foram verificados incorretamente. Ele foi criado em um gancho de detecção de alterações?

Estou um pouco confuso lendo este tópico. Este é um bug conhecido, que será resolvido em uma próxima versão? Ou este é um comportamento esperado? Em caso afirmativo, qual é a solução? Obrigado.

Não faço ideia se está relacionado ou não, mas odeio essa mensagem com paixão! isso APENAS aparece para mim durante o teste de unidade com o comando 'ng test' pronto para uso. Acho que posso ficar confuso sobre como simular com sucesso um serviço simples. Também fiquei surpreso que ninguém mais viu isso nos testes ainda, não consigo encontrar nada relacionado ao teste. Estarei observando este tópico e qualquer pesquisa por ExpressionChangedAfterItHasBeenCheckedError + teste de unidade.

Estou tentando montar um plnkr e achando muito difícil reproduzir o erro - no entanto, a depuração extensa, acho, me deixou um pouco mais perto de ver o que está acontecendo de errado (embora não de decidir se é um bug, ou um erro da minha parte).

Construí um pequeno aplicativo de amostra usando versões reduzidas dos mesmos redutores combinados na mesma ordem do aplicativo real, com o routerReducer por último. Para minha frustração, o erro não ocorreu. Então, eu defini pontos de interrupção e observei o fluxo das ações através dos redutores. O que descobri foi que os dois aplicativos estavam despachando ações na ordem inversa.

Meu redutor de IU gerencia a visibilidade e é a fonte da mudança de estado que está provocando o erro. Aqui está uma versão reduzida do redutor, mostrando as ações relevantes:


export const userInterfaceReducer = (state = INITIAL_UI(), action: any) => {
    switch (action.type) {
        case IngredientActions.LIST_INGREDIENTS:
            if (action.hideTypes) {
                return Object.assign(state, { visiblePanel: VisiblePanel.Ingredients });
            }
            return state;
        default:
            return state;
    }
}

Quando eu executo isso em meu pequeno aplicativo de teste, ele funciona - primeiro, a ação LIST_INGREDIENTS é acionada e o novo estado é retornado; então, a ação @ angular-redux / router :: UPDATE_LOCATION é acionada, e o caso padrão é atingido, retornando o mesmo estado.

Quando eu o executo no aplicativo real, a ordem ou as ações são invertidas. @ angular-redux / router :: A ação UPDATE_LOCATION dispara primeiro, retornando o estado anterior do padrão. Então LIST_INGREDIENTS é acionado, retornando o novo estado (alterado) - e o erro é gerado.

Estou completamente aberto à ideia de que fiz algo estúpido e este não é um bug real - mas se for, alguém mais viu o que fiz?

(Como uma nota de rodapé, eu verifiquei a versão 4.1.3 ... e ela também dispara os redutores redux na ordem 'correta' - ou seja, a mudança de local dispara após a ação listar ingredientes, que presumivelmente é o motivo pelo qual funciona, mas a versão 4.2 não).

Sofrendo o mesmo bug irritante quando um componente altera a propriedade do serviço. Resolvido movendo * ngIf para o componente pai. Então é definitivamente um bug

conseguiu contornar isso usando o método ngDoCheck () e chamando o método ChangeDetectorRef.detectChanges.

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

@pappacurds Não faça isso. Você causou ciclos infinitos de Detecção de Mudanças com isso.

pode haver o mesmo erro aqui https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb?p=preview

erro: previous undefined after undefiend

após atualização mesmo erro

O mesmo para mim

Tudo bem no 4.0.0, após atualizar para o 4.2.6 recebo o mesmo erro.

O artigo Tudo o que você precisa saber sobre o erro ExpressionChangedAfterItHasBeenCheckedError explica em detalhes por que esse erro ocorre e as possíveis correções

as correções são óbvias, mas mudar a estrutura de forma que a atualização faça com que os aplicativos do usuário sejam reescritos para "oferecer suporte" a novas atualizações de plataforma menores. JS ainda mantém erros antigos para manter a compatibilidade ...

Mesmo erro aqui

Também estou vendo esse problema em ~4.2.4 . O downgrade para ~4.1.3 resolveu o problema.

O problema é alterar os valores de ContentChildren , mesmo ao usar ChangeDetectorRef detectChanges() no final de AfterViewInit do pai (onde estava fazendo as alterações). Estranhamente, esse problema ocorre apenas com componentes e não com diretivas. Converti uma diretiva que estava trabalhando em 4.2.4 em um componente com um modelo de <ng-content></ng-content> e então comecei a gerar o erro ExpressionChangedAfterItHasBeenCheckedError .

Eu consegui isso enquanto trabalhava com o controle de entrada do material 2. Tentei definir o valor dele

ngAfterViewInit(): void {
this.numberFormControl.setValue(200);
}

Eu montei um Plunker simples mostrando um caso em que os erros ExpressionChangedAfterItHasBeenCheckedError só começaram a aparecer começando com 4.2.x Angular.

Eu criei um plunker simples mostrando o erro ao alterar um valor de ContentChild mesmo ao usar ChangeDetectorRef detectChanges() no componente pai ngAfterViewInit .

Edit: Também criei um plunker simples com base no meu primeiro exemplo, mas com diretiva pai em vez de componente e nenhum erro.

@saverett Estou vendo uma mensagem de erro diferente com o seu plunk

@JSMike , tive exatamente o mesmo caso do seu plunker (resolvi isso envolvendo o código que referencia ContentChild em setTimeout , plunker )

@JSMike Obrigado pelo

@matkokvesic isso não é realmente uma solução, apenas faz com que a mudança ocorra fora do gancho do ciclo de vida. O exemplo de código fornecido não causa um erro ao usar o Angular v4.1.3

@JSMike Eu concordo, é uma solução temporária. Usar elementos referenciados por ContentChildren em ngAfterViewInit estava funcionando bem antes do Angular 4.2.6.

Eu corrigi usando changeDetection: ChangeDetectionStrategy.OnPush e this.changeDetector.markForCheck(); em meu código

@touqeershafi se você usar this.changeDetector.markForCheck(); não significa que você não está usando as coisas da maneira certa

Isso ainda está acontecendo. Este é definitivamente um bug, não um comportamento esperado e deve ser corrigido.

@istiti eu entendo que não é uma maneira adequada, mas pelo menos você pode se livrar desse erro temporariamente, mas no que diz respeito ao meu projeto, eu já adicionei TODO com url do bug.

Isso ainda está acontecendo. Este é definitivamente um bug, não um comportamento esperado e deve ser corrigido.

Acho que tenho que concordar com @rwngallego ... Antes da atualização 4.2.x, eu já tinha escrito uma série de componentes de estilo / ng-content reutilizáveis ​​que dependem de @ViewChildren , @ContentChildren e criam relacionamentos entre pais e filhos por meio deles canais (obter / definir propriedades e assim por diante).

Encontro-me constantemente tendo que injetar a instância ChangeDetectorRef em todos esses construtores (incluindo classes filhas que estendem esses componentes), e não parece um comportamento pretendido ou correto.

Para aqueles que usam componente dinâmico como @saverett (usar roteador é usar componente dinâmico), acho que é o problema declarado aqui https://github.com/angular/angular/issues/15634.

Para qualquer um que esteja fazendo alterações dentro de ngAfterViewInit / ngAfterViewChecked , acho que é o esperado.

Para os outros (ainda não vi nenhum aqui) ainda é um mistério para mim.

Isso está acontecendo para mim no seguinte cenário.
Tenha um componente pai que usa um componente filho. O componente filho precisa de dados que o pai fornecerá, mas esses dados vêm de um resultado prometido no construtor pai. Então:

pai

constructor {
dataservice.getData().then((result) => {
this.source = result;
});
}

Modelo pai

<app-child [source]="source"></app-child>

componente filho

@Input() source:any[];

Este código gera esse erro, mas adicioná-lo ao componente pai resolveu o problema:

constructor(private cdRef: ChangeDetectorRef) {
dataservice.getData().then((result) => {
this.source = result;
 this.cdRef.detectChanges();
});
    }

Agora, esta é uma solução recomendada ou é uma solução alternativa para um bug e quais seriam as implicações de tal alteração? para mim, parece que você está dizendo ao Angular para detectar mudanças, o que eu acho que você deve evitar, então ...

@sulhome ... seu problema é que a promessa no construtor do pai é alterar @Input no componente filho ... durante o ciclo do CD e sua confirmação no modo dev. Essa é a razão do erro.

A linha this.cdRef.detectChanges(); adiciona o segundo ciclo do CD para que a confirmação após ele seja bem-sucedida e => sem erro.

Então tudo funciona conforme o esperado. É importante entender como funciona usar as construções certas e a lógica geral em seu código para evitar esse tipo de problema. Você pode ler sobre isso na explicação detalhada aqui: https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

@ mlc-mlapis então você está dizendo que estou fazendo certo ao adicionar this.cdRef.detectChanges(); e que isso não tem nenhuma implicação?
Eu pensei que ao ouvir ngOnChanges na criança, eu deveria escolher a mudança no @Input do pai. não é a abordagem certa para isso? Observe que eu fiz isso e ainda estava recebendo o erro, mas pensei que ngOnChanges era para isso

@sulhome Sim, this.cdRef.detectChanges(); é o que ajuda, mas também tem a consequência de que você tem 2 CDs agora ... então depende de como e onde você está usando a estratégia OnPush em componentes para eliminar a sobrecarga de mais um ciclo de CD.

Se for verdade que dataservice.getData obtém alguns dados usando http, então provavelmente a solução mais fácil seria ter um observável nesse serviço e assiná-lo a partir do componente filho. Portanto, quando os dados forem emitidos no fluxo, a criança poderá reagir a eles.

@ghetolay para registro, há alguns casos em que é um bug, veja por exemplo # 18129 que abri.

Eu tenho esse problema quando obtenho dados do componente filho com @Output.

na casa dos componentes principais.

drag_second(trigger){ console.log("dragged222222"+trigger); if(trigger){ this.isactive=true; }else{ this.isactive=false; } }
e home.html
<div class="upper" [ngClass]="{'isactive':isactive}">

isactive é alterado quando dragstart e dragend ....

mensagem de erro é
ExpressionChangedAfterItHasBeenCheckedError: a expressão foi alterada após ser verificada. Valor anterior: 'true'. Valor atual: 'false'.

ou há alguma outra maneira de alterar / adicionar atributo de classe?

Mesmo problema em 4.2.xe 4.3.0, mas não em 4.1.x.

O erro ocorre apenas em rotas que usam ng-template .

Solução: colocar todo o código de ngAfterViewInit em setTimeout resolveu o problema para mim (obrigado / créditos para @jingglang).

public ngAfterViewInit(): void {
    setTimeout(() => {
        //my code
    });
}

@Wernerson , dê uma olhada no comentário de JSMike sobre setTimeout. É importante observar que, na verdade, você está apenas alterando o código fora do gancho do ciclo de vida.

Meu problema era que eu usei ActivatedRoute params Observable para modificar algum estado em ngOnInit() . Parece que o Router emite o params synchron, portanto a atualização acontece durante o CD.
Minha solução simples para isso é atualmente:

  // !!! see delay(0)
  // results are consumed by async Pipe, where I also had the CD issue (null vs. undefined)
  this.results = this._activatedRoute.params.delay(0)
    .do(params => this.searchParameters = this._createSearchParameters(params))
    .switchMap(p => {
      let key = this.searchParameters.key();
      return this._searchResultCache.results.map(r => r.get(key));
    });

(Eu sei que este não é o código reativo / funcional mais bonito de todos;))
Então a solução foi delay(0) a emissão do parâmetro por um tick .

Espero poder ajudar outras pessoas com problemas semelhantes.
Além disso, quaisquer preocupações são bem-vindas, como minha solução é errada ou perigosa.

Qual é a situação desse problema? Ainda está aparente no Angular 4.2.6.
Isso é um problema nas compilações de produção? Como esse erro só acontecerá em compilações de depuração.

Odeio todas as soluções alternativas para executar manualmente a detecção de alterações ou mesmo executá-la em um setTimeout () ...

setTimeout() é ineficiente, porque causa um novo ciclo de detecção de alterações em todo o aplicativo, enquanto detectChanges() verifica apenas o componente atual AFAIK.

Minha equipe teve um mal-entendido central sobre o que é e o que não é ngOnInit. Os docs dizem

ngOnInit() : Inicialize a diretiva / componente após Angular exibir primeiro as propriedades vinculadas a dados e definir as propriedades de entrada da diretiva / componente. Chamado uma vez, após o primeiro ngOnChanges ().

Estávamos assinando nossos serviços (que monitoram o estado do aplicativo) de ngOnInit . Nossas assinaturas são BehaviorSubjects síncronos ; eles disparam no momento da assinatura. Portanto, "após o Angular primeiro ... definir as propriedades de entrada da diretiva / componente", estávamos imediatamente alterando os valores. Aqui está um Plunker muito simples que ilustra isso como um problema.

O exemplo em Tour Of Heroes, em última análise, usa http, que é assíncrono e, portanto, não causa problemas. Acho que essa é a fonte da nossa confusão.

O que estamos fazendo agora é assinar nossos estados síncronos de nosso construtor, para que os valores de nossos componentes sejam configurados imediatamente. Outros neste tópico sugeriram iniciar uma segunda detecção de alteração por meio de tempos limite, definindo valores mais tarde ou dizendo explicitamente ao angular para fazer isso. Isso evitará que o erro apareça, mas provavelmente é desnecessário.

O artigo que @maximusk compartilhou é de leitura obrigatória : Tudo o que você precisa saber sobre o erro ExpressionChangedAfterItHasBeenCheckedError .

Se nada mais, este tópico deve resultar em um aviso detalhado de tudo isso nos documentos oficiais do angular.

O artigo que @maximusk compartilhou é de leitura obrigatória: Tudo o que você precisa saber sobre o erro ExpressionChangedAfterItHasBeenCheckedError.

Obrigado

@adamdport

Isso funciona no caso de usar @ViewChildren @ContentChildren para modificar as propriedades filho do pai, uma vez que os filhos não estão disponíveis até que o ciclo de vida ngAfterViewInit () tenha sido executado?

Lembre-se de que uma parte desse problema é um bug confirmado e não uma falha do usuário:
https://github.com/angular/angular/issues/15634

O problema @saverett endereçado está correto, usando *ngIf estava causando o erro.

*assistindo...

Isso pode acontecer se você passar objetos para uma caixa de diálogo de Material Angular e, em seguida, usá-los diretamente na caixa de diálogo em vez de fazer this.myDataInput = Object.assign({}, dialogDataInput) . Ou seja, this.myDataInput = dialogDataInput; e então fazer algo como this.myDataInput.isDefault = !this.myDataInput.isDefault; . Como explicado acima, o componente filho (caixa de diálogo) está alterando os valores que são expostos na visualização do componente pai.

substitua o atributo por [atributo]

Alguém sabe com certeza se este é um erro intencional ou um bug?
Não é correto carregar dados no ngOnInit? Porque se eu mover o código para o construtor (carregando através de um Observable), o erro não é lançado ...

@Wernerson . Você tem * ngIf em seu html? cuidado, pois as coisas serão renderizadas após ngOnInit terminar, solong, tudo dentro de * ngIf não funcionará e pode levar a este erro.

Tem que ser retrabalhado. Com "ngIf" e controles personalizados, você obtém esta mensagem de erro. Um tempo limite certamente não é uma solução.

@ j-nord se você leu os comentários, sabe que timeout não é necessário.

@ j-nord / @dgroh Eu experimentei o mesmo. Estou usando um ng-hint que usa uma diretiva ngIf e não consigo me livrar desse erro. Você conseguiu implementar uma solução alternativa?

@Wernerson sim, uma parte desse problema é um erro intencional ou um bug: https://github.com/angular/angular/issues/15634

@ mawo87 funcionou para mim com o oculto , pois não remove o elemento do DOM. Para a nossa necessidade, isso estava ok e também era uma solução limpa.

@ mawo87
Primeiro, você deve encontrar a variável que se refere à mensagem de erro. @Angular Team : Seria bom ter o nome da variável na mensagem de erro. Isso não é fácil de encontrar por máscaras maiores e Webpack. Se você encontrou o comando onde ele acontece, o comando na linha a seguir ajudará:

this.cdr.detectionChanges();

Veja o exemplo do vermilion928 de 19.Jun.

Obrigado @dgroh e @ j-nord.
@ j-nord Eu também estava olhando para o método detecçãoChanges, mas posso não usar essa abordagem, pois no artigo a seguir ela foi desaconselhada:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

No meu caso, estou usando um serviço PageTitle e me inscrevendo nele no componente pai e, à medida que os componentes filhos são carregados no modo de exibição, eles definem o PageTitle no serviço e o pai obtém o valor e o exibe por meio da assinatura. Isso funcionou sem erros na versão 4.1.x, mas gerou o erro ExpressionChangedAfterItHasBeenCheckedError na versão 4.2x e posterior.

Tudo ainda funciona conforme o esperado e os erros não são exibidos nas compilações implantadas / de produção (devido à falta de um segundo ciclo de detecção de alterações), mas no desenvolvimento há uma quantidade enorme de vermelho no meu console.

Ainda estou extremamente incerto sobre qual é a correção apropriada (mesmo de curto prazo). Minhas assinaturas de componente pai estão no construtor e as atualizações de componente filho por meio do serviço estão em ngOnInit.

Eu li a postagem 'tudo que você precisa saber sobre ...', que foi informativa, mas não me leva a acreditar que estou fazendo algo incorreto e não fornece uma solução funcional para o problema.

@alignsoft se você definir o pagetitle nos componentes filhos no ngOnInit, então eu acredito que o erro é válido ... Veja o artigo que foi linkado antes.

@alignsoft Eu concordo, tem havido muita discussão em torno desse erro, mas sem uma instrução clara e real sobre se é válido ou não ... isso é mais um "aviso" ao desenvolvedor sobre padrões e vinculação / manipulação de dados?

No meu caso específico, estou lidando com componentes pai / filho usando @ViewChild e @ContentChild , tendo que chamar detectChanges () repetidamente para impedir que o erro apareça no console ...

@rolandoldengarm Tentei assinar o construtor pai e definir os valores em AfterViewInit nos componentes filhos e ainda assim recebo o erro. Essa é a maneira correta de lidar com isso com base no fluxo e no ciclo de detecção de alterações, e o erro é potencialmente um bug?

Por favor, seria ótimo ter algum tipo de resposta oficial aqui ...

Olhando de novo. Parece meu exemplo de https://github.com/angular/angular/issues/17572#issuecomment -315096085
deveria estar usando AfterContentInit vez de AfterViewInit ao tentar manipular ContentChildren como parte do ciclo de vida do componente.

Aqui está o plnkr atualizado com AfterContentInit e sem mais erros: http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@JSMike Eu mudei AfterViewInit para AfterContentInit , ainda o mesmo erro. Mesmo se eu atualizar meus dados dentro de OnInit o erro ocorre. Pelo que eu sei, isso está de acordo com o ciclo de vida Angular correto (certo?) Para inicializar os dados durante OnInit . Juntamente com o fato de que esse erro não ocorreu nas versões anteriores, presumo que seja um bug. No entanto, gostaria de ter algum tipo de declaração da equipe de desenvolvimento se for assim ..? : D

@Wernerson, você não deve atualizar dados dentro de ngOnInit , a menos que os dados estejam disponíveis naquele gancho de ciclo de vida. Você deve mover sua lógica para ngAfterContentInit se tiver a ver com conteúdo filho.

@JSMike Não ajudou, o erro ainda ocorre. Acabei de me inscrever para uma mudança de estado no aplicativo depois de receber os dados solicitados (de forma assíncrona) em ngAfterContentInit .

@Wernerson , você tem um exemplo de plnkr?

Amostra básica com StackBlitz
Um componente filho que emite um evento e um componente pai atualizando a variável local.
Obter erro no console js:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

Descobrimos que ngAfterContentInit só funciona se os filhos forem estáticos, mas ainda falha ao criar filhos usando *ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@soldiertt você só precisa de detectChanges () https://stackblitz.com/edit/angular-qfu74y

@JSMike se eu fizer isso, o ngAfterViewInit do componente filho não será acionado: https://stackblitz.com/edit/angular-bdwmgf

@soldiertt isso parece estranho, mas parece um problema separado que deve ser relatado.

@JSMike Minha configuração é bastante complexa com serviços, bibliotecas de terceiros, informações confidenciais, etc. etc. Infelizmente não consegui reproduzir o bug em um plunker: /

A injeção de ChangeDetectorRef funcionou para mim!

Tenho esse problema em um carrossel, porque preciso definir algumas propriedades no componente de item do carrossel, propriedade que só é conhecida depois de calculada no componente pai do carrossel. E todos os itens estão vindo de ng-content , então eu só posso trabalhar com os itens em ngAfterContentInit do componente pai do carrossel. Algo como:
carrossel:

@ContentChildren(ItemComponent) carouselItems;

ngAfterContentInit() {
  // ... some computations
  this.carouselItems.forEach((item: ItemComponent, currentIndex) => {
    item.setPropertyX(someComputedValue);
    // OR:
    item.x = someComputedValue; // of course, this would work as well
  });
}

item:

setPropertyX(value) {
  this.x = value;
}

Além do hack setTimeout() , a única outra solução seria usar um serviço e um assunto para que os itens se comunicassem com o carrossel pai? Quer dizer, isso seria ridículo ... Isso significaria que, se eu tivesse 100 itens em um carrossel, teria 100 assinaturas. E isso dobra se eu tiver 2 carrosséis. Certamente essa não pode ser a forma recomendada ?! Direito?

Na maioria das vezes, precisaríamos definir algumas propriedades em nossos componentes, propriedades que não podem ser conhecidas antes de AfterContentInit ... E em muitos casos (quando não estamos falando de 1 ou 2 componentes como isso, mas 50 ou 100 como em um carrossel) ter décimos ou centenas de assinaturas seria ridículo.

Existe alguma parte realmente legal do Angular que pode me ajudar aqui, que eu ainda não descobri?

PS Eu também tentei injetar ChangeDetectorRef e usar detach() no componente do carrossel e no componente do item. Eu esperava que isso corrigisse o erro, pois, bem ... Estou basicamente dizendo "_detecção de alteração de parafuso, na verdade não preciso disso. Por favor, por favor: não detecte NENHUMA alteração _" ... mas o erro ainda é lançado! Então, qual é o objetivo do método detach() , se ele não faz o que diz?

constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach();
}

setTimeout() não é necessário um hack , pode ser uma maneira muito válida de fazer coisas como, por exemplo, quando você reage ao view para atualizá-lo, por exemplo: alterar as ligações do template durante ngAfterViewInit .

O problema é quando você o usa como mágica sem realmente saber por quê. Mas existem cenários totalmente válidos em que usar setTimeout() é a maneira correta.

@ghetolay Não é hacky interromper o ciclo de vida do (s) componente (s) que o sistema de detecção de alterações está avaliando? Tenho seguido o caminho ChangeDetectorRef.detectChanges (), mas isso até parece um hack para mim ... se alguma dessas soluções for "aceitável", suponho que teremos que conviver com isso, mas realmente quebra qualquer uso de modificação ViewChild (ren) ou ContentChild (ren)

@fugwenna

Não é hacky interromper o ciclo de vida do (s) componente (s) que o sistema de detecção de alterações está avaliando?

Eu não entendo essa pergunta. sair do ciclo de vida?

O Angular executará uma rodada de CD cada vez que ocorrer um evento assíncrono (evento dom, xhr, setTimeout etc.), durante esse CD ele atualizará a visualização. ngAfterViewInit hook é executado depois disso, então não há mais CD planejado para ser executado e ele não será disparado novamente até que outro evento assíncrono ocorra. Usar setTimeout() é a maneira mais simples de acioná-lo novamente. Você certamente poderia fazer isso sozinho, mas acho que os benefícios são insignificantes.

@ghetolay Estou @JSMike e alguns outros tópicos que li:

@matkokvesic isso não é realmente uma solução, apenas faz com que a mudança ocorra fora do gancho do ciclo de vida. O exemplo de código fornecido não causa um erro ao usar o Angular v4.1.3

Portanto, acho que usar um setTimeout () fará com que o gancho do ciclo de vida seja "pulado" ou não avaliado como normalmente seria.

@ghetolay @fugwenna e quanto ao método detach() do ChangeDetectorRef ? Alguma ideia? Isso não deveria desativar completamente a detecção de alterações, eliminando também o erro? Não entendo porque não funciona? Ou o erro lançado é "independente" disso? O que significa que o Angular realmente não se preocupa com o fato de termos desabilitado a detecção de alterações, apenas se importa com o fato de termos alterado uma propriedade depois de configurada?
Se as pessoas ainda argumentam sobre o "ExpressionChangedAfterItHasBeenChecked" ser um bug ou não, talvez este seja definitivamente um bug ... Na minha opinião, o Angular não deveria mais se preocupar com a mudança de propriedades após terem sido definidas, se tivermos completamente detecção de mudança desativada. Especialmente a maneira como fiz isso, para o componente pai e todos os outros componentes filho, basicamente em uma árvore inteira.

Estou um pouco perdido aqui, um dos problemas que tenho é que não tenho certeza de como faria algumas coisas muito comuns sem acionar a exceção no modo dev.

Por exemplo, escrevi um componente acessador de valor de controle que encapsula um selecionador de data. Alimento esse componente com um modelo vazio que é passado para o selecionador de data internamente que o inicializa e o envia de volta pela função registrada. Claro que os filhos modificam o modelo durante o init, mas é isso que eu quero! Eu quero voltar um modelo inicializado, mas não fazer a inicialização no pai porque é o selecionador de data que entende e como fazer isso.

A questão é como eu faria isso agora? Chamar setTimeout e acionar a detecção de alterações novamente? Usar ChangeDetectorRef? Não gosto porque torna o código realmente difícil de ler. Inicializar algo em um serviço separado? Mas esse serviço está servindo apenas a um propósito muito técnico, sem nenhum valor comercial e nem tenho certeza se isso vai resolver o problema.

@fugwenna Minha principal preocupação era que funcionava no 4.1.3 e agora está quebrado. Ainda não vi ninguém na equipe angular dizer se deveríamos usar setTimeout em nossas funções de inicialização e permitir que as zonas assumam o controle, mas parece um antipadrão em comparação com o funcionamento anterior.

@ kavi87 Portanto, esta questão stackoverflow realmente me deu a solução elegante de que eu precisava. Que era para tornar o emissor de evento no meu componente filho assíncrono :
new EventEmitter(true)

Para um problema rotulado como uma regressão com 109 comentários, esse problema é desapontadoramente silencioso com qualquer palavra oficial.

Enquanto todos aqui estão fazendo um bom trabalho com as técnicas de autoajuda, eu adoraria saber se isso é realmente um bug ou apenas algum novo comportamento implementado em 4.2.x em diante. Em caso afirmativo, deveria ter sido listado como uma alteração significativa (e, portanto, não uma versão secundária?)

@MrCroft
Não tenho certeza se desativar totalmente a detecção de alterações para silenciar uma exceção é um bom caminho a seguir, embora, em teoria, eu veja como deveria / poderia fazer sentido.

Eu concordo com @JSMike e @rosslavery no que diz respeito a muito pouca resposta ou explicação de por que essa mensagem de erro surgiu após a publicação 4.2x sem qualquer documentação de alterações importantes ... no entanto, outra parte intrigante disso é como aparentemente só é incluída modo de desenvolvimento, que quase parece mais um aviso de interrupção da detecção de alterações (?)

Im meu AppComponent, tenho um div.
O único propósito desse div é mostrar uma sobreposição ocupada.
O estado de ocupado é acionado dentro de uma classe de serviço http:

app.component.html

<!-- Busy Overlay -->
<div *ngIf="busy$ | async"
     class="overlay">
    <i class="loading">
        <i><sk-wave></sk-wave></i>
    </i>
</div>

app.component.ts:

  get busy$(): Observable<boolean> {
    return this.catolService.busy$;
  }

Depois de atualizar para 4.3.4, sempre recebo um ExpressionChangedAfterItHasBeenCheckedError quando o serviço dispara o estado de ocupado.

Existe uma correção para este cenário assíncrono simples?

@ emazv72 ... esta demonstração stackblitz / plunker é igual ao seu caso? https://stackblitz.com/edit/angular-2p2h9s?file=app%2Fapp.component.ts ou https://plnkr.co/edit/RPOxSVnXvM1TqKp0peSY?p=preview

@mukunku Obrigado, vou tentar isso.

@ mlc-mlapis Mudei ligeiramente o seu plunker, meu código é exatamente https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb?p=preview

mas o êmbolo não mostra o problema. Vou tentar atualizar para a última versão 4.x para ver se foi corrigido.

obrigado

Olá a todos - desculpas pelo atraso aqui.

Portanto - este comportamento está realmente funcionando como esperado, o que eu percebo que pode ser inesperado para algumas pessoas, então deixe-me tentar explicar a mecânica.

O plunker de @saveretts é uma boa reprodução disso para percorrer (obrigado!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN?p=preview

O tldr é que o Angular sempre funcionou dessa maneira - a detecção de alterações está em execução, de cima para baixo (ou pai -> filho), em uma única passagem. Então, se tivermos:


@Component({ 
  selector: 'app-root',
  template: `
    <!-- initially falsey -->
    <div *ngIf="sharedService.someValue">NgIf Block</div>
    <child-comp></child-comp>
  `
})
class AppRoot {
  constructor(public sharedService:SharedService){}
}
@Component({ 
  selector: 'child-component',
  template: `child comp`
})
class ChildComponent {
  constructor(public sharedService:SharedService){}
  ngOnInit(){
    this.sharedService.someValue = true;
  }
}

A detecção de alterações será executada primeiro para a raiz do aplicativo, verificará todas as suas ligações e, em seguida, detectará alterações no componente filho - novamente, porque fazemos isso em uma única passagem (em vez de manter a verificação no estilo AngularJS até estável), a ligação pai (o * ngIf, neste caso) não é verificado novamente - agora seu aplicativo está em um estado inconsistente (!)

No modo de desenvolvimento, nós realmente executamos essa detecção de alteração duas vezes - na segunda vez em que ela é executada, verifica se as ligações não foram alteradas (o que indicaria um estado inconsistente) e gera o erro se tal problema for encontrado. Você pode verificar esse comportamento chamando enableProdMode() no repro: https://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56?p=preview. Observe que o erro não é gerado (porque não estamos executando o CD x2), mas o aplicativo termina em um estado inconsistente (o filho é mostrado, mas não o * ngIf), o que é uma coisa ruim.

A razão disso ter surgido recentemente é que, anteriormente, o roteador executava uma série de verificações de detecção de alterações adicionais ao inserir componentes - isso era ineficiente e causava problemas com animações e roteamento, especialmente quando as saídas do roteador estavam aninhadas em ngIfs e coisas assim. corretamente um erro de que o desenvolvedor está violando o fluxo de dados unilateral.

Eu diria que, normalmente, o código que viola esse fluxo unilateral (como o repro acima faz) está incorreto . No entanto - há casos em que você pode querer esse comportamento (na verdade, temos uma configuração semelhante na biblioteca de formulários Angular). Isso significa que você precisa acionar explicitamente outra execução de detecção de alteração (que voltaria ao componente raiz e verificaria de cima para baixo). Chamar setTimeout consegue isso, embora normalmente seja melhor usar uma promessa para acionar isso - consulte https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv?p=preview

Espero que ajude!

@robwormald Este mesmo erro estava ocorrendo com outros cenários também. Não tenho certeza se isso aborda o problema com ContentChildren . Por que uma diretiva pode alterar os valores dos filhos durante AfterContentInit sem erros, mas fazer o mesmo em um componente gera erros?

Além disso, é esperado que, se cdr.detectChanges() for chamado durante um processo de inicialização, as outras funções de inicialização acabem não sendo chamadas? Isso é algo descoberto neste tópico para o qual criei um problema separado .

@robwormald

Eu acredito que @JSMike está correto na avaliação de que esse erro ainda é lançado durante cenários onde o fluxo unilateral vem de um Pai-> Filho usando ContentChild (ren) e ViewChild (ren).

É aceitável considerar o que eu disse antes , que este erro é mais um "aviso" que afirma que o desenvolvedor está indo contra a "prática recomendada" do fluxo unilateral, mas não causando nenhum erro de quebra de aplicativo, desde a detecção de alterações só é executado uma vez durante o modo de produção (mundo real)?

@fugwenna Não, isso é realmente um erro, pois do contrário

Além disso, há esta citação dos documentos

A regra de fluxo de dados unidirecional do Angular proíbe atualizações na visualização após sua composição. Ambos os ganchos disparam após a composição da vista do componente.

Se você realmente precisar alterar alguma coisa, use um dos métodos assíncronos para fazer isso. setTimeout pode ser adequado aqui, mas esteja ciente de que isso vem com um (no momento em que este livro foi escrito) um atraso de 4ms próprio. Outra maneira é Promise.resolve().then(() => Assignment = here)

Melhor ainda é dar um passo para trás e ver por que você precisa fazer uma alteração tão tarde no processo e ver se há uma maneira mais limpa de resolver o problema. Se eu estiver fazendo isso, considero um cheiro de código.

Obrigado pela explicação detalhada @robwormald. Ainda estou confuso por que um fluxo de dados filho -> pai ainda parece funcionar ao usar uma ligação [hidden] vez de *ngIf (consulte https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u?p = visualização). Alguma idéia de por que isso ainda funciona sem gerar ExpressionChangedAfterItHasBeenCheckedError ?

O Angular usa a função checkAndUpdateView para realizar a detecção de alterações.

A representação curta desta função pode ser semelhante a:

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    // update DOM for the current view
    updateRenderer
    // perform checkAndUpdate for all child components
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);
}

É executado recursivamente graças aos métodos execEmbeddedViewsAction e execComponentViewsAction . E também o ponto-chave aqui é a verificação angular de visualizações incorporadas após as diretivas terem sido verificadas e antes de atualizar o DOM.

Para simplificar o exemplo @saverett , usarei o seguinte modelo

<div *ngIf="!sharedService.displayList">Some text</div>
<router-outlet></router-outlet>

https://plnkr.co/edit/OdkzHjEXbEd3efYAisFM?p=preview

Já sabemos que *ngIf é apenas desugar. então

<ng-template [ngIf]="sharedService.displayList">
  <div>Some text</div>
</ng-template>
<router-outlet></router-outlet>

Podemos imaginar a seguinte estrutura:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

O que a diretiva RouterOutlet faz?

Ele injeta um componente roteado em ViewContainerRef

activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
    ...
    this.activated = this.location.createComponent(factory, this.location.length, injector);

Isso significa que router-outlet cria EmbeddedView portanto, após a inicialização da diretiva RouterOutlet , temos duas visões integradas em nossa visão AppComponent .

  my-app
     |
   NgIf directive ([ngIf]="sharedService.displayList")
       NgIf EmbeddedView
   RouterOutlet directive 
      BComponent EmbeddedView

Vamos ilustrar na foto

haschanged

Agora vamos voltar à nossa função principal

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    ...
    // update DOM for the current view
    updateRenderer
    ...
}

O Angular executará a verificação na seguinte ordem:

NgIf directive ([ngIf]="sharedService.displayList") updateDirectives(1)
      NgIf EmbeddedView   execEmbeddedViewsAction(2)
RouterOutlet directive 
      BComponent EmbeddedView  execEmbeddedViewsAction(3)

Nossa diretiva NgIf será verificada primeiro (aqui sharedService.displayList=false é lembrado) e então NgOnInit método dentro de BComponent será chamado (onde estamos alterando sharedService.displayList to true ) E quando o angular executa checkNoChanges ele irá gerar o erro Expression has changed after it was checked

Então vamos substituir *ngIf por [hidden] . Agora teremos apenas uma visão incorporada inserida por RouterOutlet e nosso [hidden]="sharedService.displayList" será verificado dentro de updateRenderer

  my-app
     |
   div{Some Text} just a node
   RouterOutlet directive 
      BComponent EmbeddedView

O Angular fará uma verificação como esta:

function checkAndUpdateView(view) {
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);   BComponent.ngOnInit (1)
    ...
    // update DOM for the current view
    updateRenderer   update hidden binding (2)
    ...
}

Desta forma, a vinculação da propriedade dom não causará o erro porque atualizamos após chamar ngOnInit value.

Essa é a principal diferença entre a diretiva estrutural *ngIf e a vinculação de propriedade dom [hidden]

Obrigado pela visão geral detalhada @alexzuza! Foi muito útil!

setTimeout () realmente funcionou para mim :) Obrigado @jingglang :)
.subscribe (data => setTimeout (() => this.requesting = data, 0))

@robwormald está correto aqui. Eu tive o mesmo problema e foi mais difícil de detectar porque eu estava usando NGRX e observáveis. O problema que eu tive é que havia contêineres de componentes em níveis diferentes na hierarquia de rotas que assinavam o estado da loja. Assim que movi meus componentes de contêiner para o mesmo nível em meu módulo de recursos, o problema foi embora. Eu sei que esta é uma resposta complicada, mas se você estiver usando o padrão de contêiner / componente NGRX, espero que isso aponte sua investigação na direção certa

Com setTimeOut funciona, obrigado @jingglang
ngAfterViewInit () {
this.innerHeight = (window.innerHeight);
setTimeout (() => this.printPosition (), 0);
}

@alexzuza , isso não está acontecendo apenas para o ngIf, está acontecendo (pelo menos para mim) também com [ngClass]

@ Ninja-Coding-Git Acabei de descrever o caso específico. Eu sei que isso pode acontecer com qualquer código.

Para mim, chamar ChangeDetectorRef depois de alterar o elemento resolveu o problema:

import { Component, ChangeDetectorRef } from '@angular/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  constructor(private _cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this._cdr.detectChanges();
  }

}

Solução encontrada aqui

olá @thetylerb! você poderia elaborar mais sobre isso. Estamos usando o ngrx e simplesmente não sei como compensar isso. Acho que usar promessas em cima de observáveis ​​é muito estranho.

@ piq9117 você também pode dar uma olhada neste comentário https://github.com/angular/angular/issues/18129#issuecomment -326801192 Eu fiz sobre a interação entre a detecção de mudança e ngrx, pode ser útil, suponho

@victornoel obrigado! Isso descreve muito bem a nossa situação. Temos estruturas de dados imutáveis ​​aninhadas (immutablejs) e estamos usando canais assíncronos em todos os lugares. Meus pensamentos iniciais foram, a loja mudaria a árvore de componentes quando a loja fosse hidratada, e a árvore de componentes iria apenas compensar essa mudança e fazer outra mudança de detecção 😓

Temos estruturas de dados imutáveis ​​aninhadas (immutablejs) e estamos usando canais assíncronos em todos os lugares. Meus pensamentos iniciais foram, a loja mudaria a árvore de componentes quando a loja fosse hidratada e a árvore de componentes apenas compensaria essa mudança e faria outra detecção de mudança.

No meu projeto, temos algum tipo de estrutura de dados normalizada (em vez de estruturas de dados aninhadas), então isso atenua um pouco esse tipo de problema, até que não! : O

Você só espera:

  • alterar as estruturas de dados de armazenamento para que as alterações em uma parte não acione atualizações em outras
  • tomando cuidado para não acionar eventos de filhos de componentes que ouvem o armazenamento via canal assíncrono
  • acionando manualmente a detecção de mudança quando você aciona os eventos mencionados
  • acionar esses eventos em outro momento ou em um setTimeout

Estou usando o ngrx e tenho uma situação como esta:

Em app.component.html:
<div *ngIf='(appState | async).show'>test</div>
E em app.component.ts
this.appState= this.store.select("app");

É claro que nessa situação recebo o erro, mas quando adiciono sinalizador booleano no componente como este:
app.component.html -> <div *ngIf='show'>test</div>
app.component.ts ->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

O erro desapareceu, claro que é compreensível para mim porque, mas quero saber a sua opinião, é uma boa solução? Quais são os prós e os contras?

Não estou usando ngrx, mas estou tendo o problema de uma verificação simples com querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);

A solução de @bogdancar com ChangeDetectorRef.detectChanges() suprime o erro, mas seria bom não ter que importar ChangeDetectorRef consertando o que quer que esteja causando o erro em primeiro lugar.

Eu tive o mesmo problema ao usar rxjs no angular 4.4.4. Acabei de assinar um objeto observável fornecido pelo serviço DI. Meu trabalho abaixo:

this.eventBroadcastService.getCustomEvent({type: 'loginOpen'}).subscribe(e => { setTimeout(() => { this.isBlur = true; }, 0); });

Eu uso um setTimeout para contornar. Agora funciona, mas é complicado. Eu não me sinto bem.

ngOnChanges (changes: SimpleChanges | any) resolve o problema

O problema foi corrigido simplesmente após mover a lógica de alteração dinâmica dos parâmetros dentro do método ngDoCheck ().
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61 Acho que é uma péssima ideia. ngDoCheck está sendo muito chamado pelo Angular e se seu sourceArr tiver muitos dados, isso pode prejudicar seu desempenho.

Consulte https://angular.io/guide/lifecycle-hooks#other -angular-lifecycle-hooks

Eu prefiro recomendar por agora ngAfterViewInit com setTimeout :

setTimeout(_ => /* your code here */, 1000);

exemplo:

  public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false, 1000);
  }

@dgroh você não precisa adicionar um tempo limite real, você apenas deseja que js execute seu código em uma próxima tarefa assíncrona, o que significa outra execução de detecção de alteração.

@victornoel Eu praticamente acredito que isso funcionaria com observáveis, a menos que você chame explicitamente detectChanges de ChangeDetectorRef . Eu gostaria que tudo isso não fosse necessário.

@dgroh você deve ter me entendido mal, eu estava apenas defendendo a escrita:

public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false);
  }

Não é possível adicionar uma etapa após ngAfterViewInit como um setTimeout ? (por exemplo: ngAfterViewLoad )

@ aks4it , isso parece funcionar, mas eu adoraria mais detalhes sobre como funciona? O código de geração de componente dinâmico é síncrono?

A demonstração oficial do plunker do angular docs tem o mesmo erro. Você poderia consertar isso aí? :) Obrigado

@luckylooke

  ngAfterViewInit() {
    //this.loadComponent();
    this.getAds();
  }

isso funciona?

Eu não entendo sua exigência, mas você realmente quer um intervalo de 3 segundos? se sim, isso funciona, e mesmo se você quiser chamar diretamente loadComponent, consigo:

ngAfterViewInit() {
    setTimeout(()=>{this.loadComponent()},0)
  }

@istiti Consegui consertar ChangeDetectorRef solução alternativa, colocando detectChanges() após ... data = data

Mas como é um plunker de documentos oficiais, fiquei curioso sobre a solução oficial do problema. Não é tempo limite ou ChangeDetectorRef.

Desculpe, fui muito breve na primeira postagem .. Queria apontar a gravidade do problema, pois até os documentos angulares possuem o mesmo no código.

Apenas para sua informação, não tenho certeza do que realmente está causando aqui, mas os erros acabaram quando eu habilitei o modo prod em main.ts
`enableProdMode ()

// if (environment.production) {

// enableProdMode ();

//} `

"@ angular / core": "^ 4.2.4"
"texto datilografado": "~ 2.3.3"

@bakthaa ... é suposto comportamento porque a verificação de controle (se os valores dos componentes @Inputs() são iguais) que é chamada após cada ciclo de CD é invocada apenas no modo de desenvolvimento e não no modo de produção do aplicativo Angular. Portanto, o problema está oculto no modo de produção, mas ainda está lá ... e provavelmente pode ser uma fonte de diferentes efeitos colaterais indesejados ou enganosos no modo de produção.

Estou usando o Angular 5, consegui corrigir esse problema injetando o ChangeDetectorRef no componente e forçando-o a detectar alterações.

    constructor(private changeDetector: ChangeDetectorRef) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }

@ Sabri-Bouchlema ... sim, é uma maneira ... mas ainda assim você deve repensar mais uma vez ... por que um componente @Inputs é alterado durante um ciclo de CD ... e decidir se o a lógica está correta ... e é verdade que em 95% dos casos o código pode ser alterado para evitar o problema sem chamar detectChanges() .

Usando o angular 5.1.2. Estou recebendo este erro ao abrir uma caixa de diálogo de material no ngAfterViewChecked. Como @ aks4it relatou, tive que

Estou enfrentando o mesmo problema com a versão 4.01
ng-version = "4.0.1

O mesmo aqui com angular 4.4.6

Corrija usando o this.changeDetector.detectChanges();

Escrevi este plunker usando variáveis ​​de modelo e não entendo por que tenho ExpressionChangedAfterItHasBeenCheckedError. Se eu alterar o pedido em meus componentes, o erro desaparece:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

Eu tentei changeDetector.detectChanges, mas não funciona

@AlexCenteno ... porque a lógica de avaliação é de cima para baixo ... e value propriedade de comp1 é definida @Input() public value:String=null; primeiro e, em seguida, redefinida usando a referência de comp2.value ... <div comp1 [value]="comp2.value"></div> que é definido entretanto para Hello por meio de seu próprio @Input() ... tudo dentro de um ciclo de CD ... então a verificação repetida se as entradas nos componentes filhos forem as mesmas do início do ciclo do CD no modo de desenvolvimento, detecta a mudança na propriedade value em comp1 .

hi @ mlc-mlapis obrigado pelo seu tempo. Portanto, não há como evitar o erro? Acho que é algo com que não devo me preocupar depois de chamar enableProdMode?

@AlexCenteno ... sim, no modo prod a verificação após o ciclo do CD não é invocada ... mas não significa que você deva estar satisfeito com isso. Você deve aplicar uma lógica diferente de passar os value componentes filhos cruzados. Também existe o problema de que <div> não tem uma propriedade value . Por que você usa o componente de atributo? Por que não apenas:

<comp1 [value]="comp2.value"></comp1>
<comp2 #comp2 value="Hello"></comp2>

porque então usar @Output() em <comp2> estaria bem e permitiria que você atualizasse [value] em comp1 sem qualquer erro.

Usar [email protected] com angular@5 standart store.select lançar um aviso ExpressionChangedAfterItHasBeenCheckedError no modo dev e funcionar incorretamente no modo prod. O despacho e a seleção ocorrem em diferentes níveis / recipientes.

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': (isActiveSidenavMenu$ | async)
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  ...
}

Essa abordagem remove a mensagem de aviso, mas parece ser mais complicada:

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': isActiveSidenavMenu
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  isActiveSidenavMenu;

  ngOnInit() {
    this.isActiveSidenavMenu$.subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
  }
}

Tem alguma ideia de como resolver esse problema de forma mais correta?

@rimlin , você tentou apenas se inscrever diretamente na loja?

ngOnInit() {
  this.store.select(fromHome.isActiveSidenavMenu)
    .subsbrice(res => {
      this.isActiveSidenavMenu = res;
    });
}

Acho que tive um problema semelhante. e evitando async no modelo resolveu o problema.

@ piq9117 obrigado pelo conselho, tentei sua solução, parece ser mais claro com menos código, mas aqui também precisa chamar this.cd.detectChanges(); , caso contrário, a visualização não será atualizada.

ngOnInit() {
    this.store.select(fromHome.isActiveSidenavMenu).subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
}

Estou usando o Angular 5.2, fiz algo parecido. Funciona para mim

constructor(private changeDetector: ChangeDetectorRef) {}

ngAfterViewInit(){
  this.changeDetector.detectChanges();
}

Usando Angular 5.2, corrigido usando detectChanges ()

Sim, corrigimos isso usando a rota detectChanges () também (Angular 5.1). Estou com muitos outros aqui que pensam que isso ainda é apenas uma solução alternativa e não deve ser tratado como a resposta no longo prazo. Deve haver outro gancho de ciclo de vida para poder usar.

Acionar um ciclo de detecção de alterações totalmente novo para permitir alterações como essa parece abaixo do ideal.

Isso parece continuar e continuar, embora todas as respostas e conselhos já tenham sido postados acima.

  1. A maneira certa de consertar isso é arquitetar / reestruturar seus componentes e manipulação de dados para trabalhar com uma única passagem de detecção de mudança de cima para baixo. Isso significa que nenhum componente filho pode alterar um valor que é usado no modelo de um componente pai. Reavalie o que você construiu e verifique se os dados fluem apenas em uma direção, para baixo na árvore de componentes. Isso pode ser tão simples quanto mover seu código de ngAfterViewInit para ngOnInit em alguns casos.
  2. this.changeDetectorRef.detectChanges(); é a forma oficial e reconhecida de lidar com isso em menos de 5% dos casos em que a primeira etapa é problemática.

Outras notas:

  1. Tenha muito cuidado ao fazer qualquer coisa em ngAfterViewInit pois alterar os valores usados ​​no DOM pode causar esse erro.
  2. Tenha muito cuidado ao usar qualquer um dos ganchos de ciclo de vida Check ou Checked pois eles podem ser chamados em cada ciclo de detecção de mudança.
  3. setTimeout pode contornar isso, mas as duas soluções acima são as preferidas.
  4. Ignorar este erro no modo dev e ficar feliz que o modo de produção "conserta" o erro não é válido conforme explicado acima. Essas verificações não são feitas no modo de produção, mas o código do modo de produção ainda está deixando as alterações não detectadas e fazendo com que a visualização e os dados fiquem fora de sincronia. Isso pode ser inofensivo em alguns casos, mas pode levar à corrupção de dados / visualizações e problemas piores se for ignorado.
  5. O fluxo de dados unilateral não mudou muito no Angular, então postar que você atingiu isso na versão X do Angular não é muito útil.
  6. Adicionar mais ganchos ou estados de ciclo de vida não é uma solução para isso. Os desenvolvedores simplesmente adicionariam código que viole o fluxo de dados unilateral nesses ganchos e acionariam o mesmo erro.

Finalmente, lembre-se de que esse erro não indica um problema com o Angular. Este erro está aqui para informá-lo de que você fez algo errado em seu código e que está violando o sistema de fluxo de dados unilateral que torna o Angular rápido e eficiente.

As postagens acima e muitas postagens de blog vão mais a fundo nisso e valem a pena ler, pois você deve entender esse modelo de fluxo de dados de mão única e como construir componentes e passar dados dentro dele.

@Splaktar Seria melhor bloquear este problema (e me deletar) para tornar o comentário acima notável? A discussão já fica muito longa e a maioria deles estava apenas repetindo. 😃

@Splaktar Você

Suponha que você tenha um componente de contêiner que inclui navegação, títulos de página, etc., e carrega uma coleção de subcomponentes, cada um podendo carregar seus próprios subcomponentes. Os componentes carregados no contêiner serão alterados com base nos dados, e os subcomponentes que eles carregam serão alterados com base nos dados. Cada um desses subcomponentes precisa ser capaz de comunicar de volta ao contêiner pai informações sobre o que eles são, para que o contêiner pai possa exibir o Nav de nível superior apropriado para o usuário, talvez (trilha de navegação talvez, ou algo semelhante), também como informações que podem ser usadas em títulos para identificar no nível do contêiner o que está carregado na visualização.

Nesta, que é uma arquitetura extremamente comum em minha experiência, eu faria uso de um serviço de estado comum no qual os subcomponentes se registrariam e o componente pai assinaria para que os filhos pudessem educar os pais, e os pais pode exibir as informações apropriadas.

Isso não está seguindo uma abordagem de baixo para cima, pois o pai obteria todos os dados relevantes do serviço e enviaria todas as informações exigidas pelos componentes filhos por meio de comunicação unilateral.

Isso parece ir contra o que você está sugerindo acima e, para mim, é a fonte do problema. Quando o componente filho se registra no serviço, o componente pai lança o erro.

Isso está incorreto?

E nunca se esqueça, que existe um bug aprovado:
https://github.com/angular/angular/issues/15634

@alignsoft ... você tem uma reprodução simples do seu case e do problema como um Stackblitz? Porque parece que algo mais deve afetar o caso do que apenas a lógica que você descreveu.

@trotyl as soluções de @Splaktar não estão relacionadas ao meu problema simples aqui https://github.com/angular/angular/issues/17572#issuecomment -311589290 ou?

@ Martin-Wegner ... no seu caso basta mudar ngAfterViewInit para ngOnInit gancho.

@alignsoft , pois @ mlc-mlapis disse que um exemplo de Stackblitz seria muito útil para demonstrar a arquitetura adequada para este caso. Isso é muito possível usando o conjunto certo de serviços, observáveis, ganchos de ciclo de vida, etc. Este caso específico também seria um bom tópico para StackOverflow.

@Splaktar Depois de tirar meu prazo atual da minha mesa, vou fazer a maquete de um projeto que demonstra um caso simples.

@ mlc-mlapis uau, obrigado, funciona :)

@alignsoft , hoje eu estava criando um aplicativo que se comporta exatamente como você descreveu. @ mlc-mlapis, você pode encontrar o exemplo simplificado no ExpressionChangedAfterItHasBeenCheckedError será lançada.

No exemplo, o admin.component atua como um componente pai responsável por 3 coisas:

  • exibindo um nav esquerdo
  • um ticker principal que os subcomponentes podem usar para exibir suas próprias mensagens de ticker
  • uma seção de conteúdo principal que exibe dados específicos de subcomponentes por meio de <router-outlet></router-outlet> .

Componente Pai

<div class="ticker-container" *ngIf="tickedMessage !== ''">
  <h1>{{tickedMessage}}</h1>
  <button type="button" (click)="tickedMessage = ''">
    close
  </button>
</div>

<div class="side-nav">
  <h2>side nav</h2>
  <ul>
    <li><a routerLink="/">dashboard</a></li>
    <li><a routerLink="/apps/thirdpartycms">CMS System</a></li>
  </ul>
</div>
<div class="main-content-container">
  <router-outlet></router-outlet>
</div>
export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Ao pesquisar como comunicar dados de volta ao componente pai ao usar <router-outlet></router-outlet> , li que se eu usar @Output para enviar dados de volta ao componente pai, a diretiva de saída do roteador acabará lidar com o evento. Por ser um evento personalizado, a diretiva de saída do roteador não saberá como lidar com isso. Então, pulei esse método. Depois de ler sobre os serviços compartilhados no angular.io , escrevi o meu próprio:

Serviço compartilhado

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class AdminService {

  private _tickedMessageAnnounced = new Subject<string>();

  tickedMessageAnnounced$ = this._tickedMessageAnnounced.asObservable();

  announceTickedMessage(tickedMessage: string) {
    this._tickedMessageAnnounced.next(tickedMessage);
  }
}

Uma vez que um dos componentes filhos chamados this.adminService.announceTickedMessage(this._tickedMessage); :

Componente Criança

import { Observable } from 'rxjs/Observable';
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { AdminService } from '../../../core/admin/admin.service';

@Component({
  selector: 'cms',
  templateUrl: './thirdparty-cms.component.html',
  styleUrls: ['./thirdparty-cms.component.scss'],
})
export class ThirdPartyCmsComponent implements OnInit {
  private _tickedMessage: string;
  constructor(private adminService: AdminService) { }

  ngOnInit(): void {
    this._tickedMessage = 'Please do not create an entry for a page URL that does not exist in the CMS system';

    this.adminService.announceTickedMessage(this._tickedMessage);
  }
}

o componente pai lançou uma exceção ExpressionChangedAfterItHasBeenCheckedError porque tentou atualizar uma de suas propriedades públicas com o valor da mensagem marcada enviada pelo componente filho:

Componente pai (lança exceção)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

O que consertou ExpressionChangedAfterItHasBeenCheckedError foi executar o retorno de chamada de inscrição de forma assíncrona e aguardar a mensagem marcada:

Componente pai (com espera assíncrona)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      This clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe(async (tickedMessage: string) => {
      this.tickedMessage = await tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

Vindo do mundo WPF, parece que precisamos dizer ao thread de IU para aguardar o retorno de chamada de assinatura observável, já que os observáveis ​​estão sendo executados de forma assíncrona?

Estou apenas começando no Angular, então, por favor, tenha um pouco de compaixão pelas minhas habilidades do Angularz: P

@ktabarez Obrigado, isso me ajudou muito!

@ktabarez Obrigado! Isso parece funcionar bem! Eu estava encerrando a assinatura no componente pai em um tempo limite, que também estava funcionando, mas parecia sujo.

Estou vendo que, ao interrogar o estado de redux, é garantido o cumprimento, mas na inicialização, como quando colocado em ngOnInit em oposição a construtores

@ktabarez Muito obrigado!

Sua solução async ... await in subscribe funciona.

@ktabarez Sua solução está essencialmente fazendo o mesmo que setTimeout ({}, 0), mas parece mais elegante. Muito parecido! Também me fez perceber que tudo pode ser esperado (quase como C # await Task.FromResult (tickedMessage)), então, parabéns por isso!

Então, pergunta: por que deveríamos ter que fazer um async / await em um cálculo de uma propriedade em um observável que está obtendo dados do back-end que podem estar mudando com frequência? O que o ngClass torna isso necessário? Eu entendo isso ao usar um {observable | tubo assíncrono}. Portanto, o async / await não é a melhor abordagem para mim. (ou o pipe assíncrono não é a melhor abordagem)

Eu tenho esse problema onde meu componente pai tem vários componentes filhos. Um dos componentes filho tem uma entrada em que precisa de dados da propriedade do componente pai e a propriedade obtém seus dados de um observável / promessa (tentei os dois).

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

Me deparei com todas as soluções encontradas no segmento. Mas nada funciona no meu caso. Felizmente, alguns sugeriram que este é um problema devido à manipulação de um DOM, como colocar uma expressão em _ (ngIf) _ como exemplo.

Portanto, para evitar isso, tento fazer experiências com ng-template , ng-container e ngTemplateOutlet .

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

Consegui resolver isso sem usar ChangeDetectorRef ou setTimeout()

Para mim, esse erro ocorreu ao mostrar / ocultar o campo de acordo com a condição, consegui resolver após aplicar os valores da configuração dos campos:

this.form.get ('field').updateValueAndValidity();

Tenho tentado fazer isso com um mat-dialog que abre no init, e tentei literalmente todas as soluções que vi neste segmento (definir o tempo limite, promessa de resolução, tubo observável, após visualizar o init, após o conteúdo do init) e suas combinações. Ainda não funciona. Então eu vi @Splaktar postar sobre como isso viola o fluxo de dados

No init:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

Funções auxiliares:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

Usando angular 6.0.0-rc.1

Estou usando o angular com ngrx e recebo este erro após enviar um formulário.
corrigido pela configuração
ChangeDetectionStrategy.OnPush
no componente do formulário

ainda está tendo o problema. tentei todos os tipos de soluções alternativas. setTimeout funciona primeiro, mas o acionamento subsequente de dialog.open resulta no mesmo erro. Qualquer status sobre isso?

Eu estou enfrentando o mesmo problema. Tentei de tudo, mas parece que esse problema surge no fluxo de dados unilateral.
Estou tentando atualizar o selectedwoids. onde o problema é resolvido, mas surge o mesmo erro.

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

private setSelectedWoidsCount () {
if (this.isSelectAllWoids) {
this.selectedWoids = this.allWoids;
}
}

Porque você quebra o pipeline de renderização do Angular ...

Estou vendo erros semelhantes "ExpressionChanged ..." após alternar de 4.1.2 para 4.2.3 .

No meu caso, tenho dois componentes que interagem por meio de um serviço. O serviço é fornecido por meio do meu módulo principal e ComponentA é criado antes de ComponentB .

Em 4.1.2 , a seguinte sintaxe funcionou perfeitamente sem erros:

_Modelo de componenteA: _

<ul *ngIf="myService.showList">
...

_ComponentB Class: _

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

Em 4.2.3 , tenho que modificar o modelo de ComponentA da seguinte maneira para evitar o erro "ExpressionChanged ...":

_Modelo de componenteA: _

<ul [hidden]="!myService.showList">
...

Ainda estou tentando descobrir por que isso acaba de se tornar um problema e por que a mudança de *ngIf para [hidden] funciona.

Você descobriu como funcionava ao mudar de ngIf para [oculto]

Já existe uma solução para esse problema no Angular 6 ??

@alokkarma ... muito antigo ... você deve migrar para Angular 6 ou 5 pelo menos. Mas seu código de exemplo parece claro ... você está alterando a propriedade de serviço no componente filho B, e isso afeta algum comportamento no componente filho A. Ambos os componentes filhos são colocados no mesmo pai, então todos os três são processados ​​todos juntos em relação em um ciclo de CD. O que você espera?

O fato de o mesmo caso estar funcionando na versão 4.1.2 está relacionado a alguns bugs que foram eliminados nas versões posteriores, incluindo a 4.2.3.

Estou usando o Angular 6. Estou usando um MessageService para me comunicar entre meu componente de autenticação e meu componente de aplicativo para mostrar diferentes conteúdos da barra de navegação se o usuário estiver conectado (ou não)

Ele funciona bem sem esse erro!
Já tentei outras soluções, como EventEmitter, mas não funciona! ou recebo o mesmo erro.

@dotNetAthlete ... mostra uma demonstração de reprodução simples no Stackblitz. Falar sobre isso sem o código real é difícil.

@dotNetAthlete Estou usando o mesmo mecanismo básico onde tenho um serviço de estado que contém um título de página como um assunto de comportamento (asObservable) no componente do contêiner principal e componentes filhos carregados nele que definem o título de página apropriado no serviço de estado para o contêiner pai observar e exibir.

Sempre havia um erro de 'Expressão alterada' quando o contêiner inicializava o valor e o componente filho o atualizava imediatamente, então faço isso agora no pai / contêiner:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

Onde o serviço estadual tem:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

Isso elimina o problema. Esta solução é baseada em alguns comentários úteis de outras pessoas neste tópico.

@alignsoft - bela solução - problema corrigido no aplicativo Angular 6.

Estou vendo isso no recarregamento da página no angular 7 agora ...
Estou definindo um valor de componentes filho para os pais ngAfterViewInit
Tive que fazer isso no componente filho

  public activate() {
    setTimeout(() => this.active = true);
  }

Encontrei a solução neste tópico de problema https://github.com/angular/angular/issues/10762
Depois de usar o gancho de ciclo de vida AfterContentInit , o erro desaparece.

@alignsoft , apenas adicionando à sua alteração, uma vez que estamos usando um BehavioralSubject em vez de um Assunto,

Eu o refinei da seguinte forma:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

Mas concordo com @daddyschmack , gostaria de obter qualquer recurso que indique informações sobre qualquer uma das correções mencionadas acima ( cdr.detectionChanges() ou async await ou aftercontentchecked ), por quê [hidden] atributo *ngIf

@acidghost Obrigado por esse link, ele resolveu meu problema. Na verdade, eu desisti de tentar consertá-lo em uma área do meu aplicativo, mas quando começou a acontecer novamente em uma nova área ...

Este problema foi bloqueado automaticamente devido à inatividade.
Registre um novo problema se você estiver encontrando um problema semelhante ou relacionado.

Leia mais sobre nossa política de bloqueio automático de conversas .

_Esta ação foi executada automaticamente por um bot._

Esta página foi útil?
0 / 5 - 0 avaliações