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. למשל התבנית הבאה:
כאשר אני מסתכל על רשימה של פריטים אני רוצה שהתבנית של המוצר תיהיה עם יכולות של Binding Once ואילו כשאני מסתכל על מוצר בודד אני רוצה שאותה תבנית תיהיה Two way Data-binding. כלומר ההבדל היחידי בין התבניות זה ה- "::" שצריך להוסיף לפני שם המשתנה כדי לקבל binding once {{expression::}}. זה מקרה קלאסי לבנות Master Template שיקבע עם יהיה לפני שם המשנה ה-"::" או לא ע"י פרמטר.
דוגמא 2: שפות
כאשר אנחנו רוצים תמיכה של שפות בתוך התבניות אפשר להשתמש ב- angular-translate. אבל אפשר גם להשתמש בפתרון של Master Template ואז אנגולר יעבוד פחות קשה וגם יהיה יותר קל להבין את התבניות.
מה משותף לשתי הדוגמאות האלו? שה-MT יכול להכין את התבניות ע"פ מידע שקיים בזמן "קימפול" ואנגולר עובד על התבניות שנוצרו בזמן ריצה. ראו שרטוט.
אם לא רוצים להכין מראש את התבניות ע"פ שפה, אפשר להעביר את ה-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 עדיף לא להשתמש בטכנולוגית
האנגולר כי נשלם על זה בביצועים שלא לצורך.
אשמח לקבל תגובות !!!
אם אהבתם את הפוסט ואתם רוצים להמשיך ולהעמיק בנושא, אשמח לראות אותם בקורס
שלי על אנגולר למתקדמים.
noname
פברואר 16, 2015 at 3:54 am
next time write in english
עמית
מאי 20, 2015 at 4:38 pm
מה לגבי היבט הטסטביליות?
(בעיה אמתית שקיימת אצלי בפרויקט), צד-שרת מרנדר templates, עם i18n פנימי בצד שרת ועוד כמה תגים שרק צד-שרת יכול להבין.. איך אפשר להריץ בדיקות על directive עם templateUrl שאפילו אין לי גישה אליו בזמן הריצה של הבדיקה..?
תודה על ההתייחסות 🙂