Data binding در Angular JS 2.0 (همراه با مثال)

از زمان پیدایش فریم‌ورک‌های جاوااسکریپت، اتصال داده (data-binding) نقشی همانند ملکه در شطرنج را برای ساخت فریم‌ورک‌های محبوب داشته است. اتصال داده، به ویژه اتصال دو طرفه زحمت آپدیت‌های دستی در DOM را به طور مؤثر کاهش داده است. اما اگر بگوییم انگولار 2 اتصال داده دو طرفه ندارد چطور؟

Data binding در Angular JS 2.0 (همراه با مثال)

حیرت‌انگیز است؟ اما واقعیت دارد. اگرچه می‌توانید اتصال داده دو طرفه را با استفاده از متغیرهای ورودی و خروجی انجام دهید، اما به این مفهوم نیست که هر چیز دو طرفه‌ای را اتصال دهید. اتصال داده دو طرفه شیرین است. داده‌ها همیشه در یک جهت واحد جریان دارند، که این یک امتیاز مثبت برای افزایش عملکرد است.

نحوه کار اتصال داده (data binding) در Angular 2

همه چیز در انگولار 2 یک کامپوننت است. بنابراین تمام ساختار برنامه، درختی از کامپوننت‌ها است.

همان‌طور که در تصویر می‌بینید، هر کامپوننت یک Change Detector مربوط به خود را دارد که مسیری از اتصال را در قالب خود نگه می‌دارد. {{myValue}} و "[hisValue]=”something نوعی از اتصالات هستند که به طور کلی توسط آن مدیریت می‌شوند و تغییرات بررسی شده از کامپوننت ریشه به کامپوننت برگ‌ها یا همان گره‌های لایه‌های زیرین جریان دارد.

برای ساخت جریان داده در جهت معکوس (از برگ‌ها به ریشه)، انگولار 2 دارای رویداد emitter (EventEmitter) است. هر کامپوننت می‌تواند از یک رویداد emitter استفاده کند که می‌تواند برای آگاه ساختن والد خود از تغییر ویژگی‌های آن، استفاده شود. هنگامی که والد از تغییرات مطلع شد، می‌تواند براساس آن اشیاء را مدیریت کند. این روند، شیوه معکوس کردن جریان داده دو طرفه در انگولار2 است که می‌تواند اتصال دو طرفه نامیده شود.

مفاهیمی از اشیای تغییرپذیز و تغییرناپذیر در انگولار 2 جهت کم کردن تعداد بررسی‌ها برای تغییرات وجود دارد که بعدا در مقاله جداگانه‌ای مطرح خواهیم کرد.

اتصال داده یک طرفه

import {Component, View} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
 
@Component({
	selector: 'oneway-binding-input',
 
})
@View({
	directives: [FORM_DIRECTIVES]
	template: `
	    <h2>Simple one way binding</h2>
	    <input type="text" #myInput [ngModel]="myValue" />
	    <p>Input value is : {{myValue}}</p>
	    <button (click)="changeToRandom()">Change to random</button>
	    `
})
export class OneWayDataBinding {
	myValue = 'Paul Shan';
	changeToRandom(){
		var aNum = Math.random();
		this.myValue = aNum;
	}
}

براکت‌های مربعی این کار را برای شما انجام می‌دهند. در مثال بالا هرگاه myValue تغییر کند مقدار ورودی را تغییر می‌دهد. اما اگر مقدار ورودی را تغییر دهید، تأثیری روی myValue ندارد. دلیل آن جریان داده در یک جهت واحد است. با این حال می‌توانید با استفاده از رویدادها به صورت معکوس کارها را انجام دهید.

اتصال داده دو طرفه

import {Component, View} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
@Component({
	selector: 'twoway-binding-input',
 
})
@View({
	directives: [FORM_DIRECTIVES]
	template: `
	    <h2>Simple two way binding</h2>
	    <input type="text" #myInput [(ngModel)]="myValue" />
	    <p>Input value is : {{myValue}}</p>
	    <button (click)="changeToRandom()">Change to random</button>
 
	    `
})
export class TwoWayDataBinding {
	myValue = 'Paul Shan';
	changeToRandom(){
		var aNum = Math.random();
		this.myValue = aNum;
	}
}

براکت‌های اول همراه با رویدادها استفاده می‌شوند (keyUp، change، keyDown این‌ها نمونه‌هایی از رویدادهای پیش‌فرض هستند). بنابراین شیوه انجام کارها در مثال این گونه است، براکت‌های مربعی جریان داده یک طرفه را مدیریت می‌کنند و براکت‌های اول رویداد emitted مربوط به change detector را گرفته و مقدار پاس داده‌شده را ست می‌کنند. اگر هنوز مکانیزم جریان داده برایتان جا نیفتاده است، نگران نباشید، بعد از شروع اتصال داده inter-component در پاراگراف بعدی، این مسأله برایتان روشن خواهد شد.

از آنجایی که در انگولار 2 همه چیز در رابطه با کامپوننت‌ها است، شما اغلب یا همیشه با سناریوهای مربوط به ارتباط داده inter-component مواجه خواهید شد، هم به صورت یک طرفه و هم دو طرفه. مدیریت جریان یک طرفه نسبتا ساده‌تر است زیرا داده‌ها به‌طور پیش‌فرض از ریشه به برگ‌ها جریان دارند، اما در جریان معکوس شما باید بیشتر تلاش کرده و از رویدادهای emitter استفاده کنید.

