RSS

קטגוריה: AngularJS

OOP in Angular

אחד הדברים שאני מקפיד עליהם ב- Code Review שאני עושה בפרויקטים של Angular, זה לא להשתמש ב- $scope, במקום זה להשתמש ב- this. ראו פוסט בנושא. בעקבות שיחות רבות עם לקוחות הבנתי שאני צריך יותר לשכנע ולהראות דוגמא מלאה. בפוסט זה אני אראה איך יוצרים ירושה של controller? ולמה צריך ירושה, אם יש לנו DI?

למה צריך ירושה ב- controller? אנחנו יכולים להזריק לו איזה שרות שאנחנו רוצים.

למעשה השאלה כאן למה להשתמש בירושה אם אפשר להשתמש בהכלה?

 

תשובה:

בו נניח שיש לנו כמה סוגים של proxies, כמו למשל userProxy, carProxy ולשניהם יש את המתודות: load,save.

הקוד של ה- controller יראה כך:

(function(angular) {

    'use strict';

    //////////////// AngularJS //////////////

    var mi = angular.module('myApp', []);

    mi.controller('UserCtrl', UserCtrl);

    mi.controller('CarCtrl', CarCtrl);

 

    //////////////// JavaScript //////////////

 

    function UserCtrl($scope, userProxy) {

        $scope.items = [];

        $scope.load = function() {

            $scope.items = userProxy.load();

        };

    }

    function CarCtrl($scope, carProxy) {

        $scope.items = [];

        $scope.load = function () {

            $scope.items = carProxy.load();

        };

    }

})(angular);

מהסתכלות על הקוד אנחנו רואים שיש המון מהמשותף בין ה- controllers, ולכן נבנה BaseCtrl.

(function(angular) {

    'use strict';

    //////////////// AngularJS //////////////

    var mi = angular.module('myApp', []);

    mi.controller('UserCtrl', UserCtrl);

    mi.controller('CarCtrl', CarCtrl);

    //////////////// JavaScript //////////////

    function BaseCtrl(proxy) {

        this._proxy = proxy;

        this.items = [];

    }

    BaseCtrl.prototype.load = function() {

        this.items = this._proxy.load();

    };

 

    function UserCtrl(userProxy) {

        BaseCtrl.call(this, userProxy);

    }

    UserCtrl.prototype = Object.create(BaseCtrl.prototype);

 

    function CarCtrl(carProxy) {

        BaseCtrl.call(this, carProxy);

    }

    CarCtrl.prototype = Object.create(BaseCtrl.prototype);

  

})(angular);

הסברים:

* כל ההסברים הם על UserCtrl, אך זה תופס גם לגבי CarCtrl.

1. השם מתחיל באות גדולה (UserCtrl) כדי להדגיש את הצורך לפעיל את הפונקציה ע"י new.

2. בתוך הפונקציה UserCtrl אני קורה קודם ל- BaseCtrl, עם ה- this של UserCtrl.

3. כל Controller מבקש את ה- proxy שהוא צריך מה- DI, למשל ה- UserCtrl מקבל את ה- userProxy. הפרוקסי מוכנס ל- BaseCtrl. אפשר להגיד שכל הפרוקסים מממשים את אותו "Interface" ולכן ה- BaseCtrl יכול לעבוד אם כל סוגי הפרוקסי שמממשים את המתודות: load ו- save.

4. שימו לב שחתימת המתודות היא רק בכמות הארגומטים ולא בסוג.

 

אם כל הפרוקסים צריכים למממש את המתודות של load ו- save, למה שלא יהיה להם גם BaseProxy?

לדוגמא:

(function (angular) {

    'use strict';

    //////////////// AngularJS //////////////

    var mi = angular.module('myApp', []);

    mi.controller('UserCtrl', UserCtrl);

    mi.controller('CarCtrl', CarCtrl);

 

    mi.service('carProxy', CarProxy);

    mi.service('userProxy', UserProxy);

 

    //////////////// JavaScript //////////////

   

   

 

    function BaseProxy(data) {

        this.items = data;

    }

    BaseProxy.prototype.load = function() {

        return this.items;

    };

 

    function CarProxy($log) {

       BaseProxy.call(this, [{ id: 1, name: 'BMV', model: 1971 }]);

    }

    CarProxy.prototype = Object.create(BaseProxy.prototype);

 

    function UserProxy($log) {

        BaseProxy.call(this, [{ id: 1, name: 'Eyal', age: 1971 }]);

    }

    UserProxy.prototype = Object.create(BaseProxy.prototype);

 

})(angular);

 

הסברים:

שני הפרוקסי יורשים מ- BaseProxy, במקום לכתוב את המתודות כל פעם מחדש. ב-JS אין לנו Types ואין לנו העמסת מתודות ולכן המצבים שאנחנו יכולים "לעלות קוד" ל- Base Class רבים יותר, וחבל לא לנצל את היכולות האלו.

 

אשמח לשמוע את דעתכם J

מודעות פרסומת
 
9 תגובות

