RSS

קטגוריה: AngularJS

Filter Performance Issue

בפעם הראשונה שקראתי על Filter של אנגולר חשבתי שזה אחד הדברים היפים שיש באנגולר, בעיקר בגלל הפשטות שלו.

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

clip_image002

בדוגמאות קוד זו אנחנו רואים איך אפשר לכתוב ביטוי שלוקח את רשימת השמות ומכניס אותם לפונקציה OrderBy וארגומנט שני 'name' התוצאה נכנסת לפונקציה filter וארגומנט השני הוא הערך של שדה ה- search. הערך שמוחזר מהפונקציה filter הוא רשימת השמות שתכנסת ל- ng-repeat.

בנוסף אפשר גם לכתוב custom filter ולהשתמש בהם באותו אופן.

בעיות ביצועים עם Filters:

אנגולר מעדכן את המסך כל פעם שהוא מזהה שינוי על העץ של ה- scopes ע"י בדיקה של ה- watchers שמאוחסנים על ה- scopes. בדיקה זו מתבצעת כאשר קוראים למתודה $scope.$apply(), למשל שמשתמשים ב- ng-click, ng-model, $http. למעשה כל directive שמאזין לאירוע ( למשל UI , תקשורת ) מבצע בסוף הפעלה של פונקצית $apply. כל פעם ש- directive מפעיל את $apply עושים בדיקה לכל ה- watchers שקיימים על ה- scopes כלומר מפעילים גם את הביטוי:

names| orderBy:'name'| filter:search

גם אם הערכים של ה- search וה- names לא השתנו. יותר גרוע מזה, אם אחד ה- watchers מזהה שהיה שינוי חוזרים ובודקים את כל ה- watchers שוב, כלומר גם את הביטוי של ה- filters.

סיכום הבעיה:

אם האפליקציה שלנו מתעדכנת בתדירות גבוהה, כלומר הפונקציה $apply נקראת בתדירות גבוהה, אנחנו נסבול מהפעלה מרובה של הפונקציות filters גם במקרים שבטוח שהתוצאה לא תשתנה.

פתרון:

 להוציא את חישוב ה- filters מה- watchers, כלומר שהם לא יכתבו בתוך ה-HTML.

ראו קוד לדוגמא:

clip_image004

clip_image006

הסבר:

פונקציה calc רצה רק כאשר השדה ה- search או השדה names משתנה ולא כל פעם שמתבצעת פונקציה $apply.

 

סיכום:

השימוש ב- filters ב-HTML יצור watchers שרצים כל $apply וזה יכול ליצור לנו בעיית ביצועים, בעיקר אם התדירות של הפעלת הפונקציה $apply גבוהה וה- Filters השונים רצים על רשימות גדולות.

הפתרון לעביר את החישוב מ- HTML לקוד. הפעלת החישוב תתבצע רק כאשר שדה שיכול להשפיע על החישוב שונה.

 
תגובה אחת

פורסם ע"י ב- אוקטובר 22, 2015 ב- AngularJS, AngularJS Tips, ng-course.org

 

Upgrading the $controllerProvider

אחד הדברים המציקים לי באנגולר זה העבודה המסיבית סביב ה-this לדברים שלא אמורים להיות עליו.

כתבתי כמה טריקים איך לשפר את המצב (AngularJS Tip 5: AngularJS Arguments , AngularJS Tip 4: Dynamic Prototype Pattern ). למעשה הבעיה קיימת רק שמגדירים controller. כאשר עובדים עם factory אני כותב את הקוד בסגנון הבא:

var mi = angular.module('myApp', ['ngRoute'])            
.
factory('User'
, UserTypeFactory);

 

function UserTypeFactory($log,$http){
// constructor
function User
(name){
this.name
= name;
}

// methods
User.prototype.setName = function
(newName){
this.name
= newName;
};

return User;
}

 

עכשיו כל מי שרוצה לעבוד עם Class מסוג User רק צריך לבקש וה-$injector יתן לו אותו. שימו לב שאם אני רוצה לעשות ירושה אני יכול לבקש שיזריקו לפונקציה UserTypeFactory  את BaseUser ואז בתוך הפונקציה אני בונה את הירושה. המתודה UserTypeFactory רצה רק פעם אחת בלבד, כך אנגולר עובד.

