Archiv für den Monat Juni 2014

AngularJs und ASP.NET MVC – Einführung – Part 4


Ich werde in diesem Beitrag kurz auf den entsprechenden Projektaufbau von ASP.NET MVC mit AngularJs eingehen und dann kurz die Struktur und den Aufbau einer einfachen AngularJs Anwendung beschreiben in Verbindung mit ASP.NET MVC. Ich verwende keine WebAPI zum Abrufen der Daten sondern ganz “normale” JSON Results aus dem Standard Controller.

Im folgenden Beispiel handelt es sich nur um einen Vorschlag wie man sein MVC Projekt aufbauen kann, wenn man zusätzlich noch AngularJs für die Aktualisierung seiner Views verwendet. Ich gehe dabei beim Projektaufbau nicht von einer Single Page Application aus. Denn ich verwende eine “normale” MVC Anwendung, in der ich auch weiterhin das Routing von MVC verwende um mir einen anderen View mit neuen Daten anzuzeigen. Ich verwende AngularJs nur um die Daten eines Views asynchron zu laden und bei Bedarf zu manipulieren. Das wichtigste bei der Strukturierung ist, allerdings, das man selbst mit der Namensgebung und dem Aufbau entsprechend “klar kommt” und auch später noch die Logik dahinter versteht.

Als erstes richtet man sich ein ASP.NET MVC Standard Projekt ein und installiert über NuGet das “Angular JS Core” Paket und das “AngularJS Locale” Paket. (“AngularJS Route” wird in meinem Beispiel nicht benötigt). Das “Angular JS Locale” Paket installieren wir nur, damit später auch die richtigen Trennzeichen bei den Zahlen verwendet werden. Diese beiden JavaScript Pakete “angular.js” und “angular-locale_de-de.js” fügen wir unserem JavaScript Bundle hinzu, welches wir auf jeder Seite laden.

Beispielstruktur für unsere AngularJs Anwendung in ASP.NET MVC.

image

Wir erstellen ein extra JavaScript Verzeichnis “ScriptsApp” in dem wir unsere Custom JavaScript Dateien für dieses Projekt unterbringen. Das Verzeichnis legt man im Root Knoten der Anwendung an und nicht unterhalb des bereits bestehenden “Scripts” Verzeichnisses, damit wir beim Aufklappen des Verzeichnisses auch nur unsere Custom Scripts sehen.

Das gute an AngularJs ist, das es bereits eine gewisse Struktur unterstützt und ich werde in meinem Beispiel die Daten bereits entsprechend der Struktur anlegen.

Als erstes gibt es ein so genanntes “Mainmodul” für jeden View in der man alle Angular Module registriert die man in seiner Anwendung verwenden möchte, mit “angular.module(…)”. In einer One Page App, gibt es hier im Besten Fall nur ein Mainmodule.

Unsere “app.index.js”:

angular.module('app.index', [
               'indexCtrl',
               'homePSrv'
]).run(function($log) {
    $log.log('app.index initialized');
});

Hinweis: Ich schreibe nur in meinen “Mainmodulen” als Prefix “app.”, dann weiß man später besser ob es sich um das jeweilige Hauptmodul für den jeweiligen View handelt. Außerdem heißen die JavaScript Dateinamen immer wie das entsprechend enthaltene Modul wie “app.index” und “app.index.js”. Dann kann man später die Abhängigkeiten leichter zusammenbauen, da man nur die Dateinamen “abschreiben” muss und nicht immer die jeweilige JavaScript Datei öffnen muss.

In unserem Beispiel bedeutet dies, das unsere “app.index” die folgenden Module verwendet “indexCtrl”, und “homePSrv”. Damit die entsprechenden Module gefunden werden können, müssen wir auch die entsprechenden JavaScript Dateien in unserem Index View einbinden.

@section scripts
{
    <script type="text/javascript" src="~/ScriptsApp/views/Home/app.index.js"></script>
    <script type="text/javascript" src="~/ScriptsApp/views/Home/indexCtrl.js"></script>
    <script type="text/javascript" src="~/ScriptsApp/services/homePSrv.js"></script> 
}

