2015-09-17 6 views
5

Ich arbeite an einer Anwendung, die Änderungen automatisch speichert, wenn der Benutzer etwas ändert, zum Beispiel den Wert eines Eingabefeldes. Ich habe eine autosave Direktive geschrieben, die zu allen Formularfeldern hinzugefügt wird, die Speicherereignisse automatisch auslösen sollten.Wie verwende ich den letzten gültigen ModelValue, wenn ein Modell ungültig wird?

Vorlage:

<input ng-model="fooCtrl.name" autosave> 
    <input ng-model="fooCtrl.email" autosave> 

Richtlinie:

.directive('autosave', ['$parse', function ($parse) { 

    return { 
     restrict: 'A', 
     require: 'ngModel', 
     link: function (scope, element, attrs, ngModel) { 

     function saveIfModelChanged() { 
      // save object containing name and email to server ... 
     } 

     ngModel.$viewChangeListeners.push(function() { 
      saveIfModelChanged(); 
     }); 
     } 
    }; 
    }]); 

Bisher dies alles funktioniert gut für mich. Wenn ich die Validierung jedoch in den Mix einfüge, z. B. indem ich das Eingabefeld als gültige E-Mail-Adresse validiere, wird der Modellwert auf undefined gesetzt, sobald der viewValue in eine ungültige E-Mail-Adresse geändert wird.

Was ich tun möchte, ist dies: Denken Sie an den letzten gültigen ModelValue und verwenden Sie dies beim automatischen Speichern. Wenn der Benutzer die E-Mail-Adresse als ungültig ändert, sollte das Objekt, das name und email enthält, weiterhin auf dem Server gespeichert werden. Mit dem aktuell gültigen name und dem letzten gültigen email.

Ich begann mit den letzten gültigen modelValue wie folgt zu speichern:

Vorlage mit Validierung hinzugefügt:

<input type="email" ng-model="fooCtrl.name" autosave required> 
    <input ng-model="fooCtrl.email" autosave required> 

Richtlinie mit Spar lastModelValue:

.directive('autosave', ['$parse', function ($parse) { 

    return { 
     restrict: 'A', 
     require: 'ngModel', 
     link: function (scope, element, attrs, ngModel) { 

     var lastModelValue; 

     function saveIfModelChanged() { 

      // remeber last valid modelValue 
      if (ngModel.$valid) { 
      lastModelValue = ngModel.$modelValue; 
      } 

      // save object containing current or last valid 
      // name and email to server ... 
     } 

     ngModel.$viewChangeListeners.push(function() { 
      saveIfModelChanged(); 
     }); 
     } 
    }; 
    }]); 

Meine Frage ist, wie man lastModelValue beim Speichern verwendet, aber vor den ungültigen Wert in der Ansicht servieren?

EDIT:

Eine weitere Möglichkeit, as suggested by Jugnu below würde Verpackung und den Bau in dem Validierer zu manipulieren.

Ich habe versucht, folgende: alle vorhandenen Validatoren wickeln und den letzten gültigen Wert erinnern, es wiederherstellen, wenn Validierungen fehlschlägt:

Object.keys(ngModel.$validators).forEach(function(validatorName, index) { 
    var validator = ngModel.$validators[validatorName]; 
    ngModel.$validators[validatorName] = createWrapper(validatorName, validator, ngModel); 
}); 

function createWrapper(validatorName, validator, ngModel){ 

    var lastValid; 

    return function (modelValue){ 

    var result = validator(modelValue); 

    if(result) { 
     lastValid = modelValue; 
    }else{ 
     // what to do here? maybe asign the value like this: 
     // $parse(attrs.ngModel).assign(scope, lastValid); 
    } 

    return result; 
    }; 
} 

Aber ich bin nicht sicher, wie entweder mit diesem Ansatz fortzusetzen. Kann ich den Modellwert ohne AngularJS einstellen und versuchen, diesen neu eingestellten Wert zu validieren?

+0

Angesichts der Tatsache, dass Sie auch das Back-End Ihrer App entwickeln, würde ich in Betracht ziehen, das gesamte Problem in das Back-End auszulagern. Wenn das Backend zufällig node.js wäre, würde ich einfach diese Werte 'v' auf meine Datenbank aktualisieren, für die 'v! == undefined' gilt, und mir einige Arbeitsstunden sparen. Ich verstehe, dass Sie dies möglicherweise nicht tun können oder dieses Projekt zu Lernzwecken durchführen, nur meine Gedanken über Dev-Effizienz. –

