Menyesuaikan template dalam Directive

98

Saya memiliki formulir yang menggunakan markup dari Bootstrap, seperti berikut:

<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>

Ada banyak kode boilerplate di sana, yang ingin saya kurangi menjadi direktif baru - form-input, seperti berikut:

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

menghasilkan:

   <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>

Saya memiliki banyak pekerjaan melalui template sederhana.

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>'

        }
    })

Namun, ketika saya menambahkan fungsionalitas yang lebih canggih, saya terjebak.

Bagaimana saya bisa mendukung nilai default di template?

Saya ingin mengekspos parameter "type" sebagai atribut opsional pada direktif saya, misalnya:

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

Namun, jika tidak ada yang ditentukan, saya ingin menggunakan default "text". Bagaimana saya bisa mendukung ini?

Bagaimana cara menyesuaikan template berdasarkan ada / tidak adanya atribut?

Saya juga ingin dapat mendukung atribut "diperlukan", jika ada. Misalnya:

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

Jika requiredada dalam direktif, saya ingin menambahkannya ke yang dihasilkan <input />dalam output, dan mengabaikannya sebaliknya. Saya tidak yakin bagaimana mencapai ini.

Saya menduga persyaratan ini mungkin telah melampaui template sederhana, dan harus mulai menggunakan fase pra-kompilasi, tetapi saya bingung harus mulai dari mana.

Marty Pitt
sumber
Apakah saya satu-satunya yang melihat gajah di kamar :) -> Bagaimana jika typediatur secara dinamis melalui pengikatan misalnya. type="{{ $ctrl.myForm.myField.type}}"? Saya memeriksa semua metode di bawah ini dan tidak dapat menemukan solusi apa pun yang akan berfungsi dalam skenario ini. Sepertinya fungsi template akan melihat nilai literal dari atribut mis. tAttr['type'] == '{{ $ctrl.myForm.myField.type }}'bukannya tAttr['type'] == 'password'. Aku bingung.
Dimitry K

Jawaban:

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);
        }
    };
})
Misko Hevery
sumber
6
Ini adalah sedikit terlambat, tetapi jika dalam htmlTextAnda menambahkan ng-clicksuatu tempat, akan hanya modifikasi untuk mengganti element.replaceWith(htmlText)dengan element.replaceWith($compile(htmlText))?
jclancy
@ Misko, Anda menyebutkan untuk menyingkirkan ruang lingkup. Mengapa? Saya memiliki arahan yang tidak dapat dikompilasi saat digunakan dengan cakupan yang terisolasi.
Syam
1
ini tidak berfungsi jika htmlTextberisi arahan ng-transclude
Alp
3
Sayangnya saya telah menemukan bahwa validasi formulir tampaknya tidak berfungsi dengan ini, $errortanda pada input yang dimasukkan tidak pernah disetel. Saya harus melakukan ini dalam properti link direktif: $compile(htmlText)(scope,function(_el){ element.replaceWith(_el); });agar pengontrol formulir mengenali keberadaannya yang baru terbentuk dan memasukkannya dalam validasi. Saya tidak bisa membuatnya bekerja di properti kompilasi direktif.
meconroy
5
Oke, ini tahun 2015 di luar sana dan saya cukup yakin ada sesuatu yang salah dalam menghasilkan markup dalam skrip secara manual .
BorisOkunskiy
38

Mencoba menggunakan solusi yang diusulkan oleh Misko, tetapi dalam situasi saya, beberapa atribut, yang perlu digabungkan ke dalam html template saya, adalah direktif itu sendiri.

Sayangnya, tidak semua arahan yang dirujuk oleh templat yang dihasilkan berfungsi dengan benar. Saya tidak punya cukup waktu untuk menyelami kode sudut dan mencari tahu akar penyebabnya, tetapi menemukan solusi, yang berpotensi dapat membantu.

