-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathNG0100.en.vtt
361 lines (270 loc) · 10 KB
/
NG0100.en.vtt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
WEBVTT
Kind: subtitles
Language: en
00:00.480 --> 00:03.000
ExpressionChangedAfterItHasBeenCheckedError.
00:03.000 --> 00:08.720
You'll encounter this error if your code\hchanges a value after change detection\hhas been run and the view has been built.\h
00:08.720 --> 00:12.480
You'll only see this error in development\h
because Angular runs an additional change\h\h
00:12.480 --> 00:16.480
detection check to catch errors like\h
this that can cause erratic UI behavior.\h
00:16.480 --> 00:21.520
The extra check ensures the app is stable and that\h
all data updates have been reflected in the views.\h
00:21.520 --> 00:25.499
There are a variety of reasons the view might\h
be left in an inconsistent state,
00:25.499 --> 00:30.960
such as code\hthat updates the view during the AfterViewInit\h
lifecycle hook or when change detection triggers\h\h
00:30.960 --> 00:35.369
itself in an infinite loop, like a method that\h
returns a different value each time,
00:35.369 --> 00:38.212
or a child\hcomponent that changes the bindings on its parent.
00:38.212 --> 00:41.331
Let's start by looking at a simple reproduction\hand solution.
00:41.331 --> 00:45.120
Then, we'll take a more detailed\h
look at Angular's change detection to understand\h\h
00:45.120 --> 00:49.600
why this error happens and why it's important.
Here in the app component template we're using\h\h
00:49.600 --> 00:54.480
the ngIf directive with a loading boolean.
In the model, or our component type script,\h\h
00:54.480 --> 01:00.000
we give it a default value of true. Then, using\h
the AfterViewInit lifecycle hook we flip the\h\h
01:00.000 --> 01:03.120
value to false when it's done loading.
But when we run this code we see\h\h
01:03.120 --> 01:08.240
ExpressionChangedAfterItHasBeenCheckedError:\h
Previous value: false, Current value: true.\h
01:08.240 --> 01:12.480
Now in a more complex app it might not be\h
clear where this error originates from,\h\h
01:12.480 --> 01:16.320
but you can always assume that it has\h
something to do with a binding in the template.\h
01:16.320 --> 01:20.560
In the stack trace you'll find a link to the\h
source map for the component template that\h\h
01:20.560 --> 01:24.480
caused the error and it takes us directly\h
to the line of code that caused the issue,\h\h
01:24.480 --> 01:29.440
which is our ngIf binding to the loading boolean.
What the error is trying to tell us is that\h\h
01:29.440 --> 01:34.000
the loading value has changed after the change\h
detection cycle has completed, but what exactly\h\h
01:34.000 --> 01:37.120
is wrong with our code in this case?
The short answer is that we're using\h\h
01:37.120 --> 01:39.680
the wrong lifecycle hook.
If we move our code from\h\h
01:39.680 --> 01:44.160
AfterViewInit to OnInit the error goes\h
away and everything works perfectly.\h
01:44.160 --> 01:48.560
In other words, if you find yourself updating\h
values in AfterViewInit there's a good chance\h\h
01:48.560 --> 01:53.280
that a simple refactor to OnInit or the\h
component constructor will fix the issue.\h
01:53.280 --> 01:58.240
Okay, at this point we know we used the wrong\h
lifecycle hook and we can fix it by refactoring,\h\h
01:58.240 --> 02:03.040
but to really understand why let's do a quick\h
review of how change detection works in Angular.\h
02:03.040 --> 02:06.880
The goal of change detection is to\h
keep the model, your typescript code,\h\h
02:06.880 --> 02:10.880
in sync with the template, your html.
And it does so by looking for data\h\h
02:10.880 --> 02:15.520
changes in the component tree from top to bottom.
First it checks the parent, then the first child,\h\h
02:15.520 --> 02:19.840
second child and so on, but if we update a\h
binding on the parent after it has already\h\h
02:19.840 --> 02:24.560
been checked Angular will throw the error.
Now what we have here is a simplified\h\h
02:24.560 --> 02:30.000
breakdown of Angular's life cycle, or the steps\h
it performs when a component is first initialized.\h
02:30.000 --> 02:33.920
First it updates the bindings like\h
the ngIf directive in the template.\h
02:33.920 --> 02:37.600
Then it runs the OnInit lifecycle\h
hook, updates the DOM, then runs\h\h
02:37.600 --> 02:42.000
change detection for the child component.
Notice how the final step is AfterViewInit,\h\h
02:42.000 --> 02:44.880
and more importantly that it\h
runs after change detection.\h
02:44.880 --> 02:48.480
Basically any code that runs here\h
should not attempt to update the view\h\h
02:48.480 --> 02:53.600
and that was the root of the problem in this case.
Refactoring to OnInit works great for initial\h\h
02:53.600 --> 02:57.840
values, but if that doesn't fix the issue there\h
are a few other ways you may have encountered\h\h
02:57.840 --> 03:02.560
this error, along with additional ways to fix it.
In the component here, we're using ViewChild to\h\h
03:02.560 --> 03:06.320
grab an element from the DOM, but this\h
element won't be available until the\h\h
03:06.320 --> 03:10.080
AfterViewInit lifecycle hook is called.
But what if we can't update the state of\h\h
03:10.080 --> 03:13.040
the component until after we\h
have the ViewChild element?\h
03:13.040 --> 03:16.720
If we can't refactor to ngOnInit,\h
we have a couple of other options.\h
03:16.720 --> 03:21.440
An approach you'll often see on StackOverflow\h
answers is to make the update asynchronous.\h
03:21.440 --> 03:25.120
When we make the update async, it'll be\h
picked up on the next change detection\h\h
03:25.120 --> 03:29.440
check and prevent the error from occurring.
We can make it asynchronous by wrapping it in\h\h
03:29.440 --> 03:34.320
a set timeout with a delay of zero. That'll put\h
the update in the next macro task queue of the\h\h
03:34.320 --> 03:39.440
Javascript event loop. Alternatively we can use\h
a promise that resolves immediately, then run the\h\h
03:39.440 --> 03:44.000
update in its callback. This code will achieve the\h
same result, the only subtle difference is that\h\h
03:44.000 --> 03:48.880
it runs on the micro task queue before the end of\h
the current iteration in the browser's event loop.\h
03:48.880 --> 03:52.400
Making the update asynchronous can\h
work, however it's very implicit\h\h
03:52.400 --> 03:57.440
and should really only be used as a last resort.
It's not clear why we make this code asynchronous\h\h
03:57.440 --> 04:01.920
unless you understand the nuances of Angular\h
change detection and the browser's event loop.\h
04:01.920 --> 04:06.480
Luckily Angular provides us with a more direct\h
and explicit way to trigger change detection.\h
04:06.480 --> 04:11.680
We can manually trigger it by injecting the change\h
detector ref in the constructor of the component.\h
04:11.680 --> 04:16.320
We can then use it to manually run change\h
detection by calling the detect changes method.\h
04:16.320 --> 04:20.080
This will tell Angular to check the view and\h
its children, in which case it will notice\h\h
04:20.080 --> 04:24.240
that our loading state has changed, giving\h
us yet another way to address the error.\h
04:24.240 --> 04:28.560
Now an entirely different way you might encounter\h
this error is when you have a method, usually a\h\h
04:28.560 --> 04:33.520
getter, that doesn't return a predictable value,\h
which can cause an infinite change detection loop.\h
04:33.520 --> 04:37.200
Take for example this getter in our\h
component that returns a random number.\h
04:37.200 --> 04:40.400
If we try to use this value in the\h
template, Angular will get a different\h\h
04:40.400 --> 04:43.280
value each time it's checked.
The solution in this case\h\h
04:43.280 --> 04:47.440
is to make the method return a consistent\h
value based on the state of the component.\h
04:47.440 --> 04:51.120
In other words getters should be derived\h
directly from the component state and\h\h
04:51.120 --> 04:54.800
not values that change constantly\h
like timestamps or random numbers.\h
04:54.800 --> 05:00.240
Now let's take a look at one more example, where\h
we have both a parent and child component at play.\h
05:00.240 --> 05:05.040
The app component the parent contains the\h
loading state just like our previous examples,\h\h
05:05.040 --> 05:08.560
but instead of running the update from the\h
parent component, we'll make it happen from\h\h
05:08.560 --> 05:12.880
a child component with a custom event.
In the item component, the child,\h\h
05:12.880 --> 05:17.760
we're using the @Output decorator to create\h
a custom event along with an event emitter.\h
05:17.760 --> 05:21.680
Then during ngOnInit we'll go ahead and\h
emit the event with a value of true.\h
05:22.240 --> 05:26.560
Then back in the app component template we'll\h
go ahead and declare the item component.\h
05:26.560 --> 05:29.440
When it fires, we'll set\h
the loading value to false.\h
05:29.440 --> 05:34.080
The end result is a situation where we have a\h
child component updating the parent after change\h\h
05:34.080 --> 05:38.080
detection has already run on the parent,\h
and once again that produces the error.\h
05:38.080 --> 05:42.880
A potential solution in this case would be to\h
move the loading state into the child component\h\h
05:42.880 --> 05:47.040
and if that's not possible you might consider\h
moving this state to a shared service where it\h\h
05:47.040 --> 05:49.332
can be injected in multiple components.
05:49.332 --> 05:51.440
Let's finish up by doing a quick recap.\h
05:51.440 --> 05:56.960
The ExpressionChangedAfterItHasBeenCheckedError\h
occurs because a value in the template has been\h\h
05:56.960 --> 06:01.040
updated after change detection has finished.
Debug it by first finding\h\h
06:01.040 --> 06:04.720
the template in the stack trace.
From there you can analyze your code to determine\h\h
06:04.720 --> 06:08.640
where the value is being updated and use one of\h
the methods covered in this video to address it,\h\h
06:08.640 --> 06:13.520
such as refactoring to the OnInit lifecycle\h
hook, using change detector ref manually,\h\h
06:13.520 --> 06:18.080
making getters item potent, or making\h
your updates async as a last resort.\h
06:18.080 --> 06:23.200
Refer to the Angular documentation\h
for additional details and examples.