Angular.js: يجب ألا ينشئ ng-transclude نطاقًا جديدًا للأخوة.

تم إنشاؤها على ٢٠ ديسمبر ٢٠١٣  ·  69تعليقات  ·  مصدر: angular/angular.js

هذا أكثر من طلب تغيير وأود أن أرى ما يعتقده الآخرون.

في رأيي المتواضع ، لا ينبغي أن تنشئ ng-transclude نطاقها الخاص أو على الأقل لديها طريقة لمنعها من القيام بذلك. والسبب وراء ذلك هو أن التوجيه الذي يطلب التحويل لديه بالفعل وسائل لتحديد ما إذا كان يريد أم لا أن يكون له نطاق أو نطاق معزول أو لا يوجد نطاق على الإطلاق. يستخدم التوجيه ng-transclude لتحديد المكان الذي يريد إدراج المحتوى فيه. عندما ينشئ ng-transclude نطاق الأخوة الخاص به ، فإنه نوعًا ما يكسر توقعات التوجيه الذي يحدد نوع النطاق الذي يريده ويظهر مظهر التباس "القيمة" الشائع مقابل "object.value".

في ما يلي مثال على المكان الذي لا يكون فيه النطاق الجديد منطقيًا في رأيي:

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-transclude/>',
        replace: true,
        scope: {}
    };
});

كل ما يريده هذا التوجيه هو استبدال <box>content</box> بـ <div>content</div> والمحتوى ليكون له النطاق المعزول.

يؤدي إنشاء بنية متداخلة من التوجيهات مثل هذا إلى تلوث شجرة النطاق. فيما يلي مثال مكثف (http://plnkr.co/edit/DwukVGGprFFjQuVY8yTz) لثلاثة توجيهات متداخلة تنشئ هيكل شجرة نطاق مثل هذا:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : ng-transclude
            < Scope (006) : box
            < Scope (007) : ng-transclude
                < Scope (008) : box
                < Scope (009) : ng-transclude

لا يبدو أن هذا السلوك يضيف أي قيمة إلى الغرض منه ولكنه يخلق الكثير من الارتباك بين المبتدئين.

في الوقت الحالي ، أستخدم الحل البديل التالي الذي يحقق بالضبط ما يفعله المثال السابق:

ui.directive('box', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div/>',
        replace: true,
        scope: {},
        link: function(scope, element, attrs, transclude) {
            transclude(scope.$parent, function(content) {
                element.append(content);
            });
        }
    };
});

إليك مثال مكثف (http://plnkr.co/edit/46v6IBLkhS71L1WbUDFl) يوضح هذا المفهوم. يترك شجرة النطاق جميلة ومرتبة:

< Scope (002) : ng-app
    < Scope (003) : ng-controller
        < Scope (004) : box
        < Scope (005) : box
        < Scope (006) : box

ويعمل الربط ثنائي الاتجاه بالطريقة التي يتوقعها الكثيرون عندما يربطون "القيمة" بدلاً من "object.value". (أعتقد أن حقيقة أن تمرير "القيمة" فقط يعمل في بعض الحالات ولكن ليس الآخر وإلقاء اللوم على طبيعة الميراث النموذجي في جافا سكريبت ليس عذراً جيداً. حقيقة أن العديد من الناس يجدون هذا السلوك غير متوقع يشير إلى أن هناك عيبًا معماريًا .)

أرغب في سماع ما يعتقده الآخرون واستخدام الحالات التي يعتقدون فيها أن إنشاء نطاق شقيق جديد لـ ng-transclude أمر منطقي.

Lots of comments $compile high won't fix bug

التعليق الأكثر فائدة

شيء جيد لقد تحولت إلى Ember منذ سنوات. :)

ال 69 كومينتر

أين تنشئ كلمة transclude نطاقًا جديدًا؟ http://plnkr.co/edit/EuHaBR26JgAegQKvwOGH؟p=preview لا أرى ذلك

أنا أتحدث عن التوجيه ng-transclude. ما لديك في مثالك هو بالضبط ما يفعله عملي.

