1. AngularJS Compile Process
ההבנה איך אנגולר עובד מצריכה להבין את שלושת החלקים המרכזים של אנגולר:
מנגנון ההזרקה ()$injector
מנגנון ה-)$watch & $apply( Data Binding
Directives
בפוסט זה אני רוצה לנסות להסביר את תהליך יצירת ה- .Directiveיש הרבה חומר על זה באינטרנט וגם בספרים,
אך גם לאחר קריאה שלהם הרגשתי עדיין שחסרה לי ההבנה של התמונה המלאה בנושא. לכן, החלטתי לצלול
לתוך הקוד של אנגולר ואז לדייק בהסברים כמו למשל מה ההבדל בין Linkל- compileומה ההבדל בין ה-
controllerל- .linkעל שאלות אלו ונוספות אני אנסה להסביר באמצעות תיאור והסבר של תהליך יצירת
.directiveהפוסט מיועד לאנשים שכתבו כבר directivesורוצים להבין את תהליך יצירתם יותר לעומק.
שלב 1: $compile
כאשר אנגולר מזהה שהדף נטען ( )DOM Content Loaded Eventהוא מעביר את הדף תהליך של קומפילציה
( .)$compileהתפקיד של הקומפילציה הינו להפוך את הדף מדף סטאטטי לדף דינאמי שמחבר בין Controllersו-
Scopesל- Viewsשהם בעצם קטעים של .HTMLדף ה- HTMLהסטאטטי מכיל שני סוגים של טיפוסים שתהליך
הקומפילציה צריך לטפל בהם: ביטויים ( {{ ) }}expressionו- .Directivesבפוסט זה אתאר כיצד תהליך
הקומפילציה "מתרגם" את ה- Directivesלקוד שיוצר את ה- HTMLהדינאמי.
שלב 2: סריקת ה- HTMLבסגנון DFS
הפעולה הראשונה שהמתודה $compileמבצעת היא לסרוק את ה- ,HTMLלמצוא את כל ה- Directivesולמיין
אותם ע"פ ה- priorityשלהם. בירידה לפרטים אנו רואים שהמתודה compileקוראת למתודה compileNodes
ברקורסיה.
,function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective
{ )previousCompileContext
...//
= var compositeLinkFn
,compileNodes( $compileNodes, transcludeFn, $compileNodes
;) maxPriority, ignoreDirective, previousCompileContext
...//
{ )return function publicLinkFn(scope, cloneConnectFn, transcludeControllers
...//
;}
}
קוד 1: חלק ממתודה compile
כדי להדגים את סדר הפעולות בחרתי דוגמאת HTMLשמיצגת את רוב המקרים.
><div
>2<div directive-name directive-name
>3<div directive-name
2. Hello World...
</div>
</div>
<div directive-name directive-name2>
<div directive-name3>
Hello World...
</div>
</div>
</div>
. ליצוג של עץHTML-כדי שיהיה לנו יותר קל נעביר את ה
1
DIV
2
DIV
3
DIV
Directive-name
Directive-name
Directive-name2
Directive-name2
4
DIV
5
DIV
Directive-name3
Directive-name3
Hello World
Hello World
. כפרמטרdiv1 מקבלת אתcompile 1. המתודה
.div3 - וdiv2 עם מערך של הילדים, כלומרcompileNodes קוראת למתודהcompile . המתודהa
for loop on child ( קוראת למתודות הבאות לכל אחד מהילדיםcompileNodes . המתודהi
:)nodes
collectDirectives .1
applyDirectivesToNode .2
. על הילדים של הילדיםcompileNodes .3
3. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority,
ignoreDirective, previousCompileContext) {
//...
for (var i = 0; i < nodeList.length; i++) {
attrs = new Attributes();
directives = collectDirectives(nodeList[i], [],
attrs, i === 0 ? maxPriority : undefined, ignoreDirective);
nodeLinkFn = (directives.length)
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn,
$rootElement, null, [], [], previousCompileContext)
: null;
//...
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
!(childNodes = nodeList[i].childNodes) ||
!childNodes.length)
? null
: compileNodes(childNodes,
nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
//...
}
//...
}
compileNodes קוד 2: חלק ממתודה
:אוקי בואו נעצור כאן ונראה איך זה קורה בפועל
. היא מזהה שיש עליו שניdiv2 נקראת בפעם הראשונה כאשר היא מקבלת אתcollectDirectives המתודה
directive , לא לפני שהיא מפעילה את הפונקציה שיוצרת אתcollection- והיא מוסיפה אותם לdirectives
שנמצאוdirectives- של כל הDDO קיבלה מערך שלcompileNodes ). כלומר המתודהDDO( definition object
.div2 על
כאשר היא מעבירה את המערךapplyDirectivesToNode קוראת למתודהcompileNodes בשלב שני המתודה
ילדים ( במקרה שלנו ילדdiv2- "רואה" שיש לapplyDirectivesToNode . המתודהdiv2 שהיא מצאה עלDDO של
.div4 עם הארגומנטcompileNodes ) ולכן היא קוראת למתודהdiv4 אחד
ומקבלים מערך עם אוביקט אחד שלcollectDirectives אנחנו שוב מבצעיםcompileNodes-עכשיו שחזרנו שוב ל
string יש רקdiv4- ובגלל שלapplyDirectivesToNode . את המערך נותנים למתודהdirective-name3 שלDDO
directive- שלDDO מתוך האוביקט שלcompile- ואחרי זה לtemplate-) המתודה קוראת לhello world( בתוכו
.name3
:אם תסתכלו בקוד הדוגמא כאן, עד עכשיו הסברנו את ההדפסה של
1. $compile start
2. Directive factory 1 (DDO , directive-name)
4. 3.
4.
5.
6.
7.
8.
9.
Directive factory 2 (DDO, directive-name2)
Directive factory 3 (DDO, directive-name3)
Template 3 (DDO.template, directive-name3)
Compile 3 (DDO.compile, directive-name3)
Template 1 (DDO.template, directive-name)
Compile 1 (DDO.compile, directive-name)
Compile 2 (DDO.compile, directive-name2, have no template)
:. מכאן שהמשך ההדפסה יהיהdiv3 וכל התהליך מתחיל שוב עםdiv2 עכשיו סימנו את
10.
11.
12.
13.
14.
15.
Template 3 (DDO.template, directive-name3)
Compile 3 (DDO.compile, directive-name3)
Template 1 (DDO.template, directive-name)
Compile 1 (DDO.compile, directive-name)
Compile 2 (DDO.compile, directive-name2, have no template)
$compile Decorator took: …
הקימיים ולכן לא רואיםDDO- אלה משתמשים בDirective factory-חשוב מאד לראות שאנחנו לא קוראים שוב ל
. קיימים כברDDO- את סעיפים 4-2, כי הdiv3 בהדפסה של
:$ וסיום אנו מקבלים בגלל הקוד הבא שהוספתיcompile את הדפסה של תחילת
app.config(function ($provide) {
$provide.decorator('$compile', function ($delegate) {
var result = function ($compileNodes, transcludeFn, maxPriority, ignoreDirective,
previousCompileContext) {
var start = new Date();
console.log("$compile start");
var link = $delegate($compileNodes, transcludeFn, maxPriority, ignoreDirective,
previousCompileContext);
console.log("$compile Decorator took: " + (new Date() - start) + "ms");
return link;
};
return result;
});
});
$ כדי למדוד זמניםcompile עלdecorator :3 קוד
5. DDO שלlink-שלב 3: הפעלת פונקצית ה
compile (ראה קוד 1, המתודהscope מסתיים מקבלים פונקציה שמריצים אותה עםcompile-כאשר תהליך ה
-). הפעלת הפונקציה הזו יוצרת את הscope שאותה אנחנו מפעילים עםpublicLinkFn מחזירה מתודה
ואחריcompile . להלן הקוד שמריץ את המתודהpostLink ובסוף אתpreLink , ואחרי זה מפעילה אתcontroller
.scope זה את הפונקציה שחוזרת עם ארגומנט של
function bootstrap(element, modules) {
...
var injector = createInjector(modules);
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;
}
...
}
bootstrap קוד 4: חלק ממתודה
. שלנוHTML-להלן המשך ההדפסה של דוגמאת ה
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
controller 1
controller 2
preLink 1
preLink 2
controller 3
preLink 3
controller 4
preLink 4
postLink 4
postLink 3
postLink 2
postLink 1
controller 1
controller 2
preLink 1
preLink 2
controller 3
preLink 3
controller 4
preLink 4
postLink 4
// div3 start
6. 3 37. postLink
2 38. postLink
1 39. postLink
שלב 4: הפעלת ה-apply
כאשר הקוד מסיים את שלב 3 מתבצעת מתודת ה- applyואז מבצעים evalלכל ,watchשזה כולל את כל ה-
expressionשיש בדף.
שאלות:
מה ההבדל בין המתודות compileו- linkשל ?DDO
הדבר הבולט ביותר זה מתי נקראת כל מתודה. ה- compileנקרא מיד לאחר ה- templateולמעשה הוא יכול
לשנות את ה- templateלפני שמתבצע עליו ה- compileשל אנגולר. לעומת זאת ה- linkמתבצע לאחר שהכול עבר
compileויש גם .scopeב- linkבעיקר נרשמים לאירועים של DOMאו לשינויים ב-.scope
מה ההבדל בין המתודות של controllerל- linkשל ?DDO
ה- controllerנוצר לפני ה- preLinkכלומר ה- DOM Elementעדין יכול לעבור שינויים. מכאן נובע שב-controller
לא כותבים קוד שקשור ל-.DOM
מיד לאחר קריאה ל- controllerקוראים ל- preLinkשזה לפני הקריאה לילדים. כאשר הילדים סימו רק אז קוראים
ל-.postLink
סיכום:
אני מקווה שהצלחתי לשפוך קצת אור על תהליך הקומפילציה של אנגולר ועל סדר הפעולות שמתרחש בזמן
הקומפילציה. מידע נוסף אפשר למצוא בבלוג ובדף הפייסבוק.
אשמח לשמוע פידבק ושאלות בנושא