การปรับแต่งเทมเพลตภายในคำสั่ง


98

ฉันมีแบบฟอร์มที่ใช้มาร์กอัปจาก Bootstrap ดังต่อไปนี้:

<form class="form-horizontal">
  <fieldset>
    <legend>Legend text</legend>
    <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
        <p class="help-block">Supporting help text</p>
      </div>
    </div>
  </fieldset>
</form>

มีรหัสสำเร็จรูปจำนวนมากอยู่ในนั้นซึ่งฉันต้องการลดเป็นคำสั่งใหม่ - การป้อนรูปแบบดังต่อไปนี้:

<form-input label="Name" form-id="nameInput"></form-input>

สร้าง:

   <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
      </div>
    </div>

ฉันทำงานได้ดีมากผ่านเทมเพลตง่ายๆ

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {
                label: 'bind',
                formId: 'bind'
            },
            template:   '<div class="control-group">' +
                            '<label class="control-label" for="{{formId}}">{{label}}</label>' +
                            '<div class="controls">' +
                                '<input type="text" class="input-xlarge" id="{{formId}}" name="{{formId}}">' +
                            '</div>' +
                        '</div>'

        }
    })

อย่างไรก็ตามเมื่อฉันเพิ่มฟังก์ชันการทำงานขั้นสูงมากขึ้นทำให้ฉันติดขัด

ฉันจะรองรับค่าเริ่มต้นในเทมเพลตได้อย่างไร

ฉันต้องการแสดงพารามิเตอร์ "type" เป็นแอตทริบิวต์ที่ไม่บังคับในคำสั่งของฉันเช่น:

<form-input label="Password" form-id="password" type="password"/></form-input>
<form-input label="Email address" form-id="emailAddress" type="email" /></form-input>

"text"แต่ถ้าไม่มีอะไรจะระบุฉันต้องการที่จะเริ่มต้นกับ ฉันจะสนับสนุนสิ่งนี้ได้อย่างไร?

ฉันจะปรับแต่งเทมเพลตตามการมี / ไม่มีคุณลักษณะได้อย่างไร

ฉันยังต้องการที่จะสนับสนุนแอตทริบิวต์ "ที่จำเป็น" หากมีอยู่ เช่น:

<form-input label="Email address" form-id="emailAddress" type="email" required/></form-input>

หากrequiredมีอยู่ในคำสั่งฉันต้องการเพิ่มลงในสิ่งที่สร้างขึ้น<input />ในเอาต์พุตและไม่ต้องสนใจเป็นอย่างอื่น ฉันไม่แน่ใจว่าจะบรรลุเป้าหมายนี้ได้อย่างไร

ฉันสงสัยว่าข้อกำหนดเหล่านี้อาจก้าวไปไกลกว่าเทมเพลตทั่วไปและต้องเริ่มใช้ขั้นตอนการคอมไพล์ล่วงหน้า แต่ฉันไม่สามารถเริ่มต้นได้


ฉันเป็นคนเดียวที่เห็นช้างในห้อง :) -> จะเกิดอะไรขึ้นถ้าtypeตั้งค่าแบบไดนามิกผ่านการผูกเช่น type="{{ $ctrl.myForm.myField.type}}"เหรอ? ฉันตรวจสอบวิธีการทั้งหมดด้านล่างแล้วและไม่พบวิธีแก้ไขใด ๆ ที่จะใช้ได้ในสถานการณ์นี้ ดูเหมือนว่าฟังก์ชัน template จะเห็นค่าตามตัวอักษร ของแอตทริบิวต์เช่น แทนtAttr['type'] == '{{ $ctrl.myForm.myField.type }}' tAttr['type'] == 'password'ฉันงงงวย
Dimitry K

คำตอบ:


211
angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        compile: function(element, attrs) {
            var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
            element.replaceWith(htmlText);
        }
    };
})

6
สิ่งนี้ช้าไปหน่อย แต่ถ้าhtmlTextคุณเพิ่มที่ng-clickใดที่หนึ่งการแก้ไขเพียงอย่างเดียวจะถูกแทนที่element.replaceWith(htmlText)ด้วยelement.replaceWith($compile(htmlText))หรือไม่?
jclancy

@Misko คุณพูดถึงการกำจัดขอบเขต ทำไม? ฉันมีคำสั่งที่ไม่รวบรวมเมื่อใช้กับขอบเขตแยก
Syam

