即使 @Injectable 提供了 providedIn: 'root',服务也会创建另一个实例。我怎样才能确保服务是单例的?

Service creates another instance even though @Injectable has providedIn: 'root'. How can I make sure the service is a Singleton?

我有 2 个服务位于 /services,AccountService 和 AuthenticationService。我还有 2 个页面:accounts.page 和 add-accounts.page 位于 /accounts 中,add-accounts.page 位于 /accounts/add-accounts 中。 我在 accounts.page 和 add-accounts.page 中都使用了 AccountService,它从本地 json 加载帐户并将它们存储在 Ionic/Storage 中。所以计划是使用该服务从存储中获取帐户并添加新帐户。我在服务中有一个 ReplaySubject 来检测我想在帐户 html 页面中使用的新更改,以显示帐户数组。 我在 Angular 版本 7

中使用 Ionic 4

在四处修改后,我发现该服务正在获取 2 个实例,因此我没有向最初开始使用的 ReplaySubject 添加新的更改。我通过向我的服务的构造函数添加 console.log 并在控制台中查找发现了这一点,我在从帐户页面转到添加帐户页面后发现了相同 console.log 的 2 个实例。所以我在添加帐户页面中对 accountService 所做的更改是另一个实例,并且在第一个实例中没有更改。

当我使用我的其他服务 (AuthenticationService) 对此进行测试时,我没有在控制台中获得 2 console.log 输出。 我仔细检查了 @Injectable 装饰器是否具有 providedIn: 'root' 的正确语法,并在测试了删除 providedIn: 'root' 部分并在 [= 中导入服务的旧 angular 方法之后88=] 并将其添加到提供者数组中,我得到一个 NullInjectorError: No provider for AccoutService Error 我无法解决。

我尝试使用 ionic g service services/newService 创建一个新服务并制作了一个函数来输出一些文本。我还创建了一个 BehaviourSubject 来查看和跟踪状态变化。它看起来像这样:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NewAccoutService {

  authenticationState = new BehaviorSubject(false);
  constructor() {
    this.authenticationState.next(true);
    console.log('This text should only happen once');
  }
  consoleLog() {
    console.log('did it work?');
  }
}

我还尝试做的是创建一个新的空白离子项目并创建 2 个页面和 2 个服务。在那个项目中,他们表现得像他们应该的那样,只被创建一次。

在与我的同事交谈后,他们建议创建一个具有相同文件结构的新测试项目,并将代码 copy/paste 放入新项目中,这可能会解决问题,因为某些文件可能未创建。然而,这并没有解决,所以可能还有其他问题。

account.service.ts

这里是AccountService的相关代码

import { Injectable } from '@angular/core';
@Injectable(
  { providedIn: 'root' }
)
export class AccountService {
  changed: ReplaySubject<Account[]> = new ReplaySubject(1);
constructor(
    private http: HttpClient,
    private storage: Storage
  ) {
    console.log('Account Service constructor Text');
    // some other code
  }
}

account.page.ts

这是帐户页面的相关代码

import { AccountService } from '../services/account.service.js';
import { AuthenticationService } from '../services/authentication.service.js';
@Component({
  selector: 'app-accounts',
  templateUrl: './accounts.page.html',
  styleUrls: ['./accounts.page.scss'],
})
export class AccountsPage implements OnInit,OnDestroy {
  accounts: Account[] = [];
  accountsLoaded = false;
  subscription: any;
constructor(private http: HttpClient,
    private accountService: AccountService,
    private router: Router,
    private authService: AuthenticationService,
    private changeRef: ChangeDetectorRef,
    private testService: NewAccoutService) {
         
    }
ngOnInit() {
    this.testService.authenticationState.next(false);
    console.log('Test Service state: ', this.testService.authenticationState.value);
}
}

添加-account.page.ts

添加账号页面的相关代码如下