Hinweis: Wenn man sich mit ASP.NET MVC auskennt und man dann die Struktur und die Namen der Angular Module sieht, kann  dies durchaus zu Verwirrungen führen. Denn in Angular gibt es so genannte Service Module die aber auf ASP.NET MVC Seite die Daten aus dem Controller z.B. per JSON abrufen. Dann hat Angular aber auch Controller Module, die man auf ASP.NET MVC Seite eher mit dem Model vergleichen kann, auf das man in einem typisierten View zugreift.

Als nächstes definieren wir den Index Controller “indexCtrl” = “indexCtrl.js”

function indexCtrl($log, $scope, homePSrv) {

    //Auf dem Scope ein Personen Objekt anlegen
    $scope.person = {};
    $scope.person.firstname = "Squad";
    $scope.person.lastname = "Wuschel";
    $scope.person.age = 32;
    
    
    $scope.loadData = function() {
        //Vom Controller das die passende Personenliste abrufen
        homePSrv.GetDashboardPersons().then(function (data) {
            $scope.personList = data;
        });
    }

    $scope.listenFilter = "Ütz";
}

angular.module("indexCtrl", ["homePSrv"])
    .controller("indexCtrl", indexCtrl);

Hinweis: Der “$scope” in AngularJs ist wie die “Model” Variable in einem typisierten View in ASP.NET MVC. Denn alles was wir im $scope definieren/ablegen, können wir “direkt” im HTML Markup unserer AngularJs Anwendung abrufen, nur das wir nicht wie in ASP.NET MVC “Model.meineVariable” für den Zugriff schreiben müssen, sondern wir können im Markup direkt auf “meineVariable”, oder in unserem Beispiel auf “person” oder “personList” zugreifen. Der $scope kann per Dipendency Injection auch nicht an einen Service übergeben werden, sondern “nur” an einen Controller.

und unseren Service für den HomeController “homePSrv” = “homePSrv.js”

function homePSrv($http, $log) { 
      this.log = $log, this.http = $http; 
}

homePSrv.prototype.GetDashboardPersons = function () {
    return this.http.get('/Home/GetDashboardPersons').then(function (result) {
        return result.data;
    });
}

angular.module("homePSrv", [])
     //dieser aufruf ist bereits auf minify von Js vorbereitet,
    //denn hier werden die Parameter noch als Stringnamen 
    //in der passenden Reihenfolge mit "übergeben", damit 
    //Angular auch nach dem Minify noch weiß welche Parameter 
    //noch Injected werden sollen
    .service("homePSrv", ['$http', '$log', homePSrv]);

Einzelne Module werden in Angular genau wie unser “MainModul” angelegt, d.h. mit der gleichen Funktion “module” definiert und dann wird ein “controller” angehängt oder bei einem Service, wird ein “service” angehängt und entsprechend initialisiert mit einem Namen und einer zugehörigen Funktion und ihren Parametern.

Alle Parameter die in AngularJs verwendet werden, werden von AngularJs mit der Hilfe von Dipendency Injection (DI) übergeben, d.h. in der Funktion “indexCtrl” wird $log, $scope und homePSrv von Angular per DI übergeben. Wobei es sich bei den Parametern mit “$” um AngularJs interne Parameter/Variablen handelt. Damit Angular weiß welche individuellen Parameter übergeben werden sollen, werden diese hinter dem Modulnamen aufgelistet. In diesem Beispiel wollen wir auf das Modul “homePSrv” zugreifen und geben den entsprechenden Modulnamen an und übergeben dann den Parameter einfach mit in unserer zugehörigen Controller Funktion.

