Триггерное обновление представления компонентов из службы - Нет провайдера для ChangeDetectorRef
Я хотел бы обдумать вид моего приложения, вызванный событиями из службы.
Одна из моих служб внедряет ChangeDetectorRef. Компиляция работает, но я получаю сообщение об ошибке в браузере, когда приложение загружается: No provider for ChangeDetectorRef!
.
Я думал, что мне нужно добавить его в свой AppModule, но я не могу найти документацию, которая предполагает, что она находится в модуле, который я могу импортировать там. Я попытался добавить сам класс в массив импорта, но это вызвало ошибку. Я также получил ошибку, пытаясь добавить ее в массив поставщиков в модуле. Вот упрощенная версия моего сервиса:
import {Injectable, ChangeDetectorRef } from '@angular/core';
@Injectable()
export class MyService {
private count: number = 0;
constructor(private ref: ChangeDetectorRef){}
increment() {
this.count++;
this.ref.detectChanges();
}
И модуль приложения:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyService } from './my.service';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
providers: [ MyService ],
booststrap: [ AppComponent ]
})
export AppModule {}
UPDATE
С тех пор я попытался удалить мое использование ChangeDetectorRef, и у меня все еще есть та же проблема. Я предполагаю, что что-то не так, как я обновил конфигурацию System JS.
Я изначально создал приложение с angular -cli и пытался обновить Angular самостоятельно, так как они не обновили это. С окончательной версией Angular 2.0.0 они обновили angular -cli, чтобы использовать последнюю версию angular. Поэтому я попытаюсь использовать процедуры обновления и надеюсь, что это будет лучше.
Обновление 2
Обновление webpack/ angular -cli прошло хорошо. Теперь у меня есть приложение с Angular 2.0.0 и angular -cli 1.0.0-beta14. Я все равно получаю ту же ошибку в браузере. Я попытался удалить ChangeDetectorRef из службы, но на самом деле я этого не сделал. У меня было это в двух сервисах. Если я удалю его из обеих служб, мое приложение загрузится нормально и работает хорошо, за исключением тех случаев, когда я пытался использовать ChangeDetectorRef. Как только я добавлю его обратно в один из файлов, браузер жалуется, что не смог найти поставщика для него.
Я попробовал импортировать его в свой модуль, но это не модуль, поэтому транспилер жалуется. Я попробовал перечислить его провайдером в моем модуле, но у него нет свойства обеспечения, поэтому ретранслятор жалуется. аналогичные проблемы, если я попытаюсь поместить его в массив объявлений.
Ответы
Ответ 1
ChangeDetectorRef
здесь не используется. Он ищет изменения в заданном компоненте и его дочерних элементах.
В вашем случае Лучше использовать ApplicationRef
:
import {Injectable, ApplicationRef } from '@angular/core';
@Injectable()
export class MyService {
private count: number = 0;
constructor(private ref: ApplicationRef) {}
increment() {
this.count++;
this.ref.tick();
}
}
Я проверил это решение с помощью Observables
, и он работает без проблем:
import { ApplicationRef, Injectable } from '@angular/core';
import { Observable, ReplaySubject } from "rxjs/Rx";
import * as childProcess from 'child_process';
@Injectable()
export class Reader {
private output: ReplaySubject<string> = new ReplaySubject<string>(0);
constructor(private ref: ApplicationRef) {
var readerProcess = childProcess.spawn('some-process');
readerProcess.stdout.on('data', (data) => {
this.output.next(data.toString());
this.ref.tick();
});
}
public getOutput():Observable<string> {
return this.output;
}
}
и вот компонент, который использует его:
import {Component} from '@angular/core';
import { ReplaySubject, Observable } from "rxjs/Rx";
import { Reader } from './reader/reader.service';
@Component({
selector: 'app',
template: `
output:
<div>{{output}}</div>
`
})
export class App {
public output: string;
constructor(private reader: Reader) {}
ngOnInit () {
this.reader.getOutput().subscribe((val : string) => {
this.output = val;
});
}
}
Ответ 2
Другой вариант, если вы хотите инициировать изменение из службы, но позволяете компонентам контролировать, реагируют ли они, когда и как они реагируют, - настраивать подписки.
К вашим услугам добавьте EventEmitter
:
changeDetectionEmitter: EventEmitter<void> = new EventEmitter<void>();
Если в службе происходит что-то, что может потребовать запуска цикла обнаружения изменений, просто введите:
this.changeDetectionEmitter.emit();
Теперь в любых компонентах, которые используют этот сервис, если им нужно прослушивать возможные триггеры изменений, они могут подписаться и соответствующим образом реагировать:
this.myService.changeDetectionEmitter.subscribe(
() => {
this.cdRef.detectChanges();
},
(err) => {
// handle errors...
}
);
Мне нравится этот подход, потому что он оставляет контроль над обнаружением изменений компонентами, к которым он относится, но позволяет мне централизовать логику в сервисе. Службе не нужно ничего знать о пользовательском интерфейсе, она просто уведомляет любого, кто слушает, что-то изменилось.
Ответ 3
в последних версиях теперь является частью ядра, так что просто сделайте ссылку на ядро, но используйте необходимую переменную и сделайте трюк
import { TestBed } from '@angular/core/testing';
import { ChangeDetectorRef } from '@angular/core';
import { MyService } from './my.service';
describe('MyService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [ChangeDetectorRef] // <--- LOOK AT ME I'M A PROVIDER!
}));
it('should be created', () => {
const service: MyService = TestBed.get(MyService);
expect(service).toBeTruthy();
});
});
Надежда помогает
Ответ 4
Я знаю, что я давно этого ждал, но я попытался просто передать ChangeDetectorRef в Сервис через вызываемую функцию.
//myservice
import { Injectable, ChangeDetectorRef } from '@angular/core';
@Injectable()
export class MyService {
constructor(){}
increment(ref: ChangeDetectorRef, output: number) {
output++;
ref.detectChanges();
}
Компонент
import {Component} from '@angular/core';
import { MyService } from './myservice.service';
@Component({
selector: 'app',
template: '
output:
<div>{{output}}</div>
'
})
export class App {
public output: number;
constructor(public ref: ChangeDetectorRef, private myService: MyService) {}
ngOnInit () {
this.myService.increment(this.ref,output)
}
}
Ответ 5
Использовать ApplicationRef
- хорошее решение. У меня есть еще несколько решений:
Использование setTimeout
@Injectable()
export class MyService {
private count: number = 0;
constructor(){}
increment() {
setTimeout(() => this.count++, 0);
}
}
Использование Promise
@Injectable()
export class MyService {
private count: number = 0;
constructor(){}
increment() {
Promise.resolve().then(() => this.count++);
}
}
Оба решения вынуждают Angular запускать обнаружение изменений, если вы используете Zone.js для обнаружения изменений - это способ по умолчанию, и 99% приложений используют его.