Observable和xhr.upload.onprogress事件

时间:2016-02-08 21:35:20

标签: typescript angular rxjs

我有两项服务。

1)UploadService - 通过ajax将文件发送到服务器,并获取并存储进度值(在xhr.upload.onprogress事件处理程序内)

2)SomeOtherService使用DOM,从第一个服务获取进度值,在Bootstrap进度条上显示。

因为,xhr.upload.onprogress是异步的 - 我在第一次服务中使用Observable

constructor () {
    this.progress$ = new Observable(observer => {
        this.progressObserver = observer
    }).share();
}

private makeFileRequest (url: string, params: string[], files: File[]): Promise<any> {
    return new Promise((resolve, reject) => {
        let formData: FormData = new FormData(),
            xhr: XMLHttpRequest = new XMLHttpRequest();

        for (let i = 0; i < files.length; i++) {
            formData.append("uploads[]", files[i], files[i].name);
        }

        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.response));
                } else {
                    reject(xhr.response);
                }
            }
        };

        xhr.upload.onprogress = (event) => {
            this.progress = Math.round(event.loaded / event.total * 100);

            this.progressObserver.next(this.progress);
        };

        xhr.open('POST', url, true);
        xhr.send(formData);
    });
}

Insise第二次服务,我订阅了这位观察员:

this.fileUploadService.progress$.subscribe(progress => {
    this.uploadProgress = progress
});

this.fileUploadService.upload('/api/upload-file', [], this.file);

现在我的问题:

该代码不起作用。在第二次服务中,我只获得一次观察值,为100%。

但是如果我插入(是的,带有空回调体)

setInterval(() => {
}, 500);

在makeFileRequest方法中 - 我将每500毫秒在第二个服务中获得进度值。

private makeFileRequest (url: string, params: string[], files: File[]): Promise<any> {
    return new Promise((resolve, reject) => {
        let formData: FormData = new FormData(),
            xhr: XMLHttpRequest = new XMLHttpRequest();

        for (let i = 0; i < files.length; i++) {
            formData.append("uploads[]", files[i], files[i].name);
        }

        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.response));
                } else {
                    reject(xhr.response);
                }
            }
        };

        setInterval(() => {
        }, 500);

        xhr.upload.onprogress = (event) => {
            this.progress = Math.round(event.loaded / event.total * 100);

            this.progressObserver.next(this.progress);
        };

        xhr.open('POST', url, true);
        xhr.send(formData);
    });
}
那是什么?为什么会这样?我怎样才能正确使用带有onprogress事件的Observable而没有setInterval?

UPD:在Thierry Templier回答之后

this.service.progress$.subscribe( data => { 
    console.log('progress = '+data); 
}); 

我得到了正确的值,但这些值没有动态更新到模板内部,只是再次100%。

2 个答案:

答案 0 :(得分:3)

我没有完整的代码,因此很难给出准确的答案。

事实上你告诉我“在第二次服务中我只能观察到一次观察到的价值,在100%。”让我觉得它应该与承诺的使用有关。事实上,承诺可以被解决或拒绝一次。没有任何与可观察者相反的事件的支持。

如果您可以使用代码发布一个plunkr,我将能够调试它,所以我应该为您提供更准确的答案。

修改

我创建了一个plunkr(http://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=preview)来测试你的代码,它似乎有效。通过将承诺变为可观察的,我获得了更多进展提示。

A Broadcast intent action sent by the download manager when a download completes so you need to register a receiver for when the download is complete:

To register receiver

registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

and a BroadcastReciever handler

BroadcastReceiver onComplete=new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent intent) {
        // your code
    }
};

You can also create AsyncTask to handle the downloading of big files

Create a download dialog of some sort to display downloading in notification area and than handle the opening of the file:

protected void openFile(String fileName) {
    Intent install = new Intent(Intent.ACTION_VIEW);
    install.setDataAndType(Uri.fromFile(new File(fileName)),"MIME-TYPE");
    startActivity(install);
}

you can also check the sample link

这是我的痕迹:

makeFileRequest(url: string, params: string[], files: File[]): Observable> {
  return Observable.create(observer => {
    let formData: FormData = new FormData(),
        xhr: XMLHttpRequest = new XMLHttpRequest();

    for (let i = 0; i < files.length; i++) {
        formData.append("uploads[]", files[i], files[i].name);
    }

    xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                observer.next(JSON.parse(xhr.response));
                observer.complete();
            } else {
                observer.error(xhr.response);
            }
        }
    };

    xhr.upload.onprogress = (event) => {
        this.progress = Math.round(event.loaded / event.total * 100);

        this.progressObserver.next(this.progress);
    };

    xhr.open('POST', url, true);
    xhr.send(formData);
  });
}

有了承诺,我只得到两条痕迹

答案 1 :(得分:1)

您可以使用zone.run(() => ...)换行来解决问题。导入NgZone,将其注入构造函数,然后执行以下操作:

constructor(private zone: NgZone) {
    this.service.progress$.subscribe(progress => {
        this.zone.run(() => this.uploadProgress = progress);
    });
}

以上是与angular@2.0rc1实际配合使用的内容。以下是我认为应该起作用的,但在我的测试中根本不起作用。

您没有看到更新的原因是因为Angular 2中的更改检测是通过引用执行的,并且对您的observable属性的引用不会更改。您可以通过告诉角度它应该手动运行更改检测来解决这个问题:

this.service.progress$.subscribe(progress => {
    this.uploadProgress = progress;
    this.cd.markForCheck();
});

其中cd需要像这样注入构造函数中:

 constructor(private cd: ChangeDetectorRef) {}
ChangeDetectorRef导入@angular/core

有关详细信息,请参阅this excellent article on Thoughtram。我已经复制了最相关的花絮,但整篇文章值得一读!

  

...让我们来看看这个组件:

@Component({
  template: '{{counter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {

  @Input() addItemStream:Observable<any>;
  counter = 0;

  ngOnInit() {
    this.addItemStream.subscribe(() => {
      this.counter++; // application state changed
    })
  }
}
  

... addItemStream的引用永远不会改变,因此永远不会对该组件的子树执行更改检测。这是一个问题,因为组件在其ngOnInit生命周期钩子中订阅该流并递增计数器。这是应用程序状态更改,我们希望在我们的视图中反映出来吗?

     

...我们需要的是一种检测树的整个路径到发生变化的组件的变化的方法。 Angular不知道它是哪条路径,但我们确实如此。

     

我们可以通过依赖注入访问组件的ChangeDetectorRef,它带有一个名为markForCheck()的API。这种方法正是我们所需要的!它标记从我们的组件到根目录的路径,以检查下一次更改检测运行。

     

让我们将它注入我们的组件:constructor(private cd: ChangeDetectorRef) {}然后,告诉Angular标记从此组件到要检查的根的路径:

  ngOnInit() {
    this.addItemStream.subscribe(() => {
      this.counter++; // application state changed
      this.cd.markForCheck(); // marks path
    })
  }
  砰的一声,就是这样!