Im Folgenden kurz der ASP.NET MVC Controller den wir über unseren Server “homePSrv” abfragen.

 public class HomeController : Controller
 {
     public ActionResult Index()
     {
         return View();
     }

     public JsonResult GetDashboardPersons()
     {
         List<PersonDashboardModel> models = new List<PersonDashboardModel>();
         models.Add(new PersonDashboardModel() { Age = 22, Price = 3478, Firstname = "Jonas", Lastname = "Müller"});
         models.Add(new PersonDashboardModel() { Age = 42, Price = 5446, Firstname = "Mustava", Lastname = "Ürkütz"});
         models.Add(new PersonDashboardModel() { Age = 26, Price = 12456, Firstname = "Ützgür", Lastname = "Meier"});
         models.Add(new PersonDashboardModel() { Age = 66, Price = 2531, Firstname = "Peter", Lastname = "Neubert"});
         models.Add(new PersonDashboardModel() { Age = 22, Price = 3456, Firstname = "Atze", Lastname = "Schröder"});
         return Json(models, JsonRequestBehavior.AllowGet);
     }
 }

Hinweis: Um die Benennung der Module so einfach wie möglich zu gestalten, heißt bei mir der Modulname genauso wie der zugehörige Controller- oder Servicename die dann über DI aufgerufen werden. Im Folgenden ein Beispiel um genau zu sehen welcher Name wozu benötigt wird.

//MainModule
angular.module("app.meineApp", ["ctrl.meinController", "srv.meinService", "srv.deinService"]);

//Passender Controller
angular.module("ctrl.meinController", ["srv.meinService", "srv.deinService"])
    .controller("CtrlDiName", function ($log, SrvDiName, SrvDiDeinName) {
    $log.log("CtrlDiName wurde initialisiert");
});

//Passende Services
angular.module("srv.meinService", [])
    .service("SrvDiName", function($log) {
        $log.log("SrvDiName wurde initialisiert");
});

angular.module("srv.deinService", [])
    .service("SrvDiDeinName", function ($log) {
        $log.log("SrvDiDeinName wurde initialisiert");
    });

Auf der JavaScript Seite ist alles wichtige definiert, jetzt fehlt nur noch das passende Markup im HTML um unsere AngularJs Anwendung zum Leben zu erwecken.

Dafür gibt es so genannte Angular “directives”, diese fangen immer mit “ng-NameDerDirective” an, z.B.: “ng-app” oder “ng-controller”. Eine Direktive ist im Prinzip nichts anderes wie ein HTML Attribute das mit “ng” beginnt, wenn es sich um eine Standard Angular Direktive handelt. Diese Direktiven werden dann von Angular entsprechend erkannt und es wird die zugeordnete “Funktion/Automatisierung” ausgeführt. Für eigene Direktiven sollte man sich ein eigenes Kürzel ausdenken, um Missverständnisse zu vermeiden.
Dabei ist es wichtig das die “ng-app” Direktive nicht verschachtelt werden kann, denn Sie definiert das Mainmodul der Anwendung und in diesem Fall handelt es sich hier um “app.index” in der alle weiteren Module enthalten sind, die für unsere AngularJs Anwendung benötigt werden.

Unser “Index.cshtml” View sieht fertig, dann folgendermaßen aus.

<div ng-app="app.index" ng-controller="indexCtrl">
    <div class="row">
        <div class="col-md-12">
            <h2>Scope Person anzeigen</h2>
            <p>Vorname: {{ person.firstname }} | Nachname: {{ person.lastname }}</p>
            <p ng-bind="'Alter: ' + person.age"></p>
        </div>
    </div>

    <div class="row">
        <div class="col-md-12">
            <h2>Personen per Ajax abrufen: <button type="button" class="btn btn-default" ng-click="loadData()" >Daten laden</button> </h2>
            <br/>
            <input type="text" class="form-control" ng-model="listenFilter" placeholder="Bitte Filter hier eingeben" />
            <br/>
            <br/>
            <ul ng-repeat="pers in personList | filter:listenFilter">
                <li> {{pers.Firstname | lowercase}}, {{pers.Lastname | uppercase}} -> Alter: {{pers.Age}} und Preis: {{pers.Price | currency}} </li>
            </ul>
        </div>
    </div>
