שליחת מידע ב-POST ב-Angular

מחבר:
בתאריך:

 

נסו בעצמכם את הקוד שנפתח במדריך

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

כדי לעבוד עם מתודות של HTTP דוגמת POST ו-GET נשתמש בשירות אנגולרי ה-HTTP service.

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

כך נראה הטופס:

מראה הטופס שמשמש לשליחה של משתנים ב-POST באמצעות Angular

וזה קוד ההטמ"ל שמייצר את הטופס:

/src/app/app.component.html
---------------------------------------

<div id="theForm">
  <h2>The form</h2>

  <p>
    <label>Model</label>
    <input type="text" #carModel>
  </p>

  <p>
    <label>Price</label>
    <input type="text" #carPrice>
 </p>

  <input type="button" value="Add" (click)="addCar(carModel, carPrice)">
</div>

המידע מוזן לשדות, ונשלח עם המתודה addCar בהקלקה.

הדרך שבה מועבר המידע שמוזן לשדות היא באמצעות local reference. לדוגמה,#caPrice על שדה המחיר מעביר את המידע על השדה כפרמטר למתודה. האיור הבא מבהיר את העניין:

הדגמה של אופן הכתיבה של local reference ב-Angular2

 

ה-service

נכתוב service משלנו שתפקידו לרכז את העברת המידע על המכוניות באמצעות AJAX.

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

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

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

אחרי שהסברנו מה זה service בוא נחזור לאפליקציה שלנו.

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

/src/app/car.service.ts
---------------------------------------

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class CarService {
  constructor(private Http){}

}
  1. חייבים לייבא את הדקורטור Injectable כדי שנוכל להשתמש ב-service האנגולרי. במקרה שלנו, ה-Http service בתוך ה- service שלנו (car.service).
  2. ה-HTTP service נוצר על ידי המתכנתים של אנגולר, והוא זה שמאפשר לנו לעבוד ב-Ajax, ובהתאם אנחנו מייבאים אותו.
  3. בקונסטרקטור של ה-CarService נקים את המשתנה http שמממש את ה-HTTP service האנגולרי, ומאפשר לנו להשתמש במתודות שלו כדי לשלוח ולקבל את הנתונים.
    * חובה להגדיר את סוג המשתנה כ-Http בשביל הזרקת התלות על ידי ה-Injectable. המשמעות של הזרקת התלות היא שעכשיו המשתנה http הוא מקרה instance של השירות האנגולרי, ולפיכך יש לנו גישה דרכו למתודות ולתכונות של השירות.
    * לכל החברה שבאים מרקע של תכנות מונחה עצמים. השירות הוא הקלאס והמשתנה שהקמנו מממש אותו. לא צריך בשביל זה new כי אנגולר מספיק חכם בשביל לעשות את הזרקת התלויות בשבילנו.
    * תמשיכו לקרוא, תריצו את הקוד, ותראו בעצמכם שזה עובד.

המתודה של ה-service האנגולרי Http שבה נשתמש לשליחת הנתונים היא post. שמקבלת שני פרמטרים: כתובת ה-url והאובייקט שצריך להעביר.

http.post("url", obj);

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

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class CarService {
   constructor(private Http){}
   
   store(car:{unique_id:string, model:string, price:number}){
        return this.http.post("//localhost/store.php", car);
   }
   

}
  1. המתודה store מצפה לקבל אובייקט ובו השדות unique_id, model ו-price. ואת האובייקט הזה היא מעבירה לכתובת ה-url ב-POST.
  2. את ה-post צריך להחזיר באמצעות return.

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

/src/app/app.module.ts
---------------------------------------

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

import { CarService } from './car.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
     HttpModule
  ],
    providers: [CarService],
    bootstrap: [AppComponent]
})
export class AppModule { }

שימו לב, גם ייבאנו את ה-service באמצעות import, וגם הוספנו אותו כפריט למערך ה-providers.

 

הקומפננטה

הקומפוננטה האנגולרית מכילה את המתודה addCar, שמקבל את המידע מטופס ההטמ"ל, ושולחת את המידע ל-service.

נתחיל מייבוא ה-CarService:

/src/app/app.component.ts
---------------------------------------

import { Component } from '@angular/core';
import { CarService } from './car.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {

  cars = [];

  constructor(private carService: CarService){}

  addCar(newModel, newPrice){ }

}

לא מספיק לייבא ה-service, צריך לאתחל אותו בקונסטרקטור, ולכן:

constructor(private carService: CarService){}

המערך cars יאחסן את המכוניות באופן מקומי, על האפליקציה האנגולרית.

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

  1. לשלוח ל-service כדי לאחסן בצד השרת
  2. להוסיף פריט למערך cars.

מכיוון שאנחנו שולחים את המידע ל-service, שהוא observable, אנחנו נרשמים אליו באמצעות subscribe. ובגלל שהמתודה שאליה נרשמנו היא מסוג http נצפה לקבל שני סוגי מידע:

  1. response
  2. error

כך נראית הקריאה למתודה store.

this.carService.store({unique_id: newId, model:newModel.value, price:newPrice.value})
  .subscribe(
    (response) => console.log(response),
    (error)    => console.log(error)
  );
  1. שימו לב! אנחנו לא סתם קוראים למתודה store מה-service. אנחנו נרשמים אליה.
  2. מעבירים כפרמטר את האובייקט שמכיל את הנתונים שנאספו מהטופס.
  3. נרשמים לתגובה באמצעות subscribe.
  4. מצפים לקבל שתי סוגי תגובות, response אם המידע עבר כמו שצריך או error במקרה של שגיאה.
  5. את התגובה נציג בקונסולה באמצעות console.log