هذا طلب صحيح. كنا نفكر في هذا لـ 1.2 لكنه كان يقترب من الإصدار النهائي ولم نرغب في تقديم هذا التغيير العاجل.

يجب أن نعتبره لـ 1.3

هذا لطيف! أنا سعيد لأنكم كنتم تفكرون في الأمر بالفعل.

+1 لهذا. أعتقد أن مشكلتي مرتبطة بهذا: أستخدم ng-transclude في توجيه مع النماذج ويجب أن أذهب إلى النطاق. $$ childHead للوصول إلى كائن التحقق من صحة النموذج ولكن ليس لدي مشكلة في الوصول إلى النماذج الخاصة بي.

هنا مثال: http://fiddle.jshell.net/39cgW/3/

واجه +1 هذه المشكلة اليوم وأنا أكره رمي $parent كل مكان.

لذا ، من أجل إيجاد حل لهذا ، يبدو أن هناك احتمالين

1) قم بتغيير التوجيه ngTransclude لتحديد نطاقه (يمكن في الواقع تقليصه أكثر من ذلك بكثير ، على ما أعتقد - لا حاجة إلى وحدة تحكم)

أو

2) لا تنشئ نطاقًا جديدًا عندما لا يتم تحديد النطاق

لذلك ، يمكننا حفظ عدد قليل من البايت باستخدام الخيار 1) وهذا رائع ، 2) سيكون الحل الأصغر (حذف 3 أسطر أو نحو ذلك) وليس من الواضح بالنسبة لي أن هناك حالات استخدام يكون فيها إنشاء نطاق جديد منطقيًا هناك ( ربما يكون هناك ، لكن يبدو أنه مخالف تمامًا للطريقة الموصوفة في التضمين في المستندات)


أو ، إذا كنت تريد أن تكون رائعًا للغاية ، فربما يمكنك تجنب كسر التغييرات تمامًا من خلال السماح لـ ngTransclude بتحديد ما إذا كان يريد نطاقًا جديدًا أم لا ، عبر قيمة السمة.

أفكار؟

لا أفهم الفرق بين 1 و 2!

فقط رأيي ، لكنني أعتقد أن التوافق مع الإصدارات السابقة سيكون مهمًا لهذا التغيير. أتخيل أن هناك الكثير من التطبيقات (بما في ذلك التطبيقات) التي عملت على حل مشكلة النطاق هذه باستخدام أشياء مثل $ الوالدين والنطاق. $$ childHead. أي تحديث يغير هذا السلوك سيسبب بعض الصداع (ولكن ربما يكون من الأفضل التسبب في الصداع عاجلاً وليس آجلاً).

ومع ذلك ، من وجهة نظر نظرية ، أعتقد أنه من المنطقي أكثر أن يكون لـ ng-tranclude نفس نطاق التوجيه افتراضيًا. الهدف من تضمين المحتوى هو أنك تريده جزءًا سلسًا من المحتوى. هناك أوقات يكون لدي فيها الكثير من تداخل التوجيهات مع الترانسات ، لكنني ما زلت أريدهم أن يتصرفوا كمكون واحد كبير. وجودهم بجميع النطاقات المختلفة يجعل هذا أمرًا صعبًا للغاية.

كل أفكاري فقط. على أقل تقدير ، مجرد وجود الخيار سيكون خطوة فوق الوضع الحالي. :)

troch للتوضيح ، نقوم بحقن التالي في وحدة تحكم ngTranscludeDirective:

        // This is the function that is injected as `$transclude`.
        function controllersBoundTransclude(scope, cloneAttachFn) {
          var transcludeControllers;

          // no scope passed
          if (arguments.length < 2) {
            cloneAttachFn = scope;
            scope = undefined;
          }

          if (hasElementTranscludeDirective) {
            transcludeControllers = elementControllers;
          }

          return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
        }

يستدعي التوجيه هذه الوظيفة بدون نطاق ، وعلى هذا النحو ، فإن النطاق غير محدد ... ثم في boundTranscludeFn ، يُنشئ نطاقًا جديدًا إذا كان transcludeScope خاطئًا ...