</div>

Nach dem wir die  “ng-app” Direktive festgelegt haben, legen wir den Controller fest der verwendet werden soll mit “ng-controller” und hier verwenden wir den “indexCtrl”. Controller Direktiven können im Gegensatz zur App Direktive verschachtelt werden. Findet AngularJs die aktuelle $scope Variable nicht im aktuellen Controller $scope-Kontext, geht Angular die Controller schritt für schritt zurück und sucht die $scope Variable ebenfalls in allen anderen Controllern bis zum $rootscope.

Hinweis: Der $rootscope ist die Unterste Ebene im $scope, auf die jeder Controller zugriff hat, wenn man also etwas Anwendungsweit zur Verfügung stellen möchte, dann kann man auch den $rootscope per DI übergeben und seine Variable oder Funktion dort definieren. Bzw. in den Minimalbeispielen, die man im Netz findet, in denen kein JavaScript Code sondern nur etwas Markup verwendet wird, hier legt AngularJs selbst alles im $rootscope ab. ACHTUNG bevor man etwas im $rootScope ablegt, sollte man vorher auch übergelgt haben, ob man das ganze nicht auch in einer Factory die als Singleton Instanziert wird ablegen kann. Im $rootScope sollte man so wenig wie möglich ablegen!

Wenn man die App und den Controller definiert hat, kann man auf die $scope bzw. $rootscope Variablen zugreifen. Dafür gibt es wieder unterschiedliche Direktiven, die einfachste ist die “ng-bind” Direktive. Diese Direktive kann über “ng-bind” oder über das Markup “{{scopeVariable}}” die Daten entsprechend darstellen und ist für das einfache Anzeigen von Daten gedacht. In unserem Beispiel zeigen wir hier die Daten vom Person Objekt an.

Hinweis: Das Anzeigen von Modeldaten über die doppelt geschweiften Klammern, sollte man versuchen zu vermeiden und primär ng-bind zum Anzeigen der Daten verwenden und in unserem Fall oben einfach ein span Tag nehmen und hier ng-bind verwenden.

Um die Daten per Ajax nachzuladen, muss erst noch die Funktion “loadData” ausgeführt werden. Dafür bietet Angular die Direktive “ng-click” an, hier können wir einfach die Funktion angeben die bei einem Klick ausgeführt werden soll, in unserem Fall ist es die Funktion “loadData” die wir auf dem $scope definiert haben und diese Funktion lädt dann die entsprechenden Daten aus dem MVC Controller und legt diese auf dem $scope in der Variable “personList” ab.

Listen können mit der Direktive “ng-repeat” durchlaufen werden und man kann auf jedes einzelne Objekt zugreifen und die Daten entsprechend ausgeben. Wir verwenden hier wieder das passende “ng-bind” Markup und zusätzliche Standardausgabefilter für eine formatierte Ausgabe der Werte, wie z.B. “lowercase” oder “currency”. Es ist problemlos möglich auch eigene Filter in AngularJs zu schreiben und dann hier anzugeben.

Außerdem wenden wir einen Standard Filter auf die Liste selbst an um die Daten zu filtern mit “listenFilter”, die Variable wird bereits mit dem Wert “Ütz” vordefiniert und ist an das Eingabefeld mit der Direktive “ng-model” gebunden. Die ng-model Direktive stellt eine Zwei Wege Bindung dar, denn jeden Wert den wir hier eintragen wird auch direkt in unserem Model der zugewiesenen Variable “listenFilter” hinterlegt und direkt auf den Filter der Liste angewendet.

Die Euro Zeichen und die passenden Dezimal- und Tausendertrennzeichen werden nur entsprechend angezeigt, da wir auch die Internationalisierung von Angular mit eingebunden haben – “angular-locale_de-de.js”

image

