RSS

AngularJS Compile Process

13 ינו

AngularJS Compile Process

 

ההבנה איך אנגולר עובד מצריכה להבין את שלושת החלקים המרכזים של אנגולר:

·        מנגנון ההזרקה ($injector)

·        מנגנון ה-Data Binding ($watch & $apply)

·        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>

    <div directive-name directive-name2>

        <div directive-name3>

               Hello World…

        </div>

    </div>

    <div directive-name directive-name2>

        <div directive-name3>

               Hello World…

        </div>

    </div> 

</div>

כדי שיהיה לנו יותר קל נעביר את ה-HTML ליצוג של עץ.

clip_image002

 

1.     המתודה compile מקבלת את div1 כפרמטר.

a.      המתודה compile קוראת למתודה compileNodes עם מערך של הילדים, כלומר div2 ו- div3.

                                          i.     המתודה compileNodes קוראת למתודות הבאות לכל אחד מהילדים (for loop on child nodes):

1.     collectDirectives

2.     applyDirectivesToNode

3.     compileNodes על הילדים של הילדים.

 

 

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);

       //…

    }

    //…

}

קוד 2: חלק ממתודה compileNodes

 

אוקי בואו נעצור כאן ונראה איך זה קורה בפועל:

המתודה collectDirectives נקראת בפעם הראשונה כאשר היא מקבלת את div2. היא מזהה שיש עליו שני directives והיא מוסיפה אותם ל-collection, לא לפני שהיא מפעילה את הפונקציה שיוצרת את directive definition object (DDO). כלומר המתודה compileNodes קיבלה מערך של DDO של כל ה-directives שנמצאו על div2.

בשלב שני המתודה compileNodes קוראת למתודה  applyDirectivesToNode כאשר היא מעבירה את המערך של DDO שהיא מצאה על div2. המתודה applyDirectivesToNode "רואה" שיש ל-div2 ילדים ( במקרה שלנו ילד אחד div4) ולכן היא קוראת למתודה compileNodes עם הארגומנט div4.

עכשיו שחזרנו שוב ל-compileNodesאנחנו שוב מבצעים collectDirectives ומקבלים מערך עם אוביקט אחד של DDO של directive-name3. את המערך נותנים למתודה applyDirectivesToNode ובגלל של-div4 יש רק string בתוכו (hello world) המתודה קוראת ל-template ואחרי זה ל-compile מתוך האוביקט של DDO של directive-name3.

אם תסתכלו בקוד הדוגמא כאן, עד עכשיו הסברנו את ההדפסה של:

1.      $compile start

2.      Directive factory 1 (DDO , directive-name)

3.      Directive factory 2 (DDO, directive-name2)

4.      Directive factory 3 (DDO, directive-name3)

5.      Template 3 (DDO.template, directive-name3)

6.      Compile 3  (DDO.compile, directive-name3)

7.      Template 1 (DDO.template, directive-name)

8.      Compile 1  (DDO.compile, directive-name)

9.      Compile 2  (DDO.compile, directive-name2, have no template)

 

עכשיו סימנו את div2 וכל התהליך מתחיל שוב עם div3. מכאן שהמשך ההדפסה יהיה:

10.   Template 3 (DDO.template, directive-name3)

11.   Compile 3  (DDO.compile, directive-name3)

12.   Template 1 (DDO.template, directive-name)

13.   Compile 1  (DDO.compile, directive-name)

14.   Compile 2  (DDO.compile, directive-name2, have no template)

15.   $compile Decorator took: …

חשוב מאד לראות שאנחנו לא קוראים שוב ל-Directive factory אלה משתמשים ב-DDO הקימיים ולכן לא רואים בהדפסה של div3 את סעיפים 2-4, כי ה-DDO קיימים כבר.

את הדפסה של תחילת $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;

    });
});

קוד 3:decorator  על $compile כדי למדוד זמנים

 

 

 

שלב 3: הפעלת פונקצית ה-link של DDO

כאשר תהליך ה-compile מסתיים מקבלים פונקציה שמריצים אותה עם scope (ראה קוד 1, המתודה compile מחזירה מתודה publicLinkFnשאותה אנחנו מפעילים עם scope). הפעלת הפונקציה הזו יוצרת את ה-controller, ואחרי זה מפעילה את preLink ובסוף את postLink. להלן הקוד שמריץ את המתודה compile ואחרי זה את הפונקציה שחוזרת עם ארגומנט של 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;

    }
...
}

קוד 4: חלק ממתודה bootstrap

 

להלן המשך ההדפסה של דוגמאת ה-HTML שלנו.

16.   controller 1

17.   controller 2

18.   preLink 1

19.   preLink 2

20.   controller 3

21.   preLink 3

22.   controller 4

23.   preLink 4

24.   postLink 4

25.   postLink 3

26.   postLink 2

27.   postLink 1

28.   controller 1         // div3 start

29.   controller 2

30.   preLink 1

31.   preLink 2

32.   controller 3

33.   preLink 3

34.   controller 4

35.   preLink 4

36.   postLink 4

37.   postLink 3

38.   postLink 2

39.   postLink 1

שלב 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.

 

סיכום:

אני מקווה שהצלחתי לשפוך קצת אור על תהליך הקומפילציה של אנגולר ועל סדר הפעולות שמתרחש בזמן הקומפילציה. מידע נוסף אפשר למצוא בבלוג ובדף הפייסבוק.

אשמח לשמוע פידבק ושאלות בנושא J

 

מודעות פרסומת
 
תגובה אחת

פורסם ע"י ב- ינואר 13, 2014 ב- AngularJS

 

תגובה אחת ל-“AngularJS Compile Process

כתיבת תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

 
%d בלוגרים אהבו את זה: