Angular-Listen mit Hash filtern

Beim Stöbern in SPA-Anwendungsfällen bin ich der Idee verfallen, Listen anhand der URL bzw. den enthaltenen Hash-Werten zu filtern. Eine URL setzt sich laut RFC 3986 wie folgt zusammen.

foo://example.com:8042/over/there?name=ferret#nose \_/ \______________/\_________/ \_________/ \__/ | | | | | scheme authority path query fragment | _____________________|__ / \ / \ urn:example:animal:ferret:nose

Der letzte Teil, das Fragment, wird beim HTTP Request vom Browser nicht zum Server übermittelt. Dies macht sich unter anderem Angular.js zu Nutze, um ein clientseitiges Routing der Views durchzuführen. Das ähnelt ASP.NET MVC, ist aber nach dem # Zeichen, das wir hier als Hash Key bezeichnen. So ist es möglich, die Ergebnismenge direkt per Link anzuspringen.

Im folgenden Beispiel filtern wir mit einer Art Navigationsleiste eine Liste. Damit wir den Browser nicht überfordern, geben wir den Hashkey als Parameter an und führen somit AJAX Call Backs zum ASP.NET Web API basierten Service durch.

Der Service liefert Chatnachrichten anhand eines Chatrooms. Das VB.NET Web API Sample decodiert die URL um z.B. Sonderzeichen korrekt zu behandeln.

   1:   Function Getchatmessage(room As String) As IQueryable(Of chatmessage)
   2:       Dim r = HttpUtility.UrlDecode(room)
   3:       Return db.chatmessage.Where(Function(c) c.room.roomname = r)
   4:   End Function

 

Folgende UI bietet dem Benutzer drei Optionen:

image

Die Links werden in der Form ASPX-Seite in der FriendlyUrl Notation ohne Erweiterung, gefolgt vom Hashkey und dem Wert erzeugt.

image

Erste Lektion: Angular fügt nach dem Hashkey einen Slash (Schrägstrich) in die URL im Browser ein, obwohl im HTML Source keiner steht.

image

Ich habe dazu recherchiert und herausgefunden, dass es einen Hashbang Mode, oder HMTL5 Mode, gibt. Der genaue Grund ist mir noch unklar. Später wird ein JavaScript Replace das wieder gerade biegen.

Um die HTML-Inhalte zu erstellen, habe ich eine Server Rendering-Methode gewählt. Hier habe ich auf Webforms gesetzt. Dies ist einfacher und erlaubt auch besseres Caching, also schnellere Ausführung.

Vorbereitend definieren wir eine Angular-App samt dazu gehörigem Controller. Die Anzeige des Hash per Databinding {{}} dient Kontrollzwecken. Das Listview Web Server-Steuerelement binden wir per Model Binding an die Room Liste. Der VB.NET Codebehind Source Code findet sich im nächsten Listing.

Der Name des Rooms wird passend zur URL encodiert. Wenn der Benutzer diesen Link klickt, passiert faktisch nichts, außer dass die URL im Browser um den Hashwert ergänzt wird.

   1:  <body ng-app="App">
   2:      <form id="form1" runat="server">
   3:          <div ng-controller="chatController">
   4:              <div>Hash:{{target}}</div>
   5:              <div>
   6:                  <asp:ListView ID="roomlist" runat="server" 
ItemType="Concorde.room"
   7:                      SelectMethod="roomlist_GetData">
   8:                      <ItemTemplate>
   9:                          <a href='/chat#<%#HttpUtility.UrlEncode(Item.roomname)%>'>
  10:                              <%#Item.roomname%>
  11:                          </a>| 
  12:                  
  13:                      </ItemTemplate>
  14:                  </asp:ListView>

Dies ist der Inhalt der dazu gehörigen aspx.vb Datei. Es kommt ein Entity Framework Code First Model zum Einsatz.

   1:   Public Function roomlist_GetData() As IQueryable(Of room)
   2:          Dim db As New ConcordeContext
   3:          Return db.room
   4:   End Function

 

Nun kommt der spannende Teil, der Angular.js Controller. Dieser führt einen Callback per $http aus und übergibt dabei den Hashwert aus der URL als Parameter. Dabei hilft $location, das per Path (eigentlich sollte es hash() sein) den passenden Wert aus der URL liefert. Nach Aufruf des REST-Services stehen die JSON-Daten in der Variable data (Zeile 20) und werden direkt der Chatmessages-Eigenschaft im $Scope zugewiesen.

Das Besondere an diesem Angular-Beispiel ist, dass ein Watcher (Zeile 5) auf dem Viewmodell gesetzt wird, der die Änderung der URL als Event-Geber nutzt und den Service Call initiiert. Ohne diesen läuft der Controller nur einmal durch.

   1:  angular.module('App', [])
   2:    .controller('chatController',
function ($scope, $http,$location,$window) {
   3:      $scope.chatMessages = [];
   4:      $scope.location = $location;
   5:      $scope.$watch('location.absUrl()', function () {
   6:          $scope.target = $location.path(); 
   7:          $scope.ladeMessages();
   8:      }, true);
   9:      $scope.ladeMessages = function () {
  10:          if ($scope.target != null) { 
var para = $scope.target.replace('/', ''); };
  11:       
  12:          $http(
  13:       {
  14:           method: 'GET',
  15:           url: 'api/chatmessages',
  16:           params: {
  17:               room: para
  18:           }
  19:       })
  20:      .success(function (data, status) {
  21:          $scope.chatMessages =  data;
  22:      })
  23:      };
  24:   
  25:  });
  26:   

Nun fehlt noch der deklarative HTML5 Teil, der entweder eine Meldung für leere Daten oder die Liste anzeigt. Dies ist der einfachste Teil. Der Scope enthält die Liste der ChatMessages, die mit der ng-repeat-Direktive durchlaufen wird. Dies ähnelt einem ASP.NET Repeater Control, nur eben clientseitig.

   1:   <div ng-show="chatMessages.length==0">
   2:                keine Daten...
   3:   </div>
   4:    <div ng-repeat="msg in chatMessages">
   5:                      {{msg.Message}}
   6:   </div>
Kommentare sind geschlossen