لذا ، ما أقوله هو ، بالنسبة إلى 1) ، يمكننا ببساطة تحديد النطاق الحالي لوظيفة التحويل (نظرًا لأن هذا التوجيه هو توجيه مجاور لأي شيء قد يكون له نطاق منعزل ، يجب أن يعطينا هذا النطاق الأصلي) .

بدلاً من ذلك ، 2) ، لا تنشئ نطاقًا جديدًا وافترض فقط النطاق الحالي (في createBoundTranscludeFn) (يحتمل كسر التغيير ، وربما كسر الكثير من الاختبارات).

كلاهما بسيط للغاية.

+1

+1

+1

بالتأكيد +1

+1

+1

+1

+1 من فضلك

أنت تعرف ما سيكون رائعًا ، على الرغم من أنه قد لا يكون قابلاً للتنفيذ تمامًا في الوقت المناسب لـ 1.3 ، يمكن أن تجعل وكلاء ES6 عملية التحويل لطيفة حقًا - مواكبة خصائص نطاق التضمين ، مع الحصول على الموضع الصحيح في التسلسل الهرمي.

إذا لم يكن تطبيق Proxy متاحًا ، فمن المحتمل أن يتراجع إلى النطاق. $ new () ، لذلك قد يكون من الممكن بالفعل جعل هذا العمل في وقت مبكر إلى حد ما. الشيء الصعب هو أن المواصفات متذبذبة بعض الشيء.

لذلك ، ستظل تحصل على نطاقات غير مرغوب فيها إذا كنت تريد أن يكون التسلسل الهرمي للنطاق نظيفًا للغاية ، ولكن على الأقل سيكون لديك التأثير الجانبي لربط البيانات الذي لا ينكسر بشكل غير متوقع. لا أدري.

+1

+1

+1

+1

+1

تضمين التغريدة

أو ، إذا كنت تريد أن تكون رائعًا للغاية ، فربما يمكنك تجنب كسر التغييرات تمامًا من خلال السماح لـ ngTransclude بتحديد ما إذا كان يريد نطاقًا جديدًا أم لا ، عبر قيمة السمة.

كل ما لا يحتوي على تغييرات كسر يحصل على تصويتي

+1

+1

+1

+1

+1

+1. لطالما تساءلت عن هذا السلوك. يعجبني هذا الحل من caitp:

تضمين التغريدة

أو ، إذا أردت أن تكون خياليًا للغاية ، فربما يمكنك تجنب كسر التغييرات تمامًا عن طريق السماح> ngTransclude بتحديد ما إذا كان يريد نطاقًا جديدًا أم لا ، عبر قيمة السمة.

+1

+1

+1

بالنسبة لأولئك الذين يشاهدون هذه المشكلة ، قمت بإنشاء توجيه ng-transclude "محسّن" ، تحدد قيمة السمة الخاصة به النطاق الداخلي المطلوب ويمكن أن يكون واحدًا من 3:

  • silbing - نطاق المحتويات
  • الأصل - نطاق المحتويات المتضمنة هو نطاق العنصر حيث يحدث التضمين.
  • تابع - نطاق المحتويات المتضمنة هو نطاق تابع لنطاق العنصر حيث يحدث التضمين.

استخدام المثال:

template: 
  '<div ng-transclude="parent">' +
  '</div>'

المثال الكامل

انظر هذا المربع للحصول على مثال للجميع.

يبدو الإخراج كما يلي:

image

مصدر

.config(function($provide){
    $provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
        // Remove the original directive
        $delegate.shift();
        return $delegate;
    }]);
})