import { AccountService } from '../../services/account.service';
import { AuthenticationService } from '../../services/authentication.service';
@Component({
  selector: 'app-add-accounts',
  templateUrl: './add-accounts.page.html',
  styleUrls: ['./add-accounts.page.scss'],
})
export class AddAccountsPage implements OnInit {
public account: FormGroup;
  public currentSliderValue: number = 1;
constructor(  private platform: Platform,
                private changeRef: ChangeDetectorRef,
                private router: Router,
                private accountService: AccountService,
                private authentication: AuthenticationService,
                private testService: NewAccoutServiceService ) {
this.testService.authenticationState.next(false);
    console.log('Test Service state: ', this.testService.authenticationState.value);
}
}

当我加载帐户页面时,我希望看到以下内容:

预期结果:当我加载 accounts.page 时,我想看到 console.log 消息。 this.testService.authenticationState.valuefalse.

当我从 accounts.page 转到添加帐户时,AccountService 中的 console.log 应该 而不是 被再次调用。 this.testService.authenticationState.valuetrue.

实际结果:正在加载到 account.page 我看到 console.log 消息并且 this.testService.authenticationState.valuefalse。从 accounts.page 加载到添加-accounts.page 我得到另一个构造函数 console.log 消息也 this.testService.authenticationState.value 仍然是 false

我 运行 无法尝试,因为我对 Ionic 和 Angular 还是个新手。每个服务 都应该 作为单例工作,但不知何故不起作用。如果您需要其他代码来帮助我解决此问题,请告诉我。

编辑:

我找到了非常愚蠢的解决方案。 VS Code 曾在 accounts.page.ts 中为我的服务生成导入,出于某种未知原因,它在文件位置后添加了 .js 我的意思是看这个比较。

import { AuthenticationService } from '../services/authentication.service';

import { AuthenticationService } from '../services/authentication.service.js';

在工作中查看我的代码的 3 个人中没有一个发现这个问题。直到今天又尝试了 3 个小时并非常仔细地查看代码后,我和我的同事突然想到“为什么那个导入中有一个 .js,那是不对的”。

无论你想在何处接收主题的值,都需要通过它的可观察性订阅主题。

@Injectable({
  providedIn: 'root'
})
export class Service {

  randomSubject = new BehaviorSubject(initialValue);
  randomObservable = authenticationStateSubject.asObservable();
}

然后在你的组件中,你需要在初始化时订阅它。

export class RandomComponent implements OnInit {
 constructor(private service: Service) {}

 ngOnInit(): void {
  this.service.randomObservable.subscribe(randomValue => console.log(randomValue));
 }
}

每次分配新值时都会发生订阅回调中的任何事情。

要更新它的值,请在主题上调用 next 并传入一个值。

this.service.randomSubject.next(newValue);

请注意,但是,如果您计划执行 Http 请求来检索本地 JSON,我会使用 ReplaySubject 来代替 b/c 您不必初始化具有价值的主题。

Check this Subjects explanation out.

如果您使用延迟加载检查模块。可能服务出现在延迟加载模块提供程序中。

我找到了解决方案

对于以某种方式遇到此错误的任何人。在我的 VS Code 生成的导入中,文件位置后有一个 .js 扩展名。

之前:

import { AuthenticationService } from '../services/authentication.service.js';

之后:

import { AuthenticationService } from '../services/authentication.service';

删除此扩展程序后,它可以正常工作。我不知道为什么 VS Code 只在我的 accounts.page.ts 中做了一次,但那是另一个不再发生的问题。

providers

中删除

我有类似的问题(我尝试在单例服务中共享 rxjs 流)。我测试 angular 使用以下代码创建许多理论上的单例服务 (LoginService) 实例:

private timestamp = +new Date();

constructor() {
  this.timestamp = +new Date();
  console.log('constructor: ' + this.timestamp);
}

public getTimestamp() {
  return this.timestamp;
}

然后在不同的页面中打印 getTimestamp()。在控制台中,我多次看到构造函数 运行(但理论上对于单身人士来说,它应该只 运行 一次)。

在这一点上,我发现我将我的 LoginService 放在许多组件的提供程序部分 - 这就是为什么 angular 创建多个单例服务实例的原因 - 在全部删除之后 - 它只创建一个 :)

@Component({
    selector: '...',
    providers: [LoginService, ...],   // REMOVE this service from all components

    styleUrls: [...],
    templateUrl: '...',
})