פורסם ע"י ב- יולי 21, 2014 ב- AngularJS

 

Error Messages in AngularJS

פוסט זה נועד לכל האנשים שמתחילים לפתח Angular ולא מבינים את ההודעות השגיאה שלו. החלטתי לבחור את ההודעות השגיאה הכי פופולריות שראיתי שיש לאנשים במהלך הקורסים שלי ולהסביר מה הבעיה.

1. אין הודעת שגיאה, אך Angular לא עובד.

אם שוכחים לשים את ה- ng-app, אז כאשר Angular לא מוצא אותו הוא לא עושה כלום, כלומר לא יוצר את ה- Injector, ולכן הוא גם לא מקמפל את הדף. אפשר להפעיל את Angular ידנית ע"י המתודה bootstrap. ראו פוסט בנושא.

2. Uncaught ReferenceError: angular is not defined

שכחתם להוסיף את הקובץ angular.js לדף, ולכן אין משתנה גלובלי בשם angular. ( תאמינו לי גם זה קורה J )

 

3. Error: [ng:areq] Argument 'MainCtrl' is not a function, got undefined

השם שנתתם בדרקטיב ng-controller לא נמצא בשרות $controller, כלמור ב- $injector. בדר"כ זה קורה מהסיבות הבאות:

א. השם שברישום של הcontroller לא אותו דבר כמו השם ב ng-controller.

// script file
angular.module('myApp'
, [])

            .controller('Ctrl', function($scope) {/*Code*/});

       

// html file

<div ng-controller="MainCtrl">…</div>

 

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

ג. שכחתם להוסיף את הקובץ JS שבו מוגדר הקונטרולר לדף ה-HTML.

 

4. Uncaught Error: [$injector:modulerr] Failed to instantiate module

אנגולר לא הצליח לטעון את כל המודולים. אנגולר מוצא את המודול הראשי ( ng-app ) ואז הוא רץ על כל המודולים שרשומים ב- requires של המודול הראשי. במילים פשוטות אנגולר רץ על העץ וטוען קודם את הילדים ואחרי זה את ההורים. כאשר אנגולר מגיע לשם של מודול שהוא לא מצליח לטעון הוא זורק את הטעות הזאת.

דוגמא : angular.module('myApp', ['myModule']);

בדוגמא שלפנינו אין בכלל מודול בשם 'myModule' או שיש אך שכחתם לחבר את קובץ ה-JS של המודול לדף ה- HTML, ולכן הוא לא נטען.

 

5. Error: [$injector:unpr] Unknown provider:

בפוסט הזה הסברתי שלא משנה איך אתם רושמים את השרות שלכם בסוף זה נרשם כ- Provider.  כאשר אנגולר לא מוצא את השרות שהוא צריך להזריק לפונקציה הוא זורק את הטעות הזאת, לדוגמא:

angular.module('myApp', [])

        .controller('Ctrl',function($scope, noService) {

           

        });

הסבר:
 אנגולר לא מצליח להבין מה הוא צריך להזריק בארגומנט השני, ה- $injector לא מכיר את השם ‘noService’. יש מספר סיבות שזה יכול לקרות לכם:

א. טעות בשם

ב. שכחתם לטעון את המודול שבו מוגדר השרות.

ג. שכחתם לחבר את המודול למודול הראשי ולכן הוא לא נטען.

ד. השתמשתם ב- סוגרים מרובעים או ב- $inject, ויש לכם טעות הקלדה או אי התאמה עם מספר
    הארגומנטים. ראו דוגמא:

angular.module('myApp', [])

   .controller('Ctrl',['$scope','$log','noService',function($scope, noService) {

}]);

 

לסיכום:

