أصبحت SPAs عصرية مثل نقيض إلى مواقع الويب المقدمة من جانب الخادم. هناك إيجابيات وسلبيات ، كما هو الحال دائمًا ، ولكن يمكن للمنتجع الصحي أن يخلق تجربة مستخدم سلسة يصعب تكرارها دون مثل هذا النهج. هذا جزء من مشروع تطبيقات الويب في الإقليم الذي احتلته تقليديًا من قبل تطبيقات سطح المكتب. يتم انتقاد تطبيقات الويب عمومًا لكونها بطيئة مقارنة بتطبيقات سطح المكتب ، ولكن التطورات الرئيسية في تقنيات الويب (ولا سيما NodeJs ومحرك V8 من Google ، وكذلك الأطر مثل الزاوي و ReactJs) وكذلك الوصول السائد إلى قوة الحوسبة غير المسبوقة يعني أن هذا الأمر كثيرًا أقل من مشكلة ما كانت.
تعمل SPAS عن طريق تحميل صفحة HTML واحدة على العميل ، ثم تحديث المحتويات (و DOM) ديناميكيًا - بدلاً من استرداد صفحة HTML محدثة من الخادم كلما نقر المستخدم في أي مكان.
NodeJS
و npm
باتباع الإرشادات المناسبة هنا.TypeScript
من هنا.npm install -g @angular/cli
ng new my-project
cd my-project && ng serve
localhost:4200
في متصفح الويب الخاص بك. إذا قمت بفتح my-project/src/app/app.component.ts
يجب أن يتم الترحيب بك بشيء مثل ما يلي:
/** app.component.ts */
import { Component } from '@angular/core' ; // #1
@ Component ( { // #2
selector : 'app-root' , // #3
templateUrl : './app.component.html' , // #4
styleUrls : [ './app.component.css' ] // #5
} )
export class AppComponent {
title = 'app' ; // #6
}
دعنا نقسم هذا قليلاً:
Component
من الوحدة @angular
الموجودة في مجلد node_modules
.@Component
component فئة كمكون زاوي. يتم معلمة الديكور مع كائن خيارات ، سنستخدم فقط عدد قليل من المعلمات.selector
ما هي علامات HTML لهذا المكون-على سبيل المثال ، سيتم حقن هذا المعالجة في HTML باستخدام <app-root> … </app-root>
.templateUrl
وسيطة سلسلة تشير إلى ملف HTML الذي يعمل كجزء عرض من المكون. يمكنك أيضًا استخدام المعلمة template
لكتابة HTML مباشرة ، بدلاً من الإشارة إلى ملف. لا ينصح هذا عمومًا ، إلا إذا كان جزء العرض هو ببساطة خط أو اثنين.styleUrls
قائمة بالسلاسل ، حيث تكون كل سلسلة مسارًا إلى ملف CSS.title
من app.component.html
. انتقل إلى app.component.html
. هذه هي نقطة الدخول لتطبيقنا ، وأعلى مستوى HTML يجب أن تحرير. سيتم تقديم كل شيء في هذا السياق.
سنقوم الآن بحذف محتويات هذا الملف ، واستبداله بما يلي:
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< div class =" score-card " >
< span > Team 1 </ span >
< span > 0 : 0 </ span >
< span > Team 2 </ span >
</ div >
</ div >
</ div >
ارجع إلى متصفح الويب الخاص بك ، ويجب أن ترى أن الصفحة يتم تحديثها تلقائيًا. عظيم.
من المحتمل أن تشعر بخيبة أمل بصريًا في هذه المرحلة ، لكنه عمل مستمر.
المواد Angular هي مكتبة يحتفظ بها فريق من Google من أجل توفير مكونات تصميم المواد سهلة الاستخدام للتطبيقات الزاوية. يمكن تعديل تصميم هذه المكونات على محتوى قلبك ، ولكنه يوفر أيضًا طريقة سهلة لترقية مظهر وتطبيق نموذج أولي بأقل جهد.
دعنا نثبت المكتبات المطلوبة مع npm install --save @angular/material @angular/cdk @angular/animations
. يقوم هذا بتنزيل الملفات المطلوبة في مجلد node_modules
، ويقوم بتحديث ملف packages.json
بشكل مناسب.
عليك أيضًا إخبار Angular بتحميل المكونات ذات الصلة. سيتعين عليك القيام ببعض googling حول كيفية عمل هذا (هذا هو نوع من الشريط التعليمي 101) ، ولكن عليك فقط تعديل ملف app.modules.ts
لتضمين تلك الوحدات التي تحتاجها.
أولاً ، أضف الرسوم المتحركة مثل ذلك:
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' ;
@ NgModule ( {
...
imports : [ BrowserAnimationsModule ] ,
...
} )
ثم أضف استيراد الوحدات النمطية التي نحتاجها:
import { MatButtonModule , MatCardModule } from '@angular/material' ;
@ NgModule ( {
...
imports : [ MatButtonModule , MatCardModule ] ,
...
} )
كن على علم بأنك ستحتاج إلى استيراد أي مكونات تستخدمها في تطبيقك. السبب في عدم تضمين المكتبة بأكملها هو مساعدة webpack
على أداء هز الأشجار ، والذي يستلزم بشكل أساسي ترك قطع رمز غير مستخدمة عند تجميع جميع التعليمات .js
الخاصة بنا في عدد قليل من الملفات Minified.
بمجرد الانتهاء من ذلك ، سيكون لدينا إمكانية الوصول إلى مجموعة من المكونات المحددة مسبقًا ، ويمكنك ببساطة استيرادها كما هو مطلوب. تجدر الإشارة أيضًا إلى أن السمات المسبقة سهلة الاستخدام.
في الزاوي ، المكونات وحدات. يحتوي كل مكون على ملفات (ملفات) CSS الخاصة بها ، والتي تنطبق فقط على هذا العرض المحدد. الاستثناء الوحيد هو ملف styles.css
الموجود في دليل src
الذي ينطبق عالميًا.
لا يمثل التصميم محور هذا البرنامج التعليمي ، لذلك سنحاول تجنبه قدر الإمكان للمضي قدمًا. لكي نشعر بتحسن طفيف بشأن ما نقوم به ، إليك بعض CSS البسيطة للنسخ واللصق في ملف styles.css
.
// Outer wrapper div
. container {
text-align : center;
}
// Wrapper for the score cards
. score-card-list {
display : flex;
flex-direction : column;
align-items : center;
}
// Each score card
. score-card {
width : 550 px ;
display : flex !important ;
}
// Home team score
. home {
flex : 1 ;
text-align : right;
}
// Away team score
. away {
flex : 1 ;
text-align : left;
}
// Score section of score card
. score {
width : 100 px ;
}
قم أيضًا بتحديث HTML لتضمين الفصول:
...
< mat-card class =" score-card " >
< span class =" home " > Team 1 </ span >
< span class =" score " > 0 : 0 </ span >
< span class =" away " > Team 2 </ span >
</ mat-card >
...
إذا كنت مرتبكًا بشأن flex
، فإنني أوصي بشدة بالتحقق من Flexbox Froggy.
واحدة من مزايا استخدام إطار عمل مثل Angular هو أنه يمكننا تنفيذ بعض منطق البرمجة مباشرة في HTML. في حالتنا ، ستجعل الحياة من أجل الحياة أسهل بكثير.
جرب ما يلي:
...
< mat-card class =" score-card " *ngFor =" let team of ['Arsenal', 'Liverpool', 'Tottenham']; let i=index " >
< span class =" home " > {{ team }} </ span >
< span class =" score " > 0 : 0 </ span >
< span class =" away " > Team {{ i+1 }} </ span >
</ mat-card >
...
هذا يقدم مجموعة من المفاهيم الجديدة ، ويجب أن تنتج شيئًا مثل ما يلي:
المفاهيم التي يجب ملاحظة:
{{ 'Some text = ' + a_number }}
يتم استخدام الخدمات من أجل تجريد الوصول إلى البيانات بعيدًا عن المكونات. يتيح ذلك المكونات أن تظل هزيلة وتركز على دعم العرض. يتم تبسيط اختبار الوحدة وصيانة التعليمات البرمجية من خلال الحفاظ على فصل المسؤولية.
لإنشاء خدمة باستخدام CLI ، يمكنك استخدام ng generate service football-data
(أو ng gs football-data
للأشخاص الذين كانوا مختصرين في الوقت المحدد). سيؤدي ذلك إلى إنشاء ملفين جديدين في دليل project-dir/src/app
: football-data.service.ts
و football-data.service.spec.ts
. يتبع ذلك الاتفاقية الزاوية حيث تحصل ملفات الاختبار على امتداد .spec.ts
. على الرغم من أن الاختبار مفيد ومهم ، إلا أنه يقع خارج النطاق المباشر لهذا البرنامج التعليمي حتى تتمكن من تجاهله في الوقت الحالي (آسف ، @Cornel).
أضف الأسطر التالية إلى football-data.service.ts
:
import { Injectable } from '@angular/core' ;
import { HttpHeaders } from '@angular/common/http' ;
@ Injectable ( ) // Designate this class as an injectable service
export class FootballDataService {
// Token for accessing data on football-data.org (see: https://www.football-data.org/client/register)
HEADERS = new HttpHeaders ( { 'X-Auth-Token' : 'dc25ff8a05123411sadgvde5bb16lklnmc7' } ) ;
// Convenience constant
COMPETITION_URL = 'http://api.football-data.org/v1/competitions/' ;
// ID for the Premier League
PL_ID = 445 ;
constructor ( ) { }
}
يتم شرح الخدمات باستخدام @Injectable
، والتي تخبر المترجم أنه يمكن حقن مثيلات الخدمة في مكونات. تحتاج أيضًا إلى تحديد مزود لكل خدمة. يوفر المزود بشكل فعال المفرد على المستوى المحدد.
على سبيل المثال ، يمكنك إضافة خدمتك إلى مجموعة مقدمي الخدمات في app.module.ts
، والتي ستؤدي إلى مثيل Singleton للخدمة التي يتم عرضها في جميع أنحاء التطبيق بأكمله. بخلاف ذلك ، يمكنك إضافتها إلى قائمة مقدمي الخدمات للمكون ، والتي ستؤدي إلى إتاحة مثيل Singleton لهذا المكون وأي مكونات في سياقه.
تحذير: يصبح من السهل إنشاء تبعيات دائرية بين الخدمات بمجرد أن يزداد تطبيقك. كن على دراية بهذا. إذا كان (عندما) قد حصلت على رسالة خطأ تبدو وكأنها Cannot resolve all parameters for MyDataService(?)
، فمن المحتمل أن تكون مرتبطة بقضايا التبعية الدائرية.
سنستفيد من واجهة برمجة تطبيقات رائعة متوفرة بحرية على data.org. ستحتاج إلى الحصول على مفتاح API الخاص بك ، ولكن من السهل القيام به - ما عليك سوى اتباع التعليمات الموجودة على الموقع. هناك المزيد من الوثائق المتعمقة المتاحة ، ولكن يمكن رؤية 99 ٪ من ما تحتاجه في الأمثلة الصغيرة المذكورة هنا.
في هذا المثال ، كل ما نريد فعله حقًا هو استرداد معلومات اللعبة لجميع الألعاب في الجولة الحالية (المعروف أيضًا باسم "أسبوع اللعبة" أو "يوم المباراة"). إرجاع /v1/competitions/{id}/fixtures
نقطة نهاية هذه المعلومات ، ولكن لجميع الجولات الماضية في الموسم الحالي. من أجل الحصول على المعلومات لجولة واحدة ، نحتاج إلى تعيين معلمة matchday
، على سبيل المثال /v1/competitions/{id}/fixtures?matchday=14
.
من أجل الحصول على يوم المباراة الحالي ، يمكننا طلب جدول الدوري ، لأنه يعود ليوم المباراة الحالي بشكل افتراضي.
أولاً ، نحتاج إلى ضخ خدمة HttpClient
في FootballDataService
من أجل الاستفادة من وظائف HTTP الخاصة بـ Angular:
import { HttpClient , HttpHeaders } from '@angular/common/http' ;
...
constructor ( private http : HttpClient ) { }
. . .
هام : إضافة متغير خاص إلى مُنشئ الخدمة الزاوية أو المكون ، إلى جانب إعلان نوع TypeScript المحدد ، هو معلومات كافية لصالح Angular's Black Magic للعمل. سيقوم برنامج التحويل البرمجي الآن بحقن المثيل المناسب في هذه الخدمة ، لذلك يمكنك الوصول إليها.
دعنا نضيف وظيفة لاسترداد جدول الدوري (ويوم المباراة الحالي) من الخادم:
...
getTable ( ) {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } ) ;
}
...
سوف تساعدك TypeScript في الخروج بهذا ، لكن توقيع الطريقة لطريقة http.get
من Angular تبدو كما يلي:
/**
* Construct a GET request which interprets the body as JSON and returns it.
*
* @return an `Observable` of the body as an `Object`.
*/
get ( url : string , options ?: {
headers ?: HttpHeaders | {
[ header : string ] : string | string [ ] ;
} ;
observe?: 'body' ;
params?: HttpParams | {
[ param : string ] : string | string [ ] ;
} ;
reportProgress?: boolean ;
responseType?: 'json' ;
withCredentials?: boolean ;
} ) : Observable < Object > ;
تشير علامات السؤال إلى أن headers
المعلمات ، observe
، params
، reportProgress
، و responseType
و withCredentials
كلها اختيارية كجزء من الكائن الاختياري options
. سنكون فقط قيمًا لعنوان url
options.headers
.
قد تتساءل عن وظيفة getTable()
التي أنشأناها للتو. حسنا ، فإنه يعيد تيار Observable
. Observable
بشكل أساسي ، كما قال Luuk Gruijs ، "مجموعات كسول من قيم متعددة مع مرور الوقت". الذي يبدو مجنونا ، ولكن في الواقع واضح إلى حد ما.
باختصار ، يعتبر دفق Observable
"باردًا" حتى يتم الاشتراك فيه - أي أن المتغير يتم تحميله فقط بمجرد استخدام إخراجه. بمجرد أن يكون الدفق "ساخنًا" ، سيقوم بتحديث جميع المشتركين كلما تغيرت قيمته. إنه بديل لاستخدام Promise
للتعامل مع المواقف غير المتزامنة في JavaScript.
في هذه الحالة ، لن يتم إطلاق طلب GET
إلا بعد الاشتراك في المتغير Observable
لأن واجهة REST لن تُرجع سوى قيمة واحدة لكل مكالمة.
يمكننا استخدام قوة الكتابة الثابتة في TypeScript لجعل ذلك أكثر وضوحًا:
...
import { Observable } from 'rxjs/Observable' ;
...
getTable ( ) : Observable < any > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } ) ;
}
...
في الواقع ، نظرًا لأن وثائق كرة القدم data.org تخبرنا بالضبط بما يمكن توقعه من مكالمة REST ، يمكننا أن نذهب إلى أبعد من ذلك وتصميم الكائن أيضًا ، في src/app/models/leagueTable.ts
:
import { Team } from './team' ;
export class LeagueTable {
leagueCaption : string ;
matchday : number ;
standing : Team [ ] ;
}
وفي src/app/models/team.ts
:
export class Team {
teamName : string ;
crestURI : string ;
position : number ;
points : number ;
playedGames : number ;
home : {
goals : number ;
goalsAgainst : number ;
wins : number ;
draws : number ;
losses : number ;
} ;
away : {
goals : number ;
goalsAgainst : number ;
wins : number ;
draws : number ;
losses : number ;
} ;
draws : number ;
goalDifference : number ;
goals : number ;
goalsAgainst : number ;
losses : number ;
wins : number ;
}
الذي يسمح لنا بتحديث football-data.service.ts
إلى:
import 'rxjs/add/operator/map' ;
import { LeagueTable } from './models/leagueTable' ;
...
getTable ( ) : Observable < LeagueTable > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/leagueTable' ,
{ headers : this . HEADERS } )
. map ( res => res as LeagueTable ) ;
}
...
سيساعدنا ذلك في الحفاظ على عقلنا من خلال تقليل النموذج العقلي الذي نحتاجه إلى مواكبة ذلك مع العمل مع الأشياء المعقدة ، حيث يمكن لـ IDE إرشادنا.
ملاحظة جانبية: تخبر الكلمة as
ببساطة TypeScript أن تثق بنا حول نوع الكائن ، بدلاً من محاولة اكتشافه عبر نوع من التفتيش. خطير ولكنه مفيد ، مثل الأشياء الأكثر إثارة للاهتمام.
حسنًا ، انتقل مرة أخرى إلى src/app/app.component.ts
، وأضف الأسطر التالية من أجل ضخ FootballDataService
في المكون:
import { FootballDataService } from './football-data.service' ;
...
export class AppComponent {
title = 'app' ;
constructor ( private footballData : FootballDataService ) { }
}
الآن سنضيف أيضًا طريقة ngOnInit
إلى المكون. هذا خطاف قياسي لدورة الحياة التي توفرها Angular التي تطلق النار بعد جميع خصائص المكون المرتبط بالبيانات. إنه يطلق النار بشكل أساسي على تهيئة الكائن ، ولكن بعد ذلك بقليل من طريقة constructor
التي يتم إطلاقها قبل تسجيل جميع المدخلات والمخرجات إلى المكون.
هناك قاعدة مشتركة تتمثل في وضع الكود دائمًا الذي تريد استدعاءه على التهيئة في طريقة ngOnInit
الخاصة هذه ، بدلاً من المُنشئ. أضفه إلى مكون مثل ذلك:
import { Component , OnInit } from '@angular/core' ;
...
export class AppComponent implements OnInit {
...
constructor ( private footballData : FootballDataService ) { }
ngOnInit ( ) {
// Code you want to invoke on initialisation goes here
}
...
في حالتنا ، نريد تحميل جدول الدوري ، حتى نتمكن من إضافة شيء مثل هذا:
...
ngOnInit ( ) {
// Load league table from REST service
this . footballData . getTable ( )
. subscribe (
data => console . log ( data ) ,
error => console . log ( error )
) ;
}
...
إذا قمت بفتح وحدة التحكم في متصفح الويب الخاص بك ، فيجب أن ترى شيئًا كهذا:
عظيم. لدينا الآن الكثير من البيانات الرائعة التي يمكننا القيام بها في المستقبل (ليس أقلها روابط مباشرة إلى صور Club Crest) ، ولكن في الوقت الحالي ، نحن مهتمون حقًا بـ Natwar Tathday.
دعنا الآن نضيف وظيفة أخرى إلى خدمة البيانات الخاصة بنا من أجل الحصول على المعلومات للجولة الحالية من التركيبات:
...
import { GameWeek } from './models/gameWeek' ;
...
getFixtures ( matchDay : number ) : Observable < GameWeek > {
return this . http . get ( this . COMPETITION_URL + this . PL_ID + '/fixtures?matchday=' + matchDay , { headers : this . HEADERS } )
. map ( res => res as GameWeek ) ;
}
...
مع GameWeek
و Fixture
على النحو التالي:
// src/app/models/gameWeek.ts
import { Fixture } from './fixture'
export class GameWeek {
count : number ;
fixtures : Fixture [ ] ;
}
// src/app/models/fixture.ts
export class Fixture {
awayTeamName : string ;
date : string ;
homeTeamName : string ;
matchday : number ;
result : {
goalsAwayTeam : number ;
goalsHomeTeam : number ;
halfTime : {
goalsAwayTeam : number ;
goalsHomeTeam : number ;
}
} ;
status : 'SCHEDULED' | 'TIMED' | 'POSTPONED' | 'IN_PLAY' | 'CANCELED' | 'FINISHED' ;
_links : {
awayTeam : { href : string ; } ;
competition : { href : string ; } ;
homeTeam : { href : string ; } ;
self : { href : string ; } ;
} ;
}
من خلال معرفة يوم المباراة المكتسبة حديثًا ، يمكننا أن نطلب من خادم REST الحصول على معلومات حول الجولة الحالية من المباريات. ومع ذلك ، نحتاج إلى انتظار مكالمة الراحة الأولى لإكمالها أولاً قبل القيام بالثانية. تعني بعض إعادة النية أنه يمكننا القيام بذلك في رد الاتصال بسهولة إلى حد ما:
import { Component , OnInit } from '@angular/core' ;
import { FootballDataService } from './football-data.service' ;
import { LeagueTable } from './models/leagueTable' ;
@ Component ( {
selector : 'app-root' ,
templateUrl : './app.component.html' ,
styleUrls : [ './app.component.css' ] ,
providers : [ FootballDataService ]
} )
export class AppComponent implements OnInit {
title = 'app' ;
table : LeagueTable ;
gameweek : GameWeek ;
constructor ( private footballData : FootballDataService ) { }
ngOnInit ( ) {
this . getTable ( ) ;
}
getTable ( ) {
this . footballData . getTable ( )
. subscribe (
data => {
this . table = data ; // Note that we store the data locally
this . getFixtures ( data . matchday ) ; // Call this function only after receiving data from the server
} ,
error => console . log ( error )
) ;
}
getFixtures ( matchDay : number ) {
this . footballData . getFixtures ( matchDay )
. subscribe (
data => this . gameweek = data , // Again, save locally
error => console . log ( error )
) ;
}
}
الآن نحن نصل إلى مكان ما!
نظرًا لأننا قمنا بتعيين البيانات كمتغير عضو على مكون TypeScript الخاص بنا ، يمكن الوصول إليها مباشرة بواسطة HTML المرتبطة. في الواقع ، إذا كنت تستخدم Visual Studio Code ، محرر Microsoft مفتوح المصدر ، يمكنك إضافة مكون إضافي لخدمة اللغة الزاوية للحصول على رمز JavaScript في HTML ! مدهش. ويتم الحفاظ عليها من قبل الفريق الزاوي.
دعنا نستبدل البيانات الوهمية من قبل:
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< mat-card class =" score-card " *ngFor =" let fixture of gameweek.fixtures " >
< span class =" home " > {{ fixture.homeTeamName }} </ span >
< span class =" score " > {{ fixture.result.goalsHomeTeam }} : {{ fixture.result.goalsAwayTeam }} </ span >
< span class =" away " > {{ fixture.awayTeamName }} </ span >
</ mat-card >
</ div >
</ div >
لاحظ ?
gameweek?.fixtures
يعمل الرمز كمستحضر قصير لـ if (gameweek != null) { return gameweek.fixtures } else { return null }
، وهو مفيد بشكل لا يصدق عند الوصول إلى المتغيرات التي ستحصل عليها فقط من خلال مكالمات REST غير المتزامنة.
وآخرون!
هذا الجزء التالي ليس ضروريًا تمامًا ، ولكنه يوضح طريقة زاوية مهمة للقيام بالأشياء ، وسوف يساعد بالتأكيد على الحفاظ على الكود المعياري ويمكن احتواءه إذا قررنا أن نأخذها إلى الأمام (إلقاء نظرة على NativeScript كوسيلة لإنشاء ملف تطبيق الجوال الأصلي مع الزاوي و typeScript).
سنقوم بتخليص بطاقة درجة المباراة في مكونها الخاص. ابدأ ببعض المساعدة من CLI: ng generate component score-card
(أو ng gc score-card
). سيؤدي ذلك إلى إنشاء ملفات .ts
و .html
و .css
في src/app/score-card
.
افتح score-card.component.ts
ليستقبله ديكور مألوف:
...
@ Component ( {
selector : 'app-score-card' , // Note this!
templateUrl : './score-card.component.html' ,
styleUrls : [ './score-card.component.css' ]
} )
...
لاحظ حقل selector
-يخبرنا بكيفية الوصول إلى المكون (في هذه الحالة باستخدام علامات <app-score-card></app-score-card>
).
Refactor كودنا عن طريق تحريك اللحم من app.component.html
إلى score-card.component.html
:
<!-- app.component.html -->
< div class =" container " >
< h1 > Live football scores </ h1 >
< div class =" score-card-list " >
< app-score-card *ngFor =" let fixture of gameweek?.fixtures " [fixture] =" fixture " > </ app-score-card >
</ div >
</ div >
<!-- score-card.component.html -->
< mat-card class =" score-card " >
< span class =" home " > {{ fixture.homeTeamName }} </ span >
< span class =" score " >
{{ fixture.result.goalsHomeTeam }} : {{ fixture.result.goalsAwayTeam }}
</ span >
< span class =" away " > {{ fixture.awayTeamName }} </ span >
</ mat-card >
لاحظ أن [fixture]="fixture"
داخل علامات <app-score-card>
. هذه هي الطريقة التي نمرر بها المعلومات بين المكونات.
في بناء الجملة الزاوي ، [...]
يشير إلى المدخلات ، (…)
تشير إلى المخرجات ، و [(…)]
يدل على ربط ثنائي الاتجاه. [(…)]
يسمى أيضًا "بناء جملة Banana Box" ، وسوف تواجهه في كثير من الأحيان في شكل [(ngModel)]="someVariable"
. هذا يعني وجود ربط ثنائي الاتجاه بين قيمة المتغير ، وقيمة كائن DOM. هذا جزء رئيسي من استخدام Angular.
على سبيل المثال ، يمكننا تعيين قيمة علامة input
مباشرة إلى متغير يتم عرضه على الشاشة ، وسيتم تحديث DOM تلقائيًا كلما تغيرت قيمة عنصر input
:
< p >
What is your name?
< input type =" text " [(ngModel)] =" name " />
</ p >
< p >
Your name: {{ name }}
</ p >
يمكنك الاطلاع على مثال Plunker هنا.
العودة إلى كرة القدم: من أجل تلقي قيمة الإدخال في المكون ، نحتاج أيضًا إلى تحديث score-card.component.ts
على النحو التالي:
import { Component , Input } from '@angular/core' ;
import { Fixture } from '../models/fixture' ;
@ Component ( {
selector : 'app-score-card' ,
templateUrl : './score-card.component.html' ,
styleUrls : [ './score-card.component.css' ]
} )
export class ScoreCardComponent {
@ Input ( ) fixture : Fixture ; // Note the decorator
constructor ( ) { }
}
هناك طريقتان واضحتان لتمرير البيانات بين المكونات: باستخدام @Input
/ @Output
، واستخدام الخدمات.
@Input()
الطريقة الأولى مفيدة لتمرير البيانات بين الوالدين والأطفال ، ولكن يمكن أن تكون مملة إذا كانت البيانات بحاجة إلى السفر عبر طبقات متداخلة. يمكن تمرير البيانات إلى مكون الطفل باستخدام Decorator @Input
. سيتم تمرير متغير الإدخال هذا بالرجوع إليه إذا كان كائنًا ، أو تم تمريره بالقيمة إذا كان بدائيًا.
// someComponent.ts
// ...
import { Input } from '@angular/core'
// ...
export class SomeComponent {
// @Input() variables get set after the `constructor(...)`
// method, but before `ngOnInit()` fires
@ Input ( ) aNumber : number ; // This gets set via parent HTML
// ...
}
<!-- someComponentParent.html -->
< h1 >
I am a parent where SomeComponent gets rendered
</ h1 >
<!-- Pass a value to the aNumber variable -->
< some-component [aNumber] =" 48 " > </ some-component >
@Output()
يمكن أن تنبعث البيانات أيضًا من الطفل إلى مكون الوالدين باستخدام Decorator @Output()
. هذا ليس ملزمة الاتجاه ، بل باعثار حدث يطلق النار في أوقات محددة مسبقًا. ستكون حالة الاستخدام النموذجية لإخطار الوالد عند تغيير قيمة في الطفل.
// someComponent.ts
// ...
import { Input , Output , EventEmitter } from '@angular/core'
// ...
export class SomeComponent {
@ Input ( ) aNumber : number ;
// Emits an event (of type `number`) to the parent
@ Output ( ) numberChanged : EventEmitter < number > = new EventEmitter < number > ( ) ;
// ...
// Event emitters need to be triggered manually
// Any object can be emitted
emitValueChanged ( ) : void {
this . numberChanged . emit ( this . aNumber ) ;
}
}
<!-- someComponentParent.html -->
< h1 >
I am a parent where SomeComponent gets rendered
</ h1 >
<!-- Pass a value to the aNumber variable -->
< some-component [aNumber] =" 48 " (valueChanged) =" aNumberChanged($event) " > </ some-component >
// someComponentParent.ts
export class SomeParentComponent {
// ...
aNumberChanged ( updatedNumber : number ) : void {
console . log ( `aNumber changed to ${ updatedNumber } ` ) ;
}
// ...
}
باستخدام الخدمات
هناك العديد من الطرق لاستخدام الخدمات لتمرير البيانات بين المكونات. التفاصيل تتجاوز نطاق هذا البرنامج التعليمي ، لكن تجدر الإشارة إلى وجود مثل هذه التقنيات ، لأنه من المحتمل أن يكون مطلوبًا في أي تطبيق معقد نسبيًا.
يتمثل النمط العام في تحديد دفق Observable
في الخدمة ، حيث يمكن للمكونات دفع الرسائل ، أو الاشتراك ليتم إخطارها بالرسائل الجديدة. هذا هو فعلي حافلة الأحداث.
في الحالة العامة ، يجب كتابة الرسائل من أجل أن يكون المستمعون قادرين على التمييز الذي ينطبق. كمثال تافهة إلى حد ما ، يمكن أن تنبعث الخدمة من PersonNameChangedEvent
يمكن أن يتفاعل عليه PersonComponent
في حين أن LandingPageComponent
قد يختار تجاهل هذا الحدث بالكامل.
يوفر Angular إطارًا متجانسًا وذات الرأي للغاية في بناء التطبيقات. يوفر هذا ميزة الهيكل - تساعد الطبيعة المذكورة للإطار المستخدم على تصميم تطبيقات قابلة للتطوير من البداية ، ويتم إخفاء الكثير من التعقيد عن المطور. من ناحية أخرى ، فإن استخدام Angular (و typeScript ، لهذه المسألة) يقدم الكثير من رمز Boilerplate الذي يمكن أن يبطئك إذا كنت تقوم ببناء تطبيق صغير ، لذلك يجدر التفكير في المكان الذي يسير فيه التطبيق قبل أن يتولى الزاوي.
ومع ذلك ، قطعت CLI الزاوي شوطًا طويلاً ، وبالنظر إلى مقدار الرفع الثقيل الذي يقوم به ، ربما سأستخدم Angular لكل مشروع تقريبًا في المستقبل القريب.