[Debugging] Expression has changed after it was checked

[Debugging] Expression has changed after it was checked


[Debugging] Expression has changed after it was checked

In this video, you’ll learn what the error “ExpressionChangedAfterItHasBeenCheckedError” means, how to debug it, and prevent it from happening in the future.


Content

0.48 -> expression changed after it has been
2.159 -> checked error
3.04 -> you'll encounter this error if your code
4.72 -> changes a value after change detection
6.72 -> has been run and the view has been built
8.72 -> you'll only see this error in
9.92 -> development because angular runs an
11.759 -> additional change detection check to
13.44 -> catch errors like this
14.559 -> that can cause erratic ui behavior the
16.64 -> extra check ensures the app is stable
18.56 -> and that all data updates have been
20.08 -> reflected in the views there are a
22.16 -> variety of reasons the view might be
23.68 -> left in an inconsistent state
25.279 -> such as code that updates the view
27.039 -> during the after view init lifecycle
28.72 -> hook
29.119 -> or when change detection triggers itself
31.359 -> in an infinite loop like a method that
33.2 -> returns a different value each time
35.04 -> or a child component that changes the
36.719 -> bindings on its parent
38.399 -> let's start by looking at a simple
39.84 -> reproduction and solution
41.44 -> then we'll take a more detailed look at
43.12 -> angular's change detection to understand
45.12 -> why this error happens and why it's
46.879 -> important
47.68 -> here in the app component template we're
49.28 -> using the ng-if directive with a loading
51.68 -> boolean
52.559 -> in the model or our component type
54.16 -> script we give it a default value of
55.84 -> true
56.96 -> then using the after view init lifecycle
59.199 -> hook we flip the value to false when
60.8 -> it's done loading but when we run this
62.32 -> code we see
63.12 -> expression changed after it has been
64.72 -> checked error previous value false
66.72 -> current value true now in a more complex
69.36 -> app it might not be clear where this
71.04 -> error originates from
72.479 -> but you can always assume that it has
74 -> something to do with a binding in the
75.68 -> template
76.32 -> in the stack trace you'll find a link to
78.4 -> the source map for the component
79.92 -> template that caused the error
81.52 -> and it takes us directly to the line of
83.2 -> code that caused the issue which is our
85.04 -> ng if binding
86.159 -> to the loading boolean what the error is
88.32 -> trying to tell us
89.2 -> is that the loading value has changed
91.04 -> after the change detection cycle has
92.72 -> completed but what exactly is wrong with
94.56 -> our code in this case
95.6 -> the short answer is that we're using the
97.2 -> wrong life cycle hook if we move our
99.119 -> code from
99.68 -> after viewing it to on inet the error
101.92 -> goes away and
102.88 -> everything works perfectly in other
104.479 -> words if you find yourself updating
106.159 -> values and
106.88 -> after viewing it there's a good chance
108.56 -> that a simple refactor to on init or the
110.799 -> component constructor will fix the issue
113.28 -> okay at this point we know we used the
115.28 -> wrong lifecycle hook and we can fix it
117.119 -> by refactoring
118.24 -> but to really understand why let's do a
120.32 -> quick review of how change detection
122.079 -> works in angular
123.04 -> the goal of change detection is to keep
124.96 -> the model your typescript code
126.88 -> in sync with the template your html and
129.44 -> it does so by looking for data changes
131.28 -> in the component tree from top to bottom
133.2 -> first it checks the parent then the
134.72 -> first child second child
136.319 -> and so on but if we update a binding on
138.48 -> the parent after it has already been
140.08 -> checked
140.56 -> angular will throw the error now what we
143.12 -> have here
143.76 -> is a simplified breakdown of angular's
145.84 -> life cycle or the steps it performs when
147.92 -> a component is first initialized
150 -> first it updates the bindings like the
152.08 -> ngif directive in the template
153.92 -> then it runs the on init lifecycle hook
156.16 -> updates the dom
157.12 -> then runs change detection for the child
159.04 -> component notice how the final step is
161.04 -> after view init and more importantly
162.879 -> that it runs
163.519 -> after change detection basically any
165.84 -> code that runs here should not attempt
167.519 -> to update the view
168.48 -> and that was the root of the problem in
170.08 -> this case refactoring to on init works
172.879 -> great for initial values
174.319 -> but if that doesn't fix the issue there
176.08 -> are a few other ways you may have
177.36 -> encountered this error
178.4 -> along with additional ways to fix it in
180.239 -> the component here we're using view
181.84 -> child to grab an element from the dom
184.08 -> but this element won't be available
185.84 -> until the after view innet lifecycle
187.519 -> hook is called
188.48 -> but what if we can't update the state of
190.08 -> the component until after we have the
191.92 -> view child element
193.04 -> if we can't refactor to ng on init we
195.12 -> have a couple of other options
196.72 -> in approach you'll often see on stack
198.319 -> overflow answers is to make the update
200.56 -> asynchronous
201.44 -> when we make the update async it'll be
203.28 -> picked up on the next change detection
205.12 -> check and prevent the error from
206.56 -> occurring
207.36 -> we can make it asynchronous by wrapping
209.2 -> it in a set timeout with a delay of zero
211.599 -> that'll put the update in the next macro
213.519 -> task queue of the javascript event loop
215.599 -> alternatively we can use a promise that
217.68 -> resolves immediately
218.959 -> then run the update in its callback this
221.12 -> code will achieve the same result
222.48 -> the only subtle difference is that it
224.08 -> runs on the micro task queue before
226.08 -> the end of the current iteration in the
227.76 -> browser's event loop making the update
229.599 -> asynchronous can work
230.799 -> however it's very implicit and should
232.879 -> really only be used as a last resort
235.28 -> it's not clear why we make this code
236.72 -> asynchronous unless you understand the
238.48 -> nuances of angular change detection
240.56 -> and the browser's event loop luckily
242.48 -> angular provides us with a more direct
244.239 -> and explicit way to trigger change
245.92 -> detection we can manually trigger it
247.84 -> by injecting the change detector ref in
250 -> the constructor of the component
251.68 -> we can then use it to manually run
253.519 -> change detection by calling the detect
255.439 -> changes method
256.32 -> this will tell angular to check the view
258.16 -> and its children in which case it will
259.759 -> notice that our loading state has
261.28 -> changed
261.759 -> giving us yet another way to address the
263.68 -> error now an entirely different way you
265.6 -> might encounter this error is when you
267.04 -> have a method usually a getter
269.199 -> that doesn't return a predictable value
271.12 -> which can cause an infinite change
272.56 -> detection loop
273.52 -> take for example this getter in our
275.04 -> component that returns a random number
277.199 -> if we try to use this value in the
278.639 -> template
279.199 -> angular will get a different value each
281.12 -> time it's checked the solution in this
282.88 -> case
283.28 -> is to make the method return a
284.639 -> consistent value based on the state of
286.72 -> the component
287.44 -> in other words getters should be derived
289.44 -> directly from the component state and
291.12 -> not values that change constantly
292.72 -> like timestamps or random numbers now
294.96 -> let's take a look at one more example
296.8 -> where we have both a parent and child
298.88 -> component at play
300.24 -> the app component the parent contains
302.32 -> the loading state just like our previous
304.16 -> examples
305.039 -> but instead of running the update from
306.56 -> the parent component we'll make it
308 -> happen from a child component with a
310 -> custom event
310.96 -> in the item component the child we're
313.12 -> using the output decorator to create a
315.12 -> custom event
316 -> along with an event emitter then during
318.24 -> ng on init we'll go ahead and emit the
320.24 -> event with a value of true
322.24 -> then back in the app component template
324.08 -> we'll go ahead and declare the item
325.759 -> component
326.56 -> when it fires we'll set the loading
328.32 -> value to false the end result is a
330.56 -> situation where we have a child
332.08 -> component
332.639 -> updating the parent after change
334.08 -> detection has already run on the parent
336.08 -> and once again that produces the error a
338.32 -> potential solution in this case
339.759 -> would be to move the loading state into
341.759 -> the child component
342.88 -> and if that's not possible you might
344.4 -> consider moving this state to a shared
346.24 -> service
346.8 -> where it can be injected in multiple
348.4 -> components let's finish up by doing a
350.639 -> quick recap
351.44 -> the expression changed after it has been
353.28 -> checked error occurs
354.8 -> because a value in the template has been
356.96 -> updated after change detection has
359.12 -> finished
359.759 -> debug it by first finding the template
361.68 -> in the stack trace from there you can
363.199 -> analyze your code to determine where the
364.96 -> value is being updated
366.08 -> and use one of the methods covered in
367.44 -> this video to address it such as
369.12 -> refactoring to the onnet lifecycle hook
371.6 -> using change detector ref manually
373.52 -> making getters item potent
375.12 -> or making your updates async as a last
377.52 -> resort refer to the angular
379.039 -> documentation for additional details and
382.199 -> examples

Source: https://www.youtube.com/watch?v=O47uUnJjbJc