Das war es dann auch schon, denn so einfach kann es sein Daten mit AngularJS im DOM anzuzeigen. Sollten mir Fehler bei der Erläuterung unterlaufen sein, freue ich mich natürlich über einen entsprechenden Kommentar, denn ich bin aktuell auch noch recht neu in AngularJs.

Das ganze Beispiel kann man auch von meinem Codeplex Account herunterladen oder anschauen.

AngularJs – Debugging mit Chrome z.B. “Uncaught object” – Part 3


Eine der wichtigsten Themen beim Entwickeln ist das Debuggen bzw. die Ausgabe von Fehlermeldungen. Daher habe ich mich am Anfang etwas schwer getan bei AngularJs. Denn wenn man mit Chrome (v35.0.x) entwickelt und AngularJs v1.2x verwendet, gibt es anscheinend einen Bug, der verhindert das Fehlermeldungen auf der Konsole entsprechend ausgegeben werden oder nur die Fehlermeldung “Uncaught object” erscheint. Im Firefox besteht dieses Problem nicht, hier wird eine entsprechend aussagekräftige Fehlermeldung ausgegeben.

Da ich aber zum Entwickeln immer nur Chrome verwendet habe, war ich anfangs über keine bzw. über die nichtssagenden Fehlermeldungen nicht gerade begeistert. Hier kann man nur darauf hoffen das der Bug bald gefixt wird oder man erweitert vorläufig die “angular.js” Datei um eine Zeile, um zumindest auch “verschluckte” Fehlermeldungen im Chrome anzuzeigen, hier fehlt dann zwar der entsprechende Stacktrace, aber meist hat dies bereits für die Lösung ausgereicht.

Man erweitere die “minErr” Funktion von AngularJs um eine “console.error(message)” Zeile um eine passende Meldung auszugeben.

...
for (i = 2; i < arguments.length; i++) {
   message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
     encodeURIComponent(stringify(arguments[i]));
}
  //erweitert um die Meldung direkt auf der Konsole auszugeben
  console.error(message);

  return new Error(message);
};

Hinweis: Wenn die zusätzliche Zeile zum Anzeigen der Fehlermeldungen eingebunden ist, kann es sein das man den folgenden Fehler sieht: “[$injector:nomod] Module ’ngLocale‘ is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.” die scheint aber ebenfalls ein Bug zu sein und hat keine mir bekannten Auswirkungen auf ein funktionierendes AngularJs Programm.

Man kann natürlich auch einfach Firefox zum Entwickeln verwenden, aber für Chrome stehen noch zwei weitere Tools zur Verfügung um sich z.B. den aktuellen scope anzeigen zu lassen.

Dabei handelt es sich zum einen um “AngularJs Batarang”, was aber anscheinend nicht weiter entwickelt wird. Aber eventuell kann es einem doch hier und da helfen. Wenn man Batarang installiert hat, findet man es direkt in der Developer Toolbar von Chrome als “AngularJs” Tab.

image

Das andere Tool nennt sich “ng-inspector” und kann ebenfalls im Chrome installiert werden. Wenn der ng-inspector installiert ist, findet man ihn rechts neben der Adressleiste.

image

Der ng-inspector zeigt alle ng-elemente an und wenn man ein Element anklickt wird in der Konsole das entsprechende Element/Objekt ausgegeben. Das Tool scheint sich auch noch in der Entwicklung zu befinden, denn das github Projekt wird aktuell noch regelmäßig aktualisiert.

image

Was mir auch schon öfter weitergeholfen hat, ist die Ausgabe von Variablen direkt im HTML mit “{{varname}}” diese Syntax lässt sich aber noch entsprechend erweitern um zum Beispiel JsonDaten entsprechend Formatiert im Browser mit AngularJS anzuzeigen für das Debugging. Dafür einfach das folgende Code Snipped verwenden:

<pre>{{ctrl.ViewModel | json}}</pre>

und schon werden Json Objekte perfekt formatiert angezeigt.

Häufige Anfängerfehler mit AngularJS