Antwort

2

ich eine einfache Richtlinie erstellt habe, die auf der ng-Modell Richtlinie als Wrapper dient und halten immer den neuesten gültigen Modellwert.Es heißt valid-ng-Modell und sollte die Verwendung von ng-Modell an Orten ersetzen, an denen Sie den neuesten gültigen Wert haben möchten.

Ich habe ein Beispiel Anwendungsfall here erstellt, ich hoffe, Sie werden es mögen. Irgendwelche Ideen für Verbesserungen sind willkommen.

Dies ist der Implementierungscode für valid-ng-model Direktive.

app.directive('validNgModel', function ($compile) { 
    return { 
     terminal: true, 
     priority: 1000, 
     scope: { 
     validNgModel: '=validNgModel' 
     }, 
     link: function link(scope, element, attrs) { 

     // NOTE: add ngModel directive with custom model defined on the isolate scope 
     scope.customNgModel = angular.copy(scope.validNgModel); 
     element.attr('ng-model', 'customNgModel'); 
     element.removeAttr('valid-ng-model'); 

     // NOTE: recompile the element without this directive 
     var compiledElement = $compile(element)(scope); 
     var ngModelCtrl = compiledElement.controller('ngModel'); 

     // NOTE: Synchronizing (inner ngModel -> outside valid model) 
     scope.$watch('customNgModel', function (newModelValue) { 
      if (ngModelCtrl.$valid) { 
      scope.validNgModel = newModelValue; 
      } 
     }); 

     // NOTE: Synchronizing (outside model -> inner ngModel) 
     scope.$watch('validNgModel', function (newOutsideModelValue) { 
      scope.customNgModel = newOutsideModelValue; 
     }); 
     } 
    }; 
}); 

Edit: Richtlinie Umsetzung ohne Isolat -umfang: Plunker.

+0

Ich spielte mit dem Plunker herum und es sieht sehr vielversprechend aus! Ich werde versuchen, es in unsere Anwendung zu integrieren und mit den Ergebnissen zurück zu kommen. Denkst du, es wäre problematisch/schwierig, die Anweisungen 'autosave' und' valid-ng-model' zusammenzuführen? –

+0

Danke, ich bin froh, dass es dir gefallen hat. Das Zusammenführen der beiden Richtlinien ist kein Problem, aber meiner Meinung nach handelt es sich um zwei verschiedene Anliegen, die ich nicht gerne mische. Aber wenn Sie denken, dass es besser wäre, dann können Sie sie zusammenführen, ich kann Ihnen auch dabei helfen. –

+0

Danke, aber ich denke, ich kann die Direktiven zusammenführen. Ich frage mich jedoch noch nach einem anderen Aspekt: ​​Die Direktive you erzeugt einen isolierten Bereich, den weder ngModel noch meine Autosave-Direktive tut. Wäre es ohne einen isolierten Bereich möglich? Müssen wir dem Bereich "customNgModel" zuweisen? –

2

Da Sie das gesamte Objekt für jede Feldänderung senden, müssen Sie den letzten gültigen Zustand dieses gesamten Objekts irgendwo aufbewahren. Anwendungsfall, an den ich denke:

  1. Sie haben ein gültiges Objekt { name: 'Valid', email: 'Valid' }.
  2. Sie ändern den Namen in ungültig; Die autosave Direktive, die am Namen eingegeben wird, kennt ihren eigenen letzten gültigen Wert, so dass das korrekte Objekt gesendet wird.
  3. Sie ändern die E-Mail auch ungültig.Die autosave Direktive, die am E-Mail-Eingang platziert wird, kennt ihren eigenen letzten gültigen Wert, aber nicht den des Namens. Wenn die letzten bekannten guten Werte nicht zentralisiert sind, wird ein Objekt wie { name: 'inalid', email: 'Valid' } gesendet.