Solusinya adalah memindahkan kode, yang membuat html template, dari kompilasi ke fungsi template. Contoh berdasarkan kode dari atas:

    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
        }
    }
})
Janusz Gryszko
sumber
Ini memecahkan masalah saya dengan ng-click yang disematkan di template
joshcomley
Terima kasih, ini juga berhasil untuk saya. Ingin membungkus direktif untuk menerapkan beberapa atribut default.
martinoss
2
Terima kasih, saya bahkan tidak menyadari bahwa templat tersebut menerima suatu fungsi!
Jon Snow
2
Ini bukan solusi. Itu adalah jawaban yang tepat untuk OP. Membuat templat secara bersyarat tergantung pada atribut elemen adalah tujuan yang tepat dari fungsi templat direktif / komponen. Anda tidak harus menggunakan kompilasi untuk itu. Tim Angular sangat mendorong gaya pengkodean ini (tidak menggunakan fungsi kompilasi).
jose.angel.jimenez
Ini harus menjadi jawaban yang benar, bahkan saya tidak sadar templat mengambil fungsi :)
NeverGiveUp161
5

Jawaban di atas sayangnya tidak cukup berhasil. Secara khusus, tahap kompilasi tidak memiliki akses ke cakupan, jadi Anda tidak dapat menyesuaikan kolom berdasarkan atribut dinamis. Menggunakan tahap penautan tampaknya menawarkan fleksibilitas paling besar (dalam hal membuat dom secara asinkron, dll.) Pendekatan di bawah ini membahas bahwa:

<!-- 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));
      }
    }
  }
})

Saya telah membuat intisari dengan kode yang lebih lengkap dan penulisan pendekatannya.

JoeS
sumber
pendekatan yang bagus. sayangnya saat menggunakan dengan ngTransclude saya mendapatkan error berikut:Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Alp
dan mengapa tidak menggunakan cakupan terisolasi dengan 'field: "="'?
IttayD
Sangat bagus, terima kasih! Sayangnya pendekatan tertulis Anda sedang offline :(
Michiel
Baik intisari maupun tulisan adalah tautan mati.
binki
4

Inilah yang akhirnya saya gunakan.

Saya sangat baru di AngularJS, jadi ingin melihat solusi yang lebih baik / alternatif.

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);
            }
        }
    })

Contoh penggunaan:

<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>
Marty Pitt
sumber
10
Solusi yang lebih baik adalah: (1) gunakan fungsi kompilasi daripada fungsi tautan dan lakukan penggantian di sana. Template tidak akan berfungsi dalam kasus Anda karena Anda ingin menyesuaikannya. (2) singkirkan ruang lingkup:
Misko Hevery
@MiskoHevery Terima kasih atas umpan baliknya - maukah Anda menjelaskan mengapa fungsi kompilasi lebih disukai daripada fungsi tautan di sini?
Marty Pitt
4
Saya rasa inilah jawabannya, dari docs.angularjs.org/guide/directive : "Setiap operasi yang dapat dibagi di antara contoh direktif [misalnya, mengubah template DOM] harus dipindahkan ke fungsi kompilasi untuk alasan kinerja."
Mark Rajcok
@Marty Apakah Anda masih dapat mengikat salah satu masukan khusus Anda ke model? (mis. <form-input ng-model="appName" label="Application Name" form-id="appName" required/></form-input>)
Jonathan Wilson
1
@MartyPitt Dari buku "AngularJS" O'Reilly: "Jadi kita punya compilefase, yang berhubungan dengan mengubah template, dan linkfase, yang berhubungan dengan pengubahan data dalam tampilan. Sejalan dengan itu, perbedaan utama antara compiledan linkfungsi dalam direktif adalah bahwa compilefungsi berurusan dengan transformasi template itu sendiri, dan linkfungsi berurusan dengan membuat hubungan dinamis antara model dan tampilan. Dalam fase kedua inilah cakupan dilampirkan ke linkfungsi yang dikompilasi , dan direktif menjadi hidup melalui pengikatan data "
Julian