wenn man mit AngularJS beginnt, fragt man sich oft, warum passiert den nichts und warum wird nichts angezeigt oder warum stehen die Plain “{{varname}}” Tags noch im HTML. Dann hatte ich meist eine der folgenden Probleme zu überwinden:

  • die “ng-app” Direktive wurde nicht im HTML definiert bzw. es wurde das falsche Modul in “ng-app” zugeordnet
  • die “ng-app” Direktive wurde ausversehen doppelt bzw. verschachtelt registriert
  • ich habe vergessen den “ng-controller” zu definieren und damit war auch der richtige $scope nicht vorhanden
  • ein neues Modul “angular.module…” muss auch im zentralen app Modul hinzugefügt/registriert werden
  • wenn man für jedes Modul eine eigene JavaScript Datei anlegt, hat man evtl. vergessen die neue JavaScript Datei mit einzubinden
  • Direktiven werden in camelCase Schreibweise definiert, werden aber im HTML Code mit Bindestrich aufgerufen “camel-case”
  • Wenn man externe AngularJS Komponenten verwendet wie z.B. AngularUI, nicht vergessen das diese auch im zentralen app Modul für unsere Anwendung mit definiert werden müssen. Da diese sonst nicht gefunden werden und damit auch nicht funktionieren.

Quelle:

http://www.congral.com/2014/05/29/have-you-already-encountered-the-uncaught-object-exception/

http://ng-inspector.org/

AngularJs – ASP.NET MVC Datetime JSON Serialize und Anzeigen mit Hilfe einer Date Direktive – Part 2


Wie bereits im Part 1 erwähnt, hier ein weiterer Beitrag über AngularJs und die Verwendung einer Date Direktive.

Wenn man als Webentwickler bereits das das Wort “Datum” hört, weiss man hier wird es höchstwahrscheinlich Probleme geben. Leider hat sich hier auch mit ASP.NET MVC 4/5 nichts geändert, denn hier wird weiterhin der Microsoft Serializer für JSON verwendet und dies ist NICHT der gleiche wie bei der WebAPI (Newtonsoft.Json). Dies bedeutet, das Datumswerte standardmäßig nicht im ISO (WebAPI) Format, sondern als “/Date(12334534534)/” serialisiert werden und dieses Format kann AngularJs nicht standardmäßig “umwandeln”.

Ich habe mich für eine Lösung auf .NET Seite entschieden und habe im Controller das JsonResult überschrieben und serialisiere meine JSON Daten jetzt mit “Newtonsoft.Json”. Dadurch wird das Datum im Standard ISO Format übermittelt z.B. “2014-05-26T22:14:25.8104782+02:00”.

Erstellen einer neuen JsonResult Klasse, die für die Serialisierung “Newtonsoft.Json” verwendet.

/// <summary>
/// JsonResult überschreiben - Damit wir hier ein ISO Datumsformat zurückgeben können.
/// ACHTUNG es muss ebenfalls eine neue BasisKlasse für Controller angegeben werden "BaseController"
/// 
/// http://wingkaiwan.com/2012/12/28/replacing-mvc-javascriptserializer-with-json-net-jsonserializer/
/// </summary>
public class JsonNetResult : JsonResult
{
    public JsonNetResult()
    {
        Settings = new JsonSerializerSettings
                       {
                           ReferenceLoopHandling = ReferenceLoopHandling.Error,
                           DefaultValueHandling = DefaultValueHandling.Populate,
                           MaxDepth = 100,
                           ObjectCreationHandling =  ObjectCreationHandling.Auto
                           
                       };
    }

    public JsonSerializerSettings Settings { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && 
            string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            throw new InvalidOperationException("JSON GET is not allowed");

        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;

        if (this.ContentEncoding != null)
            response.ContentEncoding = this.ContentEncoding;
        if (this.Data == null)
            return;

        var scriptSerializer = JsonSerializer.Create(this.Settings);

        using (var sw = new StringWriter())
        {
            scriptSerializer.Serialize(sw, this.Data);
            response.Write(sw.ToString());
        }
    }
}

