في الدروس السابقة قمنا بانشاء صفحة login بالتطبيق ثم قمنا بعد بالتوجه نحو جهة ال server
وأنشئنا الكونترولر UserController
الدي يقوم بمعالجة بيانات تسجيل الدخول وتوليد token وقمنا بتجربة ارسال البيانات عن طريق POSTMAN
ونجحنا بمعالجة البيانات .
الأن سنعمل قليلا على التطبيق أي بجهة الكلاينت Client
وسوف نوجه التطبيق نحو الخادم ونرسل اليه بيانات الدخول ليقوم هو بدوره بارسال ال token ونحن نحفظه بالداكرة الداخلية للتطبيق localstorage كما وضحنا بالدرس الماضي
محتوى الدرس
- انشاء دالة submitted بصفحة login.ts التي ستقوم بالتقاط البيانات المدخلة وتحويلها الى ملف الخدمة authentication.ts
- انشاء دالة login بملف الخدمة authentication.ts التي ستقوم بالاتصال بالخادم (API) وترسل له البيانات وتنتظر الرد
تفاصيل الدرس
أول شيء سنتوجه الى صفحة login.ts من أجل انشاء دالة submitted
onSubmit(dataForm: any) {
this._submitted = true; //handler مقبض يعلن عن انه تم الضغط على زر الدخول
let data:any = { // بناء كائن يمثل بيانات تسجيل الدخول ثم ارسالها الى ملف الخدمة
username: dataForm.username,
password: dataForm.password,
}
let loading = this.loadingCtrl.create({ // انشاء نافدة انتظار
content: 'Please wait...'
});
loading.present();
this.authenticationProvider.login(data)
.subscribe(
result => {
let response:any = result;
setTimeout(() => { // غلق نافدة الانتظار
loading.dismiss();
}, 500);
this.authenticationProvider.saveToken(response.access_token).then( // دالة ننشئها بملف الخدمة وضيفتها حفظ الرمز توكن
res=>{
this.navCtrl.setRoot(ProfilePage); // بعد حفظ التوكن نقوم بتوجيه المستخدم الى صفحة البروفايل
});
},
error => {
setTimeout(() => { // غلق نافدة الانتظار
loading.dismiss();
}, 500);
if(error.status==422){
this.errorMessage = 'Incorrect username or password.';
console.log(error);
}
}
);
}
قم بعمل import
الى اللوازم التي نحتاجها وهي
- صفحة profile
- ملف الخدمة المسؤول عن authentication
- مكتبة تدير نوافد الانتظار loading
import { ProfilePage } from '../profile/profile'; //+
import { AuthenticationProvider } from '../../providers/authentication/authentication';
import {LoadingController } from 'ionic-angular';
constructor(
//....
public authenticationProvider: AuthenticationProvider,
public loadingCtrl: LoadingController,
//....
) {
//....
}
ليصبح ملف login.ts كاملا بالشكل التالي .
import { Component } from '@angular/core';
import { NavController, NavParams, LoadingController } from 'ionic-angular';
import { AuthenticationProvider } from '../../providers/authentication/authentication';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ProfilePage } from '../profile/profile'; //+
import { HomePage } from '../home/home';
import { RegisterPage } from '../register/register';
/**
* Generated class for the LoginPage page.
*
* See https://ionicframework.com/docs/components/#navigation for more info on
* Ionic pages and navigation.
*/
@Component({
selector: 'page-login',
templateUrl: 'login.html',
})
export class LoginPage {
public _loginForm:FormGroup;
public _submitted:boolean = false;
public errorMessage:string=null;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public authenticationProvider: AuthenticationProvider,
public _formBuilder:FormBuilder,
public loadingCtrl: LoadingController,
) {
this.createForm(); // تمهيد حقول فورم تسجيل الدخول مباشرة بعد اقلاع الصفحة
}
ionViewDidLoad() {
console.log('ionViewDidLoad LoginPage');
}
public createForm(){
this._loginForm = this._formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.compose([Validators.required, Validators.minLength(4)])],
});
}
homePage(){ // في اعلى صفحة تسجيل الدخول ستجد زر يوجهك نحو الصفحة الرئيسية
this.navCtrl.push(HomePage);
}
registerPage(){ // الزر الدي يوجهك نحو صفحة تسجيل الدخول
this.navCtrl.push(RegisterPage);
}
onSubmit(dataForm: any) {
this._submitted = true; //handler مقبض يعلن عن انه تم الضغط على زر الدخول
let data:any = { // بناء كائن يمثل بيانات تسجيل الدخول ثم ارسالها الى ملف الخدمة
username: dataForm.username,
password: dataForm.password,
}
let loading = this.loadingCtrl.create({ // انشاء نافدة انتظار
content: 'Please wait...'
});
loading.present();
this.authenticationProvider.login(data)
.subscribe(
result => {
let response:any = result;
setTimeout(() => { // غلق نافدة الانتظار
loading.dismiss();
}, 500);
this.authenticationProvider.saveToken(response.access_token).then( // دالة ننشئها بملف الخدمة وضيفتها حفظ الرمز توكن
res=>{
this.navCtrl.push(ProfilePage); // بعد حفظ التوكن نقوم بتوجيه المستخدم الى صفحة البروفايل
});
},
error => {
setTimeout(() => { // غلق نافدة الانتظار
loading.dismiss();
}, 500);
if(error.status==422){
this.errorMessage = 'Incorrect username or password.';
console.log(error);
}
}
);
}
}
بقيت خطوة واحدة وهي انشاء دالتين login و داالة حفظ ال token بملف الخدمة authentication.ts
تقوم دالة login بالربط بين الخادم والتطبيق حيث تقوم باستقبال البيانات من مكون الصفحة و تمررها الى الخادم وتلتقط رد الخادم على طلبها ثم تحول النتائج الى مكون الصفحة الى دالة submitted
تقوم دالة saveToken بحفظ ال token بالداكرة الداخلية للتطبيق من اجل استعماله لاحقا من أجل منح التصريح authorization
للصفحات الامنة
لانشاء دالة login
و saveToken
نتوجه الى ملف الخدمة AuthenticationProvider الموجود بالمسار src/providers/authentication/authentication.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders} from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Storage } from '@ionic/storage';
@Injectable()
export class AuthenticationProvider {
public baseUrl = 'http://yii-application/api/v1';
//public baseUrl = 'http://localhost/yii-application/api/v1';
constructor(
public httpClient: HttpClient,
public storage : Storage,
) { }
// دالة الاتصال بالخادم التي تربط الخادم بالتطبيق
login(data:any){
// بيانات الهيدر يمكنك تمرير ماتشاء بالهيدر الى الخادم
const httpOptions = {
headers: new HttpHeaders({
'charset': 'UTF-8',
})
};
let url:string = this.baseUrl+'/user/login';
return this.httpClient
.post(url,data ,httpOptions) // عملية post
.pipe(
map((response) => {
return response; // تحويل النتيجة الى المكون
})
);
}
// دالة حفظ كود توكن
saveToken(token: string): Promise<any> {
return this.storage.set('token',token);
}
}
الان انتهينا وكل شيء جاهز . ولكن قد يصادفك خطأ عند التشغيل فربما لن تعمل معك النتائج فعندما تجرب الدخول بالبيانات التي استعملتها في postman
ستلاحظ وجود خطأ في ال inspector يقول
{name: "Internal Server Error", message: "An internal server error occurred.", code: 0, status: 500}
code: 0
message: "An internal server error occurred."
name: "Internal Server Error"
status: 500
من اجل استعراض كود الخطأ يجب عليك التأكد من تنشيط تعليمة debug mode ودلك عن طريق الملف api/index.php
في بداية الملف قم بتفعيل التعليمة التالية
defined('YII_DEBUG') or define('YII_DEBUG', true); // يجب تفعيل هده التعليمة حتى تتمكن من استعراض الاخطاء بالمتصفح
بعد دلك سيعرض لك سبب الخطأ .
هدا الخطأ سببه هو ان الخادم قد تلقى بيانات على شكل POST .الى هنا كل شيء جيد .ولكنه لم يتمكن من تحليلها ولم يفهمها لأن العميل أرسل البيانات من نوع JSON .لدلك لم يتمكن الخادم من معالجتها .وعندما مرر بيانات فارغة الى الموديل وقع الخطأ .
وسبب دلك هو انه يجب أن تفهم أن تطبيقات ionic تقوم بارسال البيانات الى الخادم على شكل json بشكل افتراضي . بالتالي الخادم أدرك انه يوجد استقبال لبيانات دات نمط POST ولكنها لم تأتي كمصفوفة مثلما نفعل في صفحات الويب العادية وانما استقبلها على شكل json ولكن الخادم غير معد لفهم وتحليل طلبات بها بيانات json .
لدلك نحن أمام أحد الحلين
- اما أن نغير شكل البيانات التي سيقووووم العميل بارسالها (التطبيق) الى النمط الدي يفهمه الخادم (طلب كلاسيكي مرفق بمصفوفة POST) ونتخلى عن ارسال الطلب بال json وبهدا عندما يستقبل الخادم البيانات يتمكن من تحليلها وكأنها بيانات قادمة من متصفح أو ماشابه دلك
- واما أن نعد الخادم نفسه ونخبره بأن هده البيانات التي سيرسلها العميل هي من نوع json فادا وجدتها على هدا الشكل فقم بمعالجتها. أي أننا نعطي تصريح للخادم بان يعالج البيانات على شكل json
سنتطرق الى الحلين معا ولكن الحل المفضل هو الحل الثاني بالطبع .
وقبل دلك لابد من فهم معنى بيانات الهيدر Header
ماهو الهيدر Header
هو وسيلة تخاطب مخفية تعبر عن معلومات تفيد كلاً من العميل والخادم . يمكن ان نقول انها مصفوفة تمرر من خلالها مجموعة من المعلومات البرمجية بين الخادم والعميل
تنقسم بيانات الهيدر الى مصفوفتين وهي :
Header Requests: هي المعلومات التي يتم إرسالها من العميل (التطبيق أو المتصفح ..) إلى الخادم (Server).
Header Responses : هي المعلومات التي يتم إرسالها من الخادم إلى العميل.
الحل الأول
نستطيع ان نحدد نوعية الطلب الدي نريد ارساله من العميل الى الخادم من خلال ال headers . مثلما وضحنا أن headers هي وسيلة تخاطب بين الخادم والعميل تحتوى على بيانات request/response
حيث سنرسل البيانات على شكل multipart/form-data أو application / x-www-form-urlencoded
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'multipart/form-data',
'charset': 'UTF-8',
})
};
application / x-www-form-urlencoded :هدا النوع من المحتوى ليس بالحل الجيد لإرسال كميات كبيرة من البيانات من نوع binary (بياناتbinary هي بيانات غير أبجدية non-alphanumeric) أو ارسال نص يحتوي على أحرف غير ASCII.
multipart / form-data هدا النوع من المحتوى يستخدم في ال forms التي تحتوي على ملفات أو بيانات غير ASCII وبيانات ثنائية binary.
الحل الثاني
نقوم باعداد الخادم نفسه على معالجة البيانات التي يستقبلها بال json . والطريقة بسيطة ماعلينا سوى التوجه الى ملف الاعدادات api/config/main.php
واضافة تعليمة ال request بداخل مصفوفة components
'components' => [
// ....
'request' => [
'enableCsrfValidation' => false,
'parsers' => [
'application/json' => 'yii\web\JsonParser', // To enable parsing for JSON requests
'X-Requested-With' => 'yii\httpclient\UrlEncodedParser',
'multipart/form-data' => 'yii\web\MultipartFormDataParser',
]
],
// ....
],
طبعا الحل الثاني هو الأنسب لأن الحل الأول ضعيف وقد تجد عدة مشاكل خصوصا عندما تود ارسال بيانات كبيرة أو غير أبجدية مخالفة لنمط ASCII
كما بيانات json اسرع بالقراءة والتحليل بالمقارنة مع بيانات application / x-www-form-urlencoded أيضا ال api يفضل ان يستقبل بيانات json لانه لو حدث واردت ادماج مشروعك بتطبيق اخر قد تجده لايدعم تقنية application / x-www-form-urlencoded في حين أغلب اللغات اليوم تدعم json
بعد حفظ كافة البيانات واضافة request بملف اعدادات المشروع (api/config/main.php) بالمصوفة component .
قم بتشغيل مشروع ionic عبر التعليمة ionic serve --poll=2000
قم بالتوجه نحو صفحة login
قم بفتح inspector عن طريق الزر F12 ثم توجه الى قسم network
قم بملأ الحقول username + password
أكد تسجيل الدخول وتحقق من ارجاع كود token وتحويلك الى profile كما هو مبين بالصورة
ادا صادفتك رسالة تقول أن البيانات المدخلة غير صحيحة أو وجدت خطأ غريب ما .ضع مشكلتك بخانة التعليق لمساعدتك .أيضا بامكانك التحقق من الاتي
تأكد من فتح برنامج السرفر المحلي apache
تحقق من وجود اسم المسخدم وكلمة المرور صحيحة بقاعدة البيانات. وادا لم تعرف كيف تتحقق فما عليك سوى اتباع المسار التالي وتسجيل حساب جديد
http://localhost/yii-application/frontend/web/index.php?r=site%2Fsignup
or
http://yii-application/frontend/web/index.php?r=site%2Fsignup
أو توجه نحو phpmyadmin => قاعدة البيانات المربوطة بالمشروع => نسخ هدا الـ query
INSERT INTO `user` (`id`, `username`, `auth_key`, `password_hash`, `password_reset_token`, `email`, `status`, `created_at`, `updated_at`, `last_login_ip`, `last_login_at`, `access_token_expired_at`) VALUES (NULL, 'exportdeveloper', 'HlcASovqqZV0ZAaR1WrRynVP75Y_trdm', '$2y$13$xY/lBBFAYzmY7uKbw.mCGO7zzibPuEYzSDh8cw2Q0knTNqCREeiLO', NULL, 'contact@exportdeveloper.com', '10', '1548681108', '1549202800', '127.0.0.1', '2019-02-03 15:06:40', '2019-02-04 14:06:40')
ثم تدخل البيانات التالية بصفحة login
username : exportdeveloper
password : azeazeaze
كود المشروعين ionic + api سنضعه بالدرس المقبل .