خانه / Angular / AngularJS و $scope.apply()

AngularJS و $scope.apply()

AngularJS و $scope.apply()

اگر شما هم مقدار زیادی کد توی anggularJS نوشته باشید؛ احتمالا با $scope.apply() برخورد داشتید. در نگاه اول به نظر میرسه؛ که این تابع هم شبیه یک متدی است که شما فراخوانی می کنید تا bindingهای خود را update کنید. اما چرا واقعا $scope.$apply() وجود داره؟ و اینکه چه هنگامی بواقع شما نیاز به استفاده از اون رو دارید؟

جهت اینکه متوجه شوید که چه هنگامی واقعا شما نیاز دارید که از $apply() استفاده کنید؛ مهم است که بدونید که چرا واقعا به استفاده از $apply() نیاز هست…

جاوا اسکریپت turn based هست.

کدهای جاوا اسکریپتی که نوشته می شوند همگی به یکباره اجرا نمی شوند, در عوض اونها در چرخه هایی- turn اجرا خواهند شد. هرکدام از این turn ها بدون قطعی و از هم گسیختگی از ابتدا تا به انتها اجرا خواهند شد, و هنگامی که یک turn در حال اجرا خواهند بود؛ هیچ چیز دیگری در browser اجرا نخواهد شد. هیچ کد جاوا اسکریپت دیگری اجرا نخواهد بود؛ و page ما بطور کامل به حالت freeze می رود.

در عوض؛ هنگامی که taskای وجود دارد که زمان زیادی می برد؛ از جمله ajax requestها؛ انتظار برای رویداد یک کلیک و یا تنظیم یک timeout؛ ما callback function هایی رو تعریف می کنیم, و turn جاری رو به اتمام می رسونیم. بعدا؛ هنگامی که ajax request مورد نظر تکمیل شد؛ و یا time به اتمام رسید و یا turn جدیدی ایجاد شد callback اجرا خواهد شد تا روند اجرا به پایان برسد.

اجازه بدهید در اینجا مثالی بیاوریم:

var button = document.getElementById(‘clickMe’);

function buttonClicked () {

  alert(‘the button was clicked’);

}

button.addEventListener(‘click’, buttonClicked);

function timerComplete () {

  alert(‘timer complete’);

}

setTimeout(timerComplete, 2000);

هنگامی که کد جاوا اسکریپت load شد این فقط یک turn هست. اون یک دکمه رو پیدا می کنه؛ و سپس یک click listener رو به اون اضافه می کنه؛ و timeout رو برای اون تنظیم می کنه. و سپس turn به اتمام می رسه, و  browser در صورت نیاز web page رو بروزرسانی می کنه و در نهایت inputهای کاربر رو دریافت می کنه.

اگر browser کلیک جدیدی رو روی #clickMe تشخیص بده, turn جدیدی رو ایجاد می کنه که تابع buttonClicked رو اجرا می کنه. و هنگامی که آن تابع برگشت داده شد؛ turn مورد نظر به اتمام خواهد رسید.

کد جاوااسکریپت ما درون turnها اجرا می شود.

 حالا ما به چه صورت bindingها رو بروز رسانی کنیم؟

بنابراین angular اجازه میده که ما بخش های interface خود را به دیتا درون کدهای جاوا اسکریپت bind کنیم. اما angular کی متوجه میشه که چه موقع دیتا تغییر کرده است؛ و صفحه نیاز به بروز رسانی دارد؟

چند تا راه حل وجود داره. کد نیاز داره که بدونه چه موقع مقادیر تغییر کرده است

اکنون هیچ راه حلی توی کد خود نداریم که مستقیما تغییرات بر روی object کاملا notify شود.

بهمین دلیل دو رویکرد خواهیم داشت.

یکی استفاده از اشیا خاصی است؛ جایی که دیتا درون متدها set می شود؛ و نه assign کردن property. بنابراین تغییرات می تونه notify شه و صفحه نیز بروز رسانی بشه. مشکل عمده ی این روش این هست که ما باید object ها خاصی رو توسعه بدیم. همچنین جهت assign کردن, ما باید بجای استفاده از فرم هایی شبیه obj.key = ‘value’ از فرم هایی شبیه obj.set(‘key’ , ‘value’) استفاده کنیم. فریمورک هایی شبیه EmberJS و KnockoutJS از این استراتژی استفاده می کنند.

AngularJS اما از رویکرد دیگری استفاده می کنه: و هر valueای رو اجازه می ده که به عنوان target binding استفاده بشه. بهمین دلیل در انتهای هر turn کد angularJS؛ بررسی می کنه که آیا valueای تغییر کرده است یا خیر. و این ممکن است در ابتدا کافی نباشد؛ اما برخی استراتژی های باهوش وجود داره که performance hit رو کم می کنه. مزیت اصلی بهنگامی است که ما از objectهای ساده ای استفاده می کنیم و دیتای خود را بروزرسانی می کنیم. و تغییرات در ادامه notify خواهد شد و درون binding ما این تغییرات منعکس خواهد شد.