So ist der Vorschlag:

  1. eine hygienisiert Kopie des Objekts Halten Sie bearbeiten. Mit sanitisiert meine ich, dass alle ungültigen Anfangswerte durch gültige unberührte ersetzt werden sollten (z. B. Nullen, Nullen usw.). Zeigen Sie diese Kopie als Controller-Mitglied an, z. fooCtrl.lastKnowngood.
  2. Lassen Sie autosave den letzten bekannten guten Zustand kennen, z.B. wie:

    <input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required /> 
    
  3. Behalten Sie den letzten bekannten guten lokalen Wert in diesem Objekt; verwende den ng-model Ausdruck, z.B. as:

    var lastKnownGoodExpr = $parse(attrs.autosave); 
    var modelExpr = $parse(attrs.ngModel); 
    
    function saveIfModelChanged() { 
        var lastKnownGood = lastKnownGoodExpr(scope); 
    
        if (ngModel.$valid) { 
         // trick here; explanation later 
         modelExpr.assign({fooCtrl: lastKnownGood}, ngModel.$modelValue); 
        } 
    
        // send the lastKnownGood object to the server!!! 
    } 
    
  4. Senden Sie das Objekt lastKnownGood.

Der Trick, seine Schwächen und wie sie verbessert werden kann: Objekt anders als der aktuelle Bereich Wenn der lokalen Modellwertes für das Objekt lastKnownGood Einstellung einen Kontext verwenden; Dieses Objekt setzt voraus, dass der Controller fooCtrl heißt (siehe Zeile modelExpr.assign({fooCtrl: lastKnownGood}, ...)). Wenn Sie eine allgemeine Richtlinie möchten, können Sie die Wurzel als ein anderes Attribut passieren, z.B .:

<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required 
    autosave-fake-root="fooCtrl" /> 

Sie auch einige Parsen der ng-model Ausdruck tun kann, sich um die erste Komponente zu bestimmen, z.B. Teilstring 0 → 1. Auftreten des Punktes (wieder stark vereinfachend).

Ein weiterer Mangel ist, wie Sie komplexere Pfade (im allgemeinen Fall), z. fooCtrl.persons[13].address['home'].street - aber das scheint nicht Ihr Anwendungsfall zu sein.


By the way, dies:

ngModel.$viewChangeListeners.push(function() { 
    saveIfModelChanged(); 
}); 

kann vereinfacht werden:

ngModel.$viewChangeListeners.push(saveIfModelChanged); 
+0

Danke, ich mag die Vorschläge! Das lässt mich denken: Wenn ich anfange, an dem Modellausdruck zu fummeln, könnte ich einen Proxy für das ursprüngliche Objekt erstellen. Ich würde dann Werte auf den Proxy setzen, wenn das ursprüngliche Modell ungültig wird und das Proxy-Objekt an den Server senden. –

+0

Das kommt der Idee nahe und könnte auch funktionieren, wenn ich es richtig mache - ein Beispiel würde helfen. –

1

Angular Standard Validierer nur Wert zuweisen, wenn seine gültige E-Mail modellieren, dass address.To zu überwinden Sie müssen die Standardvalidatoren überschreiben.

Weitere Referenz siehe: https://docs.angularjs.org/guide/forms#modifying-built-in-validators

Sie können eine directive erstellen, die Modellwert bis zu einem gewissen Umfang Variable zuweisen invalide und dann können Sie es verwenden.

Ich habe eine kleine Demo für die E-Mail-Validierung erstellt, aber Sie können sie auf alle anderen Validatoren erweitern.

Hier Geige: http://plnkr.co/edit/EwuyRI5uGlrGfyGxOibl?p=preview

+0

Wrapping und Manipulation der Build-in-Validatoren scheint eine gute Alternative zu sein. Ich habe versucht, diesen Weg zu gehen, bin aber geblieben. Könntest du mir bitte meinen Schnitt zur Frage ansehen? Hast du noch weitere Tipps? –

+0

Ich sehe das nicht als eine Option, das Überschreiben der Validatoren bedeutet, dass sie Validierungserfolg zurückgeben müssen, selbst wenn die Validierung nicht erfolgreich ist, um den Modellwert beizubehalten. Das würde ihre Funktionalität zerstören. Diese Zeile stammt aus dem Quellcode für ngModelCtrl und wird ausgeführt, nachdem alle Validatoren ausgeführt wurden: ctrl. $ ModelValue = allValid? modelValue: undefiniert; –

+0

@ TimBüthe Hoffe, das wird für dich funktionieren –