הכל מתחיל בפקודת ng s . הקסם מתרחש , המנוע של אנגולר מתחיל לעבוד ופתאום יש לנו אפליקציה עם שלום עולם. יופי, כמה פשוט, אפשר להתחיל לייצר קומפוננטות ולעשות בלגן. אבל בשביל שלא נעשה בלגן יש כמה דברים שכדאי להכיר, על מנת לעבוד יעיל יותר, כך שנקבל בסופו של דבר מוצר טוב יותר שקל יותר לתחזק. נתחיל בהבנה ממש בסיסית של הדברים רק בשביל לגשת לנושא המרכזי של מדריך זה שהוא היכרות וחשיבות angular lifecycle.
אז בגדול, הקומפיילר של אנגולר רץ, עושה המון דברים שהם פחות רלוונטיים אלינו (כרגע) ואז מגיע השלב המעניין, הקובץ הראשון של אנגולר בשלב הראנטיים, פוגש את ngmodule , בשלב זה אנגולר אוסף את כל המודולים אימפורטס דקלריישנס הפרוביידרים ואז לבוטסטרפ ושם אנגולר מתחיל להסתכל על הקומפונטטנה שממנה מתחיל להיבנות עץ הקומפוננטות שלנו.
יש לנו עץ קומפוננטות שנבנה על בסיס ההיררכיות, האפליקציה עובדת איזה יופי, מה עכשיו?
עכשיו יש דטה שמוזרם דרך עץ הקומפוננטות, הדטה עובר גם שינויים בדרך, מה זה אומר בעצם , רינדורים..
אנגולר רנדרינג על קצה המזלג
בלי להיכנס לעמוק של דברים,
נתחיל ישר עם changedetection
שבודק את השינויים שהתרחשו בקומפוננה כלשהיא ומחלחל כלפי מטה.
כברירת מחדל, כל קומפוננטה תגיב לכל שינוי ותרנדר את התצוגה
Changedetectionstrategy.default
מה שאומר שאנגולר יהיה רגיש לכל שינוי שיתבצע במידע שמחזיקה הקומפוננטה וירנדר אותה ואת כל הקומפוננטות הקשורות לה. מה שעלול לגרום לbad performance. לצורך בדיקה והבנה תוך כדאי עבודה הכנתי אפליקציה פשוטה מאד עם המון בעיות , שניתן לסדר עם הבנה בסיסית של change detection וכן יש חשיבות להבנה של lifecycle hooks של אנגולר שגם כן נכסה את החומר הזה באותה נשימה.
אז שניה לפני הפרקטיקה, בואו נעיף מבט על משהו שאנחנו מכירים ונזכר ברצף הפעולות של lifecycle hooks