ממליץ לכם בחום לבקר באתר של אנגולר ולראות את כל סוגי השגיאות. (https://docs.angularjs.org/error )

בנוסף מי שרוצה לקרוא ולהבין איך בדיוק אנגולר מתחיל לעבוד, יוצר את ה- Injector ומקמפל את הדף יכול לקרוא את הפוסטים שלי באתר http://ng-course.org/ .

אם עדין יש לכם רעב לעוד… אתם מוזמנים לקורסים שלי. הקורס הקרוב נפתח בשבוע הבא ה-15 ליולי. פרטים נוספים לחצו כאן.

 
השארת תגובה

פורסם ע"י ב- יולי 12, 2014 ב- AngularJS, ng-course.org

 

תגים:

Scope.$apply Life Cycle

Scope.$apply Life Cycle

בפוסט זה אני רוצה להסביר את סדר האירועים כאשר מבצעים $apply. בנוסף אני אעמוד על ההבדלים בין $evalAsync, $$postDigest ו- $timeout. חשוב לדעת את ההבדלים בין הפונקציות בעיקר כאשר בונים directives שמשתמשים הרבה ב- jQuery. בפוסט זה אני לא מסביר מה $apply עושה, כי על זה יש פוסט מעולה.

1. מתי קוראים ל- $apply?

כאשר נורה האירוע DOMContentLoaded אנגולר מתחיל את העבודה שלו ( פירוט מלא יותר אתם יכולים לקורא בפוסטים קודמים ) בלבצע compile על הדף ואז לקרוא ל- $apply. ראו קוד מתוך מתודה doBootstrap.

 

injector.invoke(function(scope, element, compile, injector, animate) {

     scope.$apply(function() {

                    element.data('$injector', injector);

                    compile(element)(scope);

        });

      });

 

המשמעות של הקוד הזה שהפעלת כל ה- directives על הדף מתבצעת תחת $apply.

2. אז מתי צריך לקרוא ל- $apply באופן יזום?


כאשר צריך להגיב לאירועים, למשל של ממשק משתמש או תקשורת צריך בסוף האירוע להפעיל את
$apply. דוגמת קוד מ- ng-event.

forEach(

  'click dblclick mousedown mouseup mouseover mouseout mousemove
mouseenter mouseleave keydown keyup keypress submit focus blur copy
cut paste'
.split(' '),

  function(name) {

    var directiveName = directiveNormalize('ng-' + name);

    ngEventDirectives[directiveName] = ['$parse', function($parse) {

      return {

        compile: function($element, attr) {

          var fn = $parse(attr[directiveName]);

          returnfunction(scope, element, attr) {

            element.on(lowercase(name), function(event) {

              scope.$apply(function() {

                fn(scope, {$event:event});

              });

            });

          };

        }

      };

   }];

  }

);

כאן אנחנו רואים קוד מתוחכם שמייצר directive לכל אירוע, למשל ng-click. בכל ה- directive יש רישום לאירוע (element.on()) והפונקציה שנקראת בעת האירוע קוראת ל- $apply. זאת הסיבה שכאשר אתם משתמשים ב- ng-click אתם לא צריכים לקורא ל- $apply.
אנגולר בשירותים  
$http ו- $timeout גם מפעיל את $apply
אחרי ביצוע הפונקציות שנרשמו לאירועים אסיכרונים.

אז מתי כן צריך לקרוא ל- $apply? כאשר אנחנו נרשמים לאירועים, לא דרך directive או שרות של אנגולר, צריך לבצע הפעלת $apply, לדוגמא עבודה עם $.ajax.

 

3. מה סדר הפעולות כאשר מבצעים $apply?

הקוד של $apply מאוד פשוט. אתם יכולים לראות שהוא מבצע את הקוד שלכם ואחרי זה מבצע את $digest שבודק מה השתנה בעץ של ה- scope. שוב ממליץ בחום לקורא את הפוסט הזה.

 

clip_image0024. מה סדר הפעולות ב- $digest?

 

אפשר לחלק את הפונקציה $digest לשלושה חלקים עיקריים:

1. $evalAsync Queue

2. Traverse Scope Loop (TSL)

3. $$postDigest

 

הסברים:

החלק העיקרי של $digest זה TSL, החלק הזה אחראי בקוד למצוא מה השתנה בעץ של Scopes ולהפעיל את הפנקציות שנרשמו לשינוי במידה ויש ($watch.). אם אנגולר מזהה שינוי בעץ, השדה dirty יהיה במצב true ואז צריך לחזור לבצע שוב את הסריקה על העץ ( בציור זה שני החצים הסגולים ). הסיבה שחוזרים לסרוק שוב את העץ כי יכול להיות שהפונקציה שנרשמה לשינוי ביצעה בעצמה שינוי באחד ה- Scopes. במידה ואנגולר יגיעה לסריקה ה-10 של העץ ברצף, הוא יזרוק טעות ויצא מהפונקציה של ה- $digest.

5. מתי משתמשים ב- $evalAsync?

השימוש ב-$evalAsync לא נפוץ אך במצבים מסוימים זה מאוד יכול לעזור. תחשבו שאתם רוצים למשל לבנות directive שרוצה לבצע את הפעולות שלו על ה-DOM אחרי שכל ה-directives סיימו לבצע את עבודתם. למשל כאשר בניתי Group Validation in ng-repeat הייתי חייב לבדוק אם יש כפילות רק אחרי ש- ng-repeat סיים לייצר את כל הילדים שלו ובצע את כל ה- directives על כל ילד, ולכן כתבתי את הקוד בתוך $evelAsync.

6. מתי משתמשים ב- $$postDigest ?

מאוד לא נפוץ, הדולר הכפול מסמן פונקציה פרטית. אחרי שה- $digest סיים לעבור על כל העץ של ה- scopes, השורות האחרונות של הפונקציה זה לבצע את כל המתודות שרשומות ב- $$postDigest.

דוגמא לשימוש ב- $$postDigest:

angular.module("app", [])

  .directive('postDigest', function ($document) {

      return {

        template : '<div ng-if="test">
<div id="eyal"></div>
</div>'
,

        link : function(scope, element) {

           // Not Work

           var eyal = $('#eyal', element);

           eyal.append($('<div>Link</div>'));


           // Work

           scope.$$postDigest(function() {

                var eyal2 = $('#eyal', element);

                eyal2.append($('<div>$$postDigest</div>'));

           });

         }

       }

   });

הסבר:

ב- template יש ng-if והוא מיצר את הילדים רק כאשר ה- $digest עובד ואז מופעל ה- $watch של ה- ng-if. כמו שהסברתי בסעיף 1, אחרי שאנגולר מקמפל את הדף הוא מבצע את $apply. ה- div עם ה-id='eyal' לא יהיה בעץ עד שה-$digest יעבוד וזה קורה אחרי ביצוע של הפונקציה link של postDigest directive, מכאן שאנחנו חייבים לעטוף את הקוד ב- $$postDigest כדי לבצע את הקוד אחרי שכל ה- wachers עבדו. היה אפשר להשתמש גם ב- $timeout אך זה פחות יעיל.

 

סיכום

בפוסט זה סקרתי את סדר הפעולות של $digest ו- $apply. מי שאהב את העומק של הדברים ורוצה להמשיך להעמיק בנושא מוזמן לבוא לקורס שלי AngularJS Deep Dive Course.

image

 
תגובה אחת

פורסם ע"י ב- מאי 29, 2014 ב- AngularJS

 

Forms in AngularJS

Forms in AngularJS

הסיבה ליצירת השפה JavaScript היתה הרצון ליצר שפה שתוכל להכניס קצת לוגיקה לטפסים. מאז הטכנולוגיה מאוד התקדמה והיום AngularJS מאפשר לנו לכתוב לוגיקה לטפסים בארכיטקטורה של MVC ולבצע את המשימות של Data-Binding  ובדיקת תקינות הקלט בצורה פשוטה מאוד. בפוסט זה אני רוצה לצלול ולהסביר את היכולות המלאות שיש לנו ב-AngularJS בנושא טיפול בטפסים.

Data Binding

בחיבור בין התג input ל- scope מתבצע ע"י ng-model. ה- ng-model הוא data binding דו כיווני, כלומר שהמשתמש משנה את הערך של ה- value של התג input מתבצע עידכון ב-scope וכנ"ל גם ההפך. עד גירסא 1.3 כל לחיצה על המקשים (keydown)  גרמה לעדכון ה-scope. מגרסה 1.3 אפשר להגדיר את האירוע שיגרום לעדכון ע"י ng-model-option. האירוע שמעדכן את התג input זה $watch על השדה המתאים.  ראו תמונה.

clip_image002

ng-model Directive

ה- ng-model מחבר בין שדה ב-scope לתג input, בנוסף הוא מחבר את עצמו ל- form directive. ראו קוד, את DDO של ng-model.

{

    require: ['ngModel', '^?form'],

    controller: NgModelController,

    link: function(scope, element, attr, ctrls) {

      // notify others, especially parent forms

      var modelCtrl = ctrls[0],

          formCtrl = ctrls[1] || nullFormCtrl;

 

      formCtrl.$addControl(modelCtrl);

 

      scope.$on('$destroy', function() {

        formCtrl.$removeControl(modelCtrl);

      });

    }

  }

מהקוד אנחנו מבינים שב- NgModelController נמצאת כל הלוגיקה של החיבור בין scope לתג input. פונקצית ה-link מחברת את ng-model ל- form במידה והוא נמצא.

NgModelController

ל- NgModelController יש מספר משימות:

1. ביצוע data binding בין שדה ב-scope והתג input.

2. ביצוע תקינות קלט ע"פ סוג השדה ו- attributes שמוגדרים על התג של input ראה דוגמא:

<input type="number" name="age" ng-model="user.age"
           
min="0" max="120" required />

3. ניהול המצבים השונים שהתג input  יכול להיות בהם. המצבים הם:

A.     "נקי" ($dirty) או "מלוכלך" ($pristine), כלומר האם השדה שונה ע"י המשתמש.

B.     תקין ($valid) או לא תקין ($invalid) ע"פ הגדרות השדה וה- attributes שיש על התג.

C.     שגיאות ($error), פירוט השגיאות.

 

4.     הוספה או הורדה של class התואם את המצב שהתג נמצא בו.

5.     חשיפה של מתודות ל- directives אחרים שיוכלו להשתמש ב- NgModelController לביצוע פעולות אלו.

 

הארכיטקטורה של NgModelController

clip_image004

העברת המידע מהתג input ל- scope:

ה- ngModelController לא מחובר ישירות לשום תג. כדי שיהיה עידכון של השדה ב- scope צריך להפעיל את המתודה $setViewValue. התג input  מקבל את ngModelController (require: '?ngModel') ומפעיל את המתודה של ה- $setViewValue על כל keydown.

המתודה $setViewValue אחראית על כמה משימות:

1.     עידכון השדה $viewValue

2.     עידכון המצב ל- $dirty את התג input והתג form, כמו כן את ה- classes המתאימים. ראו קוד:

    // change to dirty

    if (this.$pristine) {

      this.$dirty = true;

      this.$pristine = false;

      $animate.removeClass($element, PRISTINE_CLASS);

      $animate.addClass($element, DIRTY_CLASS);

      parentForm.$setDirty();

    }

3.     הפעלת המתודות שרשומות ב- $parsers. מתודות אלו מקבלות את ה- value ומחזירות value חדש. למשל לקבל ערך של כסף עם פסיקים (1,800 ₪ ) ולהחזיר אותו כמספר תקין (1800). למתודות אלו אסור לזרוק שגיאות. ראו קוד:

forEach(this.$parsers, function(fn) {

      value = fn(value);

});

4.     הכנסת הערך האחרון שהתקבל מה- $parsers לתוך ה- $modelValue ועדכון השדה המתאים ב- scope. עכשיו רק נותר להפעיל את כל מי שנרשם ל- $viewChangeListeners. ראו קוד:

if (this.$modelValue !== value) {

  this.$modelValue = value;

  // Update scope field

  ngModelSet($scope, value);

  forEach(this.$viewChangeListeners, function(listener) {

        try {

          listener();

        } catch(e) {

          $exceptionHandler(e);

        }

      });

    }

העברת המידע מ scope לתג input:

ה- ngModelController מבצע $watch על השדה המתאים. כאשר ה- $watch מזהה שהשתנה הערך בשדה מעבירים את הערך החדש דרך כל $formatters ואת הערך האחרון שמים ב- $viewValue ובסוף מעדכנים את ה-UI ע"י הפעלת המתודה $render(). ראו קוד:

$scope.$watch(function ngModelWatch() {

    var value = ngModelGet($scope);

 

    // if scope model value and ngModel value
    // are out of sync

    if (ctrl.$modelValue !== value) {

 

      var formatters = ctrl.$formatters,

          idx = formatters.length;

 

      ctrl.$modelValue = value;

      while(idx–) {

        value = formatters[idx](value);

      }

 

      if (ctrl.$viewValue !== value) {

        ctrl.$viewValue = value;

        ctrl.$render();

      }

    }

    return value;

  });

נקודות חשובות שיש לשים לב אליהן:

1.     אין שינוי מצב ל- $dirty כמו שהיה במתודה $setViewValue. שדה נחשב "מלוכלך" רק אם המשתמש הקליד לתוכו.

2.     המתודה $render() לא עושה כלום !!! מי שמשתמש ב- ngModelController צריך לתת לה את המימוש. ראו את המימוש ש- directive input נותן לה:

ctrl.$render = function() {

    element.val(ctrl.$isEmpty(ctrl.$viewValue) ?
                                
" : ctrl.$viewValue);

  };

3.     הסדר של הפעלת ה- $formatters הפוך מהסדר של הפעלת ה- $parsers.

 

ngModelController Custom Validations

ngModelController מאפשר לנו להוסיף מתודות ל- $parssers ול- $formatters על פי הצורך. המתודות שאני מוסיף יכולות לשנות את הערך ולעשות בדיקת תקינות. דיווח על תקינות הערך מתבצע ע"י המתודה $setValidity. המתודה מקבלת שני ערכים, הראשון validationErrorKey והשני isValid. ראו קוד לדגומא של directive לבדיקת תקינות של מספר integer:

var INTEGER_REGEXP = /^\-?\d*$/;

app.directive('integer', function () {

  return {

    require: 'ngModel',

    link: function (scope, elm, attrs, ctrl) {

        ctrl.$parsers.unshift(function (viewValue) {

              if (INTEGER_REGEXP.test(viewValue)) {

                 // it is valid

                 ctrl.$setValidity('integer', true);

                 return viewValue;

               } else {

    // it is invalid, return undefined (no model update)

                  ctrl.$setValidity('integer', false);

                  return undefined;

               }

           });

         }

      };

  });

 

השימוש ב- $setValidity קובע אם הערך תקין או לא. ראו קוד של מתודה $setValidity.

 

var invalidCount = 0;

 

this.$setValidity = function(validationErrorKey, isValid) {

   // Purposeful use of ! here to cast isValid to boolean    
   // in case it is undefined

   if ($error[validationErrorKey] === !isValid) return;

   

   if (isValid) {

      if ($error[validationErrorKey]) invalidCount–;

      if (!invalidCount) {

        toggleValidCss(true);

        this.$valid = true;

        this.$invalid = false;

     }

   } else {

      toggleValidCss(false);

      this.$invalid = true;

      this.$valid = false;

      invalidCount++;

   }

 

   $error[validationErrorKey] = !isValid;

   toggleValidCss(isValid, validationErrorKey);

 

parentForm.$setValidity(
  validationErrorKey, isValid,
this );

};

דגשים:

המתודה מטפלת בשלוש משימות, ניהול המצב תקין או לא תקין וה-CSS המתאים. ניהול השגיאות וה- CSS המתאים, ועדכון האבא (form) את המצב שלו. שימו לב שה- $error מחזיק ערך הפוך מהמצב הנוכחי, זה די מבלבל.  ראו דוגמא.

 

Input Directive

התג input משמש גם כתג חוקי ב- HTML וגם כ- directive. כמו שראינו קודם ה- ngModelController לא יכול לעבוד לבד. ה- input directive מגדיר את האירועים שיפעילו את $setViewValue() , $render() ואת בדיקות התקינות שלו. בהסתכלות על הקוד של אנגולר אנחנו רואים שיש לכל type את הקוד שלו. ראו קוד:

var inputType = {

    'text': textInputType,

    'number': numberInputType,

    'url': urlInputType,

    'email': emailInputType,

    'radio': radioInputType,

    'checkbox': checkboxInputType,

 

    'hidden': noop,

    'button': noop,

    'submit': noop,

    'reset': noop,

    'file': noop

};

var inputDirective = function($browser, $sniffer) {

  return {

    restrict: 'E',

    require : '?ngModel',

    link: function(scope, element, attr, ctrl) {

      if (ctrl) {

        (inputType[lowercase(attr.type)] || inputType.text)
          (scope,element,attr,ctrl,$sniffer,$browser);

      }

    }

  };

};

מהקוד אנחנו מבינים שהקוד האמיתי נמצא ב- textInputType ( בהנחה שאנחנו מגדירים את ה- type="text" ) בהסתכלות על הקוד של המתודה textInputType אנחנו רואים שהיא מחולקת לשלושה חלקים עיקריים:

1.     ביצוע בדיקות תקינות, pattern, minLength ו- maxlength.

2.     הגדרת הפונקציה $render() ל- ngModelController כמו שכתבתי קודם.

3.     ההזנה לאירועים של keydown, paste, cut, change ו- input. כל האירועים אלו גורמים לעדכון השדה המתאים של ה- scope.

הקוד של המתודה טיפה ארוך וקצת נראה "מפחיד" אך אלו שלושת הדברים שהוא עושה.

המתודות של ה- types האחרים עושות אותו דבר רק עם הגדרות של בדיקות תקינות אחרות. למשל numberInputType מגדיר בנוסף לבדיקות שיש ב- text את הבדיקות של min,max ו- number.

 

HTML Validation vs. AngularJS Validation

יש הרבה בילבול בין תגים שהם חלק מהתקן של 5 HTML לבין directives של AngularJS. באיור אתם יכולים לראות את כל התגים החוקים ב- 5 HTML.

clip_image006

 

כמו שאתם רואים בציור יש כפילות בין 5 HTML לאנגולר. אנגולר דורס את ההגדרות של 5 HTML ונותן להם את המימוש שלו. הסיבה לעשות את זה היא, כדי לקבל תמיכה בכל הדפדפנים (ראו תמיכת דפדפנים של אנגולר) ולא רק באלו שתומכים ב- 5 HTML. במבט יותר מדויק בקוד של אנגולר אנחנו רואים שלחלק מהתגים ו- attributes הוא דורס אותם ע"י directives וחלק הוא משתמש בהם כ- attributes רגילים שהוא קורא את הערכים שלהם.

להלן רשימת ה- directives שיש לאנגולר בתחום הטפסים:

Requires

Directive

E

form

E

Input

E

textarea

E

Select

E

Option

A

required

A

ngModel

A

ngOptions

A

ngChange

A

ngRequired

A

ngValue

 

 

 

מהרשימה הנ"ל אנחנו מבינים שכל השאר הם attributes רגילים שאנגולר משתמש בהם כדי להבין מה אתם רוצים:

Ø  Max & Min

Ø  Maxlength & Minlength

Ø  Pattern

ה- Attribute של require כן מקבל directive, ראו קוד:

var requiredDirective = function() {

  return {

    require: '?ngModel',

    link: function(scope, elm, attr, ctrl) {

      if (!ctrl) return;

      attr.required = true; // force truthy in case we are on non input element

 

      var validator = function(value) {

        if (attr.required && ctrl.$isEmpty(value)) {

          ctrl.$setValidity('required', false);

          return;

        } else {

          ctrl.$setValidity('required', true);

          return value;

        }

      };

 

      ctrl.$formatters.push(validator);

      ctrl.$parsers.unshift(validator);

 

      attr.$observe('required', function() {

        validator(ctrl.$viewValue);

      });

    }

  };

};

ע"פ הקוד אנחנו רואים ש- require directive מקבל משמעות עם יש את ng-model.

על מנת למנוע התנגשויות בין אנגולר ל- 5 HTML בנושא בדיקות התקינות אנחנו שמים על התג form את novalidate וכך הדפדפן לא מבצע את הבדיקות אלא רק אנגולר עושה בדיקות תקינות.

 

Form Directive

יש מספר סיבות שבגללן צריך את ה- form directive, האחת, לבטל את ההתנהגות הטבעית של התג form  שמנסה לשלוח את הנתונים לשרת ובחלק מהדפדפנים גם עושה טעינה מחדש של הדף. השניה,להוסיף יכולת של ניהול מצבים של הטופס בדומה למה שהיה לנו בתג input.

FormController

המצבים שאותם מנהל ה- FormController לטופס הם:

Default Value

State

attrs.name || attrs.ngForm

$name

False

$dirty

True

$pristine

True

$valid

False

$invalid

 

המתודות שאותם חושף ה- FormController הם:

תיאור

מתודות

הוספה של תג input

$addControl

הורדה של תג input

$removeControl

מנהל את המצבים של $valid, $invalid ו- $error. בנוסף מעדכן את ה- class המתאים בהתאם למצב.

$setValidity

מנהל את המצבים $dirty ו- $pristine.

$setDirty

מנהל את המצבים $dirty ו- $pristine.

$setPristine

 

בהסתכלות בקוד של ה- FormController אנחנו רואים שיש תמיכה ב- form מקונן.

 

סיכום

בפוסט זה עברתי על כל היכולות שיש לאנגולר בטיפול בטפסים. ההבנה איך המנגנונים האלו עובדים תאפשר לכם לכתוב קוד יותר טוב וגם להרחיב את הקיים. בגירסה 1.3 הוסיפו יכולות נוספות לטפסים אך זה בפוסט אחר.

 

מאמר זה הוא חלק מהקורס Angular Deep Dive. אתם מוזמנים לבוא לקורס שיפתח ב-14.5.2014. מלאו את הפרטים ונחזור אליכם בהקדם.

clip_image008

 
2 תגובות

פורסם ע"י ב- אפריל 16, 2014 ב- AngularJS

 

How to Build AngularJS Modules Dump

כל אחד שעובד בפרויקט גדול שאול אותי אם יש דרך לצייר את העץ של המודולים באנגולר ומה יש בתוכם. התשובה הקצרה כן, זה אפילו לא הרבה קוד וזה נחמד J

 

הנחיות בסיס:

1. אסור לשנות את הקובץ של AngularJS ואת התנהגותו הטבעית.

2. אסור לשנות את הקוד שלנו בגלל ה-dump.

 

הנחיות שימוש והתקנה:

להוסיף את הסקריפט מיד אחרי AngularJS וזהו, סיממנו.

clip_image002[4]
עכשיו מתי שאתם רוצים אתם יכולים להקליד angularEx.getModulesTree() ולראות את העץ של הקשרים. למי שרוצה יוכל להוסיף קובץ של plugins כדי שהוא יוכל לעבוד עם directive שיצייר לו את זה יפה. שלב ב'.

 

בעיות שצריך להתמודד איתם:

1. איך מזהים את המודול הראשי?

2. איך מקבלים גישה לכל המודולים שבזיכרון?

3. איך בונים את העץ? מודול מכיל רק רשימה של string שהיא הבנים שלו.

 

פתרונות:

1. הדרך לזהות את המודול הראשי בלי התערבות  בקוד, כמו שאנגולר עושה את זה במתודה AngularInit, כלומר יעבוד רק עם יש ng-app. ההפעלה של המתודה צריכה להיות באירוע של DOMContentLoaded רק אז אנחנו יודעים בוודאות שכל המודולים נוצרו. (בהנחה שאתם לא משתמשים ב- requires.js או טכנולוגיה דומה ). שימו לב שאני עוצר את הריצה של אנגולר וממשיך אותה באופן יזום. ראו קוד ופוסט קודם.

 

2. אי אפשר לקבל גישה לכל המודולים !!! לכן נכתוב Decorator למתודה angular.module(name,requires,config) ראו קוד:

angular.module = function (moduleName, req,config) {

        var mi = moduleFn(moduleName, req, config);

        originalModules[mi.name] = mi;

        return mi;

    };

ע"י דריסה של המתודה כל פעם שיטען קובץ ויצור מודול אני אדע על כך.

3. בנית העץ פשוטה כי יש לנו את המודול הראשי ואובייקט שמכיל את כל המודולים ע"פ שם, סוג של dictionary, וכל מודול מכיל את כל הבנים שלו. בקיצור רקורסיה לבנית העץ.

סימנו, לקוד לחץ כאן.

דוגמא לאייך נראה האובייקט: ( רק שדות לא שקיים מופיעים )

clip_image004[4]

סימנו שלב א' לקבל את העץ כאובייקט, עכשיו אפשר לכתוב Directive שברגע ששמים אותו על הדף הוא מצייר את העץ.

(קצר בזמן, פירוט מלאה בקורס שלי)

אפשר להשתמש למשל ב- angular.treeview, צריך עכשיו לכתוב עוד רקורסיה שממירה את האובייקט למבנה של העץ. כמובן שאת הכל אורזים לקובץ moduleTree, בתוך הקובץ מגדירים את ה- directive עם תבנית שמשתמשת ב- angular.treeview ובלינק לחבר את המידע.

אשמח כמו תמיד לקבל תגובות.

חג שמח J

בתאריך 14.5.2014 יהיה קורס למפתחים מנוסים באנגולר, הקורס יעסוק באיך אנגולר עובד ואיך אפשר לשפר אותו.

הגעתם עד לפה אז נתן לכם את הלינק לדוגמא המלאה:
http://angular-js.azurewebsites.net/Demos/Samples/01-AngularEx/index.html

 
השארת תגובה

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

 

AngularJS Extension for Module Function

 

בעקבות בקשות רבות לקוד של ההרחבה של Angular בתחום המודול, העלתי אותו ל-gitHub, קוד ראשוני, אשמח לקבל פידבקים. ( הקובץ נקראה angularEx.js )

התקנה:

לשים את הקובץ מיד אחרי angular.js.

דוגמא:

clip_image001[4]

היתרונות שב- AngularEx.js:

1. לא צריך יותר לתחזק את ה- requires של המודול המרכזי, גם לא את ngRoute ואת ngAnimate למרות שהם לא מודלים שלכם.

2. במודולים שלכם אין הבדל בין יצירת מודול לבקשת מודול. אם אין את המודול הוא יוצר אותו אם יש הוא מחזיר אותו. בגלל השיפור הזה אין תלויות בין הקבצים השונים שרושמים לאותו מודול. עד עכשיו הקובץ שיוצר את המודול חייב להיות ראשון לפני הקבצים שמבקשים להרחיב את המודול הקיים.

3. אם אין requires, לא צריך לתת מערך ריק.

4. אין שינוי בחתימת המתודה חוץ מיצירת המודול הראשי שצריך לעביר בארגומנט האחרון true. צריך להשלים בחתימה את הארגומנט ה-3 של הפונקציה config.

 

לינק לדוגמא מלאה, באתר של הקורס שלי. ( לחץ כאן , אתם צריכים ללחוץ F12 כדי לראות את הקוד.)

 

 
תגובה אחת

פורסם ע"י ב- אפריל 8, 2014 ב- AngularJS

 

AngularJS Bootstrap



ng-header

השלב שבין הקלדת URL של אפליקציה ועד שהיא רצה הוא קריטי להרבה חברות.בפוסט זה אני רוצה לתאר את כל הדרכים להריץ את Angular, ולהסביר יתרונות וחסרונות של כל שיטה.

1. ng-app

הדרך הפשוטה ביותר להתחיל את Angular זה ע"י השימוש ב- ng-app. הכל מתחיל ע"י השורות קוד אלו בAngular.

 

jqLite(document).ready(function() {

    angularInit(document, bootstrap);

  });

 

האירוע קורה רק אחרי שכל הדף וקבצי ה-JS נטענו. כלומר כל המודולים נוצרו ורשמו להם את מה שצריך, controllers,filters,directives וכ"ו.

במתודה angularInitAngular יחפש את ng-app ואז יקרא למתודה bootstrap ראו קוד:

bootstrap(appElement, module ? [module] : []);

ה- appElement זה האלמנט עם ה- ng-app. ה- module זה הערך שיש ב- ng-app,אם הוא ריק אז נוצר מערך ריק.

angularInit מחפש את האיבר הראשון שיש עליו ng-app ואז הוא קורא ל-bootstrap . אי אפשר להפעיל בצורה זו שני אפליקציות על דף.

 

2. קריאה יזומה ל- bootstrap

מסעיף 1 אנחנו מבינים שאנחנו יכולים לקרוא לבד למתודה bootstrap בלי העזרה של Angular. היתרון בכך שאנחנו יכולים לקבוע מה האירוע שיגרום לAngular לעלות. למשל אחרי log-in או סתם לחיצה על כפתור. יתרון נוסף שאנחנו יכולים לקרוא למתודה bootstrap מספר פעמים וכך להפעיל כמה אפליקציות Angular על אותו דף, או אחת אחרי השניה. למשל אפליקציה רזה לניהול ה- log-in ורק אחרי שהמשתמש אושר לסגור את האפליקציה הראשונה ולפעיל את האפליקציה השנייה. אנחנו יכולים גם בין הפעלות של האפליקציות להחליט איזה קבצים JS אנחנו רוצים לטעון על סמך סוג המשתמש.

 

3. חצי אוטומטי

האפשרות האחרונה היא לחבר בין סעיף 1 לסעיף 2. Angular מאפשר לנו להשתמש ב- ng-app כדי לסמן את האלמנט של האפליקציה אך לעצור את העלייה האוטומטית של Angular ע"י הגדרה של משתנה גלובאלי, window.name = 'NG_DEFER_BOOTSTRAP!'. Angularלא ימשיך את העלייה שלו עד שנקרא בצורה מפורשת למתודה angular.resumeBootstrap([modules]). העצירה של הAngular מאפשרת לנו למשל לטעון מודולים ע"פ דרישה ורק שכל המודולים הגיעו להמשיך את פעולת העלייה של Angular.

 

סיכום

לא צריך לפחד מאפשרות 2 שבה אנחנו לוקחים שליטה על העלאת האפליקציה, בגלל הרצון לגמישות ודינאמיות איזה מודולים יטענו. אני מאמין שבאפליקציות מורכבות וגדולות זה מאוד יכול לעזור. בהסתכלות על מה יהיה ב- Angular 2.0 אנחנו רואים שהם יותר חושבים על סעיף 2 כדרך הטובה ביותר. ראו מסמכי איפיון, דוגמא של קוד.

 
השארת תגובה

פורסם ע"י ב- מרץ 29, 2014 ב- AngularJS