البحث في الموقع
المحتوى عن 'وراثة'.
-
تستخدم الأصناف في البرمجة الغرضية التوجيه من أجل إنشاء الأغراض، وإعطاء القيم الابتدائية للمتغيرات المستنسخة. لكل صنف اسم خاص يعبر عن نوعه ووظيفته. تعريف الأصناف جميع الأصناف في Ruby مستنسخة من الصنف الرئيسي Class، حيث يتم تعريف الأصناف في لغة Ruby كما يلي: يبدأ الصنف بالكلمة الدلالية class وبعدها يكتب اسم الصنف. يجب أن يبدأ اسم الصنف بحرف كبير. وفي حال كان اسم الصنف مكون من عدة كلمات، يتم تمييز هذه الكلمات بكتابة أول حرف من كل كلمة بالنمط الكبير وباقي الأحرف صغيرة، كونه لا يمكن استخدام فراغات فاصلة بين الكلمات في أسماء الأصناف. يمكن أن يحتوي الصنف على متغير الصنف، والمتغير المستنسخ، ودالة الصنف، بالإضافة لإمكانية استدعاء الدوال الأخرى مثل الدالة attr_accessor (دوال الولوج). ينتهي الصنف بالكلمة الدلالية end. يوضح المثالي التالي طريقة تعريف الأصناف: class SampleClass # some code describing the class behavior def sample_method end end الاستنساخ يتم إنشاء الأغراض من الأصناف بعملية تسمى الاستنساخ، حيث يصبح الغرض عبارة عن نسخة من الصنف الذي تم استنساخه عنه. تنفذ عملية الاستنساخ باستخدام الدالة new مع اسم الصنف المراد إنشاء الغرض منه كما هو موضح بالمثال التالي: std1 = Student.new(parameters) المتغيرات المستنسخة وهي المتغيرات التي يتم تعريفها ضمن دوال الأصناف بإضافة الرمز @ لبداية اسم المتغير. وينحصر نطاقها فقط على الغرض المُنشأ من الصنف، ولا يمكن الولوج إليها وإظهار قيمها أو تعديلها إلاّ من خلال الدالة التي عُرّفت فيها (دوال الولوج). سيتم توضيح هذه المتغيرات من خلال المثال التالي: class Calculation @x = 100 def add_num @x = 200 end def show_data puts 'Value of x is : '، @x end end instance = Calculation.new instance.show_data instance.add_num instance.show_data Output Value of x is : Value of x is : 200 نلاحظ أن الخرج الأول الدالة show_data من الغرض instance والمُنشأ من الصنف Calculation فارغ، لأن قيمة المتغير المستنسخ @x مازالت nil (لا شيء) بالرغم من أنه تم إسناد القيمة 100 لهذا المتغير في بداية تعريف الصنف Calculation، وذلك لأن طريقة التعريف هذه خاطئة للمتغير المستنسخ @x، حيث يجب أن يتم تعريفه ضمن دوال الصنف فقط، بالتالي تم تجاهل هذا التعريف. أما الخرج الثاني لهذه الدالة أصبح 200 لأنه تم تنفيذ الدالة add_num (التي عُرّف فيها) من الغرض instance والتي قامت بتهيئة المتغير المستنسخ @x بالقيمة 200. دوال الولوج وهي دوال الصنف التي تعرَّف ضمنها المتغيرات المستنسخة، حيث تُستخدم هذه الدال من أجل الولوج إلى المتغيرات المستنسخة للأغراض من أجل قراءة قيمها أو تعديلها. يوضح المثال التالي هذه الدوال: class MyData def set_data(n) @x = n end def get_data @x end end d = MyData.new puts d.set_data(1000) puts d.get_data Output 1000 1000 حيث يحتوي الصنف MyData على دالتي ولوج هما: الدالة set_data لتعديل قيمة المتغير المستنسخ @x، والدالة get_data لقراءة قيمة هذا المتغير المستنسخ. متغيرات الأصناف وهي المتغيرات المعرفة في بداية الصنف بإضافة الرمز @@ لبداية اسم المتغير، حيث يتم مشاركة قيمها مع جميع الأغراض المُنشأة من هذا الصنف وكذلك مع جميع الأصناف المورَّثة من هذا الصنف، على عكس المتغيرات المستنسخة التي لا يتم مشاركة قيمها بين الأغراض. والمثال التالي يوضح هذه المتغيرات: class MyData @@x = 1 def add_n @@x = @@x + 10 end def value @@x end end ins1 = MyData.new ins2 = MyData.new puts ins1.value ins1.add_n puts ins1.value puts ins2.value Output 1 11 11 المتغيرات المستنسخة للأصناف تختلف هذه المتغيرات عن متغيرات الأصناف، بأنه يتم مشاركة قيمها مع الأغراض المُنشأة من هذا الصنف فقط، كما هو موضح بالمثال التالي: class Employee class << self; attr_accessor :instances; end def store self.class.instances ||= [] self.class.instances << self end def initialize name @name = name end end class Overhead < Employee; end class Programmer < Employee; end Overhead.new('Martin').store Overhead.new('Roy').store Programmer.new('Erik').store puts Overhead.instances.size # => 2 puts Programmer.instances.size # => 1 Output 2 1 دوال الأصناف يتم تعريف دوال الأصناف بنفس طريقة تعريف دوال Ruby الاعتيادية باختلاف وحيد، وهو إضافة اسم الصنف أو الكلمة الدلالية self وبعدها نقطة قبل اسم الدالة. يتم استدعاء دوال الأصناف على مستوى الصنف الذي عرفت فيه دون الحاجة لوجود غرض مُنشأ من هذا الصنف. كما يمكن لدوال الأصناف الولوج إلى متغيرات الأصناف لكنها لا تستطيع الولوج إلى المتغيرات المستنسخة (فقط دول الولوج تستطيع ذلك). يوضح المثال التالي كيفية تعريف واستدعاء دوال الأصناف: class Student def self.no_students puts 'There are five hundred students in my school.' end end Student.no_students Output There are five hundred students in my school. مستويات الوصول لدوال الأصناف هنالك ثلاث مستويات للوصول إلى دوال الأصناف هي: الدوال العامة: إن دوال الأصناف في Ruby هي دوال عامة بشكل افتراضي، أي يمكن لأي أحد الوصول إليها، وتشكل واجهة التخاطب للأصناف، حيث يتم من خلالها تبادل المعطيات بين الصنف والعالم الخارجي. الدوال الخاصة: يتم تحديدها بالكلمة الدلالية private، وهي للاستخدام الداخلي فقط ضمن الصنف (يمكن استدعائها خارجياً عن طريق دالة عامة تحوي على هذه الدالة الخاصة مثل whisper_louder)، حيث يمكن الوصول إليها واستخدامها فقط من قبل الدوال الموجودة ضمن تعريف الصنف نفسه أو الأصناف المورَّثة منه، بشرط أن يكون استدعائها صريح (مثل whisper) دون أن تكون مضمّنة في غرض آخر حتى ولو كان من نفس الصنف (مثل self.whisper). عند الرغبة باستدعاء هذه الدوال خارج الصنف عن طريق استدعائها بشكل مضمّن في الدوال العامة كما هو موضح بالمثال التالي: class Person def speak puts "Hey، Tj!" end def whisper_louder whisper end def whisper_louder_with_self self.whisper end # private methods are for internal usage within the defining class private def whisper puts "His name's not really 'Tj'." end end a_hater = Person.new a_hater.speak # استدعاء دالة عامة a_hater.whisper # استدعاء دالة خاصة خارج الصنف a_hater.whisper_louder # استدعاء دالة عامة تحوي على دالة خاصة صريحة a_hater. whisper_louder_with_self # استدعاء دالة عامة تحوي على دالة خاصة مضمنة Output "Hey، Tj!" NoMethodError "His name's not really 'Tj'." NoMethodError الدوال المحمية: ويتم تحديدها بالكلمة الدلالية protected، وهي نفس الدوال الخاصة بفارق أساسي هو إمكانية استدعائها داخليا بشكل مضمّن في غرض من نفس الصنف كما هو موضح بالمثال التالي: class User def say_secret_with_self self.secret end protected def secret "secret" end end u = User.new u.say_secret_with_self # => استدعاء دالة عامة تحوي على دالة محمية مضمنة Output "secret" دوال القراءة والكتابة الشخصية تُنشأ المتغيرات المستنسخة لكل غرض مُنشأ من صنف، لتستخدم بشكل أساسي ضمن هذا الغرض، لكنها لا تشبه الدوال الخاصة من حيث اقتصارها على الاستخدام الداخلي. حيث يمكن إظهار وتعديل قيمها من خارج الغرض، وذلك عن طريق تعريف دوال القراءة والكتابة الشخصية (مشابه لدوال الولوج). يوضّح المثال التالي طريقة تعريف دالة القراءة (student_name) التي تقوم بقراءة قيمة المتغير المستنسخ: class Student def initialize(student_name، id) @student_name = student_name @id = id end def student_name @student_name end end std1 = Student.new("Sara"، 5) puts std1.student_name Output Sara والمثال التالي يوضّح طريقة تعريف دالة الكتابة (sname=) والتي تقوم بتغيير قيمة المتغيّر المستنسخ: class Student def initialize(sname) @sname = sname end def sname @sname end def sname=(new_name) @sname = new_name puts @sname end end # you could the access @sname variable as a = Student.new('Sara') puts a.sname a.sname ='Robin' Output Sara Robin دوال القراءة والكتابة الافتراضية يمكن تنفيذ جميع وظائف قراءة وتعديل قيم المتغيرات المستنسخة عن طريق الدوال attr_accessor و attr_reader و attr_writer والموجودة في لغة Ruby بشكل افتراضي، حيث تستخدم الدالة attr_accessor لتنفيذ عمليتي القراءة والكتابة معًا في هذه المتغيرات كما هو موضح بالمثال التالي: class Student def initialize(sname) @sname = sname end attr_accessor :sname end # attr_accessor generates variable @sname accessor methods automatically: a = Student.new('Sara') puts a.sname a.sname = 'Robin' puts a.sname Output Sara Robin أما الدالة attr_reader فتستخدم لتنفيذ عملية القراء فقط، والدالة attr_writer فتستخدم لتنفيذ عملية الكتابة فقط كما هو موضّح بالمثال التالي: class Student attr_reader :sname attr_writer :sname end s1 = Student.new s2 = Student.new s1.sname = "Sara" s2.sname = "Robin" puts s1.sname puts s2.sname Output Sara Robin ثوابت أصناف لغة Ruby يمكن أن تعرّف الثوابت ضمن الأصناف، لكن يمكن الولوج إليها من خارج الأصناف على خلاف المتغيرات المستنسخة التي تحتاج لدوال خاصة لتنفيذ ذلك، ويوضح المثال التالي طريقة تعريف الثوابت ضمن الأصناف، واستخدامها داخل وخارج الأصناف: class ConstClass Color1='Red' Color2='Green' Color3='White' def show puts "#{Color1} #{Color2} #{Color3}" end end puts( ConstClass::Color1) ConstClass.new.show Output Red Red Green White الوراثة وهي العلاقة بين صنفين، حيث يمكن لصنف أن يرث الوظائف والمتغيرات من صنف أعلى منه مرتبة، ويسمى عندها الصنف الموروث بصنف الأب أو الصنف الأساس، ويسمى الصنف الوارث بصنف الابن، هذا ولا تسمح Ruby بالوراثة المتعددة، أي لكل صنف في Ruby صنف أساس واحد. يوضح المثال التالي طريقة توريث جميع الوظائف والمتغيرات الغير خاصة لصنف آخر (صنف الابن): class Student def schooldetails puts 'There are 700 students in our school.' end end class Classfive < Student # < means inherit def class5 puts 'There are 75 students in Class V.' end end instance = Classfive.new instance.class5 instance.schooldetails Output There are 75 students in Class V. There are 700 students in our school. حيث تم في هذا المثال توريث الصنف Classfive من الصنف Student. يستطيع الصنف الابن الولوج إلى دوال الصنف الأب، عن طريق الكلمة الدلالية super، كما هو موضّح بالمثال التالي: class Student def schooldetails puts 'There are 700 students in our school.' end end class Classfive < Student # < means inherit def schooldetails super puts 'There are 75 students in Class V.' end end instance = Classfive.new instance.schooldetails Output There are 700 students in our school. There are 75 students in Class V. يمكن في الوراثة المتعددة الولوج إلى دوال الصنف الأب باستخدام أسماء بديلة عن طريق الدالة alias، كما هو موضّح بالمثال التالي: class V def ccccc "Class V " end end class VI < V alias vcv ccccc def ccccc vcv + "VI " end end class VII < VI def ccccc vcv + "VII " end end puts V.new.ccccc puts VI.new.ccccc puts VII.new.ccccc Output Class V Class V VI Class V VII حيث تم استخدام الاسم vcv كبدل لاسم الدالة ccccc المعرّفة في الصنف V، ثم تم استخدام هذا الاسم البديل في الأصناف الوارثة كون اسم دوالها هو نفس الاسم الأصلي لدالة الصنف V. الأصناف الفريدة تحتوي لغة Ruby على طريقة لتعريف دوال محددة من أجل غرض معين، حيث تسمى هذه الدوال بالدوال الفريدة. فعند القيام بتعريف دوال فريدة في غرض ما، فإن لغة Ruby تقوم تلقائياً بإنشاء صنف جديد لاحتواء هذه الدوال الفريدة فقط، والذي يسمى عندها بالصنف الفريد. ويفيد هذا الصنف عند الرغبة باستدعاء دواله في أقسام مختلفة من التطبيق البرمجي، مثل تأمين الاتصال مع النظام الخارجي أو الولوج لقاعدة البيانات أو غيرها. ويوضح المثال التالي هذه الأصناف: tutorial1 = String.new def tutorial1.size "Learn Ruby" End tutorial2 = String.new puts tutorial1.singleton_methods puts tutorial2.singleton_methods Output Learn Ruby String 0 حيث تم استخدام الدالة singleton_method للحصول على قائمة أسماء جميع الدوال الفريدة الموجودة في الغرض.هذا ويمكن الولوج إلى صنف الغرض الفريد باستخدام الصيغة التالي: class << object كما هو موضح بالمثال التالي: arra1 = [] class << arra1 def foo "Hello World!" end end puts arra1.singleton_methods Output foo ترجمة –وبتصرّف- للمقال Ruby Classes حقوق خلفية الصورة البارزة محفوظة لـ all-free-download.com
-
ما الذي تعنيه كلمة "مجال" برأيك؟ قد تبدو من اسمها بأنّها تشير إلى جزءٍ من شيفرة التّطبيق، ربّما تم عملها لنتجنّب استخدام المجال العام الذي يسبّب العديد من المشاكل. يبدو أنّ الأمر بسيط، وإنّه لمن الحكمة أن يقوم إطار العمل بتطبيق شيءٍ كهذا، ربّما ليس علينا التّفكير في أمر المجالات أكثر من ذلك، هل يمكننا الانتقال للفصل التالي؟ ليس بهذه السّرعة، لن يكون هذا الفصل طويلًا ولكنه سيغطي أمرين شديدي الأهمّيّة: وراثة المجالات، وهرميّة المجالات، فقد يحوي تطبيق Angular نموذجيًّا على العشرات، المئات وربّما الآلاف من المجالات. قبل أن نبدأ، لنقم بإعداد البيئة لتُلائم أمثلة هذا الفصل. التهيئةفي فصل المبادئ، تعلّمنا كيف نقوم بإضافة التّوجيه ng-app إلى العنصر الذي نرغب بأن تقوم Angular بمعالجته. <body ng-app="app"> <!-- الأمثلة هنا --> </body>الوسيط الذي نمرره إلى التّوجيه ng-app هو اسم الوحدة التي تشكل جذر التطبيق الحالي (في المثال قمنا باستخدام الاسم app على سبيل الاصطلاح). سنقوم بتغطية الوحدات بعمق في فصل لاحق. أما الآن فاعتبر السّطور التّالية مجرّد شيفرات اصطلاحية يمكنك نسخها دون فهم محتواها. angular.module('app', []); angular.module('app').config(['$controllerProvider', function($controllerProvider) { $controllerProvider.allowGlobals(); }]);والآن بعد كتابة تلك الشّيفرات الخارجة عن الدّرس، يمكننا الدخول في الموضوع. scope$في الفصل الماضي، المتحكمات، تعلّمنا كيفيّة تحضير النّموذج عن طريق ربط العناصر (الخصائص properties) إلى المرجع scope$. لنقم بتكرار التمرين ثانيةّ. function NameController($scope) { $scope.name = "First"; }باستخدام التّوجيه ng-controller يمكننا استدعاء تابع التّحكم المكتوب أعلاه، وذلك ضمن سياق أحد عناصر المستند، وعندها ستكون أيّ بيانات قمنا بإسنادها للمجال متاحةً للاستخدام داخل العنصر. <p ng-controller="NameController"> {{name}} </p>الناتج Firstعناصر المستند التي تقع خارج العنصر الذي قمنا باستدعاء المتحكّم NameController فيه، لن تكون قادرةً على الوصول إلى العناصر المرتبطة بالمتحكّم، لنجرّب ذلك. <div> <p> Outside the scope: {{name}} </p> <div ng-controller="NameController"> <p> Inside the scope: {{name}} </p> </div> </div>الناتج Outside the scope: Inside the scope: Firstوهذا المثال يوضّح ارتباط عمل المتحكّم والمجال معًا، والآن لننظر إلى الحالة المعاكسة، تطبيق Angular بمجال واحد. rootScope$تحاول Angular جاهدةً أن تتأكد من ابتعادنا عن المشاكل أثناء استخدام المجالات، لذا تقوم بإنشاء مجالٍ جديد لكلّ متحكّم. يتمّ بناء المجالات وفق بنيةٍ هرميّة، ويتربّع في جذر هرم المجالات في كلّ تطبيقات Angular مجالٌ أبٌ وحيدٌ لجميع المجالات، يمكننا الوصول إلى هذا الجذر باستخدام الوسيط ذو الاسم المخصص rootScope$ داخل المتحكّم، واستخدامه بدلًا من المرجع المعتاد (والمستحسن) scope$. function RootNameController($rootScope) { $rootScope.name = "First"; }الشّيفرة واضحة وليس فيها صعوبة، وهي تعمل بشكل طبيعي. <p ng-controller="RootNameController"> {{name}} </p>الناتج Firstولكنّ المشاكل تبدأ بالظّهور عندما نحاول الوصول إلى عنصر له الاسم نفسه من متحكّم آخر. function SecondRootNameController($rootScope) { $rootScope.name = "Second"; }هذا لا يبشّر بالخير، فالمرجع rootScope$ وحيد (singleton) داخل التّطبيق، ولا يمكن أن يكون هناك سوى عنصر واحد له الاسم name. <p ng-controller="RootNameController"> {{name}} </p> <p ng-controller="SecondRootNameController"> {{name}} </p>الناتج Second Secondمن الواضح أنّ المتحكّم SecondRootNameController قد استبدل القيمة التي أسندها RootNameController للعنصر name. حسنًا، هذه هي مشكلة المتغيرات العامة، أليس كذلك؟ العزلتقدم Angular لنا بيئة آمنة عن طريق إنشاء مجالٍ خاصّ بكلّ متحكّم، لنقم بإعادة كتابة المتحكّمات السّابقة لتقوم بتهيئة النّموذج تهيئةً صحيحة، باستخدام المرجع scope$ بدلًا من rootScope$، وحاول مقارنة ذلك مع المثال السّابق الذي استخدمنا فيه rootScope$ لتعرف سبب إنشاء مجال لكلّ متحكّم، سنستخدم المتحكّمين التاليين: function SecondNameController($scope) { $scope.name = "Second"; }<p ng-controller="NameController"> {{name}} </p> <p ng-controller="SecondNameController"> {{name}} </p>الناتج First Secondأظهر المثال المخرجات الصحيحة، القيمة الصحيحة للعنصر name لكلا المتحكّمين. هذا العزل هو النّتيجة التي نحصل عليها عندما لا يكون أيٌّ من المتحكّمين ابنًا للآخر، أيّ أننا لم نقم بتعريف أحد المتحكّمين داخل عنصر في المستند محتوًى داخل متحكّمٍ آخر. ما الذي سيحدث لو قمنا بجعلهم متداخلين بدلًا من ذلك؟ المجالات المتداخلةبتعديلٍ صغيرٍ على المثال السابق، سنقوم بتحريك SecondNameController إلى عنصرٍ ابن للعنصر div الذي يقوم بتحميل المتحكّم NameController، وهذه الحالة هي ما يسمّى بالمجالات المتداخلة (nested). <div ng-controller="NameController"> <p> {{name}} </p> <p ng-controller="SecondNameController"> {{name}} </p> </div>الناتج First Secondالأمور تسير على ما يُرام، ولا يزال العنصر name معزولًا في كلا المجالين، ماذا لو قمتَ بعكس ترتيب العنصرين p في المثال السابق؟ هيا قم بتجربة ذلك، يُفترض بأنك سترى أنّ العنصرين name قد انعكس ترتيبهما. في الحقيقة، لا يعني ذلك بأنّ المجالين معزولين، فـAngular تقوم بتنظيم المتحكّمات في بنية هرميّة بالاعتماد على موضع المتحكّم في بنية المستند، والمتحكّم الابن يرث عناصر وخصائص أبيه. يرجع السبب في عدم حدوث تغيير في قيمة العنصر name في المثال السابق، إلى خاصّيّة التظليل، حيث يقوم العنصر name في الابن بتظليل قيمة العنصر name في الأب. لنجرّب الحصول على دليلٍ على هذا السلوك عن طريق تغيير اسم العنصر في المتحكّم الابن. function ChildController($scope) { $scope.childName = "Child"; }سنحاول إخراج قيمتي العنصرين في كلا المجالين، الأب والابن. <div ng-controller="NameController"> <p> {{name}} and {{childName}} </p> <p ng-controller="ChildController"> {{name}} and {{childName}} </p> </div>الناتج First and First and Childمن الواضح أنّ المتحكّم الأب NameController لا يملك صلاحيّةً للوصول إلى عناصر المتحكّم الابن، بينما يمكن للمتحكّم الابن ChildController أن يصل إلى عناصره وعناصر أبيه. بما أنّ العنصر name هو عنصر موروث من الأب، لا بدّ من أنّ التغيير على قيمته في مجال المتحكّم الابن سيؤدّي إلى تغيير قيمته في مجال الأب. سنضيف خانة إدخال ونربطها بالعنصر name. <div ng-controller="NameController"> <p> {{name}} </p> <p ng-controller="ChildController"> {{name}} </p> <input type='text' ng-model='name'> </div>إذا حاولت تعديل قيمة العنصر name في المثال السابق، ستجد أنّه يعمل كما هو متوقّع، فقيمة العنصر name تتغيّر في كلا المجالين، ولكن لاحظ بأنّنا نقوم بالتّغيير ضمن مجال الأب فقط. الوراثةقد ترغب أيضًا بتغيير قيمة العنصر name أيضًا في مجال الابن، ربّما تظنّ بأنّ هذا التغيير يجب أن ينعكس أيضًا على المتغير name في مجال الأب، لنجرّب ذلك، سنقوم بإضافة خانة إدخال لتسمح لنا بتعديل العنصر name في مجال الابن أيضًا. في العرض الخاصّ بالمثال، قم بالخطوات التالية بنفس الترتيب الذي سأذكره لك:أوّلًا، غيّر القيمة في خانة الإدخال العليا، ستلاحظ أنّ جميع القيم المرتبطة بالعنصر name قد تمّ تحديثها.ثانيًا، غيّر القيمة في خانة الإدخال السّفلى. <div ng-controller="NameController"> <p> name: {{name}} <br> <input type='text' ng-model='name'> </p> <p ng-controller="ChildController"> name: {{name}} <br> <input type='text' ng-model='name'> </p> </div>هل فاجَأَتك النّتيجة؟ تستخدم Angular طريقة JavaScript في وراثة الهيكل الخارجي (prototypal inheritance) هذا جيّدٌ إن كنت متمرّسًا فيها، فليس عليك تعلّم أيّ شيءٍ جديدٍ هنا، ولكنّه سيّءٌ لمن لم يواجه هذا النّوع من الوراثة من قبل، ففهمه ليس أمرًا بديهيًّا. تقول القاعدة: “إنّ تغيير قيمة عنصر ما في كائن في JavaScript، يؤدّي إلى إنشاء هذا العنصر في الكائن”، وهذه القاعدة البسيطة تشكّل مشكلةً أثناء التّعامل مع العناصر الموروثة التي تكون مظلّلة بالعنصر الخاص بالكائن الابن. حدث الأمر كالتّالي: في البداية لم يكن هناك أيّ عنصر اسمه name داخل الابن، ولكن عندما قمنا بتعديل النّصّ في خانة الإدخال السّفلى قامت Angular بإسناد النّص إلى العنصر name في الابن حيث تمّ إنشاء هذا العنصر في الابن، وعند حدوث ذلك، قام هذا العنصر بتظليل العنصر name في الأب، ومن ثمّ لم يعُد بإمكاننا الوصول إليه من الابن. كيف يمكننا التّعامل مع هذا الأمر في Angular، بحيث نتمكّن من تعديل بيانات النّموذج في مجالٍ موروث؟ لن يكون ذلك صعبًا، سيكون علينا فقط نقل العنصر name إلى داخل كائنٍ آخر. function InfoController($scope) { $scope.info = {name: "First"}; }function ChildInfoController($scope) { $scope.info.childName = "Child"; }<div ng-controller="InfoController"> <p> {{info.name}} and {{info.childName}} <br> <input type='text' ng-model='info.name'> </p> <p ng-controller="ChildInfoController"> {{info.name}} and {{info.childName}} <br> <input type='text' ng-model='info.name'> </p> </div>لاحظ أن ChildInfoController يعتمد على أبيه في إنشاء الكائن info، ما الذي سيحدث لو قمنا بتعديل شيفرة المتحكّم ChildInfoController واستبدلنا جسم التابع بالعبارة: scope.info = {childName: "Second$"};جرّب ذلك، سترى بأننا عدنا إلى إنشاء عنصرٍ جديد في الابن، مع تأثير التظليل الذي رأيناه سابقًا. scope.$watchتتعامل Angular أثناء عمليات الربط ثنائيّ الاتجاه مع العناصر في المجال بالطريقة التالية: عندما تستخدم خانة الدّخل المرتبطة للقيام بأيّ تغيير، يتمّ تحديث واجهة المستخدم في كلّ مكان، وهذا يختلف عن “الخصائص المحسوبة”(computed properties) وهي البيانات المأخوذة من مجال بيانات آخر. في المثال التالي، العنصر sum هو خصيصة محسوبة. function SumController($scope) { $scope.values = [1,2]; $scope.newValue = 1; $scope.add = function() { $scope.values.push(parseInt($scope.newValue)); }; // Broken -- doesn't trigger UI update $scope.sum = $scope.values.reduce(function(a, b) { return a + b; }); }يتم حساب قيمة العنصر sum فعليًّا عن طريق عملية بسيطة تستخدم التابع reduce في العبارة الأخيرة في المتحكّم SumController. في القالب الخاص بهذا المثال، سنستخدم أداة الدّخل select للسّماح للمستخدم باختيار الرّقم (2،1 أو 3) لإضافته إلى نهاية المصفوفة values.(كنقطة جانبيّة: لاحظ أنّ المتحكّم يعطي قيمةً ابتدائيّة للمتغيّر newValue, ولو لم نقم بذلك لكانت Angular ستضيف الاختيار الفارغ للقائمة في العنصر select، وذلك لتجنّب القيمة العشوائيّة التي يخزّنها newValue للخيار الأوّل المولّد عن طريق التّوجيه ng-options. هذا السلوك لا علاقة له بالمجالات ولكنّ العلم به أمرٌ مفيد.) <p ng-controller="SumController"> <select ng-model="newValue" ng-options="n for n in [1,2,3]"></select> <input type="button" value="Add" ng-click="add()"> The sum of {{values}} is {{sum}}. </p>عند النقر على Add ستتغيّر القيمة المعروضة لـsum، ولكن للأسف، الشّيفرة الخاصة بالمتحكّم تحتوي على أخطاء، ولن يعمل المتحكّم كما توقّعنا. لنقم الآن بتصحيح الخطأ، وذلك بنقل العبارة التي تقوم بالحساب الحقيقي للقيمة sum إلى تابع استدعاءٍ خلفيّ (callback). وعندما سنمرر هذا التابع كوسيط إلى scope.$watch$ مع وسيط آخر يمثّل عبارة المتابعة (في هذه الحالة هو اسم العنصر الذي يتم حساب sum منه)، سيؤدّي ذلك إلى جعل sum يتمّ إعادة حسابها كلّما تغيّر values. function SumController($scope) { $scope.values = [1,2]; $scope.newValue = 1; $scope.add = function() { $scope.values.push(parseInt($scope.newValue)); }; $scope.$watch('values', function () { $scope.sum = $scope.values.reduce(function(a, b) { return a + b; }); }, true); }والآن ستتغيّر قيمة العنصر sum ديناميكيًّا عند إضافة العناصر للمصفوفة. scope.$applyجميع التّوجيهات المدمجة في Angular والخاصة بعمليّة الربط ثنائيّ الاتجاه كاملة المزايا، ولكنّك قد تجد من فترة إلى أخرى سلوكًا تحتاج إلى إضافته. مثلًا، ماذا لو أردنا أن يقوم المستخدم بمسح الحالتين الحالة الحاليّة لخانة الدخل النّصّيّة و الحالة المرتبطة بها وذلك عندما يضغط زرّ esc؟ كيف يمكننا كتابة شيفرة التّعامل مع هذا الحدث؟ <div ng-controller="EscapeController"> <input type="text" ng-model="message"> is bound to "<strong ng-bind="message"></strong>". Press <code>esc</code> to clear it! </div>يجب علينا أوّلًا أن نصرّح عن المتغيّر ذو الاسم المخصّص element$ وتمريره كوسيط للمتحكّم، وذلك للسّماح لـAngular بحقن مرجعٍ في العنصر المرتبط بالمتحكّم. يمكننا استخدام التابع bind لتسجيل استدعاء خلفيّ لحدث keyup الذي يختبر ضغط الزّرّ esc، وداخل تابع الاستدعاء الخلفيّ هذا، سنقوم بتحديث عنصر المجال. قم بتجربة المثال التالي، اكتب شيئًا ثم اضغط esc. function EscapeController($scope, $element) { $scope.message = ''; $element.bind('keyup', function (event) { if (event.keyCode === 27) { // esc key // Broken -- doesn't trigger UI update $scope.message = ''; } }); }ليس بعد، بما أنّنا نتعامل مباشرةً (تقريبًا) مع عناصر المستند، سنحتاج إلى إخبار Angular عندما نريد أن تقوم بإعادة رسم العرض. نقوم بذلك عن طريق تغليف التّغييرات التي نقوم بها بتابع استدعاءٍ خلفيّ نمرّره إلى scope.$apply$. function EscapeController($scope, $element) { $scope.message = ''; $element.bind('keyup', function (event) { if (event.keyCode === 27) { // esc key $scope.$apply(function() { $scope.message = ''; }); } }); }جرّبها الآن، بعد أن أخبرنا Angular بما نريد، سيعمل كلّ شيء كما يجب. خلاصةإن حاولت تطبيق مفاهيم نموذج-عرض-متحكم (Model-view-controller (MVC على Angular، ستكون المجالات لغزًا بالنّسبة لك، قد تظنّ بأنّ الأمر بسيط، وأنّ المجالات هي جزءٌ من طبقة النّموذج، ولكن في Angular لا يكون الكائن نموذجًا حتّى يكون قابلًا للوصول إليه كعنصرٍ في المجال. ولكنّ القصّة تصبح أكثر إثارةً عندما ترى طريقة ارتباط المجالات بالمستند عن طريق المتحكّمات والتّوجيهات. بوضع سؤالنا الأكاديميّ جانبًا، فإن المجالات بديهيّةٌ وسهلة الاستخدام كما بيّنت أمثلة هذا الفصل. ترجمة وبتصرّف للفصل الرابع من كتاب: Angular Basics لصاحبه: Chris Smith.