javascript - bootstrap - angularjs controller dependency



Come riconciliare la regola "usa sempre i punti con ngModel" di Angular con gli ambiti isolati? (2)

Pensare agli ambiti sta andando leggermente sulla strada sbagliata, e non penso che la transizione abbia molto a che fare con questo. Per farlo "correttamente", è necessario integrarsi con ngModelController . Ciò consente a eventuali parser e formattatori integrati successivi (che potrebbero contenere logica di convalida) per essere eseguiti nei momenti appropriati. È un po 'complicato perché ne hai 2: quello principale nell'applicazione e quello nel modello della direttiva, e ognuno ha 2 "pipeline" da integrare con:

  • valore del modello -> visualizza valore
  • visualizza valore -> valore del modello

Il valore di visualizzazione del genitore ngModelController viene quindi utilizzato come valore del modello del ngModelController interno. Quindi l'aspetto generale delle condutture è simile

  • valore del modello principale -> valore della vista genitore -> valore del modello interno -> valore della vista interna
  • valore della vista interna -> valore del modello interno -> valore della vista genitore -> valore del modello genitore

Per fare questo:

  • Assicurati di aver require: 'ngModel' nella definizione della direttiva, per avere accesso al genitore ngModelController

  • Le modifiche dal genitore ngModelController verso l'interno, vengono eseguite utilizzando il metodo $render del genitore ngModelController , utilizzando il suo $viewValue . Ciò garantisce che tutte le funzioni nei $formatters padre siano state eseguite.

  • Le modifiche avviate dall'utente dalla direttiva interna vengono eseguite aggiungendo una funzione al suo array $viewChangeListeners , che chiama $setViewValue sul genitore ngModelController . Per accedere a questo dall'ambito della funzione di collegamento, è necessario un modulo con nome e elementi di input. Un leggero fastidio è che il modulo è registrato solo nell'ambito della direttiva dopo che la sua funzione di collegamento della direttiva è stata eseguita, quindi è necessario un osservatore per accedervi.

  • Solo in caso di stranezze, assicurati che il modello in formInputText si trovi in ​​un oggetto. (Non sono sicuro che ciò sia tecnicamente necessario)

  • Quindi non è necessario avere il modello nell'oggetto scope della direttiva interna.

Mettendo insieme questo,

app.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      label: '@'
    },
    require: 'ngModel',
    link: function(scope, element, attrs, ngModelController) {
      scope.model = {};

      // Propagate changes from parent model to local
      ngModelController.$render = function() {
        scope.model.value = ngModelController.$viewValue;
      };

      // Propagate local user-initiated changes to parent model
      scope.$watch('form', function(form) {
        if (!form) return;
        form.input.$viewChangeListeners.push(function() {
          ngModelController.$setViewValue(form.input.$modelValue);
        });       
      });
    }
  };
});

E il suo modello sembra

<form-control label="{{label}}" ng-form name="form">
  <input type="text"
         class="form-control"
         name="input"
         ng-model="model.value">
</form-control>

Questo può essere visto lavorando su http://plnkr.co/edit/vLGa6c55Ll4wV46a9HRi?p=preview

Sto lavorando su un'app Angular che utilizza Bootstrap.

Per ridurre al minimo l'impronta Bootstrap sul mio codice HTML, ho introdotto due direttive per i moduli:

form-control.js

module.directive('formControl', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-control.tmpl.html',
    scope: {
      label: '@'
    },
    transclude : true
  };
});

form-control.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10"
       ng-transclude>
  </div>
</div>

Ho anche una manciata di "estensioni" per questa direttiva, per i vari campi di immissione dei moduli. per esempio:

forma-input-text.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      label: '@',
      value: '=ngModel'
    }
  };
});

modulo di ingresso-text.tmpl.html

<form-control label="{{label}}">
  <input type="text"
         class="form-control"
         ng-model="value">
</form-control>

app.html

<form-input-text label="Name"
                 ng-model="person.name">
</form-input-text>

Qui mi imbatto in un problema. Ci sono un certo numero di ambiti in gioco in questo esempio:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "John" // inherited from isolateScope
};

Se cambio il testo nella casella di testo di input, solo transcludeScope viene modificato:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "Alice" // overrides value from isolateScope
};

Questo perché <input> è associato direttamente a una proprietà di transcludeScope . transcludeScope.value viene modificato direttamente e l'ambito padre isolateScope non viene modificato. Pertanto, qualsiasi modifica del modello nell'input non ritorna mai ad appScope .

Quello che mi piacerebbe fare è creare un legame bidirezionale tra appScope.person.name e una proprietà nidificata di isolateScope , ad es. isolateScope.model.value .

Idealmente mi piacerebbe dichiarare la mia direttiva in questo modo:

forma-input-text.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      model: {
        label: '@',
        value: '=ngModel'
      }
    }
  };
});

Ciò consentirebbe alla parte transclusa di collegarsi a model.value , che renderebbe visibili le modifiche a isolateScope, che a sua volta propagherebbe le modifiche in isolateScope a appScope .

Questo utilizzo non sembra essere direttamente supportato da Angular.

Qualcuno può indicarmi una funzionalità di Angular che supporti questo caso d'uso o, in caso contrario, fornire una soluzione alternativa?

Modificare:

Per ora, la mia soluzione è incorporare il form-control del form-input-text nel modello form-input-text .

modulo di ingresso-text.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10">
    <input type="text"
           class="form-control"
           ng-model="value">
  </div>
</div>

Ciò elimina l'ambito figlio introdotto ng-transclude , ma duplica anche il markup, che è quello che speravo di refactoring in un unico posto.


Answer #1

Vorrei utilizzare un controllo personalizzato per il tuo caso, come descritto qui , rendendo le direttive personalizzate <form-input-*> true. Ha bisogno di un lavoro aggiuntivo. Per delineare una versione semplicistica di una soluzione:

forma-input-text.js

app.directive('formInputText', function() {
    return {
        restrict : 'E',
        template : '<form-control label="{{label}}"><input type="text" class="form-control" /></form-control>',
        scope: {
            label: '@'
        },
        require: 'ngModel',
        link: function(scope, elem, attrs, ngModel) {
            var input = angular.element(elem[0].querySelectorAll("input")[0]);

            ngModel.$render = function() {
                input.val(ngModel.$viewValue || '');
            };

            input.on('blur keyup change', function() {
                scope.$apply(read);
            });

            function read() {
                ngModel.$setViewValue(input.val());
            }
        }
    };
});

In breve, require ngModel e implementa i suoi metodi, come da documentazione. Il ngModel è solo un'altra direttiva applicata al tuo controllo e altre cose funzioneranno, ad esempio validatori personalizzati, ng-required e così via.

Un violino funzionante: http://jsfiddle.net/1n53q59z/

Ricorda che potrebbe essere necessario apportare qualche ritocco a seconda del tuo caso d'uso.





angularjs-scope