פרויקט אנגולר angular lifecycle and change detection
הורידו את הפרויקט מגיטהאב , שימו לב שיש ברנץ אחד של התיקון (master) ואחד של השגוי (incorrect):
Codcodile angular lifecycle and change detection – github
ולאחר ריצת הפרויקט תראו שיש בוא כל מיני בעיות. תוכלו לנסות לפתור אותם לבד, ולאחר מכן להסתכל על הפתרון וכיצד אפשר לסדר את הפרויקט כך שיעבוד בצורה תקינה שאינה פוגמת ביצועים באפליקציה.
מה צריך לעשות
משימה ראשונה:
שימו לב שיש שגיאה בקונסול – ExpressionChangedAfterItHasBeenCheckedError Explained. נובעת מהסיבה שבוצע שינוי בVIEW לאחר שאנגולר בדק אם היו שינויים כל שהם ברכיב (changedetection). ולכן אנגלור זורק את השגיאה כי השינוי לא קרה בסדר של הלייפסייקל , כפי שאפשר לראות השגיאה לא שובר את האתר באמת. אבל מבחינת אנגולר, מצב זה תקין.
ברוב המקרים זה קורה כי השתמשנו בlifecycle hook לא מתאים שמבצע עדכון של הקומפוננטה. אך במקרה שלנו אנחנו רוצים להשאיר את העדכון בngAfterViewInit או ngAfterViewChecked ולנסות לסדר את הבעיה עם ChangeDetectionStrategy . כך למעשה אנגולר השליטה בChangeDetection תהיה בידיים שלנו.
משימה שניה:
למנוע מריצות מיותרות של פונקציות. שימו לב שכל שינוי קטן באפליקציה, למשל הקלדה בINPUT מבצעת הדפסות לקונסול (check renders) בפונקציות שנמצאות בכלל ברכיבים לא קשורים לINPUT מה שמעיד על רנדורים מיותרים שעולים לנו בביצועים.
משימה שלישית:
שימו לב שלחוצים על הצאקבוקס להוסיף לdone זה לא עובד. יש כאן בעיה שצריך לבדוק במקרה זה כי ChangeDetectionStrategy לא עובד לטובתנו. כי הוא לא רגיש לשינוים במקרה הזה נוכל להגיד ל לאנגולר כי יש שינוי ואנחנו מעוניינים שהתייחס אליו . אז זה נעשה עם ChangeDetectorRef detectChanges.
הערה חשובה:
שימוש לב לPIPE שלנו, הוא מוגדר על pure:false . זה גם נושא שיש להיזהר ממנו, כי עלול גם לזלול לכם משאבים מיותרים. עם ChangeDetectionStrategy.OnPush לרוב לא תהיה לכם בעיה איתו.
חשוב לציין! התרגיל הוא לא best practice
הדוגמה של האפליקציה אינה פתרון טוב, היא רק באה להציג את הנושא של רגישות אנגולר לשינויים ואת הלייפסיקל . כל השימושים הללו באים לידי ביטוי באפליקציות יותר רציניות כמובן ויש להם שימוש יעיל מאד. כאן יכולנו לסדר את הכל בצורה אחרת . אבל אז לא היינו לומדים להשתמש בכלים אלו.
להלן כמה קבצי דוגמה של האפליקציה השגויה
import { AfterViewChecked, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { HandleListService } from './handle-list.service'; export enum StatusTodo { NEW, DONE } export interface ITodos { text: string; id: number; status: StatusTodo } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], // changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent implements OnInit, DoCheck, AfterViewChecked, OnDestroy { public todos: ITodos[]; public counter: number = 0; public timer : ReturnType<typeof setTimeout> = null; public time : string = new Date().toLocaleTimeString(); constructor(private todosService : HandleListService, private cdr:ChangeDetectorRef){}; ngDoCheck() { if(this.counter < this.todos.length) { this.todos = this.todosService.todos; } }; ngOnInit(){ this.todos = this.todosService.todos; this.timer = setInterval(()=> { this.time = new Date().toLocaleTimeString(); this.cdr.detectChanges(); }, 1000); } ngAfterViewChecked(): void{ this.counter = this.todos.length; } ngOnDestroy(){ clearInterval(this.timer); } }
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, Input, OnInit } from '@angular/core'; import { ITodos, StatusTodo } from 'src/app/app.component'; import { HandleListService } from 'src/app/handle-list.service'; @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', styleUrls: ['./todo-list.component.scss'], // changeDetection: ChangeDetectionStrategy.OnPush }) export class TodoListComponent implements OnInit, DoCheck { @Input() todos: ITodos[]; @Input() counter : number = 0; public statusNew : StatusTodo = StatusTodo.NEW; public indexChecked : number = 0; constructor(private todosService : HandleListService, private cdr: ChangeDetectorRef) { } ngDoCheck(): void { } ngOnInit(): void { } checkDetec() { console.log('check renders') console.log(this.counter) } moveToDone(index: number): void{ this.todosService.changeStatus(index , StatusTodo.DONE); // this.cdr.detectChanges(); } }
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ITodos, StatusTodo } from 'src/app/app.component'; import { HandleListService } from 'src/app/handle-list.service'; @Component({ selector: 'app-add-todo', templateUrl: './add-todo.component.html', styleUrls: ['./add-todo.component.scss'], changeDetection: ChangeDetectionStrategy.Default }) export class AddTodoComponent implements OnInit { public newToDo : string = null; constructor(private todosService : HandleListService, private cdr: ChangeDetectorRef) { } ngOnInit(): void { } public addTodo() : void { const newTodo : ITodos = {text: this.newToDo , id: Math.random()*1356, status: StatusTodo.NEW}; this.newToDo = null; this.todosService.addTodo(newTodo); } }