63

我想更新我的应用程序视图,由服务中的事件触发。

我的一项服务注入了 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 {}

更新

从那以后,我尝试删除对 ChangeDetectorRef 的使用,但我仍然遇到同样的问题。我猜我更新系统 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的地方。一旦我将它添加回其中一个文件,浏览器就会抱怨无法找到它的提供者。

我尝试在我的模块中导入它,但它不是模块,所以编译器抱怨。我尝试在我的模块中将它列为提供程序,但它没有提供属性,因此转译器会抱怨。如果我尝试将其放入声明数组中,则会出现类似问题。

4

6 回答 6

63

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;
        });
    }
}
于 2016-10-16T21:28:05.407 回答
9

如果您想从服务触发更改但允许组件控制它们是否、何时以及如何响应,另一个选择是设置订阅。

在您的服务中,添加EventEmitter

changeDetectionEmitter: EventEmitter<void> = new EventEmitter<void>();

当服务中发生可能需要触发变更检测周期的事情时,只需发出:

this.changeDetectionEmitter.emit();

现在,在任何使用此服务的组件中,如果他们需要监听可能的更改触发器,他们可以订阅并做出适当的反应:

this.myService.changeDetectionEmitter.subscribe(
    () => {
      this.cdRef.detectChanges();
    },
    (err) => {
      // handle errors...
    }
  );

我有点喜欢这种方法,因为它将变更检测的控制权交给了它所属的组件,但允许我将逻辑集中在服务中。该服务不需要了解有关 UI 的任何信息,它只是通知正在收听的任何人发生了某些变化。

于 2018-02-11T21:20:33.850 回答
1

我知道我早就应该这样做了,但我只是尝试通过被调用的函数将 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)
    }
}
于 2019-08-19T12:46:51.783 回答
0

在最新版本中现在是核心的一部分,所以只需引用核心但使用所需的变量并且应该做技巧

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

希望有所帮助

于 2017-06-17T11:44:42.240 回答
-1

使用ApplicationRef是一个很好的解决方案。我还有几个解决方案:

  1. 使用setTimeout

    @Injectable()
    export class MyService {
      private count: number = 0;
      constructor(){}
    
      increment() {
        setTimeout(() => this.count++, 0);
      }
    }
    
  2. 使用Promise

    @Injectable()
    export class MyService {
      private count: number = 0;
      constructor(){}
    
      increment() {
        Promise.resolve().then(() => this.count++);
      }
    }
    

如果您使用 Zone.js 进行变更检测,这两种解决方案都会强制 Angular 触发变更检测——这是默认方式,99% 的应用程序都使用它。

于 2019-10-19T14:00:16.370 回答
-2

您需要在组件级别将服务添加到提供程序:

@Component({
  // ...
  providers: [MyService]
})
export class SomeComponent {
  // ...
}
于 2020-08-16T22:08:02.977 回答