How can I maintain the state of the progress dialog throughout my Angular 2 application?
I would like to keep the state of the Md Dialog alive even by closing the dialog. So that I can keep the download status throughout the application. My plan is to keep the upload response in the service to keep the upload progress and the icon will show up in the toolbar. The dialog box reinitializes each time. How can I maintain the state of the download progress dialog throughout the application?
app.component.ts
import { Component, NgZone, Inject, EventEmitter } from '@angular/core';
import { NgUploaderOptions, UploadedFile, UploadRejected } from 'ngx-uploader';
import { MdDialog, MdDialogRef, MdDialogConfig } from '@angular/material';
import { Router } from '@angular/router';
import { UploadService } from './upload.service';
import './operators';
@Component({
moduleId: module.id,
selector: 'sd-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
temp:any;
dialogRef: MdDialogRef<DialogComponent>;
config: MdDialogConfig = {
disableClose: true
};
constructor(public dialog: MdDialog, private router: Router, public uploadService: UploadService ) {
this.temp = this.uploadService.getUpload();
}
openDialog() {
this.dialogRef = this.dialog.open(DialogComponent, this.config);
}
}
app.component.html
<md-progress-bar mode="determinate"
[value]="temp.progress.percent"
color="primary"
class="progress-bar-margins">
</md-progress-bar>
<span>{{temp.progress.percent}}%</span>
Dialog component
export class DialogComponent {
options: NgUploaderOptions;
response: any;
sizeLimit: number = 1024 * 1024 * 50; // 50MB
previewData: any;
errorMessage: string;
inputUploadEvents: EventEmitter<string>;
temp:any;
constructor(@Inject(NgZone) private zone: NgZone, public uploadService: UploadService) {
this.options = new NgUploaderOptions({
url: 'http://api.ngx-uploader.com/upload',
filterExtensions: false,
allowedExtensions: ['dsn'],
data: { userId: 12 },
autoUpload: false,
fieldName: 'file',
fieldReset: true,
maxUploads: 2,
method: 'POST',
previewUrl: true,
withCredentials: false
});
this.inputUploadEvents = new EventEmitter<string>();
}
startUpload(view:any) {
this.inputUploadEvents.emit('startUpload');
}
beforeUpload(uploadingFile: UploadedFile): void {
if (uploadingFile.size > this.sizeLimit) {
console.log('File is too large!');
this.errorMessage = 'File is too large! Please select valid file';
uploadingFile.setAbort();
}
}
handleUpload(data: any) {
setTimeout(() => {
this.zone.run(() => {
this.response = data;
this.uploadService.uploadData = data;
this.temp = this.uploadService.getUpload();
if (data && data.response) {
this.response = JSON.parse(data.response);
}
});
});
}
handlePreviewData(data: any) {
this.previewData = data;
}
}
upload.component.html
<button type="button" class="start-upload-button" (click)="startUpload()">Start Upload</button>
</div>
< <div *ngIf="previewData && !response">
<img [src]="previewData">
</div>
<div>
<md-progress-bar mode="determinate"
[value]="temp.progress.percent"
color="primary"
class="progress-bar-margins">
</md-progress-bar>
<span>{{temp.progress.percent}}%</span>
</div>
upload.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/Rx';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Rx';
@Injectable()
export class UploadService {
uploadData :any;
constructor() {
console.log('Global Service initialised');
}
getUpload() {
return this.uploadData;
}
}
source to share
Use the architectural flow pattern to create frontend interfaces:
https://facebook.github.io/flux/ (just read about it, don't actually use the facebook API).
The pattern has proven to be very useful for maintaining application state across multiple components, especially for large scale applications.
The idea is simple - in a stream architecture, data is always sent in one direction:
This is true even if an action is activated in the UI:
In your Angular2 app, the dispatcher is your Observers implemented in your service (any component that injects the service can subscribe to it) and the store is a cached copy of the data to aid in firing events.
Here's an example ToDoService that implements the Flux architecture:
import { Injectable } from '@angular/core';
import {Http } from '@angular/http';
import { BehaviorSubject, Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';
export interface ToDo {
id: number;
name:string;
isComplete: boolean;
date: Date;
}
@Injectable()
export class ToDoService {
public todoList$:Observable<ToDo[]>;
private subject: BehaviorSubject<ToDo[]>;
private store: {
todos: ToDo[];
}
public constructor(private http:Http) {
this.subject = new BehaviorSubject<ToDo[]>([]);
this.todoList$ = this.subject.asObservable();
this.store = {
todos: []
};
}
public remove(todo:ToDo) {
this.http.delete(`http://localhost/todoservice/api/todo/${todo.id}`)
.subscribe(t=> {
this.store.todos.forEach((t, i) => {
if (t.id === todo.id) { this.store.todos.splice(i, 1); }
});
let copy = this.copy(this.store).todos;
this.subject.next(copy);
});
}
public update(todo:ToDo): Promise<ToDo> {
let q = this.http.put(`http://localhost/todoservice/api/todo/${todo.id}`, todo)
.map(t=>t.json()).toPromise();
q.then(x=> {
this.store.todos.forEach((t,i) => {
if (t.id == x.id) { Object.assign(t, x); }
let copy = this.copy(this.store).todos;
this.subject.next(copy);
});
});
return q;
}
public getAll() {
this.http.get('http://localhost/todoservice/api/todo/all')
.map(t=>t.json())
.subscribe(t=> {
this.store.todos = t;
let copy = Object.assign({}, this.store).todos;
this.subject.next(copy);
});
}
private copy<T>(t:T):T {
return Object.assign({}, t);
}
}
Several things can be noticed about this service:
- The service keeps a cached copy of the data store
{ todos: ToDo[] }
- It provides Observable that components can subscribe (if interested)
- It uses a BehaviourSubject that is private to its implementation. A BehaviorSubject will give the initial value when signed. This is handy if you want to initialize an Observable with an empty array to start with.
- Whenever a method is called that mutates the data store (delete or update), the service forces the web service call to update its persistent store, and then updates the data store in the cache before releasing the updated list
ToDo[]
to all its subscribers - A copy data is emitted from the service to prevent unexpected data from propagating in the opposite direction (this is important to maintain the flow pattern).
Any component that DI introduces to Service has the ability to subscribe to the todoList$
observable.
The next component we use asynchronous channel instead of directly subscribe to todoList$
:
Component.ts
ngOnInit() {
this.todoList$ = this.todoService.todoList$;
}
Component.html
<li class="list-group-item" *ngFor="let item of todoList$ | async">
{{ item.name }}
</li>
Whenever a method is called that changes its internal storage, the service updates all of its components, regardless of which component initiated the change.
The Flux pattern is a great template for managing complex user interfaces and reducing communication between components. Instead, the communication between the Service and the Component, and the interaction is basically that the component subscribes to the service.
source to share