1
สิ่งนี้ใช้ไม่ได้หากhtmlTextมีคำสั่ง ng-transclude
Alp

3
น่าเสียดายที่ฉันพบว่าการตรวจสอบแบบฟอร์มดูเหมือนจะใช้ไม่ได้กับสิ่งนี้$errorแฟล็กบนอินพุตที่แทรกจะไม่ได้รับการตั้งค่า ฉันต้องทำสิ่งนี้ภายในคุณสมบัติลิงก์ของคำสั่ง: $compile(htmlText)(scope,function(_el){ element.replaceWith(_el); });เพื่อให้ตัวควบคุมของแบบฟอร์มรับรู้การมีอยู่ที่เกิดขึ้นใหม่และรวมไว้ในการตรวจสอบความถูกต้อง ฉันไม่สามารถทำให้มันทำงานในคุณสมบัติคอมไพล์ของคำสั่งได้
meconroy

5
เอาล่ะมันปี 2015 ออกมีและผมค่อนข้างแน่ใจว่ามีบางสิ่งบางอย่างที่ไม่ถูกต้องอย่างมากในการสร้างมาร์กอัปในสคริปต์ด้วยตนเอง
BorisOkunskiy

38

พยายามใช้โซลูชันที่ Misko เสนอ แต่ในสถานการณ์ของฉันแอตทริบิวต์บางอย่างซึ่งจำเป็นต้องรวมเข้ากับ html เทมเพลตของฉันเป็นคำสั่ง

น่าเสียดายที่คำสั่งทั้งหมดที่อ้างถึงโดยเทมเพลตผลลัพธ์ไม่ได้ทำงานอย่างถูกต้อง ฉันไม่มีเวลามากพอที่จะดำดิ่งสู่รหัสเชิงมุมและค้นหาสาเหตุที่แท้จริง แต่พบวิธีแก้ปัญหาซึ่งอาจเป็นประโยชน์

วิธีแก้ปัญหาคือการย้ายโค้ดซึ่งสร้างเทมเพลต html จากฟังก์ชันคอมไพล์เป็นเทมเพลต ตัวอย่างตามรหัสจากด้านบน:

    angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        template: function(element, attrs) {
           var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
             return htmlText;
        }
        compile: function(element, attrs)
        {
           //do whatever else is necessary
        }
    }
})

สิ่งนี้ช่วยแก้ปัญหาของฉันด้วยการคลิก ng-click ในเทมเพลต
joshcomley

ขอบคุณสิ่งนี้ได้ผลสำหรับฉันเช่นกัน ต้องการรวมคำสั่งเพื่อใช้แอตทริบิวต์เริ่มต้นบางอย่าง
martinoss

2
ขอบคุณฉันไม่รู้ด้วยซ้ำว่าเทมเพลตนั้นยอมรับฟังก์ชัน!
Jon Snow

2
นี่ไม่ใช่วิธีแก้ปัญหาชั่วคราว เป็นคำตอบที่ใช่สำหรับ OP การสร้างเทมเพลตตามเงื่อนไขขึ้นอยู่กับคุณลักษณะขององค์ประกอบคือวัตถุประสงค์ที่แน่นอนของฟังก์ชันเทมเพลตคำสั่ง / องค์ประกอบ คุณไม่ควรใช้คอมไพล์สำหรับสิ่งนั้น ทีม Angular สนับสนุนการเข้ารหัสรูปแบบนี้อย่างมาก (ไม่ใช้ฟังก์ชันคอมไพล์)
jose.angel.jimenez

นี่ควรเป็นคำตอบที่ถูกต้องแม้ว่าฉันจะไม่รู้ว่าเทมเพลตก็ใช้ฟังก์ชัน :)
NeverGiveUp161

5

คำตอบข้างต้นน่าเสียดายที่ไม่ได้ผล โดยเฉพาะอย่างยิ่งขั้นตอนการคอมไพล์ไม่มีสิทธิ์เข้าถึงขอบเขตดังนั้นคุณจึงไม่สามารถปรับแต่งฟิลด์ตามแอตทริบิวต์แบบไดนามิกได้ การใช้ขั้นตอนการเชื่อมโยงดูเหมือนจะให้ความยืดหยุ่นมากที่สุด (ในแง่ของการสร้างโดมแบบอะซิงโครนัสเป็นต้น) แนวทางด้านล่างกล่าวถึงที่:

<!-- Usage: -->
<form>
  <form-field ng-model="formModel[field.attr]" field="field" ng-repeat="field in fields">
</form>
// directive
angular.module('app')
.directive('formField', function($compile, $parse) {
  return { 
    restrict: 'E', 
    compile: function(element, attrs) {
      var fieldGetter = $parse(attrs.field);

      return function (scope, element, attrs) {
        var template, field, id;
        field = fieldGetter(scope);
        template = '..your dom structure here...'
        element.replaceWith($compile(template)(scope));
      }
    }
  }
})

ฉันได้สร้างส่วนสำคัญที่มีโค้ดที่สมบูรณ์มากขึ้นและเขียนวิธีการ


แนวทางที่ดี ขออภัยเมื่อใช้กับ ngTransclude ฉันได้รับข้อผิดพลาดต่อไปนี้:Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Alp

และทำไมไม่ใช้ขอบเขตแยกกับ 'field: "="'?
IttayD

ดีมากขอบคุณ! น่าเสียดายที่แนวทางการเขียนของคุณออฟไลน์ :(
Michiel

ทั้งส่วนสำคัญและการเขียนเป็นลิงก์ที่ตายแล้ว
binki

4

นี่คือสิ่งที่ฉันใช้

ฉันยังใหม่กับ AngularJS มากดังนั้นฉันอยากเห็นโซลูชันที่ดีกว่า / ทางเลือกอื่น ๆ

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {},
            link: function(scope, element, attrs)
            {
                var type = attrs.type || 'text';
                var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
                var htmlText = '<div class="control-group">' +
                    '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                        '<div class="controls">' +
                        '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                        '</div>' +
                    '</div>';
                element.html(htmlText);
            }
        }
    })

ตัวอย่างการใช้งาน:

<form-input label="Application Name" form-id="appName" required/></form-input>
<form-input type="email" label="Email address" form-id="emailAddress" required/></form-input>
<form-input type="password" label="Password" form-id="password" /></form-input>

10
ทางออกที่ดีกว่าคือ: (1) ใช้ฟังก์ชันคอมไพล์แทนการเชื่อมโยงฟังก์ชันและทำการแทนที่ที่นั่น เทมเพลตจะไม่ทำงานในกรณีของคุณเนื่องจากคุณต้องการปรับแต่ง (2) กำจัดขอบเขต:
Misko Hevery

@Misko ขอบคุณมากสำหรับคำติชม - คุณช่วยอธิบายได้ไหมว่าทำไมฟังก์ชันคอมไพล์ถึงได้รับการสนับสนุนให้ใช้ฟังก์ชันลิงก์ที่นี่
Marty Pitt

4
ฉันคิดว่านี่คือคำตอบจากdocs.angularjs.org/guide/directive : "การดำเนินการใด ๆ ที่สามารถใช้ร่วมกันระหว่างอินสแตนซ์ของคำสั่ง [เช่นการเปลี่ยนเทมเพลต DOM] ควรย้ายไปยังฟังก์ชันคอมไพล์ด้วยเหตุผลด้านประสิทธิภาพ"
Mark Rajcok

@Marty คุณยังสามารถผูกอินพุตที่กำหนดเองเข้ากับโมเดลได้หรือไม่? (เช่น<form-input ng-model="appName" label="Application Name" form-id="appName" required/></form-input>)
Jonathan Wilson

1
@MartyPitt จากหนังสือ "AngularJS" ของ O'Reilly: "เราจึงมีcompileขั้นตอนที่เกี่ยวข้องกับการเปลี่ยนเทมเพลตและlinkเฟสซึ่งเกี่ยวข้องกับการปรับเปลี่ยนข้อมูลในมุมมองตามบรรทัดเหล่านี้ข้อแตกต่างหลักระหว่างcompileและlinkฟังก์ชั่นในการสั่งก็คือcompileฟังก์ชั่นการจัดการกับเปลี่ยนแม่แบบของตัวเองและlinkฟังก์ชั่นการจัดการกับการทำให้การเชื่อมต่อแบบไดนามิกระหว่างรูปแบบและมุมมอง. มันอยู่ในขั้นตอนที่สองนี้ว่าขอบเขตจะแนบไปรวบรวมlinkฟังก์ชั่นและสั่งกลายเป็นสดผ่านข้อมูลผูกพัน "
Julian
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.