How can I maintain the state of dialog box with progress all over my Angular 2 application?
Asked Answered
B

1

2

I would like to keep the state of the Md Dialog alive even I close the dialog.So that I can keep the upload status active all over the application. My plan is to store the upload response in the service to maintain the upload progress and an icon will be given in the toolbar.The dialog box reinitializes everytime. How can I maintain the state of dialog box with upload progress all over 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;
    }
}
Boxboard answered 14/2, 2017 at 7:4 Comment(0)
S
2

Use the flux architectural pattern for building UI interfaces:

https://facebook.github.io/flux/ (just read about it, don't actually use the facebook API).

It turns out that the pattern is very useful for maintaining application state across multiple components - especially for large scale applications.

The idea is simple - in a flux architecture, data always flows in one direction:

enter image description here

This is true, even when there is an action triggered from the UI:

enter image description here

In your Angular2 application, the dispatcher are your Observables implemented on 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 emitting events.

Here is an example of a 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);
    }

}

There are several things to notice about this service:

  • The service stores a cached copy of the data store { todos: ToDo[] }
  • It exposes an Observable that components can subscribe to (if they are interested)
  • It uses a BehaviourSubject which is private to its implementation. A BehaviorSubject will emit an initial value when subscribed to. This is handy if you want to initialize the Observable with an empty array to begin with.
  • Whenever a method is called that mutates the data store (remove or update), the service makes a web service call to update its persistent store, it then updates its cached data store before emitting the updated ToDo[] list to all of its subscribers
  • A copy of the data is emitted from the service to prevent unexpected data changes from propagating in the opposite direction (this is important for maintaining the flux pattern).

Any component that DI injects the Service has an opportunity to subscribe to the todoList$ observable.

In the following component, we take advantage of the async pipe instead of subscribing to the todoList$ observable directly:

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 on the service that modifies its internal store, the service updates all of its component subscribers, regardless of which component initiated the change.

The Flux pattern is an excellent pattern for managing complex UIs and reducing the coupling between components. Instead, the coupling is between the Service and the Component, and the interaction is mainly for the component to subscribe to the service.

Stole answered 14/2, 2017 at 8:35 Comment(5)
Thanks for your response.Can you guide me to implement this on my scenario?Boxboard
@AmalShehu Have you managed to do it? I'm looking to accomplish something very similar (I've just posted again and awaiting for a response here github.com/jkuri/ngx-uploader/issues/252#issuecomment-298224867) I think pixelbits' answer would have been shorter and taken less time if he would just have given an ngx-uploader example :) Understanding flux architecture doesn't help me in this case, where I simply don't know how to decouple my file input from the service I would create. And as long as I can't do that, as soon as I navigate away from the uplaod component, I stop getting dataCombative
@Combative Yes, You can achieve by storing the data you want to persist in a behaviour subject on a service.In angular services are singleton.So it ie straight forward.You can check this repo github.com/amalshehu/data-persistence.gitBoxboard
I get the theory... but I'm afraid I don't know how to do that. If anybody knows a link to an example (ngx-uploader example), that would be great...Combative
^ an example that keeps track of uploads progress while navigating through the app (after the component that contains the ngFileDrop and ngFileSelect directives is destroyed)Combative

© 2022 - 2024 — McMap. All rights reserved.