.directive( 'ngTransclude', function() {
  return {
    restrict: 'EAC',
    link: function( $scope, $element, $attrs, controller, $transclude ) {
      if (!$transclude) {
        throw minErr('ngTransclude')('orphan',
         'Illegal use of ngTransclude directive in the template! ' +
         'No parent directive that requires a transclusion found. ' +
         'Element: {0}',
         startingTag($element));
      }

      var iScopeType = $attrs['ngTransclude'] || 'sibling';

      switch ( iScopeType ) {
        case 'sibling':
          $transclude( function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'parent':
          $transclude( $scope, function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'child':
          var iChildScope = $scope.$new();
          $transclude( iChildScope, function( clone ) {
            $element.empty();
            $element.append( clone );
            $element.on( '$destroy', function() {
              iChildScope.$destroy();
            });            
          });
          break;
      }
    }
  }
})

نظرًا لأن المشكلة التي أثارتها سابقًا في # 8609 قد تم إغلاقها كنسخة مكررة من سلسلة الرسائل هذه ، فأنا أعيد ذكرها هنا.

في رأيي ، فإن الطريقة الحالية التي يتم بها إنشاء نطاق للجزء المشتمل من DOM غير منطقية إلى حد كبير!
إنه يتعارض مع التدفق الطبيعي في الزاوية ، ويجب إصلاحه!

فيما يلي مقتطف من مشكلتي السابقة:

لقد أنشأت قطعة صغيرة لتوضيح

الجاني الرئيسي في هذا التوجيه

     function pane() {
        return {
           restrict: 'E',
           transclude: true,
           scope: {
              title: '@'
           },
           template: '<div style="border: 1px solid black;">' +
              '<div style="background-color: gray">{{title}} (isolate scope id: {{$id}})</div>' +
              '<ng-transclude></ng-transclude>' +
              '</div>'
        };
     }

عندما يفعل المستخدم شيئًا كهذا:

<form>
    <pane title='enter your name'>
         <input type='text ngModel='username'>
    </pane>
    <pane title='enter your token'>
         <input type='text ngModel='token'>
   </pane>


ستكون النتيجة مفاجئة للكثيرين ، وخاصة المستخدمين الجدد. وهذه حالة استخدام مبسطة (بشكل مفرط). حاول وضع عرض بعض رسائل التحقق هناك ؛)

هذه حالة استخدام مختلفة حيث بدأت هذه المشكلة ، لكنني أوافق على أنها نفس المشكلة بشكل أساسي!

من المنطقي أكثر أن تستبدل النطاق بعنصر dom بدلاً من ذلك. إذا كانت هناك حاجة فعلية إلى نطاق جديد ، فيمكن للمستخدم وضع ngController على العنصر ngTransclude أي حال. أو يمكن أن يكون هناك علامة اختيارية على ngTransclude تقوم بتشغيل واحد. يمكن استخدام هذا العلم لحل حالة الاستخدام # 5489 أيضًا. ويمكن استخدامه للحل المقدم أيضًا.

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

يبدو أن +1 التوجيهات المتضمنة تنشئ نطاقًا حتى عندما يُطلب منك عدم القيام بذلك. مثال http://plnkr.co/edit/Wn81IBkE87vtigXvjmIa؟p=preview

+1

+1

+1

+1

+1

+1

+1

+1

+ 1 - هل هناك أي حلول صالحة لهذا؟

nikkwong ، هناك أرض أظهرت حلاً بديلًا.

حسنًا ، حتى إذا وصل هذا فلن يكون حتى 1.5.x.

لكن قبل ذلك لدي قلق. السبب الأساسي لإنشاء نطاق جديد (أطلق عليه اسم شقيق النطاق المعزول ، أو بشكل أكثر دقة طفل النطاق الأصلي من حيث يأتي المحتوى المتضمن) هو منع تسرب الذاكرة.

قارن هذا المكبس:
http://plnkr.co/edit/3NVxdYGy1AFDvD0M2BYI؟p=preview

بإصدار يعيد استخدام النطاق من حيث يأتي التحويل الأصلي:
http://plnkr.co/edit/MXFz2awcqwQQ7R882Xwz؟p=preview

في الإصدار الثاني ، عندما تقوم بتبديل المحتوى المتضمن وإيقاف تشغيله ، يستمر عدد المراقبون في الارتفاع - تسرب للذاكرة.

petebacondarwin ، يجب أن يكون بالتأكيد نطاقًا جديدًا حتى يمكن تدميره مع مراقبيه. أعتقد أن معظم الحزن بشأن التحويل يرجع إلى أن ng-transclude سيخلق نطاقًا شقيقًا للنطاق المحتوي ، مما يجعل من غير الممكن الوصول إلى متغيراته من خلال الميراث النموذجي. أو انا مخطئ؟

لقد أعدنا توضيح أن المشكلة هي الطريقة التي يعمل بها الربط ذو الاتجاهين الزاوي ، ومن المؤكد أن ng-transclude ينشئ نطاقًا فرعيًا ، ولكن ستكون هناك مناسبات عديدة حيث ستجد نفسك في نفس الموقف ، على سبيل المثال ، ينشئ ng-تكرار نطاقًا فرعيًا. بعد النظر في الكود الزاوي ، أدركنا أن مشكلة الربط ثنائي الاتجاه لا تعمل مع سمة الكائن ، ولكنها تعمل بشكل جيد مع الكائنات نفسها

المشكلة: http://plnkr.co/edit/Wn81IBkE87vtigXvjmIa؟p=preview

الحل: http://plnkr.co/edit/KShClgQVwIjscXPzVRwR؟p=preview

إنه ليس مثاليًا ولكن معرفة هذا أدى إلى حل الكثير من مشكلاتنا ، فلا تقم أبدًا بإلزام اتجاهين على سمة

لاحظ أن هذا الحل قد اقترحه بالفعل مبيكوفسكي ، عندما أثار القضية ، أنا فقط أعطي مثالاً حيث استغرق الأمر بعض الوقت لمعرفة ذلك.

petebacondarwin سيكون فقط تسربًا مؤقتًا ، حتى يتم تدمير نطاق
لا تزال نقطة تحتاج إلى الاهتمام. ربما يكون التحذير الكبير في المستندات حول هذا التسرب المحتمل كافيًا لإصلاحه؟

حسنًا ، أي تسريب لـ JS يكون مؤقتًا فقط حتى تقوم بتحديث المتصفح ؛-)
في 8 سبتمبر 2015 ، الساعة 16:41 ، كتب "Sander Elias" [email protected] :