אם אני מחזיר new User() בפונקציה UserTypeFactory במקום רק User קבלתי את האפקט של פונקציה service אך עם היתרון שהצלחתי ליצור private static משתנים ( הכוונה ל- $ log ו- $http ).

ומה עם Controllers?

כאן יש בעיה קשה כי ה- controllers מקבלים גם שרותים כמו $log ו- $http אך גם $scope. ה-$scope הוא לא singleton ואחרים כן. ( אישית אני חושב שזה תכנון רע מאוד מה שהצוות של אנגולר עשה כאן)

פתרון פשוט:

function UserCtrlFactory($scope,userBL,$log,$q){
   function UserCtrl
($scope) {
      this.id = $scope.$id
;
      this.name = userBL.name;       

   
}
   UserCtrl.
prototype.update = function
(name) {
     $log.
debug('update'
);
     name +=
'!'
;
     userBL.
update.call(this
, name);

    };
  angular.extend( UserCtrlFactory.
prototype
,
  UserCtrl.
prototype
);
  UserCtrl.
apply(this, arguments
);
}

 

צורת כתיבה זו מאפשרת לי לכתוב controller שבתוכו כתוב ה-controller כמחלקה. בנוסף הצלחתי להגדיר את $log ו- $http כ- private static ואילו ה- $scope יעבור ל-controller הפנימי דרך ה- constructor.

יש פה הרבה טריקים של JavaScript בשתי השורות האחרונות, אך כאן לא המקום להסביר אותם.

הפתרון הזה פשוט אך לא טוב לביצועים כי כל פעם שיוצרים את ה-controller (למשל מעבר בין דפים ) ה- controller הפנימי גם נוצר וכל הפונקציות שמוגדרות ב- prototype שלו. זה בדיוק אותה הבעיה שעובדים עם $scope ולכן מומלץ מאוד לא להגדיר מתודות על ה-$scope.

מכאן הגעתי להחלטה שאין ברירה או שכותבים ng-controller חדש שיטפל בבעיה או פתרון יותר תשתיתי זה לשדרג את $controllerProvider.

אחרי השדרוג אפשר לכתוב controller בצורה הבאה:

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

                .controller('UserCtrl',UserCtrlFactory);

 

UserCtrlFactory.isFactory = true;
function UserCtrlFactory(userBL,$log,$q){
  function UserCtrl
($scope) {
    this.id = $scope.$id
;
    this.name = userBL.name;       

    }
  UserCtrl.
prototype.update = function
(name, address) {
    $log.
debug('update'
);
    name +=
'!'
;
    userBL.
update.call(this
, name);
  };
  return UserCtrl
;
}

 

הערות:

על מנת שאנגולר ידע שהפונקציה UserCtrlFactory היא מיצרת controller ולא ה- controller עצמו, אני מוסיף לה שדה isFactory עם ערך true.

התיקון שעשיתי בקוד של אנגולר זה להוסיף את השורות הבאות כאן.

// Start : Eyal Vardi
if(expression.isFactory){

  expression = $injector.invoke(expression);
 
controllers[constructor
] = expression; } // End : Eyal Vardi

הסבר:

כאשר אנגולר מוצא את שם ה- controller הוא מאחסן אותו במשתנה בשם expression. אני בודק אם זה באמת הקונטרולר או הפונקציה שעוטפת את הקונטרולר, אם זה מעטפת אני מריץ אותה ומאחסן את ה-controller האמיתי ב- expression וגם ב- controllers[constructor] כדי שפעם הבאה נקבל רק את הקונטרולר האמיתי ולא את העטיפה.

שיקולים בעד:

הכתיבה עכשיו של controllers הרבה יותר אלגנטית ויהיה קל יותר להפוך את הקוד ל- ES6 או לאנגולר 2.

שיקולים נגד:

שינויים בקוד של אנגולר יכולים ליצור בעיות בעתיד. ע"פ דעתי זה שינוי קטן שעושה נפלאות לקוד.

 

מה אתם חושבים?

רוצים להכיר עוד טריקים מהסוג הזה, בואו ליום של Angular Hacking.

 

 

 

 
השארת תגובה

פורסם ע"י ב- ספטמבר 27, 2015 ב- AngularJS

 

Master Template in AngularJS

Master Template in AngularJS

 

אנגולר מאפשר לנו לכתוב תבניות מאוד חכמות, וע"י Directives אנחנו יכולים לארוז אותם לתוך תג. למשל בדוגמא של accordion ו- expander, התוצאה הסופית היא שזה מתורגם לHTML מאוד גדול ביחס לתבנית.

