Eigentlich müsste es heißen: Der Kampf des Dateiuploads gegen Angular.JS. Das Framework unterstützt uns dabei in keiner Weise. Wir müssen es an mehreren Stellen sogar austricksen.
Die Programmieraufgabe umfasst folgende Bauteile
- Webservice, der die Datei entgegennimmt und speichert
- HTML User Interface mit Input Type File
- JavaScript Code für Ajax und Scope
Um volle Kontrolle über die hochgeladenen Dateien zu haben, bezüglich Typ, Größe oder Speicherort, bleibt nur ein Service am Web Server. In diesem Fall ist dieser per ASP.NET Web API und VB.NET erstellt.
1: Public Class fileuploadController
2: Inherits ApiController
3: <HttpPost>
4: Public Sub UploadFile()
5: If HttpContext.Current.Request.Files.AllKeys.Any() Then
6: Dim f = HttpContext.Current.
Request.Files("UploadedImage")
7: If f IsNot Nothing Then
8: Dim fp = Path.Combine(
HttpContext.Current.Server.MapPath("~/upload"),
Path.GetFileName(f.FileName))
9: f.SaveAs(fp)
10: End If
11: End If
12: End Sub
13: End Class
Das UX wird aus optischen Gründen auf einen Button reduziert, der den Betriebssystem-File-Open-Dialog öffnet und beim Schließen den Upload anstößt. Dadurch können wir das UI per Bootstrap schöner stylen, da die leere Textbox des Type File nicht angezeigt wird. Allerdings muss ein zweiter Button das Click Event des Fileupload-Buttons auslösen.
Das geschieht in Zeile 6 und zeigt bereits eine Reihe von Problemen mit Angular. Eigentlich sollte der Onclick-Handler nicht im HTML-Code auftauchen. Die Alternative ist eine Angular-Direktive mit rund 15 Zeilen Code, mit der dann auch noch ein zusätzliches Attribut in das Input-Element gelegt werden muss. Das ist mehr Aufwand und der Code ist dadurch leider auch nicht besser lesbar.
Außerdem kann man leider die DOM-Selector-Eigenschaften nicht verwenden, auch wenn ein angular.element(‘#id’) uns dies glauben lässt. Damit dieser Code funktioniert, muss erst noch jQuery eingebunden werden, da JQLite diese Selektoren nicht beinhaltet. Dann könnte man aber gleich $(‘#id’) nutzen. Es drängt sich die Frage auf, warum das komplette jQuery Framework überhaupt Overhead erzeugen soll, wenn es die benötigte JavaScript-Funktion bereits gibt. Also wird hier document.querySelector verwendet, um das Click Event durchzureichen.
Noch schlimmer wird es, wenn man aus normalem JavaScript auf das Angular Framework zugreifen muss. Dies ist hier nötig, da die Value Eigenschaft, die die ausgewählte Datei beinhaltet, nicht per ng-model bindbar ist. Auch für dieses Problem lässt sich eine umfangreiche Direktive schreiben oder einfach das OnChange-Event auf ein Event des $Scope durchreichen (Zeile 4)
1: <body ng-controller="uploadController">
2: <label for="fileUpload"></label>
3: Select <input id="fileUpload" type="file"
style="visibility:hidden;position:absolute;top:0;left:0"
4: onchange="angular.element(this).scope().onChange(this)" />
5:
6: <input id="btnUploadFile" type="button" value="Upload File"
onclick="document.querySelector( '#fileUpload').click()" />
7: <img id="progress" src="img/upload-indicator.gif" ng-show="warten"/>
Zusätzlich wird noch ein animiertes GIF verwendet, das die Uploadaktivität visualisiert und hier durchaus elegant per ng-show an die Eigenschaft Warten des $scope gebunden wird.
Zuletzt muss noch die eigentliche Upload-Logik geschrieben werden. Dazu wird ein Formdata-Objekt erstellt und mit den Datei-Meta-Informationen und dem Stream gefüllt. Dann wird per $http der Post auf den Upload-Service angestoßen. Um ein Problem mit Angular und dem benötigten Multipart-Formdata-Header zu umgehen, fügen wir in Zeile 17-19 Workaround-Code ein.
1: var app=angular.module("App",[])
2: .controller("uploadController", function ($scope,$http) {
3: $scope.warten = false;
4: $scope.onChange = function (dateien)
5: {
6: $scope.warten = true;
7: var file = dateien.files[0];
8: var data = new FormData();
9: data.append('UploadedImage', file);
10: data.append('FileName', file.name);
11: data.append('fileSize', file.size);
12: data.append('fileType', file.type);
13:
14: $http
15: .post('/api/fileupload/uploadfile' , data,
16: {
17: transformRequest: angular.identity,
18: headers: { 'Content-Type': undefined }
19: })
20: .success(function () {
21: $scope.warten = false;
22: })
23: .error(function () {
24: $scope.warten = false;
25: alert('error');
26: });
27: };
28: });
Sie möchten mehr erfahren und in die Programmierung mit AngularJS einsteigen? Dann sind Sie bei diesem AngularJS Training richtig.“