اتصال یک طرفه inter-component

کامپوننت داخلی (inner component)

import {Component, View, Input} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
 
@Component({
	selector: 'inner-component-db',
 
})
@View({
	directives: [FORM_DIRECTIVES]
	template: `
	    <h3>Thhis is nested inner</h3>
	    <input type="text" #myInput [(ngModel)]="hisValue" />
	    <p>Input value is : {{hisValue}}</p>
	    `
})
export class InnerComponentDataBinding {
	@Input() hisValue:any;
}

کامپوننت خارجی (outer component)

import {Component, View} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
import {InnerComponentDataBinding} from './inner.ts';
 
@Component({
	selector: 'outer-component-db-1',
 
})
@View({
	directives: [FORM_DIRECTIVES, InnerComponentDataBinding]
	template: `
		<h2>Nested Components one way</h2>
	    <h3>This is nested outer</h3>
	    <input type="text" #myInput [(ngModel)]="myValue" />
	    <p>Input value is : {{myValue}}</p>
	    <inner-component-db [hisValue]="myValue"></inner-component-db>
	    `
})
export class OuterComponentDataBinding1 {
	myValue = "Paul Shan";
}

متغیر ورودی به طور خودکار ویژگی‌های data-bound را هنگام تغییر آپدیت می‌کند. با استفاده از این روش ما یک کامپوننت داخلی ایجاد کردیم که hisValue یک متغیر ورودی است. کامپوننت‌های خارجی از کامپوننت داخلی استفاده می‌کنند و در حین استفاده از آن hisValue کامپوننت داخلی با ویژگی myValue خودش بایند می‌شود.

حالا می‌بینید اگر myValye کامپوننت خارجی را توسط باکس ورودی تغییر دهید، مقدار کامپوننت داخلی هم تغییر می‌کند. اما عکس این قضیه صحیح نیست. اگر آن را در باکس ورودی کامپوننت داخلی تغییر دهید، روی مقدار کامپوننت خارجی تأثیری ندارد.

در مثال اتصال دو طرفه بالا، دیدیم که اگر یک جفت براکت گرد ( یا براکت‌های اول) را بگذاریم، اتصال دو طرفه را برای ما انجام می دهد. بنابراین اگر الگویی در ویژگی‌های خارجی ایجاد کنیم، همان رویکرد در اینجا کار خواهد کرد، مانند زیر:

&lt;inner-component-db [(hisValue)]="myValue"&gt;&lt;/inner-component-db&gt;

خوب، کار نمی‌کند. در مثال اتصال داده کار می‌کرد چون انگولار 2 به طور پیش‌فرض یک رویداد emitter را در ورودی کامپوننت ست کرده بود. اما کامپوننت درونی، کامپوننتی است که توسط خودمان ایجاد شده است. بنابراین خودمان باید رویداد emitter را با متغیر خروجی ایجاد کنیم تا جریان داده به روش معکوس ایجاد شود. در پاراگراف بعدی این مسأله را توضیح می‌دهیم.

اتصال دو طرفه Inter component

کامپوننت داخلی (inner)

import {Component, View, Input, Output, EventEmitter} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
 
@Component({
	selector: 'inner-component-db',
 
})
@View({
	directives: [FORM_DIRECTIVES]
	template: `
	    <h3>Thhis is nested inner</h3>
	    <input type="text" #myInput [value]="hisValue" (keyup)="onHisValueChang(myInput)" />
	    <p>Input value is : {{hisValue}}</p>
	    `
})
export class InnerComponentDataBinding {
	@Input() hisValue:any;
	@Output() hisValueChange = new EventEmitter();
	onHisValueChang(element){
		this.hisValue = element.value;
		this.hisValueChange.next(this.hisValue)
	}
}

کامپوننت خارجی (outer)

import {Component, View} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
 
import {InnerComponentDataBinding} from './inner.ts';
 
@Component({
	selector: 'outer-component-db-2',
 
})
@View({
	directives: [FORM_DIRECTIVES, InnerComponentDataBinding]
	template: `
		<h2>Nested Components two way</h2>
	    <h3>This is nested outer</h3>
	    <input type="text" #myInput [(ngModel)]="myValue" />
	    <p>Input value is : {{myValue}}</p>
	    <inner-component-db [(hisValue)]="myValue"></inner-component-db>
	    `
})
export class OuterComponentDataBinding2 {
	myValue = "Paul Shan";
	onHisValueChange(val){
		this.myValue = val;
	}
}

حالا یک جفت براکت گرد را در کامپوننت خارجی خود قرار داده‌ایم و تغییراتی را در کامپوننت داخلی ایجاد کرده‌ایم. ما یک رویداد emitter به نام hisValueChange ساخته‌ایم، که رویدادی را درون تابع onHisValueChang، با مقدار آپدیت‌شده، می‌فرستد. و onHisValueChang رویداد keyUp باکس ورودی کامپوننت داخلی را فعال می‌کند.