2016-02-08 4 views
3

У меня есть две службы.Наблюдаемое событие и xhr.upload.onprogress

1) UploadService - отправить файл через в AJAX на сервер, а также получить и значение магазина прогресс (внутри обработчика событий xhr.upload.onprogress)

2) SomeOtherService работал с DOM, получить значение прогресса от первой услуги, отобразили его на панели результатов Bootstrap.

Потому что, xhr.upload.onprogress является asynchronious - я использую 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); 
    }); 
} 

Что это? Почему это произошло? Как я могу исправить использование Observable с событием onprogress без этого setInterval?

UPD: после того, как Тьерри TEMPLIER ответ внутри

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

я получил правильные значения, но значения динамически, только на 100% снова не обновляется внутри шаблона.

ответ

3

У меня нет полного кода, поэтому сложно дать вам точный ответ.

Тот факт, что вы говорите, что «во второй службе я получаю наблюдаемое значение только один раз, на 100%». заставляет меня думать, что это должно быть связано с использованием обещаний. Собственно говоря, обещания могут быть разрешены или отвергнуты один раз. Нет поддержки нескольких событий, противоречащих наблюдаемым.

Если вы можете разместить plunkr с кодом, я смогу его отладить, поэтому я должен предоставить вам более точный ответ.

Редактировать

Я создал plunkr (http://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=preview), чтобы проверить свой код, и кажется, что он работает. Меняя обещание на наблюдаемое, я получаю больше намеков о прогрессе.

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 

С обещанием, я получаю только два следа

+0

Я обновил свой ответ с plunkr ... –

+0

Спасибо, теперь внутри '$ .subscribe ( данные this.service.progress => { console.log ('прогресс =' + данные) }); ' У меня есть правильные значения, но эти значения не обновляются внутри шаблона. –

+0

важная вещь - внутреннее обновление шаблона шага на 100% –

1

Вы можете решить эту проблему путем обертывания zone.run(() => ...).Импорт NgZone, вводят его в конструкторе, и сделать что-то вроде этого:

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

выше то, что на самом деле работает с [email protected] Ниже я думаю, что я должен работать, но просто не работал в моих тестах.

Причина, по которой вы не видите обновления, заключается в том, что обнаружение изменений в Угловом 2 выполняется по ссылке, а ссылка на ваше наблюдаемое свойство не изменяется. Вы можете решить эту проблему, говоря Угловое, что он должен работать обнаружение изменений вручную следующим образом:

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 и увеличивает счетчик. Это изменение состояния приложения, и мы хотим, чтобы это нашло свое отражение в нашем представлении?

... нам нужен способ обнаружения изменений для всего пути дерева к компоненту, где произошло изменение. Угловой не может знать, какой путь он есть, но мы это делаем.

Мы можем получить доступ к компоненту ChangeDetectorRef через инъекцию зависимостей, которая поставляется с API под названием markForCheck(). Этот метод делает именно то, что нам нужно! Он отмечает путь от нашего компонента до тех пор, пока не будет проверен корень для следующего запуска обнаружения изменений.

Давайте впрыскивать его в наш компонент: constructor(private cd: ChangeDetectorRef) {} Тогда, скажите Угловая отметить путь от этого компонента до тех пор, пока корень не будет проверено:

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

Boom, вот оно!

Смежные вопросы