بررسی تغییرات Angular CLI 8.1
همانطور که از اسم آن نیز مشخص است، ng-template به معنای قالب انگیولار است و هدف از آن، ارائهی قسمتی از قالب نهایی یک کامپوننت میباشد. فریم ورک Angular از دایرکتیو ng-template در پشت صحنهی دایرکتیوهای ساختاری مانند ngIf، ngFor و ngSwitch استفاده میکند. برای مثال، قسمت if، تبدیل به یک ng-template شده و else آن نیز تبدیل به یک ng-template ضمنی دیگر خواهد شد.
روش فعالسازی و نمایش قالبها
باید دقت داشت که تعریف یک ng-template سبب رندر هیچگونه خروجی در صفحه نمیشود و باید به طریقی درخواست فعالسازی و رندر آنرا ارائه داد.
<div class="lessons-list" *ngIf="lessons else loading"> ... </div> <ng-template #loading> <div>Loading...</div> </ng-template>
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کنندهی div اصلی تشکیل میشود که از دید ما پنهان است.
استفاده از ngIf برای نمایش یک قالب، یکی از روشهای کار با آنها است. روش دیگر، استفاده از ng-container است:
<ng-container *ngTemplateOutlet="loading"></ng-container>
سطوح دسترسی در قالبها
اکنون این سؤال مطرح است: «آیا یک قالب میدان دید متغیرهای خاص خودش را دارد؟ این قالب به چه متغیرهایی دسترسی دارد؟»
درون بدنه یک تگ ng-template، به همان متغیرهایی که در قالب خارجی آن قابل دسترسی هستند، دسترسی خواهیم داشت؛ برای نمونه در مثال فوق به همان متغیر lessons. به عبارتی تمام وهلههای ng-templateها، به همان متغیرهای زمینهی قالبی که درون آن جایگرفتهاند، دسترسی دارند. به علاوه هر قالب میتواند متغیرهای خاص خود را نیز تعریف کند.
در ادامه قالب یک کامپوننت را به صورت ذیل فرض کنید:
<ng-template #estimateTemplate let-lessonsCounter="estimate"> <div> Approximately {{lessonsCounter}} lessons ...</div> </ng-template> <ng-container *ngTemplateOutlet="estimateTemplate;context:ctx"> </ng-container>
export class AppComponent { totalEstimate = 10; ctx = {estimate: this.totalEstimate}; }
این متغیر lessonsCounter تنها داخل این قالب است که قابل مشاهده و دسترسی میباشد و نه خارج از آن. مقدار این متغیر نیز توسط عبارت estimate تامین میشود. این عبارت زمانیکه ng-container سبب وهله سازی estimateTemplate میشود، توسط شیء ویژهای به نام context مقدار دهی خواهد شد.
برای اینکه عبارت estimate در قالب، قابل استخراج از شیء context باشد، باین دقیقا خاصیتی به همین نام در این شیء تعریف شده باشد (و برای سایر متغیرها نیز به همین ترتیب). به همین جهت است که خاصیت عمومی ctx در کلاس AppComponent به صورت یک شیء دارای خاصیت estimate تعریف شدهاست تا بتوان نگاشتی را بین این مقدار و عبارت estimate برقرار کرد.
نکته 1: اگر در اینجا متغیری تعریف شود، اما محل تامین آن مشخص نگردد، به دنبال خاصیتی به نام implicit$ خواهد گشت. برای مثال در قالب ذیل، متغیر default تعریف شدهاست؛ اما عبارت تامین کنندهی آن مشخص نیست:
<ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container> <ng-template #templateRef let-default> <div> '{{default}}' </div> </ng-template>
export class AppComponent { exampleContext = { $implicit: 'default context property when none specified' }; }
نکته 2: نحوهی تعریف شیء context را به صورت ذیل نیز میتوان مشخص کرد:
[ngOutletContext]="exampleContext"
دسترسی به قالبها در کدهای کامپوننتها
در اینجا قالبی را مشاهده میکنید که توسط یک template reference variable به نام defaultTabButtons مشخص شدهاست:
<ng-template #defaultTabButtons> </ng-template>
export class AppComponent implements OnInit { @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>; ngOnInit() { console.log(this.defaultTabButtonsTpl); } }
یکی از کاربردهای این قابلیت، امکان تعویض پویای قالبهای یک دربرگیرندهاست:
<ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons"> </ng-container>
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی میشود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر میخواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم میتوان یک خاصیت عمومی مزین شدهی با Input از نوع TemplateRef را مشخص کرد:
@Input() headerTemplate: TemplateRef<any>;
<tab-container [headerTemplate]="defaultTabButtons"></tab-container>
ابتدا شیء user، در بالاترین سطح، دریافت شده و به صفحهای خاص از طریق ویژگیهای props ارسال میشود:
<Page user={user} />
<PageLayout user={user} />
<NavigationBar user={user} />
ایجاد شیء Context در برنامههای React
React Context، راه حلی است جهت به اشتراک گذاری دادهها، در بین انواع و اقسام کامپوننتهای یک برنامه، بدون اینکه نیازی باشد این اطلاعات را توسط props، از یک سطح، به سطحی دیگر، به صورت دستی انتقال داد. برای ایجاد یک نمونهی از آن، ابتدا پوشهی جدید src\contexts را افزوده و سپس فایل src\contexts\userContext.js را درون آن، با محتوای زیر ایجاد میکنیم:
import React from "react"; export const UserContext = React.createContext({ user: {} }); export const UserProvider = UserContext.Provider; export const UserConsumer = UserContext.Consumer;
تامین یک شیء Context در برنامه، در یک کامپوننت کلاسی و یا تابعی
تا اینجا یک شیء Context را به همراه اجزای export شدهی Provider و Consumer آن ایجاد کردیم. اکنون نوبت به پیاده سازی قسمت Provider آن است:
import "../../App.css"; import React, { Component } from "react"; import { UserProvider } from "../../contexts/userContext"; import Main from "./Main"; class App extends Component { state = { user: { name: "User 1" } }; componentDidMount() { // get user from the server or local storage and then set the currently logged in user to the this.state } render() { return ( <> <h1>App Class</h1> <UserProvider value={this.state.user}> <Main /> </UserProvider> </> ); } } export default App;
در ادامه قصد داریم اطلاعات این شیء user موجود در state را با تمام کامپوننتهایی که در درخت رندر کامپوننت جاری قرار میگیرند و با کامپوننت Main شروع میشوند، به اشتراک بگذاریم. این به اشتراک گذاری با import شیء UserProvider از ماژول contexts/userContext به نحوی که مشاهده میکنید، انجام میشود. شیء UserProvider، کار محصور سازی کامپوننت Main را انجام میدهد. سپس این Provider میتواند مقداری را توسط ویژگی value خود دریافت کند که برای مثال در اینجا شیء user است. اکنون این value تا n سطح بعدی که از کامپوننت Main مشتق میشوند نیز در دسترس خواهد بود.
یک نکته: متد React.createContext به همراه یک آرگومان defaultValue اختیاری است که در اختیار Consumerهای آن قرار داده میشود؛ اگر Provider متناظر با آن، در درخت کامپوننتهای برنامه، یافت نشود. یعنی تعریف Provider الزامی نیست. اگر نیاز است مقدار ثابتی را بین چندین کامپوننت به اشتراک بگذارید، فقط کافی است آنها را توسط React.createContext مقدار دهی اولیه کرده و ... استفاده کنید:
export const DefaultRouteContext = React.createContext({ path: '/welcome' });
خواندن شیء Context در کامپوننتی دیگر
اکنون که یک تامین کنندهی Context را ایجاد کردیم، برای خواندن اطلاعات آن در درخت کامپوننتهای محصور شدهی توسط UserProvider، میتوان به صورت زیر عمل کرد:
import React from "react"; import { UserConsumer } from "../../contexts/userContext"; export default function Main(props) { return ( <> <UserConsumer> {value => <div>User name: {value.name}.</div>} </UserConsumer> </> ); }
خروجی برنامه پس از این تغییرات به صورت زیر است:
ساده سازی دسترسی به UserConsumer توسط useContext Hook
نحوهی تعریف یک Provider و محصور سازی فرزندانی که باید از آن ارثبری کنند، در بین کامپوننتهای کلاسی و تابعی، یکی است. اما در کامپوننتهای تابعی حداقل میتوان نحوهی دسترسی به UserConsumer را به نحو زیر توسط useContext Hook ساده کرد:
import React, { useContext } from "react"; import { UserContext } from "../../contexts/userContext"; export default function Main() { const value = useContext(UserContext); return ( <> <div>User name: {value.name}.</div> </> ); }
مزیت دیگر این روش، ساده سازی کار با چندین شیء Context است. برای مثال اگر دو شیء Context را تعریف کرده باشید، خواندن دو مقدار از آنها، پیشتر چنین شکل تو در تویی را توسط دو Consumer پیدا میکرد:
function HeaderBar() { return ( <CurrentUser.Consumer> {user => <Notifications.Consumer> {notifications => <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> } } </CurrentUser.Consumer> ); }
function HeaderBar() { const user = useContext(CurrentUser); const notifications = useContext(Notifications); return ( <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> ); }
ارسال اطلاعات به کامپوننت Context Provider، از طریق کامپوننتهای فرزند
تا اینجا با استفاده از React Context، اطلاعات یک Provider را با فرزندان آن به اشتراک گذاشتیم؛ عکس این عمل نیز میسر است. برای اینکار، همانند تمام کامپوننتهای دیگری که برای ارسال اطلاعات به فراخوان خود از طریق رخدادها عمل میکنند، میتوان یک متد رویدادگردان را در کامپوننت والد، به استفاده کنندهی از Context ارسال کرد:
import "../../App.css"; import React, { Component } from "react"; import { UserProvider } from "../../contexts/userContext"; import Main from "./Main2"; class App extends Component { state = { user: { name: "User 1" } }; componentDidMount() { // get user from the server or local storage and then set the currently logged in user to the this.state } logout = () => { console.log("logout"); this.setState({ user: {} }); }; render() { const contextValue = { user: this.state.user, logoutUser: this.logout }; return ( <> <h1>App Class</h1> <UserProvider value={contextValue}> <Main /> </UserProvider> </> ); } } export default App;
اکنون تمام استفاده کنندههای از این UserProvider میتوانند با فراخوانی متد منتسب به logout، سبب پاک شدن اطلاعات کاربر موجود در state کامپوننت App، به روز رسانی state و در نتیجهی آن، رندر مجدد کامپوننت و ارائهی یک UserProvider جدید، با اطلاعاتی جدید به فرزندان آن شوند:
import React, { useContext } from "react"; import { UserContext } from "../../contexts/userContext"; export default function Main() { const { user, logoutUser } = useContext(UserContext); return ( <> <div>User name: {user.name}.</div> <button type="button" className="btn btn-primary" onClick={logoutUser}> Logout user </button> </> ); }
روش انجام اینکار بدون استفاده از useContext را نیز در ادامه مشاهده میکنید که در ابتدا نیاز به تعریف تابعی را دارد که همان خواص استخراجی را دریافت میکند. سپس باید بر اساس آنها، المانهای مدنظر نمایش نام کاربر و دکمهی خروج او را بازگشت داد:
import React from "react"; import { UserConsumer } from "../../contexts/userContext"; export default function Main(props) { return ( <> <UserConsumer> {({ user, logoutUser }) => ( <> <div>User name: {user.name}.</div> <button type="button" className="btn btn-primary" onClick={logoutUser} > Logout user </button> </> )} </UserConsumer> </> ); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-30-part-04.zip
10) Practical .NET: Powerful JavaScript With Upshot and Knockout
The Microsoft JavaScript Upshot library provides a simplified API for
retrieving data from the server and caching it at the client for reuse. Coupled
with Knockout, the two JavaScript libraries form the pillars of the Microsoft
client-side programming model.
9) On VB: Database Synchronization with the Microsoft Sync
Framework
The Microsoft Sync Framework is a highly flexible framework
for synchronizing files and data between a client and a master data store. With
great flexibility often comes complexity and confusion, however.
8) C# Corner: Performance Tips for Asynchronous Development in
C#
Visual Studio Async is a powerful development framework, but it's
important to understand how it works to avoid performance hits.
7) 2 Great JavaScript Data-Binding Libraries
JavaScript
libraries help you build powerful, data-driven HTML5 apps.
6) On VB: Entity Framework Code-First Migrations
Code First
Migrations allow for database changes to be implemented all through code.
Through the use of Package Manager Console (PMC), commands can be used to
scaffold database changes.
5) C# Corner: The New Read-Only Collections in .NET 4.5
Some
practical uses for the long-awaited interfaces, IReadOnlyList and
IReadOnlyDictionary, in .NET Framework 4.5.
4) C# Corner: Building a Windows 8 RSS Reader
Eric Vogel
walks through a soup-to-nuts demo for building a Metro-style RSS reader.
3) C# Corner: The Build Pattern in .NET
How to separate
complex object construction from its representation using the Builder design
pattern in C#.
2) Inside Visual Studio 11: A Guided Tour
Visual Studio 2012
(code-named Visual Studio 11 then) is packed with new features to help you be a
more efficient, productive developer. Here's your guided tour.
1) HTML5 for ASP.NET Developers
The technologies bundled as
HTML5 finally support what developers have been trying to get HTML to do for
decades.
نحوهی رندر لیستی از اشیاء در کامپوننتهای React
فرض کنید میخواهیم لیستی از تگها را رندر کنیم. برای این منظور ابتدا دادههای مرتبط را به خاصیت state کامپوننت، اضافه میکنیم:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] };
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا میتوان توسط متد map، هر المان آرایهی تگها را به یک المان React تبدیل و سپس رندر کرد:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] }; render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> <ul> {this.state.tags.map(tag => ( <li>{tag}</li> ))} </ul> </div> ); }
هرچند اکنون لیستی از تگها در مرورگر رندر شدهاند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شدهاست. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه میکنیم:
<li key={tag}>{tag}</li>
رندر شرطی عناصر در کامپوننتهای React
در اینجا میخواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المانها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
renderTags() { if (this.state.tags.length === 0) { return <p>There are no tags!</p>; } return ( <ul> {this.state.tags.map(tag => ( <li key={tag}>{tag}</li> ))} </ul> ); }
render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> {this.renderTags()} </div> ); }
state = { count: 0, tags: [] };
روش دوم حل این نوع مسالهها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
{this.state.tags.length === 0 && "Please create a new tag!"}
اما این روش چگونه کار میکند؟! در اینجا && را به دو مقدار مشخص اعمال کردهایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشتهای مشخص. در جاوا اسکریپت برخلاف سایر زبانهای برنامه نویسی، میتوان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشتهی خالی به false تعبیر میشود و اگر تنها دارای یک حرف باشد، true درنظر گرفته میشود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر میشوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت میدهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر میشود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت میدهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.
مدیریت رخدادها در React
همانطور که در تصویر فوق نیز مشاهده میکنید، رخدادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه مینویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نامها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخدادگردان در اینجا، با ذکر فعل handle شروع میشود:
handleIncrement() { console.log("Increment clicked!"); }
<button onClick={this.handleIncrement} className="btn btn-secondary btn-sm" > Increment </button>
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمهی Increment، یک console.log صورت میگیرد.
در ادامه میخواهیم در این رخدادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ میکنیم:
handleIncrement() { console.log("Increment clicked!", this.state.count); }
bind مجدد شیء this در رخدادگردانهای React
در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا میخواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبانهای برنامه نویسی، متفاوت رفتار میکند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی میشود، this میتواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز میگرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیشفرض ارجاعی را به شیء سراسری window مرورگر، بازگشت میدهد و اگر strict mode فعال باشد، تنها undefined را بازگشت میدهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت میکنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
constructor() { super(); console.log("constructor", this); this.handleIncrement = this.handleIncrement.bind(this); }
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمهی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهندههای مرورگر ظاهر میشود.
این یک روش است که کار میکند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمیکنند؛ بلکه آنرا به ارث میبرند. بنابراین ابتدا کدهای سازندهی فوق را حذف میکنیم (چون دیگر نیازی به آنها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function میکنیم:
handleIncrement = () => { console.log("Increment clicked!", this.state.count); }
به روز رسانی state در کامپوننتهای React
اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کردهایم، میخواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آنرا افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آنها در UI، مستقیما تغییر نمیدهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
handleIncrement = () => { this.state.count++; };
در کدهای فوق هرچند با کلیک بر روی دکمهی Increment، مقدار count افزایش یافتهاست، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمیکنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر میشود:
Line 33:5: Do not mutate state directly. Use setState() react/no-direct-mutation-state
برای رفع این مشکل باید از یکی از متدهای به ارث برده شدهی از کلاس پایهی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام میکنیم که state تغییر کردهاست (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبهی تغییرات کرده و در نتیجه قسمتهای متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی میکند.
زمانیکه از متد setState استفاده میکنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه میشوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی میکنند:
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمهی Increment کلیک کنید. اینبار ... کار میکند! چون React از تغییرات مطلع شدهاست:
وقتی state تغییر میکند، چه اتفاقاتی رخ میدهند؟
با فراخوانی متد this.setState، به React اعلام میکنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار میدهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شدهاست. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار میدهد و محاسبه میکند که در اینجا دقیقا کدام المانها نسبت به قبل تغییر کردهاند. برای نمونه در اینجا تشخیص میدهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده میشود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی میکند و نه کل DOM را (و نه اعمال مجدد کل المانهای حاصل از متد render را).
این مورد را میتوان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شمارهها را نمایش میدهد، کلیک راست کرده و گزینهی inspect را انتخاب کنید. سپس بر روی دکمهی Increment کلیک نمائید. مرورگر قسمتی را که به روز میشود، با رنگی مشخص و متمایز، به صورت لحظهای نمایش میدهد:
ارسال پارامترها به متدهای رویدادگردان
تا اینجا متد handleIncrement، بدون پارامتر تعریف شدهاست. فرض کنید در یک برنامهی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمیتوان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شدهاست.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی میکند:
doHandleIncrement = () => { this.handleIncrement({ id: 1, name: "Product 1" }); };
handleIncrement = product => { console.log(product); this.setState({ count: this.state.count + 1 }); };
هرچند این روش کار میکند، اما بیش از اندازه طولانی شدهاست. راه حل بهتر، استفاده از یک inline function است:
onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-04-part02.zip
کتاب Bing Maps V8 Succinctly
At least 80% of all information being collected by enterprises includes geolocation data. The Bing Maps V8 library is a very large collection of JavaScript code that allows web developers to place a map on a webpage, query for data, and manipulate objects on a map, creating a geo-application. In Bing Maps V8 Succinctly, James McCaffrey takes readers through utilizing this library, from creating the simplest application that uses it, to mastering more advanced functions like creating color-gradient legends and custom-styled Infobox objects.
- Getting Started
- Fundamental Techniques
- Working with Data
- Advanced Techniques
emit$:
دو کنترلر به نامهای FirstCtrl و SecondCtrl داریم. FirstCtrl به عنوان والد کنترلر Second است(در این مورد در این پست توضیح داده شده است).
پس فایل html نیز به صورت زیر خواهد بود:
<body ng-app> <div ng-controller="FirstCtrl"> <p>{{title}}</p> <div ng-controller="SecondCtrl"> <button ng-click="onUpdate()">Update First Ctrl Title</button> </div> </div> </body>
function ChildCtrl($scope){ $scope.onUpdate = function(){ this.$emit("Update_Title", "Good Bye"); }; }
function FirstCtrl($scope){ $scope.title= "Hello"; $scope.$on("Update_Title", function(event, message){ $scope.title= message; }); }
»پارامتر دوم سرویس on$ برابر با مقدار جدید ارسال شده توسط سرویس emit$ است.
»نام رویدادی که به عنوان پارامتر به on$ پاس داده میشود باید برابر با نام رویداد پاس داده شده به emit$ باشد.
»میتوان چندین پارامتر را با استفاده از emit$ ارسال کرد و در سرویس on$ با تعریف متغیر به تعداد پارامترها مقادیر آنها را دریافت نمود.
broadcast$
همان طور که مشاهده کردید SecondCtrl در محدوده FirstCtrl تعریف شده است. در نتیجه به راحتی با استفاده از سرویس emit$ توانستیم یک رویداد را منتشر نماییم. اما نکته مهم این است که اگر قصد داشته باشیم یک رویداد را از کنترلر والد (در این جا FirstCtrl است) منتشر نماییم به طوری که در کنترلرهای فرزند قابل دریافت باشد(حرکت رویداد بالا به پایین است)، باید از broadcast$ استفاده کنیم.
»broadcast$ فقط از نظر کاربرد با emit$ متفاوت است و در پیاده سازی کاملا مشابه هستند.
یک مثال:
function ParentCtrl($scope){ $scope.foo = "Hello"; $scope.$on("UPDATE_PARENT", function(event, message){ $scope.title= message; $scope.$broadcast("DO_BIDDING", { buttonTitle : "Taken over", onButtonClick : function(){ $scope.title= "HAHA this button no longer works!"; } }); }); } function ChildCtrl($scope){ $scope.buttonTitle = "Update Parent"; $scope.onButtonClick = function(){ this.$emit("UPDATE_PARENT", "Updated"); }; $scope.$on("DO_BIDDING", function(event, data){ for(var i in data){ $scope[i] = data[i]; } }); }
اگر حالت فرزند و والد بین کنترلرها نباشد چه؟
در این حالت باید rootScope$ را به کنترلر مورد نظر تزریق نمایید و سپس با استفاده از سرویس broadcast$ یا emit$ رویدادتان را منتشر کنید. مثال:
'use strict'; angular.module('myAppControllers', []) .controller('FirstCtrl', function ($rootScope) { $rootScope.$broadcast('UPDATE_ALL'); Or $rootScope.$emit('UPDATE_ALL'); });
از آن جا که حرکت بالا به پایین event bubbling بسیار هزینه برتر است نسبت به حرکت پایین به بالا در نتیجه سعی کنید تا جای ممکن از rootScope$.$broadcast استفاده نکنید. در این جا توضیح کاملی درباره دلایل عدم استفاده از rootScope$.$broadcast داده شده است.
هم چنین میتوانید یک مثال Live را نیز برای مقایسه بین emit$ و broadcast$ در این جا مشاهده کنید.
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. 35 | counters[index] = { ...counter }; // cloning an object 36 | counters[index].value++; 37 | console.log("this.state.counters", this.state.counters[index]); > 38 | this.setState({ counters }); | ^ 39 | }; 40 | 41 | render() {
انتشار VS2010
Error 1935.An error occurred during the installation of assembly 'Microsoft.VisualStudio.Debugger.Runtime,version="10.0.0.0",publicKeyToken="b03f5f7f11d50a3a",processorArchitecture="MSIL",fileVersion="10.0.30319.1",culture="neutral"'. Please refer to Help and Support for more information. HRESULT: 0x8002802F. assembly interface: , function: CreateAssemblyCache, component: {813438C3-8069-495F-9AE3-CA6499E3021B}