<accordion>

   <h1>Eyal – {{date}}</h1>

   <expander class='expander'

             ng-repeat='item in expanders'

             expander-title='{{item.title}}'>

        {{item.text}}

   </expander>

</accordion>

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

דוגמאות למצבים שאני חושב שגישה זו עושה הגיון.

דוגמא 1: Binding Once or Not

תבנית עם Binding Once שלפעמים אני רוצה לבטל את ה- Binding Once. למשל התבנית הבאה:

clip_image002כאשר אני מסתכל על רשימה של פריטים אני רוצה שהתבנית של המוצר תיהיה עם יכולות של Binding Once ואילו כשאני מסתכל על מוצר בודד אני רוצה שאותה תבנית תיהיה Two way Data-binding. כלומר ההבדל היחידי בין התבניות זה ה- "::" שצריך להוסיף לפני שם המשתנה כדי לקבל binding once {{expression::}}. זה מקרה קלאסי לבנות Master Template שיקבע עם יהיה לפני שם המשנה ה-"::" או לא ע"י פרמטר.

 

דוגמא 2: שפות

כאשר אנחנו רוצים תמיכה של שפות בתוך התבניות אפשר להשתמש ב- angular-translate. אבל אפשר גם להשתמש בפתרון של Master Template ואז אנגולר יעבוד פחות קשה וגם יהיה יותר קל להבין את התבניות.

מה משותף לשתי הדוגמאות האלו? שה-MT יכול להכין את התבניות ע"פ מידע שקיים בזמן "קימפול" ואנגולר עובד על התבניות שנוצרו בזמן ריצה. ראו שרטוט.

clip_image004

אם לא רוצים להכין מראש את התבניות ע"פ שפה, אפשר להעביר את ה-MT לצד השרת ואז להוסיף ל-URL ערך שמציין את השפה הרצויה. ( לדוגמא : http://evardi.com/product.html?lag=heb  ) פתרון לגישה זו פרסמתי בפוסט AngularJS Tips 1–Directive TemplateURL.

כאשר לא רוצים לערב את השרת, אפשר לעשות אותו דבר בצד של אנגולר.

השאלה שמתעוררת היא  באיזה טכנולוגיה נשתמש כדי ליצור את התבניות של אנגולר? אפשר handlebars למשל, אבל למה לא להשתמש בפתרון ש- EcmaScript 6.0 מביא לשולחן?

לדוגמא:

(function (angular) {
"use strict"
;
///////////// AngularJS Code ///////
var mi = angular.module('myApp'
, [])
.factory(
'lang'
,langFactory)
.directive(
'helloWorld'
,helloWorldDirective);

function langFactory(){
return
{
he:{ hello : "
שלום" },
en :{ hello : "hello"
}
};
}

function helloWorldDirective(lang,$log){
return
{
template :
function
($compileNode, templateAttrs){
var
template = `<div>
<b> ${lang.he.hello}<b> : {{
name}}

                  </div>`;
return template.toString();
}
};
}
})(
angular
);

 

הסברים:

כאשר אנגולר מקמפל קטע HTML והוא מזהה את הדיראקטיב hello-world הוא קורא ל-template. ואז נוצרת תבנית עם השפה הרצויה שאותה אנגולר מקמפל. הבעיה שאם הדיראקטיב נמצא מספר פעמים על הדף הפונקציה של template תקרא מספר פעמיים. לכן צריך להוסיף מנגנון של Chace. אם חושבים על זה פעם שניה אפשר בשלב ה-run לעבד את התבניות ולהכניס אותם ל- $templateChaceואז הדראקטייב לא צריך פונקציה מיוחדת ואפשר לחזור לעבוד עם templateUrl.

 

דוגמא:

(function (angular) {
    "use strict";
    ///////////// AngularJS Code ///////
    var mi = angular.module('myApp', [])
        .factory('lang',langFactory)
        .directive('helloWorld2',helloWorldDirective2)
        .run(function($templateCache,lang){
            var template = `<div>
            <b> ${lang.he.hello}<b> : {{name}}
            </div>`;
            $templateCache.put("helloworld.html",template.toString());
        });
    function langFactory(){
        return{
          he:{ hello : "שלום" },
          en :{ hello : "hello" }
        };
    }
    function helloWorldDirective2(lang,$log){
        return{
            templateUrl : "helloworld.html"
        };
    }
})(angular);
 
הסברים:
בשלב העלייה של אנגולר אנחנו בונים את התבניות ומכניסים להם את התרגומים. 
כמובן שאפשר להמשיך לשפר את המנגנון ולעבוד עם קבצים, לעבד אותם ואז להכניס אותם 
ל-$templateCache. אני אישית חושב שצריך לבצע את זה בצד השרת, הכי טוב מבחינת ביצועים.
 
דוגמא 3:
יש הרבה דיראקטייב שהם statics, כלומר הם מקבלים דרך ה- attributes ערכים קבועים כלומר 
אי אפשר לחבר את ה-attributes האלה ל-data binding ואז הם מוחלפים בתבנית. 
כלומר הערכים של ה- attributesנקבעים בזמן כתיבת ה-HTML.

למשל:
<image-button img="face.jpg" imgAlign="left">
    Hello World
</image-button>
 
הדיראקטייב הזה מיצר כפתור שבנוי מתמונה וטקסט ואפשר לקבוע עם התמונה תהייה באחד 
מהצדדים ימין, שמאל, למטה או למעלה. הדראקטייב הזה הואstatic  הוא נועד רק כדי לחסוך 
שורות קוד ב- HTML. 
 
דארקטייב כזה מאוד קל כאשר כותבים אותו באנגולר בצורה נאיבית. הבעיה שמהר מאוד י
וצרים scope ו- watchים, דברים אלו יוצרים בעיות ביצועים שלא לצורך.
 
פתרון:
 
function imageButtonDirective($log){
    return{
        template : function($compileNode, attrs){
            var template = `<div>
            <img src=${attrs.img} align=${attrs.imgalign}/>
            ${$compileNode.html()}
            </div>`;
            $log.debug(template.toString());
            return template.toString();
        }
    };
}
 
כאשר אנחנו בונים את התבנית ב- template אנחנו מקבלים מספר יתרונות:
1. שימוש בדראקטייב זה ב- ng-repeat הפונקציה ליצירת התבנית תופעל רק פעם אחת. שימו לב, 
אפשר לעשות את זה גם במתודה compile, אך יש בזה שתי חסרונות:
A.      אי אפשר להוסיף דראקטייב בשלב ה- compile בלי לקמפל שוב את התבנית עם $compile.
B.      קידוד תבניות הרבה יותר נוח בשיטה declarative מאשר בשיטה של imperative.
2. אפשר לעבוד עם Chace ע"פ הערכים שמקבלים ב- attributes ולכן מקבלים ביצועים טובים יותר.
3. גם במקרה הזה אפשר היה לעבד את המידע בצד השרת ואפילו להשתמש בתוכנה כמו grant כלומר 
לבצע את העיבוד בזמן ה- build.
 
סיכום:
החיים הם לא שחור או לבן, יש הרבה מאוד אפור...
במקרים מסוימים כאשר אפשר לעבד את ה-HTML בזמן ה-build עדיף לא להשתמש בטכנולוגית 
האנגולר כי נשלם על זה בביצועים שלא לצורך.
 
אשמח לקבל תגובות !!!
 
אם אהבתם את הפוסט ואתם רוצים להמשיך ולהעמיק בנושא, אשמח לראות אותם בקורס 
שלי על אנגולר למתקדמים.

 

 

 

 
2 תגובות

פורסם ע"י ב- פברואר 14, 2015 ב- AngularJS, AngularJS Tips

 

AngularJS Tip 3 – JavaScript Properties in AngularJS


אחת השאלות השכיחות ששואלים אותי, מה הדרך לחבר את
bl שלנו ל- Controller כך שיהיה לנו קל לחבר אותו ב- Binding ב- HTML (ng-model) ?

אפשרות 1:

function UserCtrl(userBL){
    this._bl = userBL;
    this.name    = userBL.name;
    this.address = userBL.address; // address is an object.

}

 

<div ng-controller="UserCtrl as vm">
    <input type="text" ng-model="vm.name" placeholder="name"><br>
    <input type="text" ng-model="vm.address.street" placeholder="street"><br>
    <input type="text" ng-model="vm.address.house" placeholder="house"><br>   
    <br>
    name : {{vm._bl.name}}<br>
    Address : {{vm._bl.address | json }}<br>
</div>

 

 

שפת ה-JavaScript תעתיק את name כ- Copy By Value, ולכן כאשר תעדכנו את הערך ע"י תג input לא יהיה קישור ל- userBL.name.  ב- address (אוביקט) ה-JS מעתיקה את ה-reference כ- Copy By Value ולכן הקישור נשמר.

שימו לב, אם בזמן ריצה יבצעו:

userBL.address = {strees:’abc’, house: 1};
 
נקבל ניתוק מה- this.address, הוא יצביע על האוביקט הישן.
 
אפשרות 2:
 
function UserCtrl(userBL){
    this._bl = userBL;
    Object.defineProperties(this,{
       name : {
           get : function(){
               return userBL.name;
           },
           set : function(value){
               userBL.name = value;
           }
       },
       address : {
           get : function(){
               return userBL.address;
           },
           set : function(value){
               userBL.address = value;
           }
       }
    });
}
 
באפשרות זו יצרתי properties ולכן ה- set’sעובדים על ה-userBL ישירות וכך לא נאבד את ה- binding.
אפשר להמשיך את הגישה הזו גם לקשר שבין ה- BLוה-DAL.
 
(function (angular) {
    ‘use strict’;
    //////////////// AngularJS //////////////
    var mi = angular.module(‘myApp’, [])
    .factory(‘userBL’,blFactory)
    .factory(‘userDTO’,dtoFactory)
    .controller(‘UserCtrl’,UserCtrl);
    //////////////// JavaScript //////////////
    function dtoFactory($log){
        return {
            name: ‘bl-name’,
            address: {
                street: ‘abc’,
                house: 47
            }
        };
    }
    function blFactory($log,userDTO){
        var blUser = Object.defineProperties({},{
            name : {
                get: function(){
                    return userDTO.name;
                },
                set : function(value){
                    userDTO.name = value;
                }
            },
            address : {
                get: function(){
                    return userDTO.address;
                },
                set : function(value){
                    userDTO.address = value;
                }
            }
        });      
        return blUser;
    }

    function UserCtrl(userBL){
        this._bl = userBL;
        Object.defineProperties(this,{
           name : {
               get : function(){
                   return userBL.name;
               },
               set : function(value){
                   userBL.name = value;
               }
           },
           address : {
               get : function(){
                   return userBL.address;
               },
               set : function(value){
                   userBL.address = value;
               }
           }
        });
    }   
})(angular);
 
 

הכתיבה של property קצת ארוכה מבחינת syntax אז כתבתי מתודה עזר:

function createProperty(source,wrapper,property){
    Object.defineProperty(wrapper,property,{
        get: function(){
            return source[property];
        },
        set : function(value){
            source[property] = value;
        }
    });
}
 
ועכשיו ה-controller יכתב כך:
function UserCtrl(userBL){
    this._bl = userBL;
    createProperty(userBL,this,’name’);
    createProperty(userBL,this,’address’);
}
 

שימו לב שבניתי את ה- properties על ה- this, אפשר לבנות אותו ישירות על ה- $scope, לא רעיון טוב. אני הייתי בונה אותו על ה- prototype כדי לא לשכפל פונקציות, של ה- get וה- set לכל מופע של ה- Controller.

function UserCtrl (userBL){
    this._bl = userBL;
    createProperty(userBL,UserCtrl.prototype,'name');
    createProperty(userBL,UserCtrl.prototype,'address');
}

 

הערה אחרונה: ngModelOptions

יש אפשרות באנגולר 1.3 להשתמש ב- ng-model-options="{ getterSetter: true }"זה תחליף ל-properties ע"י הטכנולוגיה של אנגולר, אך אני מאמין שכתיבה של property אמיתי יותר נכונה ויכולה לעבוד בכל השכבות. ראו דוגמא.

 

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

חומר זה הוא חלק מהקורס AngularJS  שאני מעביר בחברות.

 
3 תגובות

פורסם ע"י ב- נובמבר 16, 2014 ב- AngularJS

 

ngModelController 1.3 Internal

 

יש הרבה מאמרים על מה חדש באנגולר 1.3 בנושא הטפסים. אחד הטובים שבהם הוא Taming Forms in AngularJS 1.3. מה שלי היה חסר זה להבין לעמוק את סדר הפעולות של הדברים.

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

Capture

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

למאמר בנושא שכתבתי על גירסה 1.2 לחצו כאן.

 

 
השארת תגובה

פורסם ע"י ב- נובמבר 3, 2014 ב- AngularJS

 

קורס AngularJS מחזור 5

בעקבות הביקוש הרב לפתיחת קורס ציבורי נוסף. החלטתי לפתוח בספטמבר סדנא של 3 ימים על Angular ו-JavaScript.  היום הראשון של הקורס על JavaScript ושאר הימים על AngularJS.

תאריכים: יום שלישי ה-16 עד יום חמישי ה-18 לספטמבר.

קהל יעד: מפתחים מקצועיים שיכולים שיכולים לקלוט הרבה חומר בשלושה ימיים.

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

1.     ביצוע הקורס האון-לין החינמי של Code School.

2.     ביצוע התרגיל TV Show, בכיתה נקח את התרגיל הזה ונבנה אותו מחדש כמו שצריך.

3.     להסתכל על הסרט http://www.objectplayground.com/

4.     לעבור על אתר הקורס http://ng-course.org

מי שיתכונן לפני הקורס יוכל לקבל מהקורס הרבה יותר !!!

לרישום ופרטים נוספים לחצו כאן.

 

Become an expert in AngularJS with this 3 days in-depth training course. This course will teach you the AngularJS fundamentals and the internal. The course will cover directives, binding, filters, ngRepeat, testing, isolate scopes and much more, with real-world examples. ng-course, the best course in Israel.

JavaScript fundamentals

·        Scope & Function Context

·        Closures

·        this keyword

·        Object-Oriented in JavaScript (slides)

·        Async and Parallel in JavaScript (slides)

·        JavaScript Design Pattern

AngularJS Building Blocks for Building SPA (slides)

·        Template & live data binding ( Directives & $scope )

·        Model, View & Controller (MVC)

·        Dependency Injection ( AngularJS services )

·        Modules

·        LAB : TV Show SPA

Forms in AngularJS (slides | post)

·        ng-model directive

·        ngModelController & FormController

·        Custom Validation

·        Input directive

AngularJS Filters (slides)

·        Filter Syntax

·        AngularJS Filters

·        Custom filters

Communication (slides)

·        $.Ajax vs. $http

·        $resource

·        Promises ($q) vs. Calbacks

·        Offline / Online

·        LAB: Full TV Show Web App

Custom Directive (slides)

·        Template

·        Scope (post)

·        Compile function

·        Link function

·        Controller

·        Transclude

·        Animation (slides)

·        Tips & tricks

Routing and Navigation (slides)

·        $location service

·        ng-view directive

·        $route service and route object

·        Navigation flow

·        Routing broadcasted events

·        Resolve option and promise

·        Cancelling route changes

AngularJS Testing (slides)

·        Unit Testing (Jasmine.js)

·        AngulrJS Mock API's (ngMock)

·        Unit Testing Tools

·        E2E Testing

·        ngMockE2E – $httpBackend

·        Protractor Tool

AngularJS Animation (slides)

·        CSS3-enabled Animations

·        Directive That Support Animation

·        JavaScript-enabled Animations

Using Animations in your own directives

 
2 תגובות

פורסם ע"י ב- אוגוסט 11, 2014 ב- AngularJS, Course, ng-course.org

 

AngularJS AMD Style

הצלחתי לגרום לקוד הזה לעבוד J J J J

var mi = angular.module('myApp', [

        './ngXXX', // load async

        './ngYYY', // load async

        {

            name: 'ngRoute',

            url: '/scripts/angular-route.js'

        }

    ]);

מה מיוחד בקוד הזה?

1.     לא צריך לשים על ה-HTML את התג <script src="ngXXX.js">, חוץ מהקובץ של המודול הראשי.

2.     אנגולר עובד כאן בסגנון של require של נוד. כלומר הוא מזהה את ההתחלה של "./" ואז הוא עושה בקשה לשרת להביא את הקובץ.

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

4.     כל המודולים נטענים אסינכרונית ורק שכולם נטענו אנגולר רץ.

5.     לא נגעתי בקובץ angular.js.

 

אני אפרסם את הקוד בהמשך. אני רוצה להוסיף תכונה של on Demand , שרק שצריך שרות מהמודול הוא יטען.

ההצלחה הזאת עשתה לי אושר קטן בתקופה הקשה הזאת.

אשמח לשמוע דעות ורעיונות לתוספות.

 
2 תגובות

פורסם ע"י ב- יולי 23, 2014 ב- 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