בניית יישום אינטרנט עם מגף קפיץ וזוויתי

1. סקירה כללית

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

במדריך זה, נשתמש באביב אתחול ליישום backend של RESTful וב- Angular ליצירת חזית מבוססת JavaScript.

2. יישום אתחול האביב

הפונקציונליות של אפליקציית ההדגמה שלנו תהיה די פשטנית. זה רק יצטמצם לאחזור ולהצגה של רשימה של ישויות JPA ממאגר H2 בזיכרון, ומתמשכות חדשות דרך טופס HTML רגיל.

2.1. התלות של Maven

להלן התלות של פרויקט Spring Boot:

 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime 

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

גרסת מסד הנתונים H2 מנוהלת גם על ידי האב האב אתחול.

2.2. מחלקת הישויות של JPA

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

משתמש בכיתה ציבורית @Entity {@Id @GeneratedValue (אסטרטגיה = GenerationType.AUTO) מזהה פרטי ארוך; פרטי סופי מחרוזת; דוא"ל מחרוזת סופי פרטי; // קונסטרוקטורים סטנדרטיים / סטרים / getters / toString} 

2.3. ה UserRepository מִמְשָׁק

מכיוון שנצטרך פונקציונליות בסיסית של CRUD ב- מִשׁתַמֵשׁ ישויות, עלינו להגדיר גם א UserRepository מִמְשָׁק:

ממשק ציבורי @ מאגר UserRepository מרחיב את CrudRepository {} 

2.4. בקר ה- REST

עכשיו, בואו נשתמש ב- API של REST. במקרה זה, זה רק בקר REST פשוט.

@RestController @CrossOrigin (origins = "// localhost: 4200") UserController בכיתה ציבורית {// קונסטרוקציות סטנדרטיות פרטי UserRepository סופי UserRepository; @GetMapping ("/ users") רשימה ציבורית getUsers () {return (List) userRepository.findAll (); } @PostMapping ("/ משתמשים") addUser ריק (משתמש משתמש @RequestBody) {userRepository.save (משתמש); }} 

אין שום דבר מורכב מטבעו בהגדרת ה- UserController מעמד.

כמובן, פרט היישום היחיד שכדאי לציין כאן הוא השימוש ב- @ CrossOrigin ביאור. כפי שהשם מרמז, ההערה מאפשרת שיתוף משאבים בין-מקור (CORS) בשרת.

שלב זה לא תמיד הכרחי. מכיוון שאנו פורסים את חזית הזווית שלנו אל // localhost: 4200 ואת backend המגף שלנו ל // localhost: 8080, אחרת הדפדפן היה דוחה בקשות מאחד לשני.

לגבי שיטות הבקר, getUser () מביא את כל מִשׁתַמֵשׁ ישויות ממאגר המידע. באופן דומה, ה הוסף משתמש() השיטה ממשיכה ישות חדשה במסד הנתונים, המועברת בגוף הבקשה.

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

2.5. אתחול היישום של אתחול האביב

לסיום, בואו ניצור מחלקה רגילה של Boot Bootstrapping באביב ונאכלס את מסד הנתונים בכמה מִשׁתַמֵשׁ ישויות:

@SpringBootApplication מחלקה ציבורית יישום {public static void main (String [] args) {SpringApplication.run (Application.class, args); } @Bean CommandLineRunner init (UserRepository userRepository) {return args -> {Stream.of ("John", "Julie", "Jennifer", "Helen", "Rachel"). ForEach (name -> {User user = new משתמש (שם, name.toLowerCase () + "@ domain.com"); userRepository.save (user);}); userRepository.findAll (). forEach (System.out :: println); }; }}

עכשיו, בואו נפעיל את היישום. כצפוי, עלינו לראות רשימה של מִשׁתַמֵשׁ ישויות שהודפסו למסוף בעת ההפעלה:

משתמש {id = 1, שם = ג'ון, [מוגן בדוא"ל]} משתמש {id = 2, שם = ג'ולי, [מוגן באמצעות דוא"ל]} משתמש {id = 3, שם = ג'ניפר, [דוא"ל מוגן]} משתמש {id = 4 , שם = הלן, [דוא"ל מוגן]} משתמש {id = 5, שם = רחל, [דוא"ל מוגן]}

3. היישום הזוויתי

עם הפעלת יישום ה- Spring Boot ההדגמה שלנו, בואו ניצור כעת יישום Angular פשוט, המסוגל לצרוך את ממשק ה- API של בקר REST.

3.1. התקנת CLI זוויתית

נשתמש ב- Angular CLI, כלי שורת פקודה רב עוצמה, כדי ליצור את היישום Angular שלנו.

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

לאחר שהתקנו את npm (Node Package Manager), נפתח מסוף פקודות ונקליד את הפקודה:

npm להתקין -g @ angular / [מוגן בדוא"ל]

זהו זה. הפקודה שלעיל תתקין את הגרסה האחרונה של Angular CLI.

3.2. פרויקט פיגומים עם CLI זוויתי

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

במקום זאת, נניח ל- CLI של Angular לעשות את העבודה הקשה עבורנו. אז בואו נפתח מסוף פקודות, ואז נווט לתיקיה בה אנו רוצים שייווצר היישום שלנו והקלד את הפקודה:

ng זוויתית חדשה

ה חָדָשׁ הפקודה תיצור את כל מבנה היישום בתוך ה- מטבע זוויתי מַדרִיך.

3.3. נקודת הכניסה של היישום הזוויתי

אם נסתכל בתוך מטבע זוויתי בתיקייה, נראה ש- Angular CLI יצר למעשה פרויקט שלם עבורנו.

קבצי היישומים של Angular משתמשים ב- TypeScript, ערכת-על מוקלדת של JavaScript שמתכנסת ל- JavaScript רגיל. עם זאת, נקודת הכניסה של כל יישום זוויתי היא ישנה רגילה index.html קוֹבֶץ.

בואו נערוך את הקובץ באופן הבא:

    מגף קפיץ - יישום זוויתי 

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

שימו לב למנהג תגים בתוך סָעִיף. ממבט ראשון, הם נראים די מוזרים, כמו אינו מרכיב HTML 5 סטנדרטי.

בואו נשמור עליהם שם, כמו הוא בורר השורשים בו Angular משתמשת לעיבוד רכיב הבסיס של היישום.

3.4. ה app.component.ts רכיב שורש

כדי להבין טוב יותר כיצד Angular קושר תבנית HTML לרכיב, בוא נלך אל src / app ספריה וערוך את app.component.ts קובץ TypeScript - רכיב הבסיס:

ייבא את {Component} מ- '@ angular / core'; @Component ({selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']}) מחלקת ייצוא AppComponent {title: string; constructor () {this.title = 'מגף קפיץ - יישום זוויתי'; }}

מסיבות ברורות, לא נצלול עמוק לתוך למידת TypeScript. למרות זאת, בואו נשים לב שהקובץ מגדיר AppComponent class, שמכריז על שדה כותרת מהסוג חוּט (מעטפת תחתונה). בהחלט, הוא מקליד JavaScript.

בנוסף, הקונסטרוקטור מאותח את השדה עם a חוּט ערך, שהוא די דומה למה שאנחנו עושים בג'אווה.

החלק הרלוונטי ביותר הוא ה @רְכִיב סמן מטא נתונים או קישוט, המגדיר שלושה אלמנטים:

  1. בוחר - בורר ה- HTML המשמש לקישור הרכיב לקובץ תבנית ה- HTML
  2. templateUrl - קובץ תבנית ה- HTML המשויך לרכיב
  3. styleUrls - קובץ CSS אחד או יותר המשויכים לרכיב

כצפוי, אנו יכולים להשתמש ב- app.component.html ו app.component.css קבצים להגדרת תבנית HTML וסגנונות CSS של רכיב הבסיס.

סוף - סוף, ה בוחר אלמנט קושר את כל הרכיב ל- בורר הכלול ב index.html קוֹבֶץ.

3.5. ה app.component.html קוֹבֶץ

מאז app.component.html קובץ מאפשר לנו הגדר את תבנית ה- HTML של רכיב הבסיס - ה AppComponent class - נשתמש בו ליצירת סרגל ניווט בסיסי עם שני כפתורים.

אם נלחץ על הכפתור הראשון, Angular יציג טבלה המכילה את רשימת מִשׁתַמֵשׁ גופים המאוחסנים במסד הנתונים. באופן דומה, אם נלחץ על השנייה, היא תציג טופס HTML, בו נוכל להשתמש להוספת ישויות חדשות למסד הנתונים:

{{ כותרת }}

  • משתמשים ברשימה
  • הוסף משתמש

חלק הארי של הקובץ הוא HTML סטנדרטי, עם כמה אזהרות שכדאי לשים לב אליהם.

הראשון הוא ה {{ כותרת }} ביטוי. הפלטה המתולתלת הכפולה {{ שם משתנה }} הוא מציין המיקום בו Angular משתמשת לביצוע אינטרפולציה משתנה.

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

הדבר השני שיש לציין הוא routerLink תְכוּנָה.

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

בכל מקרה, תבנית ה- HTML המשויכת לרכיב התואם תוצג בתוך ה- מציין מיקום.

3.6. ה מִשׁתַמֵשׁ מעמד

מאז היישום Angular שלנו יביא ויימשך מִשׁתַמֵשׁ ישויות במסד הנתונים, בואו נבצע מודל תחום פשוט עם TypeScript.

בואו נפתח קונסולת מסוף וניצור a דֶגֶם מַדרִיך:

ng ליצור משתמש בכיתה

CLI זוויתי ייצור ריק מִשׁתַמֵשׁ מעמד. בואו לאכלס אותו בכמה שדות:

מחלקת ייצוא משתמש {id: string; שם: מחרוזת; דוא"ל: מחרוזת; }

3.7. ה שירות משתמש שֵׁרוּת

עם הדומיין בצד הלקוח שלנו מִשׁתַמֵשׁ המחלקה כבר מוגדרת, בואו וניישם כעת מחלקת שירות המבצעת בקשות GET ו- POST אל // localhost: 8080 / משתמשים.

זה יאפשר לנו לצמצם את הגישה לבקר ה- REST בכיתה אחת, בה נוכל לעשות שימוש חוזר לאורך היישום כולו.

בואו נפתח מסוף קונסולה ואז ניצור a שֵׁרוּת בספרייה, ובתוך אותה ספריה, הוצא את הפקודה הבאה:

ng ליצור שירות משתמשים

עכשיו, בואו נפתח את user.service.ts קובץ ש- CLI Angular זה עתה יצר ומשנה אותו:

ייבא את {Injectable} מ- '@ angular / core'; ייבא את {HttpClient, HttpHeaders} מ- '@ angular / common / http'; ייבא את {User} מ- '../model/user'; ייבא את {Observable} מ- 'rxjs / Observable'; @Injectable () מחלקת ייצוא UserService {משתמשים פרטייםUrl: מחרוזת; קונסטרוקטור (פרטי http: HttpClient) {this.usersUrl = '// localhost: 8080 / משתמשים'; } public findAll (): נצפה {להחזיר this.http.get (this.usersUrl); } ציבורי שמור (משתמש: משתמש) {להחזיר this.http.post (this.usersUrl, משתמש); }}

איננו זקוקים לרקע מוצק על TypeScript כדי להבין כיצד שירות משתמש עבודות בכיתה. במילים פשוטות, הוא נכלא בתוך רכיב רב פעמי כל הפונקציונליות הנדרשת לצריכת ממשק ה- API של בקר REST שהטמענו בעבר באביב המגף.

ה מצא הכל() השיטה מבצעת בקשת GET HTTP אל // localhost: 8080 / נקודת קצה של משתמשים דרך Angular's HttpClient. השיטה מחזירה נצפה מופע המכיל מערך של מִשׁתַמֵשׁ חפצים.

כמו כן, ה לשמור() השיטה מבצעת בקשת POST HTTP אל // localhost: 8080 / משתמשים.

על ידי ציון הסוג מִשׁתַמֵשׁ בתוך ה HttpClientבשיטות הבקשה נוכל לצרוך תגובות אחוריות בצורה קלה ויעילה יותר.

לבסוף, בואו שימו לב לשימוש ב- הזרקת () סמן מטא נתונים. זה מסמן כי יש ליצור ולהזריק את השירות באמצעות מזרקי התלות של Angular.

3.8. ה UserListComponent רְכִיב

במקרה זה, שירות משתמש class הוא הרובד האמצעי הדק שבין שירות REST לשכבת המצגת של היישום. לכן עלינו להגדיר רכיב האחראי על הצגת רשימת מִשׁתַמֵשׁ הישויות נמשכו במסד הנתונים.

בוא נפתח קונסולת מסוף, ואז ניצור a רשימת משתמש ספרייה וליצור רכיב רשימת משתמשים:

ng ליצור רשימת משתמשים של רכיבים

CLI זוויתי ייצור מחלקה ריקה ריקה המיישמת את ה- ngOnInit מִמְשָׁק. הממשק מכריז על וו ngOnInit () שיטה, אותה אנגולר מכנה לאחר שסיימה לאתחל את המחלקה המיישמת, ולאחר שהתקשרה גם לבנאי שלה.

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

ייבא את {Component, OnInit} מ- '@ angular / core'; ייבא את {User} מ- '../model/user'; ייבא את {UserService} מ- '../service/user.service'; @Component ({selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css']}) מחלקת ייצוא UserListComponent מיישמת את OnInit {משתמשים: משתמש []; בנאי (userService פרטי: UserService) {} ngOnInit () {this.userService.findAll (). הירשם כמנוי (data => {this.users = data;}); }} 

יישום ה- UserListComponent הכיתה די מסבירה את עצמה. זה פשוט משתמש ב- FindAll של UserService () שיטה להביא את כל היישויות שנמצאו במסד הנתונים ולאחסן אותם ב- משתמשים שדה.

בנוסף, עלינו לערוך את קובץ ה- HTML של הרכיב, user-list.component.html, כדי ליצור את הטבלה המציגה את רשימת הישויות:

#שֵׁםאימייל
{{ תעודת זהות של משתמש }}{{ שם משתמש }}{{user.email}}

שימו לב לשימוש ב- * ngFor הוֹרָאָה. ההנחיה נקראת a מַהְדֵר, ואנחנו יכולים להשתמש בו לצורך איטרציה על תוכן של משתנה ועיבוד חוזר של אלמנטים HTML. במקרה זה, השתמשנו בו לעיבוד דינמי של שורות הטבלה.

בנוסף, השתמשנו באינטרפולציה משתנה להצגת ה- תְעוּדַת זֶהוּת,שֵׁם, ו אימייל של כל משתמש.

3.9. ה UserFormComponent רְכִיב

באופן דומה, עלינו ליצור רכיב המאפשר לנו להתמיד בחדש מִשׁתַמֵשׁ אובייקט במסד הנתונים.

בואו ניצור טופס משתמש ספריה והקלד את הדברים הבאים:

ng ליצור צורת משתמש רכיב 

לאחר מכן, בואו נפתח את user-form.component.ts הקובץ והוסף ל UserFormComponent בכיתה שיטה לחיסכון מִשׁתַמֵשׁ לְהִתְנַגֵד:

ייבא את {Component} מ- '@ angular / core'; ייבא את {ActivatedRoute, Router} מ- '@ angular / router'; ייבא את {UserService} מ- '../service/user.service'; ייבא את {User} מ- '../model/user'; @Component ({selector: 'app-user-form', templateUrl: './user-form.component.html', styleUrls: ['./user-form.component.css']}) מחלקת ייצוא UserFormComponent {user : משתמש; קונסטרוקטור (מסלול פרטי: ActivatedRoute, נתב פרטי: נתב, משתמש פרטי שירות: UserService) {this.user = משתמש חדש (); } onSubmit () {this.userService.save (this.user). subscribe (result => this.gotoUserList ()); } gotoUserList () {this.router.navigate (['/ משתמשים']); }}

במקרה הזה, UserFormComponent לוקח גם א שירות משתמש למשל בבנאי, ש- onSubmit () השימוש בשיטה לשמירת המסופק מִשׁתַמֵשׁ לְהִתְנַגֵד.

מכיוון שעלינו להציג מחדש את רשימת הישויות המעודכנת לאחר שהתמידנו ברשימה חדשה, אנו קוראים gotoUserList () לאחר ההכנסה, המפנה את המשתמש אל ה- / משתמשים נָתִיב.

בנוסף, עלינו לערוך את ה- user-form.component.html הקובץ וצור את טופס ה- HTML לשמירה על משתמש חדש במסד הנתונים:

 שם נדרש לשם הדוא"ל. חובה לשלוח 

במבט אחד, הטופס נראה די סטנדרטי. אבל זה מקיף הרבה פונקציונליות של Angular מאחורי הקלעים.

בואו נשים לב לשימוש ה שלח הנחיה, המכנה onSubmit () שיטה בעת הגשת הטופס.

לאחר מכן הגדרנו את משתנה לתבנית #userFormאז אנגולר מוסיף אוטומטית NgForm הוראה, המאפשרת לנו לעקוב אחר הצורה כולה.

ה NgForm ההוראה מחזיקה את הפקדים שיצרנו עבור רכיבי הטופס באמצעות ngModel הנחיה ו שֵׁם תכונה וגם עוקב אחר נכסיהם, כולל מצבם.

ה ngModel ההוראה נותנת לנו פונקציונליות מחייבת נתונים דו כיוונית בין פקדי הטופס לבין מודל התחום בצד הלקוח - ה- מִשׁתַמֵשׁ מעמד.

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

בנוסף, ngModel מאפשר לנו לעקוב אחר המצב של כל בקרת טופס ולבצע אימות בצד הלקוח, על ידי הוספה לכל שליטה על מחלקות CSS ומאפייני DOM שונים.

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

3.10. ה app-routing.module.ts קוֹבֶץ

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

זה המקום בו RouterModule נכנס לשחק. אז בואו נפתח את app-routing.module.ts קובץ, ולהגדיר את התצורה של המודול, כך שיוכל לשלוח בקשות לרכיבים התואמים:

ייבא את {NgModule} מ- '@ angular / core'; ייבא {Routes, RouterModule} מ- '@ angular / router'; ייבא את {UserListComponent} מ- ./user-list/user-list.component '; ייבא את {UserFormComponent} מ- ./user-form/user-form.component '; מסלולים const: Routes = [{path: 'users', component: UserListComponent}, {path: 'adduser', component: UserFormComponent}]; @NgModule ({ייבוא: [RouterModule.forRoot (מסלולים)], ייצוא: [RouterModule]}) מחלקת ייצוא AppRoutingModule {} 

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

מסלול מורכב משני חלקים:

  1. נתיב - א חוּט שתואם את כתובת האתר בשורת הכתובת של הדפדפן
  2. רְכִיב - הרכיב ליצור כאשר המסלול פעיל (מנוווט)

אם המשתמש לוחץ על משתמשים ברשימה כפתור, המקשר אל / משתמשים נתיב, או מזין את כתובת האתר בשורת הכתובת של הדפדפן, הנתב יעביר את ה- UserListComponent קובץ התבנית של הרכיב ב- מציין מיקום.

כמו כן, אם הם לוחצים על הוסף משתמש כפתור, זה יהפוך את UserFormComponent רְכִיב.

3.11. ה app.module.ts קוֹבֶץ

בשלב הבא עלינו לערוך את ה- app.module.ts קובץ, כך ש- Angular תוכל לייבא את כל המודולים, הרכיבים והשירותים הנדרשים.

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

ייבא את {BrowserModule} מ- '@ angular / platform-browser'; ייבא את {NgModule} מ- '@ angular / core'; ייבא את {AppRoutingModule} מ- './app-routing.module'; ייבא את {FormsModule} מ- '@ angular / forms'; ייבא את {HttpClientModule} מ- '@ angular / common / http'; ייבא את {AppComponent} מ- ./app.component '; ייבא את {UserListComponent} מ- ./user-list/user-list.component '; ייבא את {UserFormComponent} מ- ./user-form/user-form.component '; ייבא את {UserService} מ- ./service/user.service '; @NgModule ({הצהרות: [AppComponent, UserListComponent, UserFormComponent], יבוא: [BrowserModule, AppRoutingModule, HttpClientModule, FormsModule], ספקים: [UserService], bootstrap: [AppComponent]}) מחלקת ייצוא AppModule {}

4. הפעלת האפליקציה

לבסוף, אנו מוכנים להריץ את היישום שלנו.

כדי להשיג זאת, ראשית נפעיל את יישום Spring Boot, כך ששירות REST חי ומקשיב לבקשות.

לאחר הפעלת יישום Spring Boot, בואו נפתח קונסולת פקודות והקלד את הפקודה הבאה:

לשרת - לפתוח

זה יפעיל את שרת הפיתוח החי של Angular וגם יפתח את הדפדפן בכתובת // localhost: 4200.

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

באופן דומה, לחיצה על הכפתור השני תציג את טופס ה- HTML לשמירה על ישות חדשה:

5. מסקנה

במדריך זה, למדנו כיצד לבנות יישום אינטרנט בסיסי עם Spring Boot ו- Angular.

כרגיל, כל דגימות הקוד המוצגות במדריך זה זמינות ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found