【问题标题】:Observable and xhr.upload.onprogress event可观察的和 xhr.upload.onprogress 事件
【发布时间】:2016-05-18 17:08:42
【问题描述】:

我有两个服务。

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);
    });
}

那是什么?为什么会这样?如果没有 setInterval,我如何正确使用 Observable 和 onprogress 事件?

UPD:在 Thierry Templier 内部回答之后

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

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

【问题讨论】:

    标签: typescript angular rxjs


    【解决方案1】:

    我没有完整的代码,所以很难给你一个准确的答案。

    事实上,您说“在第二次服务中,我只得到一次观察值,100%。”让我觉得应该和promise的使用有关。事实上,promise 可以被解决或拒绝一次。不支持与可观察对象相反的多个事件。

    如果您可以发布带有代码的 plunkr,我将能够对其进行调试,因此我应该为您提供更准确的答案。

    编辑

    我创建了一个 plunkr (http://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=preview) 来测试您的代码,它似乎可以正常工作。通过将 promise 更改为 observable,我可以获得更多进度提示。

    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);
      });
    }
    

    这是我的痕迹:

    app.component.ts:18 progress = 21
    app.component.ts:18 progress = 44
    app.component.ts:18 progress = 71
    app.component.ts:18 progress = 100
    

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

    【讨论】:

    • 谢谢,现在在this.service.progress$.subscribe( data =&gt; { console.log('progress = '+data); }); 里面我得到了正确的值,但是模板里面的值没有更新。
    • 重要的事情 - 内部模板进度值再次更新为 100%
    • 好的。如何在组件中显示进度值?
    • 直接通过{{ uploadProgress }},其中up​​loadProgress - 第二个服务的属性。
    • Thierry,我也遇到了类似的问题。进度条不会自动更新。我必须点击。从控制台,进度数据正确显示。 stackoverflow.com/questions/37046757/…
    【解决方案2】:

    您可以通过使用zone.run(() =&gt; ...) 包装来解决此问题。导入NgZone,将其注入到你的构造函数中,然后执行如下操作:

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

    以上是 angular@2.0rc1 的实际工作。以下是我认为应该有效的方法,但在我的测试中根本不起作用。

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

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

    cd 需要像这样注入到您的构造函数中:

     constructor(private cd: ChangeDetectorRef) {}
    

    @angular/core 导入ChangeDetectorRef 之后。

    更多详情可以查看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
        })
      }
    

    砰,就是这样!

    【讨论】:

      猜你喜欢
      • 2015-10-18
      • 1970-01-01
      • 1970-01-01
      • 2019-08-15
      • 2017-12-12
      • 2012-07-24
      • 2019-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多