جهت اینک این استراتژی کار کند؛ نیاز داریم که بدونیم چه موقع دیتاها احتمال تغییرشون بیشتره؛ و  اینجا هنگامی است که $scope.$apply() وارد بازی می شه.

$apply() و $digest()

مرحله ای که بررسی می کند ببیند که آیا مقداری تغییر کرده است متدی دارد به نام $scope.$digest(). و اینجا جایی است که اتفاقات مهم رخ خواهد داد؛ اما معمولا ما مستقیما اون رو فراخوانی نمی کنیم. در عوض ما از $scope.$apply() که این تا بع در درون خود $scope.$digest() رو صدا می زنه رو استفاده می کنیم.

$scope.$apply() یک تابع و یا یک Angular expression رو می گیره و اون رو اجرا می کنه. و سپس $scope.$digest() رو به منظور اینکه bindingها و یا watcherها رو بروزرسانی کنه فراخوانی می کنه.

خوب کی شما نیاز دارید که $apply() رو صدا بزنید؟ در حقیقت خیلی به ندرت. AngularJS تقریبا تمام کد رو درون $apply() فراخوانی می کنه. Eventهایی شبیه ng-click؛ Controller initializaion؛ callbackهای سرویس $http همگی درون $scope,$apply()؛ wrap شده اند. بهمین دلیل شما نیازی نیست که اون رو صدا بزنید و در حقیقت نمی توانید هم. چون فراخوانی $apply() درون $apply() باعث بروز خطا و exception خواهد شد.

شما نیازی به استفاده از $apply() ندارید اگر کد شما درون turnهای جدید کمی پیش خواهد رفت. و فقط هنگامی که turnای از درون کتابخانه ی AngularJS ایجاد نشده نیاز هست. در عوض turnهای جدید؛ می تونید که کد خود رو درون $scope.$apply()؛ wrap کنید. در ایجا یک مثال داریم. ما از setTimeout استفاده می کنیم؛ پس از تاخیری در turn جدید تابعی را اجرا خواهند کرد. بنابراین AngularJS در مورد turn جدید اطلاعی ندارد که بروزرسانی ها رو منعکس کند.

کد جاوا اسکریپت

function Ctrl($scope) {

$scope.message = “Waiting 2000ms for update”;

setTimeout(function () {

   $scope.message = “Timeout called!”;

   // AngularJS unaware of update to $scope    }, 2000);}

کد html

<div ng:app ng-controller=”Ctrl”> {{message}}</div>

 

اما هنگامی که ما کد این turn رو درون $scope.$apply()؛ wrap می کنیم؛ تغییرات notify خواهد شد و page بروز رسانی خواهد شد.

کد جاوا اسکریپت

function Ctrl($scope) {

  $scope.message = “Waiting 2000ms for update”;

  setTimeout(function () {

    $scope.$apply(function () {

      $scope.message = “Timeout called!”;

   });    }, 2000);}

 کد html

<div ng:app ng-controller=”Ctrl”> {{message}} <br></div>

به جهت راحتی AngularJS سرویس $timeout را ارائه می ده که شبیه setTimeout هست؛ اما بصورت اتوماتیک کد شما رو درون $apply() بصورت پیش فرض wrap می کند. از اون استفاده کنید؛ نه از setTimeout

اگر شما کدی بنویسید که از Ajax بدون استفاده از $http؛ استفاده می کنید و یا از event بدون استفاده از ng-* مربوط به Angular؛ Listen می کنید و یا timeout رو set می کنید بدون اینکه از $timeout استفاده کنید؛ شما بصورت کد عمل wrap کردن کد خود رو درون $scope.$apply() انجام دهید.

$scope.$apply() در مقایسه با $scope.$apply(fn)

گاها سناریوهایی می بینیم که دیتا به روزرسانی شده اند؛ و سپس $scope.$apply() بدون پارامتر فراخوانی شده. این می تونه نتیجه مورد نظر رو بده؛ اما برخی موقعیت ها رو در این شرایط از دست خواهید داد.

اگر کد شما درون تابعی wrap نشده و به عنوان پارامتر به $apply() فرستاده نشه؛ و در این بین خطایی رخ بده که این خطا خارج از AngularJS رخ بده؛ که بدین معنی است که مکانیزم handle کردن خطا درون برنامه شده استفاده شده که موجب میشه که این از بین بره. $apply() نه تنها کد شما رو اجرا می کنه؛ بلکه آن را درون یک ساختار try/catch اجرا می کنه؛ بهمین دلیل خطاها همیشه گرفته خواهد شد؛ و $digest() درون بلاک finally فراخوانی خواهد شد؛ بدین معنی است که چه خطایی رخ بدهد و یا خیر $digest اجرا خواهد شد. و این بهتر است.

درباره ی masoud@admin

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

در تلگرام هم همراه شما هستم

اگر علاقمند به معماری نرم افزار و مبحث محبوب مایکروسرویس هستید؛ در کانال با ما همراه باشید. اطلاعات مفید زیادی در این کانال انتظار شما را می کشند. فقط کافیست دکمه ی پیوستن را بفشارید.

پیوستن بستن