נכתוב את המתודה addCar:

addCar(newModel, newPrice){
    let newId = this.generateId();

    this.carService.store({unique_id: newId, model:newModel.value, price:newPrice.value})
     .subscribe(
       (response) => console.log(response),
       (error)    => console.log(error)
     );

    this.cars.push({unique_id: newId, model:newModel.value, price:newPrice.value});
}

מה עושה המתודה addCar?

  1. קוראת למתודה generateId כדי לקבל מחרוזת אקראית שתזהה את המכונית בהמשך (מיד נכתוב את המתודה).
  2. נרשמת לשירות ה-store של ה-service.
  3. מוסיפה את הפריט לעותק המקומי של מערך המכוניות באמצעות push.

המתודה generateId מייצרת מחרוזת אקראית באורך 10 תווים שתשמש בתור המזהה הייחודי של כל פריט מכונית שנוסיף:

private generateId(){
    let str = "";
    let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";

    for (var i = 0; i < 10; i++){
        str += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    return str;
}

כך נראית הקומפוננטה הכוללת את הקוד שפיתחנו עד כה:

/src/app/app.component.ts
---------------------------------------

import { Component } from '@angular/core';
import { CarService } from './car.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  cars = [];

  constructor(private carService: CarService){}

  addCar(newModel, newPrice){
    let newId = this.generateId();

    this.carService.store({unique_id: newId, model:newModel.value, price:newPrice.value})
      .subscribe(
        (response) => console.log(response),
        (error)    => console.log(error)
      );

      this.cars.push({unique_id: newId, model:newModel.value, price:newPrice.value});
    }

    private generateId(){
        let str = "";
        let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";

        for (var i = 0; i < 10; i++){
            str += possible.charAt(Math.floor(Math.random() * possible.length));
        }

        return str;
    }

}

 

הקוד בצד השרת

את הקוד בצד השרת כתבתי כסקריפט -PHP שיושב על מסד נתונים mySQL. הכי פשוט שאפשר. העקרונות נכונים לכל שפה.

להלן קוד ה-mysql לטבלה cars שמשמשת לאחסון המידע באפליקציה:

CREATE TABLE IF NOT EXISTS `cars` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `unique_id` varchar(20) NOT NULL,
  `model` varchar(60) NOT NULL DEFAULT '',
  `price` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

הקובץ connect.php כולל שני חלקים:

  1. ה-headers שנדרשים כדי לקלוט את המידע שנשלח ב-POST משרת חיצוני (השרת שעליו רצה האפליקציה האנגולרית).
  2. קוד להתקשרות עם מסד הנתונים.

connect.php
----------------

<?php
/** 
 * Set the headers	
 */
header("Access-Control-Allow-Origin: //localhost:4200"); // *
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET,POST"); // HEAD,OPTIONS
header("Access-Control-Allow-Headers: Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");		
	
/** 
 * Handle the database
 */	
// db credentials
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'angular2_tutorial');

// Connect with the database.
function connect()
{
  $connect = mysqli_connect(DB_HOST ,DB_USER ,DB_PASS ,DB_NAME);

  if (mysqli_connect_errno($connect))
  {
    die("Failed to connect:" . mysqli_connect_error());
  }

  mysqli_set_charset($connect, "utf8");


  return $connect;
}

$con = connect();

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

header('Access-Control-Allow-Origin: //localhost:4200'); // *

מורה לשרת לקבל את הפניות שמקורם בכתובת //localhost:4200 שעליה רצה האפליקציה האנגולרית שלי (אני מריץ את שני השרתים זה של האפליקציה וזה של ה-PHP על מחשבי האישי).

יתר ה-headers מתירים שליחה או קבלת נתונים (GET או POST) והתקשרות עם השרת החיצוני.

הפונקציה connect יוצרת את ההתקשרות עם מסד הנתונים.

 

צד השרת כולל קובץ נוסף. הקובץ store.php מקבל, וממצה את הנתונים שנשלחים ב-post, מוודא את המידע, ומאחסן במסד הנתונים.

store.php
------------

<?php
/**
 * Store the new data
 */
require 'connect.php';

// Get the posted data.
$postdata = file_get_contents("php://input");
if(isset($postdata) && !empty($postdata))
{
  // Extract the data.
  $request = json_decode($postdata);
	
  // Validate.
  if(trim($request->unique_id) == '' || trim($request->model) == '' || (int)$request->price < 1)
  {
    return;
  }
    
  // Sanitize.
  $uid   = mysqli_real_escape_string($con, $request->unique_id);
  $model = mysqli_real_escape_string($con, $request->model);
  $price = mysqli_real_escape_string($con, $request->price);

  // Store.
  $sql = "INSERT INTO `cars`(`id`,`unique_id`,`model`,`price`) VALUES (null,'$uid', '$model','$price')";

  mysqli_query($con,$sql);
}

המידע ב-POST נקלט על ידי השורה הבאה:

$postdata = file_get_contents("php://input");

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

המידע שמתקבל הוא אובייקט ולכן המיצוי נעשה באמצעות json_decode.

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

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

למדריכים נוספים בסדרת האנגולר

הוסף תגובה חדשה

 

= 8 + 6

תמונת המגיב

איתי בתאריך: 12.11.2017

תוכל להרחיב בבקשה על השורה:
$postdata =
file_get_contents("php://input");

תמונת המגיב

יוסי בן הרוש בתאריך: 12.11.2017

זה אמצעי להעביר נתונים ב-post בפורמט של json.

תמונת המגיב

דניאל בתאריך: 16.04.2018

פה: constructor(private Http){} זה לא עבד לי עד שהצהרתי על המשתנה כ http constructor(private Http:Http){}