petebacondarwin https://github.com/petebacondarwin سيكون فقط ملف
تسرب مؤقت ، حتى يتم تدمير نطاق التثبيت. هنا (أ
plunk) [http://plnkr.co/edit/Q587WQnX0u0u7JjhtCxa؟p=preview] الذي يظهر
أنه إذا قمت بتبديل نطاق التعليق ، فسيحصل كل شيء
صدر على ما يرام.
لا تزال نقطة تحتاج إلى الاهتمام. ربما يكون Waring كبير في
مستندات حول هذا التسرب المحتمل قد تكون كافية لإصلاحه؟

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/angular/angular.js/issues/5489#issuecomment -138603298
.

SanderElias مستخدمو تطبيق Angular مشغولون من البداية إلى النهاية.
نحن نرى بالفعل أن استخدام الذاكرة يتزايد خلال اليوم ويجب أن نكون حذرين للغاية فيما نضعه على الصفحة ، فإن إدخال المزيد من التسريبات المحتملة أمر محفوف بالمخاطر.

troch - يُنشئ التضمين في الواقع نطاقًا فرعيًا للنطاق الذي تم العثور فيه على المحتوى المتضمن في الأصل. تم كسر هذا في بعض الإصدارات السابقة (بالتأكيد في الإصدار 1.2) حيث تم بدلاً من ذلك إنشاء عنصر فرعي للوالد لنطاق التوجيهات الحالي. هذا يعني أن التضمينات المتداخلة بعمق حصلت بالفعل على نطاق التضمين الخاطئ.

يجب أن أتفق مع petebacondarwin ، وحتى عندما لا يكون السلوك الحالي بديهيًا بنسبة 100٪ ، فإن السلوك الحالي يعمل بشكل أفضل لمنع أي تسرب. أنا أميل إلى إغلاق هذه المشكلة لأن Wont Fix

شيء جيد لقد تحولت إلى Ember منذ سنوات. :)

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات