View updatet niet bij change detection in Angular 5

Tijdens ontwikkeling van een Angular 5 applicatie voor Android en iOS kwam ik erachter dat Angular soms de view niet update na een change. In dit geval gebeurde het op het moment dat ik de taal van het device opvroeg met behulp van een Cordova language plugin. In dit artikel probeer ik te beschrijven hoe ik tot een oplossing kwam. Daarnaast hoop ik context te kunnen geven waaruit blijkt dat dit logisch en voorspelbaar gedrag is.

Onderzoek

Toen ik begon met Angular 2 hoorde ik verschillende geruchten dat Angular 2 geen $scope meer heeft. En dat dat veel problemen zou oplossen die komen kijken bij het gebruik van $scope.$watch en $scope.$apply. In Angular is dan weliswaar geen $scope meer, maar er moet natuurlijk nog steeds iets vergelijkbaars met de digest loop van AngularJS zijn. Angular moet nog steeds weten wanneer de view geüpdatet moet worden na een wijziging.

Om te weten hoe Angular werkt, ben ik begonnen bij de tour of heroes. Hoe de digest loop in Angular werkt wordt in deze tutorial niet besproken. Tijdens deze tutorial kom je geen situatie tegen waarin de view niet wordt geüpdatet. Nergens wordt überhaupt een digest loop of iets dergelijks genoemd. Hierdoor, samen met de geruchten dat er geen $scope meer is in Angular, werd ik op het verkeerde been gezet; door niet meer te zoeken in de richting van een “digest loop”.

Zoeken naar ‘waarom Angular de view niet update’ bracht mij op allerlei forums die niet veel meer informatie bevatten dan een aantal Ionic problemen. De beschreven problemen leken niks te maken te hebben met het probleem dat ik ondervond; het niet updaten van de DOM na het ophalen van de ingestelde taal van een device. Totdat ik ergens iets las over Zone.js.

Angular blijkt het moment van renderen van de view te bepalen met behulp van Zone.js. Zelfs tijdens het schrijven van dit artikel heb ik moeite met het vinden van de juiste informatie over hoe Zone.js nu precies werkt met Angular.

Wat is/doet Zone.js?

Zone.js is een onafhankelijke npm module die los van Angular gebruikt kan worden.

Zone.js biedt een execution context voor asynchrone taken. Denk hierbij aan DOM events, XMLHttpRequests, EventEmitters, setTimeout, setInterval, requestAnimationFrame, Promises en meer.

Zone.js biedt de mogelijkheid een groep asynchrone taken binnen een eigen context (Zone) te plaatsen.

Met deze informatie bedacht ik, dat als Angular de view niet update na een wijziging, dit wellicht niet binnen de Angular Zone was gebeurd. M.a.w. Angular weet niet dat er iets is gewijzigd. Hoe werkt dit dan precies?

Aanname

Angular gebruikt dus Zone.js om het moment te bepalen wanneer de view geüpdatet moet worden. Zou het dan kunnen zijn dat Angular de Zone.js context (NgZone) is verloren op het moment dat met een externe plugin (in mijn geval de Cordova language plugin) een asynchrone call wordt geresolved? Kan het zijn dat de language plugin van Cordova ervoor zorgt dat wat Angular opvraagt niet geresolved wordt binnen de Angular Zone?

Oplossing

Nu ik een idee had wat er aan de hand zou kunnen zijn, kon ik gericht zoeken hoe ik deze aanname kan reproduceren en oplossen. Zo kwam ik op het volgende artikel over zones en change-detection in Ionic en Angular.

Hoe kunnen we er nu voor zorgen dat Cordova het opvragen van de ingestelde taal van het device wel resolvet binnen de Angular Zone, zodat Angular weet dat de view geüpdatet moet worden?

Het volgende code voorbeeld laat een call zien waarmee de taal wordt opgehaald. In het voorbeeld wordt dit gedaan met Cordova, maar dit voorbeeld representeert elke actie die buiten de Angular Zone wordt gedaan.

Of in TypeScript

in een Angular component zou dit er als volgt uit kunnen zien:

Wat moeten we doen om Angular te laten weten dat de view geüpdatet moet worden als Cordova terug komt met de taal?

De code die binnen Angular Zone moet worden uitgevoerd moet worden gewrapt door slechts 1 functie:

In de component zou dit er als volgt uitzien;

Nu krijgt de callback functie die wordt uitgevoerd door Cordova de Zone mee waarmee Angular kan bepalen dat de view opnieuw  gerenderd moet worden.

Conclusie

In Angular JS kon de view geforceerd opnieuw gerenderd worden met $scope.$apply() bij bijvoorbeeld een addEventListener. Vanaf Angular 2 kan iets vergelijkbaars worden gedaan door de code met zone.run() uit te voeren. Dit artikel gaat over de situatie waarbij de view niet updatet doordat Angular de Zone verliest. Natuurlijk zijn er meer oorzaken te bedenken waarom Angular de DOM niet updatet. Dit artikel beschrijft het bestaan van NgZone en dat dit de oorzaak zou kunnen zijn voor onverwacht gedrag bij asynchrone taken.

Tweet about this on TwitterShare on LinkedIn

Reacties

Het e-mailadres wordt niet gepubliceerd.

*