Um im Controller weiter wie gewohnt auf “JsonResult” zugreifen zu können, kann man noch eine Abstrakte Basisklasse anlegen, in der man “JsonResult” überschreibt.

public abstract class BaseController : Controller
{
    protected override JsonResult Json(object data, string contentType,
        Encoding contentEncoding, JsonRequestBehavior behavior)
    {
        return new JsonNetResult
        {
            Data = data,
            ContentType = contentType,
            ContentEncoding = contentEncoding,
            JsonRequestBehavior = behavior
        };
    }
}

Jetzt erhalten wir in JavaScript ein ISO formatiertes Datum. Wenn wir dieses Datum mit Hilfe von Angular Bearbeiten wollen und im Eingabefeld nicht das komplette ISO Format stehen soll muss man eine Direktive für das Anzeigen eines Datums erstellen. Wir verwenden hierfür zusätzlich noch die JavaScript Bibliothek momentJs um das ISO Datum in der AngularJs Direktive entsprechend zu parsen und das passende Format auszugeben.

Die Direktive selbst wird einfach per Attribut auf unserem input Feld eingebunden und wandelt für die Anzeige das ISO Datum in das Format um, welches wir angegeben haben und in das Model selbst wird wieder ein ISO Formatiertes Datum zurückgeschrieben. In meinem Beispiel verliert man hier natürlich die Uhrzeit die wir anfangs übergeben haben.

angular.module("app.datetimeDirectives", [])
       .directive("datetimeInputTwoWayFilter", function ($log) {
           return {
               //Damit das ISO Format Datum was wir per JSON von MVC bekommen
               //auch im Standard Format angezeigt wird, kann man keinen Standard
               //Filter verwenden bei "ng-model". Sondern man muss über die Direktive gehen.
               //Die direktive wird als Attribut beim passenden Inputfeld gesetzt.
               //Wir verwenden momentJs um das Datum aus dem ISO Format zu parsen und dann
               //wieder entsprechend anzuzeigen beim "formater" und beim "parser" geben wir wieder
               //das passende ISO Format an unser Model zurück.
               require: "ngModel",
               link: function (scope, element, attrs, ngModelCtrl) {
                   //http://java.dzone.com/articles/parsers-and-formatters-custom
                   ngModelCtrl.$parsers.push(function (data) {
                       //convert data from view format to model format
                       var time = new moment(data, "DD.MM.YYYY");
                       //Wieder ins ISO Format zurückkonvertieren
                       return time.format(); //converted
                   });

                   ngModelCtrl.$formatters.push(function (data) {
                       //das Datum vom ISO in das unten angegebene Format überführen.
                       var time = new moment(data);
                       data = time.format("DD.MM.YYYY");
                       //convert data from model format to view format
                       return data; //converted
                   });
               }
           }
       })

Verwendet wird die Direktive dann folgendermaßen

<input type="text" ng-model="DataModel.Startdatum" datetime-input-two-way-filter class="form-control" name="inputStartDatum" placeholder="Startdatum">

Im Eingabefeld für “Startdatum” sehen wir unsere Direktive live in Action und im Eingabefeld “Enddatum” habe ich die Direktive entfernt und hier wird das ISO Formatierte Datum angezeigt.

image

Wenn man den Standard Serialisierer verwendet von Microsoft, dann sieht das ganze ohne Direktive z.B. folgendermaßen aus

image

MomentJs würde zwar auch diesen Datumstring wieder fehlerfrei in ein “richtiges” Datum parsen, ich würde hier persönlich aber das ISO Format vorziehen, da man hier auch direkt beim Debuggen ein konkretes Datum “auslesen” kann und nicht auf den String “/Date(1234…)/” angewiesen ist.

(Es ist also auch möglich ohne zusätzlichen Aufwand auf .NET Seite das Datum entsprechend anzuzeigen – dies habe ich auch erst beim Schreiben dieses Beitrags gemerkt – momentJs scheint hier ganze Arbeit zu leisten.)