بهتر است از ابزارهای مرتبطتری استفاده کنید. مانند:
KendoUI در اساس یک سری کامپوننت است که به صورت افزونههای jQuery تهیه شدهاند (که به همراه فایلهای تعاریف TypeScript هم هستند). بنابراین مطلب «روش استفادهی از jQuery در برنامههای AngularJS 2.0» در مورد آن صادق است. همچنین تیم KendoUI به صورت اختصاصی در حال تهیهی کامپوننتهای مخصوص AngularJS 2.0 است: Kendo UI for Angular 2
در مورد این کامپوننتهای ویژه در «Kendo UI R3'16 Release Webinar» یک دموی مفصل ارائه شدهاست.
در مورد این کامپوننتهای ویژه در «Kendo UI R3'16 Release Webinar» یک دموی مفصل ارائه شدهاست.
در ادامهی بحث ترکیب کامپوننتها، پس از نمایش لیستی از کامپوننتهای شمارشگر و مقدار دهی عدد آغازین آنها، به همراه مدیریت حذف هر ردیف در قسمت قبل، اکنون میخواهیم دکمهای را اضافه کنیم تا تمام شمارشگرها را به حالت اول خودشان بازگرداند. برای این منظور دکمهی Reset را به ابتدای المانهای کامپوننت Counters اضافه میکنیم:
سپس متد رویدادگردان handleReset آنرا به صورت زیر با تنظیم مقدار value هر counter به صفر و بازگشت آن و در نهایت به روز رسانی state کامپوننت با این آرایهی جدید، پیاده سازی میکنیم:
اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمهی Reset کلیک کنیم ... اتفاقی رخ نمیدهد! حتی اگر به افزونهی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننتهای شمارشگر، به درستی صورت گرفتهاست؛ اما تغییرات به DOM اصلی منعکس نشدهاند:
البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آنها، مقدار count به صورت زیر مقدار دهی شدهاست که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی میشود:
این قطعهی از کد، تنها زمانی اجرا میشود که یک وهله از کلاس کامپوننت Counter، در حال ایجاد است. به همین جهت زمانیکه صفحه برای بار اول بارگذاری میشود، مقدار آغازین count به درستی دریافت میشود. اما با کلیک بر روی دکمهی Reset، هرچند مقدار value هر شیء counter تعریف شدهی در کامپوننت والد تغییر میکند، اما local state کامپوننتهای فرزند به روز رسانی نمیشوند و مقدار جدید value را دریافت نمیکنند. برای رفع یک چنین مشکلی نیاز است یک مرجع مشخص را برای مقدار دهی stateهای کامپوننتهای فرزند ایجاد کنیم.
حذف Local state
اکنون میخواهیم در کامپوننت Counter، قسمت local state آنرا به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننتها، «Controlled component» نیز میگویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف میکنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آنها را تغییر میدهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شدهاست:
از این جهت که دیگر دارای local state نیستیم، داشتن متد this.setState در اینجا بیمفهوم است. در یک کامپوننت کنترل شده، هر زمانیکه قرار است دادهای ویرایش شود، این کامپوننت باید رخدادی را صادر کرده و از والد خود درخواست تغییر اطلاعات را ارائه دهد؛ شبیه به this.props.onDelete ای که در قسمت قبل کامل کردیم. بنابراین کل متد handleIncrement را نیز حذف میکنیم. اینبار رخداد onClick، سبب بروز رخداد onIncrement در والد خود خواهد شد:
همچنین دو متد دیگری که ارجاعی را به this.state داشتند، به صورت زیر جهت استفادهی از this.props.counter.value، به روز رسانی میشوند:
تا اینجا به صورت کامل local state این کامپوننت حذف و با this.props جایگزین شده و در نتیجه تحت کنترل کامپوننت والد آن قرار میگیرد.
در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخداد onIncrement رسیدهی از کامپوننتهای فرزند، تعریف میکنیم:
سپس ارجاعی از این متد را به ویژگی onIncrement تعریف شدهی در المان Counter، متصل میکنیم:
اکنون هر زمانیکه بر روی دکمهی Increment کلیک شود، this.props.onIncrement آن، سبب فراخوانی متد handleIncrement والد خود خواهد شد.
پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
همانطور که در قسمتهای قبل نیز عنوان شد، در React نباید مقدار state را به صورت مستقیم ویرایش کرد؛ مانند مراجعهی مستقیم به this.state.counters[index] و سپس تغییر خاصیت value آن. بنابراین باید یک clone از آرایهی counters و سپس یک clone از شیء counter رسیدهی از کامپوننت فرزند را ایجاد کنیم تا این cloneها دیگر ارجاعی را به اشیاء اصلی ساخته شدهی از روی آنها نداشته باشند (مهمترین خاصیت یک clone) تا اگر خاصیت و مقداری را در آنها تغییر دادیم، دیگر به شیء اصلی که از روی آنها clone شدهاند، منعکس نشوند. در اینجا از spread operator برای ایجاد این cloneها استفاده شدهاست. اکنون مقادیر خواص این cloneها را تغییر میدهیم و درنهایت این counters جدید را که خودش نیز یک clone است، به متد this.setState جهت به روز رسانی UI و همچنین state کامپوننت، ارسال میکنیم.
تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننتها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز میگردند:
همگام سازی چندین کامپوننت با هم زمانیکه رابطهی والد و فرزندی بین آنها وجود ندارد
در ادامه میخواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتمهای انتخاب شدهی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننتهای React را مطابق شکل فوق تغییر میدهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشهای انتخاب کرده که سایر کامپوننتها از آن مشتق میشوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش میدادیم، اضافه میکنیم:
سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه میکنیم تا منوی راهبری سایت را نمایش دهد:
اکنون به App.js مراجعه کرده و متد render آنرا جهت نمایش درخت کامپوننتهایی که مشاهده کردید، تکمیل میکنیم:
ابتدا کامپوننت NavBar در بالای صفحه رندر میشود و سپس کامپوننت Counters در میانهی صفحه. چون در اینجا چندین المان قرار است رندر شوند، از React.Fragment برای محصور کردن آنها استفاده کردهایم.
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شدهاست:
اکنون میخواهیم تعداد کامپوننتهای شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننتهای Counter رندر شدهی توسط آن انتقال دادیم. استفادهی از این ویژگی به دلیل وجود رابطهی والد و فرزندی بین این کامپوننتها میسر شد. اما همانطور که در تصویر درخت کامپوننتهای جدید تشکیل شده مشاهده میکنید، رابطهی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح میشود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آنها انتقال داد؟ در یک چنین حالتهایی که رابطهی والد و فرزندی بین کامپوننتها وجود ندارد و میخواهیم آنها را همگام سازی کنیم و دادههایی را بین آنها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.
انتقال state به یک سطح بالاتر
برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آنرا به همراه تمامی متدهایی که آنرا تغییر میدهند و از آن استفاده میکنند، انتخاب و cut میکنیم. سپس به کامپوننت App مراجعه کرده و آنها را در اینجا paste میکنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل میکنیم. این مرحلهی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح میکنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آنها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگیهایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آنها دسترسی داشته باشد:
پس از این تعاریف میتوانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
در اینجا سه رویدادگردان و یک خاصیت counters، از طریق خاصیت props والد کامپوننت Counter که اکنون کامپوننت App است، خوانده میشوند.
پس از این نقل و انتقالات، اکنون میتوانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز میتوانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی میتوان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننتهایی که مقدار value آنها بیشتر از صفر است، اشاره میکند، از طریق خاصیت props خوانده و نمایش میدهیم:
که با ذخیره کردن این فایل و بارگذاری مجدد برنامه در مرورگر، به خروجی زیر خواهیم رسید:
کامپوننتهای بدون حالت تابعی
اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شدهاست و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین میشود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را میتوان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننتهای بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، میتوان از یک function استفاده کرد (به همین جهت به آن functional میگویند). احتمالا نمونهی آنرا با کامپوننت App پیشفرض قالب create-react-app نیز مشاهده کردهاید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آنرا از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، میتوان به صورت زیر عمل کرد:
برای اینکار قسمت return متد render کامپوننت را cut کرده و به داخل تابع NavBar منتقل میکنیم. بدنهی این تابع را هم میتوان توسط میانبر sfc که مخفف Stateless Functional Component است، در VSCode تولید کرد.
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاسهای استاندارد کامپوننت کار میکند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.
البته لازم به ذکر است که انتخاب بین «کامپوننتهای بدون حالت تابعی» و یک کامپوننت معمولی تعریف شدهی توسط کلاسها، صرفا یک انتخاب شخصی است.
یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، میتوان نوشت:
در این حالت شیء props دریافت شده توسط ویژگی Objects Destructuring، به totalCounters تجزیه میشود و سپس میتوان تنها از همین متغیر دریافتی، به صورت {totalCounters} در کدها استفاده کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-08.zip
<button onClick={this.handleReset} className="btn btn-primary btn-sm m-2" > Reset </button>
handleReset = () => { const counters = this.state.counters.map(counter => { counter.value = 0; return counter; }); this.setState({ counters }); // = this.setState({ counters: counters }); };
اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمهی Reset کلیک کنیم ... اتفاقی رخ نمیدهد! حتی اگر به افزونهی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننتهای شمارشگر، به درستی صورت گرفتهاست؛ اما تغییرات به DOM اصلی منعکس نشدهاند:
البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آنها، مقدار count به صورت زیر مقدار دهی شدهاست که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی میشود:
class Counter extends Component { state = { count: this.props.counter.value };
حذف Local state
اکنون میخواهیم در کامپوننت Counter، قسمت local state آنرا به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننتها، «Controlled component» نیز میگویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف میکنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آنها را تغییر میدهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شدهاست:
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
<button onClick={() => this.props.onIncrement(this.props.counter)} className="btn btn-secondary btn-sm" > Increment </button>
getBadgeClasses() { let classes = "badge m-2 badge-"; classes += this.props.counter.value === 0 ? "warning" : "primary"; return classes; } formatCount() { const { value } = this.props.counter; // Object Destructuring return value === 0 ? "Zero" : value; }
در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخداد onIncrement رسیدهی از کامپوننتهای فرزند، تعریف میکنیم:
handleIncrement = counter => { console.log("handleIncrement", counter); };
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} onIncrement={this.handleIncrement} />
پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
handleIncrement = counter => { console.log("handleIncrement", counter); const counters = [...this.state.counters]; // cloning an array const index = counters.indexOf(counter); counters[index] = { ...counter }; // cloning an object counters[index].value++; console.log("this.state.counters", this.state.counters[index]); this.setState({ counters }); };
تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننتها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز میگردند:
همگام سازی چندین کامپوننت با هم زمانیکه رابطهی والد و فرزندی بین آنها وجود ندارد
در ادامه میخواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتمهای انتخاب شدهی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننتهای React را مطابق شکل فوق تغییر میدهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشهای انتخاب کرده که سایر کامپوننتها از آن مشتق میشوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش میدادیم، اضافه میکنیم:
import App from "./App"; ReactDOM.render(<App />, document.getElementById("root"));
سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه میکنیم تا منوی راهبری سایت را نمایش دهد:
import React, { Component } from "react"; class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar </a> </nav> ); } } export default NavBar;
اکنون به App.js مراجعه کرده و متد render آنرا جهت نمایش درخت کامپوننتهایی که مشاهده کردید، تکمیل میکنیم:
import "./App.css"; import React from "react"; import Counters from "./components/counters"; import NavBar from "./components/navbar"; function App() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters /> </main> </React.Fragment> ); } export default App;
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شدهاست:
اکنون میخواهیم تعداد کامپوننتهای شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننتهای Counter رندر شدهی توسط آن انتقال دادیم. استفادهی از این ویژگی به دلیل وجود رابطهی والد و فرزندی بین این کامپوننتها میسر شد. اما همانطور که در تصویر درخت کامپوننتهای جدید تشکیل شده مشاهده میکنید، رابطهی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح میشود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آنها انتقال داد؟ در یک چنین حالتهایی که رابطهی والد و فرزندی بین کامپوننتها وجود ندارد و میخواهیم آنها را همگام سازی کنیم و دادههایی را بین آنها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.
انتقال state به یک سطح بالاتر
برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آنرا به همراه تمامی متدهایی که آنرا تغییر میدهند و از آن استفاده میکنند، انتخاب و cut میکنیم. سپس به کامپوننت App مراجعه کرده و آنها را در اینجا paste میکنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل میکنیم. این مرحلهی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح میکنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آنها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگیهایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آنها دسترسی داشته باشد:
render() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters counters={this.state.counters} onReset={this.handleReset} onIncrement={this.handleIncrement} onDelete={this.handleDelete} /> </main> </React.Fragment> ); }
پس از این تعاریف میتوانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
render() { return ( <div> <button onClick={this.props.onReset} className="btn btn-primary btn-sm m-2" > Reset </button> {this.props.counters.map(counter => ( <Counter key={counter.id} counter={counter} onDelete={this.props.onDelete} onIncrement={this.props.onIncrement} /> ))} </div> ); }
پس از این نقل و انتقالات، اکنون میتوانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز میتوانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی میتوان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
<NavBar totalCounters={this.state.counters.filter(c => c.value > 0).length} />
سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننتهایی که مقدار value آنها بیشتر از صفر است، اشاره میکند، از طریق خاصیت props خوانده و نمایش میدهیم:
class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {this.props.totalCounters} </span> </a> </nav> ); } }
کامپوننتهای بدون حالت تابعی
اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شدهاست و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین میشود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را میتوان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننتهای بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، میتوان از یک function استفاده کرد (به همین جهت به آن functional میگویند). احتمالا نمونهی آنرا با کامپوننت App پیشفرض قالب create-react-app نیز مشاهده کردهاید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آنرا از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، میتوان به صورت زیر عمل کرد:
import React from "react"; // Stateless Functional Component const NavBar = props => { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {props.totalCounters} </span> </a> </nav> ); }; export default NavBar;
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاسهای استاندارد کامپوننت کار میکند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.
البته لازم به ذکر است که انتخاب بین «کامپوننتهای بدون حالت تابعی» و یک کامپوننت معمولی تعریف شدهی توسط کلاسها، صرفا یک انتخاب شخصی است.
یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، میتوان نوشت:
const NavBar = ({ totalCounters }) => {
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-08.zip
استفادهی گستردهی از jQuery به همراه برنامههای AngularJS 2.0 ایدهی خوبی نیست؛ زیرا jQuery و کدهای آن، ارتباط تنگاتنگی را بین DOM و JavaScript برقرار میکنند که برخلاف رویهی فریمورکهای MVC است. اما با این حال استفادهی از افزونههای بیشمار jQuery در برنامههای AngularJS 2.0، جهت غنا بخشیدن به ظاهر و همچنین کاربردپذیری برنامه، یکی از استفادههای پذیرفته شدهی آن است.
روش استفادهی از jQuery نیز در حالت کلی همانند مطلب «استفاده از کتابخانههای ثالث جاوا اسکریپتی در برنامههای AngularJS 2.0» است؛ اما به همراه چند نکتهی اضافهتر مانند محل فراخوانی و دسترسی به DOM، در کدهای یک کامپوننت.
هدف: استفاده از کتابخانهی Chosen
میخواهیم جهت غنیتر کردن ظاهر یک دراپ داون در برنامههای AngularJS 2.0، از یک افزونهی بسیار معروف jQuery به نام Chosen استفاده کنیم.
تامین پیشنیازهای اولیه
میتوان فایلهای این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است اینکار را توسط bower مدیریت کنیم. این کتابخانه هنوز دارای بستهی رسمی npm نیست (و بستهی chosen-npm که در مخزن npm وجود دارد، توسط این تیم ایجاد نشدهاست). اما همانطور که در مستندات آن نیز آمدهاست، توسط دستور ذیل نصب میشود:
برای مدیریت این مساله در ویژوال استودیو، به منوی project و گزینهی add new item مراجعه کرده و bower را جستجو کنید:
در اینجا نام پیش فرض bower.json را پذیرفته و سپس محتوای فایل ایجاد شده را به نحو ذیل تغییر دهید:
ساختار این فایل هم بسیار شبیهاست به ساختار package.json مربوط به npm. در اینجا کتابخانهی chosen در قسمت لیست وابستگیها اضافه شدهاست. با ذخیره کردن این فایل، کار دریافت خودکار بستههای تعریف شده، آغاز میشود و یا کلیک راست بر روی فایل bower.json دارای گزینهی restore packages نیز هست.
پس از دریافت خودکار chosen، بستهی آنرا در مسیر bower_components\chosen واقع در ریشهی پروژه میتوانید مشاهده کنید.
استفاده از jQuery و chosen به صورت untyped
سادهترین و متداولترین روش استفاده از jQuery و افزونههای آن شامل موارد زیر هستند:
الف) تعریف مداخل مرتبط با آنها در فایل index.html
که در اینجا jQuery و بوت استرپ و chosen به همراه فایل css آن اضافه شدهاند.
ب) تعریف jQuery به صورت untyped
همینقدر که این یک سطر را به ابتدای ماژول خود اضافه کنید، امکان دسترسی به تمام امکانات جیکوئری را خواهید یافت. البته بدون intellisense و بررسی نوعهای پارامترها؛ چون نوع آن، any یا همان حالت پیش فرض جاوا اسکریپت تعریف شدهاست.
محل صحیح فراخوانی متدهای مرتبط با jQuery
در تصویر ذیل، چرخهی حیات یک کامپوننت را مشاهده میکنید که با تعدادی از آنها پیشتر آشنا شدهایم:
همچنین جیکوئری نیاز دارد تا بداند هم اکنون قرار است با چه المانی کار کنیم و کامپوننت بارگذاری شده کدام است. برای این منظور، یکی از سرویسهای توکار AngularJS 2.0 را به نام ElementRef، به سازندهی کلاس تزریق میکنیم. توسط خاصیت this._el.nativeElement آن میتوان به المان ریشهی کامپوننت جاری دسترسی یافت.
تهیهی کامپوننت نمایش یک دراپ داون مزین شده با chosen
در ادامه قصد داریم نکاتی را که تاکنون مرور کردیم، به صورت یک مثال پیاده سازی کنیم. به همین جهت فایل جدید using-jquery-addons.component.ts را به پروژه اضافه کنید به همراه فایل قالب آن به نام using-jquery-addons.component.html؛ با این محتوا:
الف) کامپوننت using-jquery-addons.component.ts
ب) قالب using-jquery-addons.component.html
با این خروجی
توضیحات
کلاس UsingJQueryAddonsComponent، اینترفیس AfterViewInit را پیاده سازی کردهاست؛ تا توسط متد ngAfterViewInit بتوانیم با عناصر DOM کار کنیم. هرچند در کل اینکار باید صرفا محدود شود به مواردی مانند مثال جاری و در حد آغاز یک افزونهی jQuery و اگر قرار است تغییراتی دیگری صورت گیرند بهتر است از همان روش binding توکار AngularJS 2.0 استفاده کرد.
در سازندهی کلاس، سرویس ElementRef تزریق شدهاست تا توسط خاصیت this._el.nativeElement آن بتوان به المان ریشهی کامپوننت جاری دسترسی یافت. به همین جهت است که پس از آن از متد find، برای یافتن دراپ داون استفاده شدهاست و سپس chosen به نحو متداولی به آن اعمال گردیدهاست.
در اینجا هر زمانیکه یکی از آیتمهای دراپ داون انتخاب شوند، مقدار آن به خاصیت selectedValue انتساب داده شده و این انتساب سبب فعال سازی binding و نمایش مقدار آن در ذیل دراپ داون میگردد.
در قالب این کامپوننت هم با استفاده از ngFor، عناصر دراپ داون از آرایهی dropDownItems تعریف شده در کلاس کامپوننت، تامین میشوند. متغیر محلی item تعریف شدهی در اینجا، در محدودی همین المان قابل دسترسی است. برای مثال از آن جهت تنظیم دومین آیتم لیست به صورت انتخاب شده، در حین اولین بار نمایش view استفاده شدهاست.
استفاده از jQuery و chosen به صورت typed
کتابخانهی jQuery در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jquery دارای فایل d.ts. خاص خود است. برای نصب آن میتوان از روش ذیل استفاده کرد:
پس از افزودن فایل typings این کتابخانه، در ابتدای فایل main.ts یک سطر ذیل را اضافه کنید:
اینکار سبب خواهد شد تا intellisense درون ویژوال استودیو بتواند مداخل متناظر را یافته و راهنمای مناسبی را ارائه دهد.
در ادامه به فایل using-jquery-addons.component.ts مراجعه کرده و تغییر ذیل را اعمال کنید:
فایل d.ts. اضافه شده، امکان استفادهی از نوع جدید JQueryStatic را مهیا میکند. پس از آن در کدهای تعریف شده، chosen به رنگ قرمز در میآید، چون در تعاریف نوع دار jQuery، این افزونه وجود خارجی ندارد. برای رفع این مشکل، فایل typings/browser/ambient/jquery/index.d.ts را گشوده و سپس به انتهای آن، تعریف chosen را به صورت دستی اضافه کنید:
به این ترتیب این متد به عنوان جزئی از متدهای زنجیروار jQuery، شناسایی شده و قابل استفاده خواهد شد.
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید.
روش استفادهی از jQuery نیز در حالت کلی همانند مطلب «استفاده از کتابخانههای ثالث جاوا اسکریپتی در برنامههای AngularJS 2.0» است؛ اما به همراه چند نکتهی اضافهتر مانند محل فراخوانی و دسترسی به DOM، در کدهای یک کامپوننت.
هدف: استفاده از کتابخانهی Chosen
میخواهیم جهت غنیتر کردن ظاهر یک دراپ داون در برنامههای AngularJS 2.0، از یک افزونهی بسیار معروف jQuery به نام Chosen استفاده کنیم.
تامین پیشنیازهای اولیه
میتوان فایلهای این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است اینکار را توسط bower مدیریت کنیم. این کتابخانه هنوز دارای بستهی رسمی npm نیست (و بستهی chosen-npm که در مخزن npm وجود دارد، توسط این تیم ایجاد نشدهاست). اما همانطور که در مستندات آن نیز آمدهاست، توسط دستور ذیل نصب میشود:
bower install chosen
در اینجا نام پیش فرض bower.json را پذیرفته و سپس محتوای فایل ایجاد شده را به نحو ذیل تغییر دهید:
{ "name": "asp-net-mvc5x-angular2x", "version": "1.0.0", "authors": [ "DNT" ], "license": "MIT", "ignore": [ "node_modules", "bower_components" ], "dependencies": { "chosen": "1.4.2" }, "devDependencies": { } }
پس از دریافت خودکار chosen، بستهی آنرا در مسیر bower_components\chosen واقع در ریشهی پروژه میتوانید مشاهده کنید.
استفاده از jQuery و chosen به صورت untyped
سادهترین و متداولترین روش استفاده از jQuery و افزونههای آن شامل موارد زیر هستند:
الف) تعریف مداخل مرتبط با آنها در فایل index.html
<script src="~/node_modules/jquery/dist/jquery.min.js"></script> <script src="~/node_modules/bootstrap/dist/js/bootstrap.min.js"></script> <script src="~/bower_components/chosen/chosen.jquery.min.js"></script> <link href="~/bower_components/chosen/chosen.min.css" rel="stylesheet" type="text/css" />
ب) تعریف jQuery به صورت untyped
declare var jQuery: any;
محل صحیح فراخوانی متدهای مرتبط با jQuery
در تصویر ذیل، چرخهی حیات یک کامپوننت را مشاهده میکنید که با تعدادی از آنها پیشتر آشنا شدهایم:
روش متداول استفاده از jQuery، فراخوانی آن پس از رخداد document ready است. در اینجا معادل این رخداد، hook ویژهای به نام ngAfterViewInit است. بنابراین تمام فراخوانیهای jQuery را باید در این متد انجام داد.
همچنین جیکوئری نیاز دارد تا بداند هم اکنون قرار است با چه المانی کار کنیم و کامپوننت بارگذاری شده کدام است. برای این منظور، یکی از سرویسهای توکار AngularJS 2.0 را به نام ElementRef، به سازندهی کلاس تزریق میکنیم. توسط خاصیت this._el.nativeElement آن میتوان به المان ریشهی کامپوننت جاری دسترسی یافت.
constructor(private _el: ElementRef) { }
تهیهی کامپوننت نمایش یک دراپ داون مزین شده با chosen
در ادامه قصد داریم نکاتی را که تاکنون مرور کردیم، به صورت یک مثال پیاده سازی کنیم. به همین جهت فایل جدید using-jquery-addons.component.ts را به پروژه اضافه کنید به همراه فایل قالب آن به نام using-jquery-addons.component.html؛ با این محتوا:
الف) کامپوننت using-jquery-addons.component.ts
import { Component, ElementRef, AfterViewInit } from "@angular/core"; declare var jQuery: any; // untyped @Component({ templateUrl: "app/using-jquery-addons/using-jquery-addons.component.html" }) export class UsingJQueryAddonsComponent implements AfterViewInit { dropDownItems = ["First", "Second", "Third"]; selectedValue = "Second"; constructor(private _el: ElementRef) { } ngAfterViewInit() { jQuery(this._el.nativeElement) .find("select") .chosen() .on("change", (e, args) => { this.selectedValue = args.selected; }); } }
ب) قالب using-jquery-addons.component.html
<select> <option *ngFor="let item of dropDownItems" [value]="item" [selected]="item == selectedValue"> {{item}} option </option> </select> <h4> {{selectedValue}}</h4>
توضیحات
کلاس UsingJQueryAddonsComponent، اینترفیس AfterViewInit را پیاده سازی کردهاست؛ تا توسط متد ngAfterViewInit بتوانیم با عناصر DOM کار کنیم. هرچند در کل اینکار باید صرفا محدود شود به مواردی مانند مثال جاری و در حد آغاز یک افزونهی jQuery و اگر قرار است تغییراتی دیگری صورت گیرند بهتر است از همان روش binding توکار AngularJS 2.0 استفاده کرد.
در سازندهی کلاس، سرویس ElementRef تزریق شدهاست تا توسط خاصیت this._el.nativeElement آن بتوان به المان ریشهی کامپوننت جاری دسترسی یافت. به همین جهت است که پس از آن از متد find، برای یافتن دراپ داون استفاده شدهاست و سپس chosen به نحو متداولی به آن اعمال گردیدهاست.
در اینجا هر زمانیکه یکی از آیتمهای دراپ داون انتخاب شوند، مقدار آن به خاصیت selectedValue انتساب داده شده و این انتساب سبب فعال سازی binding و نمایش مقدار آن در ذیل دراپ داون میگردد.
در قالب این کامپوننت هم با استفاده از ngFor، عناصر دراپ داون از آرایهی dropDownItems تعریف شده در کلاس کامپوننت، تامین میشوند. متغیر محلی item تعریف شدهی در اینجا، در محدودی همین المان قابل دسترسی است. برای مثال از آن جهت تنظیم دومین آیتم لیست به صورت انتخاب شده، در حین اولین بار نمایش view استفاده شدهاست.
استفاده از jQuery و chosen به صورت typed
کتابخانهی jQuery در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jquery دارای فایل d.ts. خاص خود است. برای نصب آن میتوان از روش ذیل استفاده کرد:
npm install -g typings typings install jquery --save --ambient
/// <reference path="../typings/browser/ambient/jquery/index.d.ts" />
در ادامه به فایل using-jquery-addons.component.ts مراجعه کرده و تغییر ذیل را اعمال کنید:
// declare var jQuery: any; // untyped declare var jQuery: JQueryStatic; // typed
interface JQuery { //... chosen(options?:any):JQuery; } declare module "jquery" { export = $; }
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید.
مسیرراهها
کتابخانه Angular Material 6x
یک نکتهی تکمیلی: استفاده از این DatePicker در برنامههای Blazor SSR
اگر علاقمند باشید تا از این DatePicker در برنامههای Blazor SSR بهصورت یک کامپوننت استفاده کنید، روش کار به صورت زیر است:
- نیاز به نسخهی اصلاح شدهی آن خواهید داشت.
- سپس هر المان ورودی که مزین به ویژگی data-dnt-date-picker بود، یافت شده و این DatePicker به آن متصل میشود.
- همچنین از این جهت که در برنامههای Blazor SSR، ویژگی enhanced navigation و نمایش Ajax ای صفحات با fetch، فعال است، باید حالت enhancedload را تحت نظر قرار داده و این کوئری گرفتن یافتن عناصر با ویژگی data-dnt-date-picker و اتصال مجددا به آنها را مدیریت کرد.
- تا همینجا و با این تنظیمات، نمایش این DatePicker فعال میشود و ... کار میکند. اگر علاقمند به تبدیل آن به یک کامپوننت مخصوص Blazor SSR هم هستید، میتوانید از کامپوننت DntInputPersianDatePicker.razor و کدهای آن، ایده بگیرید که جزئیات آن پیشتر در مطلب جاری بررسی شدهاست؛ هرچند این مطلب بیشتر به سایر نگارشهای Blazor میپردازد.
در قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهدهی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها میتوان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد میتواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتمهای سریع و سادهای در نظر گرفته است که آنها را در این بخش مورد برسی قرار خواهیم داد.
Pipeهای Pure و Impure
Pipeها کلا در دو دستهی Pure و Impure قرار میگیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.
Pure Pipe
این نوع Pipeها تنها زمانی فراخوانی مجدد میشوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند Date ، Array ، Function و Object نیز تغییر محض محسوب میشود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمیشوند:
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمیشود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آنها یک تغییر محض محسوب میشود.
حالا میتوان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا بهروزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت میگیرد).
Impure Pipe
این نوع Pipeها در اغلب رخدادهای کامپوننت از جمله فشره شدن کلید یا حرکت ماوس و رخدادهای دیگر فراخوانی مجدد میشوند. با در نظر گرفتن این نگرانی، هنگام پیاده سازی این نوع Pipeها باید مراقب بود؛ زیرا این نوع Pipeها با اجرای طولانی خود میتوانند رابط کاربری شما را نابود کنند. برای درک کامل تفاوت این دو نوع از Pipeها مثالی را دنبال میکنیم.
مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایهای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد میکنیم:
همانطور که مشخص است این Pipe در متد transform، آرایهای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت میدهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیهی اعداد از یک تا ده، تعریف میکنیم:
برای نمایش این اعداد در رابط کاربری تگهای زیر را به قالب کامپوننت خود اضافه میکنیم:
همچنین با استفاده از تگهای زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر میگیریم:
تگهای زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه میکنیم:
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers بهروز میشوند، ولی Pipe، متوجه تغییری بر روی آرایه نشدهاست و همچنان اعداد قبلی را نمایش میدهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipeها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.
برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را بهروز رسانی میکند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راهحل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار سادهای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راهحل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شدهاند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیادهسازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد.
Pipeهای Pure و Impure
Pipeها کلا در دو دستهی Pure و Impure قرار میگیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
Pure Pipe
این نوع Pipeها تنها زمانی فراخوانی مجدد میشوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند Date ، Array ، Function و Object نیز تغییر محض محسوب میشود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمیشوند:
numbers.push(10); obj.name = ‘javad’;
حالا میتوان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا بهروزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت میگیرد).
Impure Pipe
این نوع Pipeها در اغلب رخدادهای کامپوننت از جمله فشره شدن کلید یا حرکت ماوس و رخدادهای دیگر فراخوانی مجدد میشوند. با در نظر گرفتن این نگرانی، هنگام پیاده سازی این نوع Pipeها باید مراقب بود؛ زیرا این نوع Pipeها با اجرای طولانی خود میتوانند رابط کاربری شما را نابود کنند. برای درک کامل تفاوت این دو نوع از Pipeها مثالی را دنبال میکنیم.
مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایهای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد میکنیم:
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'evenNumbers' }) export class EvenNumbersPipe implements PipeTransform { transform(numbers: Array<number>): Array<number> { var x=numbers.filter(r => r % 2 == 0); return x; } }
// . . . import { EvenNumbersPipe } from './pipes/even-numbers.pipe' @NgModule({ declarations: [ . . . EvenNumbersPipe ], . . . }) export class AppModule { }
سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیهی اعداد از یک تا ده، تعریف میکنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
<h1>All numbers</h1> <span *ngFor="let number of numbers"> {{number}} </span>
<p> <input type="text" #number /> <input type="button" (click)="numbers.push(number.value)" value="Add number"/> </p>
تگهای زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه میکنیم:
<h1>even numbers</h1> <span *ngFor="let number of numbers | evenNumbers"> {{number}} </span>
برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را بهروز رسانی میکند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p> <input type="text" #number /> <input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/> </p>
@Pipe({ name: 'evenNumbers', pure: false }) export class EvenNumbersPipe implements PipeTransform { //… }
کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شدهاند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیادهسازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد.
بازخوردهای دوره
صفحات مودال در بوت استرپ 3
باتشکر. فرم مودال من بصورت لوکال نمایش داده میشه ولی زمانی که کد رو پابلش میکنم و روی سرور قرار میدم مودال نمایش داده نمیشه ، کنسول مرورگر رو هم برای بررسی خطا چک کردم ولی خطایی صادر نشده . در نتیجه متوجه منشاء اشکال نمیشوم.
کد من :
سایر جزئیات مودال هم طبق مثال انتهای مطلب نوشته شده و نتیجه دلخواه رو بصورت لوکال میگیرم.اگر امکانش هست راهنمائی بفرمائید.
کد من :
grid.Column(columnName: "Description", header: "شرح", format: item => @Html.Raw( $"<a data-toggle='modal' class='fa fa-id-card-o' href={@renderModalPartialViewUrl+'/'+item.Id} data-target='#myModal'></a>")),
اشتراکها
معرفی کامپوننت EasyQuery
از کامپوننت EasyQuery برای ارائه یک رابط کاربر پسند (UserFriendly) جهت جستجو و فیلترینگ پیشرفته بر روی اطلاعات توسط کاربران استفاده میشود. این کامپوننت به کاربران اجازه میدهد تا اطلاعاتی را که نیاز دارند به راحتی و بدون نیاز به کمک شما یا تیم پشتیبانی، فراهم نمایند.
نسخه آزمایشی گزارش گیری ساخته شده با EasyQuery تحت وب
نسخههای آزمایشی دیگر بر روی ASP.NET MVC و Silverlight و ASP.NET AJAX و Win Forms
دانلود کدهای نسخه آزمایشی تحت وب
با سلام.
امکان مرتب سازی یک table با این کامپوننت وجود دارد؟
سعی من بی نتیجه بود. با تشکر.