Attachments management with Angular 2

A common issue that we faced in our applications is implement a component to allow the management of the attachment upload.

We need to insert a file input field in the page, grab the change event of the field, extract the file and send it to a service.

Recently I have needed to implement this functionality with Angular 2, so I’m going to explain what I have done.

Services

First of all I implement two different services, one for the file metadata and one for the blob object.

Based on a recent post, I use a base class WebApi and I define the service url:


import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Attachment } from "./attachment.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class AttachmentService extends WebApi<Attachment> {
constructor(public http: Http) {
super("/api/attachments", http);
}
}

The referenced service is a simple Restful service.

The second one is a service for the blob upload:


import { Injectable } from "@angular/core";
import { Http, Headers, RequestOptions, Response } from "@angular/http";
import { Observable } from "rxjs/Observable";
import { FileBlob } from "./fileBlob.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class FileBlobService extends WebApi<FileBlob> {
constructor(public http: Http) {
super("/api/fileBlobs", http);
}

public DownloadFile(id: string) {
window.open("api/fileBlobs/GetFileBlob?id=" + id, '_blank');
}

public PostFile(entity: File): Observable<File> {
let formData = new FormData();
formData.append(entity.name, entity);

return this.http.post(this.url, formData).map(this.extractData).catch(this.handleError);
}
}

The PostFile method compose a HTML FormData object with the content of the file and post it to a specific WebApi.

The DownloadFile method is simplier and call a service in a new window that returns the content of the file.

The server-side method is look like this:


public class FileBlobsController : ApiController
{
private readonly Context _db = new Context();

[ResponseType(typeof(Guid))]
public async Task<IHttpActionResult> PostFileBlob()
{
if (!Request.Content.IsMimeMultipartContent())
throw new Exception();

var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);

HttpContent content = provider.Contents.First();
var fileName = content.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await content.ReadAsByteArrayAsync();

var fileBlob = new FileBlob()
{
Id = Guid.NewGuid(),
Name = fileName,
File = buffer
};

_db.FileBlobs.Add(fileBlob);
await _db.SaveChangesAsync();

return Ok(fileBlob.Id);
}
}

We have to use the MultiPartMemoryStreamProvider to retrieve the content of the file and store it in a specific table.

Component

We need two methods, the first one to download the existing attachment, the second one to add a new attachment:


import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Constants } from "../shared/commons";
import { Attachment } from "./attachment.model";
import { FileBlobService } from "./fileBlob.service";
import { AlertService } from "../core/alert.service";

@Component({
moduleId: module.id,
selector: "attachment",
templateUrl: "attachment.component.html"
})

export class AttachmentComponent {
@Input() placeholder: string;
@Input() name: string;
@Input() validationEnabled: boolean;
@Input() attachment: Attachment;
@Output() onSaved = new EventEmitter<Attachment>();
public fileBlob: File;

constructor (private fileBlobService: FileBlobService, private alertService: AlertService) {}

public DownloadAttachment() {
this.fileBlobService.DownloadFile(this.attachment.IdFileBlob);
}

public AddAttachment(event) {
let attachments = event.target.files;
if (attachments.length > 0) {
let file:File = attachments[0];
this.fileBlobService.PostFile(file).subscribe(
(res) => {
let id: string = Constants.guidEmpty;

if (this.attachment != null)
id = this.attachment.Id;

this.attachment = {
Id: id,
IdFileBlob: res.toString(),
Name: file.name,
Size: file.size
};

this.onSaved.emit(this.attachment);
},
(error) => this.alertService.Error(error));
}
}

...
}

The AddAttachment method deserves an explanation; it accepts an event parameter, fired by the file input filed of the ui when a new attachment is selected.

The method retrieves the file from the event and pass it as a parameter to the PostFile method that we have seen above.

Once saved, an object with the file metadata is created and passed with the onSaved event to the parent component, that it deal with the object:


export class InvoiceDetailComponent {
...

public onAttachmentSaved(attachment: Attachment) {
this.attachment = attachment;
}
}

Module

We define a feature module like this:


import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";

import { SharedModule } from "../shared/shared.module";
import { AttachmentComponent } from "./attachment.component";
import { AttachmentService } from "./attachment.service";
import { FileBlobService } from "./fileBlob.service";

let options: any = {
autoDismiss: true,
positionClass: 'toast-bottom-right',
};

@NgModule ({
imports: [
SharedModule,
HttpModule
],
exports: [
AttachmentComponent
],
declarations: [
AttachmentComponent
],
providers: [
AttachmentService,
FileBlobService
]
})

export class AttachmentModule {}

The module exports the component and provides the services discussed above.

View

We have to implement the view for the attachment module:

<div *ngIf="AttachmentIsNull()">
<label class="btn btn-primary" for="fileBlob">
<i class="fa fa-paperclip"></i> {{ "ATTACHINVOICE" | translate }}
<input id="fileBlob" type="file" [(ngModel)]="fileBlob" (change)="AddAttachment($event)" [required]="validationEnabled" style="display: none;" />
</label>
</div>
<div *ngIf="!AttachmentIsNull()">
<span *ngIf="attachment" (click)="DownloadAttachment()">{{attachment.Name}}</span>
<input type="button" class="btn btn-primary" value="Upload new" (click)="UploadNewAttachment()" />
</div>

In the view we have a file input field that bind the change event with the AddAttachment method.

The additional buttons allow us to clear the current attachment and upload a new one.

The last change is in the parent view:


<form #invoiceForm="ngForm">
<div class="form">
...
<div class="form-group">
<label for="attachment">{{ "ATTACHMENT" | translate }}</label>
<attachment placeholder="ATTACHMENT" name="attachment" [attachment]="attachment" (onSaved)="onAttachmentSaved($event)" [validationEnabled]="validationEnabled"></attachment>
</div>
</div>
</form>

We have added the attachment component in the view and we have binded the onSaved event, in order to retrieve the file metadata.

You can find the source code here.

 

 

 

 

Advertisements
Attachments management with Angular 2

Http service in Angular 2

Use the http service in Angular 1 meant to deal with promises and defer, because it was based on APIs exposed by the $q service.

Angular 2 makes a step ahead and the new implementation of the http service involves the observable pattern, with the using of RxJS javascript library.

Why this big change? Because an observable is a powerful way to observe the behavior of a variable and its changes, for example when we assign a value or change it.

What we can do to use the http service efficiently is implement a typescript class that exposes the CRUD operation on a generic entity, based on a Web API.

Base class

This class exposes five basic methods to do simple CRUD operations; the first step is import the libraries:


import { Http, Response, Headers, RequestOptions } from "@angular/http";
import { Observable } from "rxjs/Observable";

We import a bunch of objects of the http angular library and the observable library.

Now we can implement the class:


export class WebApi<T> {
protected url: string;
protected options: RequestOptions;

constructor(url: string, public http: Http) {
this.url = url;
let headers = new Headers({ "Content-Type": "application/json" });
this.options = new RequestOptions({ headers: headers });
}

public GetAll(): Observable<T[]> {
return this.http.get(this.url, this.options).map(this.extractData).catch(this.handleError);
};

public Get(id: string): Observable<T> {
return this.http.get(this.url + "/" + id, this.options).map(this.extractData).catch(this.handleError);
}

public Put(id: string, entity: T): Observable<boolean> {
return this.http.put(this.url + "/" + id, JSON.stringify(entity), this.options).map(this.extractResponseStatus).catch(this.handleError);
}

public Post(entity: T): Observable<T> {
return this.http.post(this.url, JSON.stringify(entity), this.options).map(this.extractData).catch(this.handleError);
}

public Delete(id: string): Observable<boolean> {
return this.http.delete(this.url + "/" + id, this.options).map(this.extractResponseStatus).catch(this.handleError);
}

protected extractData(res: Response) {
let body = res.json();
return body || {};
}

protected handleError(error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg);
return Observable.throw(errMsg);
}

private extractResponseStatus(res: Response) {
return res.ok;
}
}

This is a typed class, with it we are able to define an instance of the class for a specific entity.

All the methods return an Observable property, dealing with the http service; every observable response uses a method to return the json part of the response and another method to handle and log response errors.

This is a class that will be used for our services.

The service

A specific service extends the base class and it can add some specific methods.

The implementation is look like this:


import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Customer } from "./customer.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class CustomerService extends WebApi<Customer> {
constructor(public http: Http) {
super("/api/customers", http);
}
}

The service extends the WebApi typed class and injects the base url of the Web API and the http service instance.

Using

In a controller we can use the service to retrieve the list of the customers:


.....

import { CustomerService } from "./customer.service";
.....
export class CustomerComponent implements OnInit {
.....
constructor(private customerService: CustomerService, .....) {}

ngOnInit() {
.....
this.Load();
}

public Load() {
.....
this.customerService.GetAll().subscribe(
(data) => {
.....
this.customers = data;
.....
},
(error) => this.alertService.Error(error));
}
}

As you seen above, the GetAll method returns an observable property, so in order to retrieve the reponse, we need to subscribe to it.

The subscribe method can accepts two functions as parameters; the first one will be called if we’ll receive a successfully response; the second one will be executed if an error occurred.

The source code of this topic is available here.

 

Http service in Angular 2