服务会创建另一个实例,即使@Injectable具有ProvideIn:'root'。如何确定该服务是Singleton?

时间:2019-04-02 16:12:19

标签: angular ionic-framework ionic4

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

修改之后,我发现该服务正在获得2个实例,因此我不会对最初开始的ReplaySubject添加新的更改。通过将console.log添加到我的服务的构造函数中,并从帐户页面转到添加帐户页面后,在控制台中查看,我发现了同一console.log的2个实例。因此,我对add-accounts页面中的accountService所做的更改是另一个实例,并且在第一个实例中没有更改。

当我用其他服务(AuthenticationService)测试它时,在控制台中没有得到2 console.log输出。 我仔细检查了@Injectable装饰器是否具有providedIn:'root'的正确语法,并测试了删除providedIn:'root'部分并将其导入app.module.ts并将其添加到provider数组,我得到一个NullInjectorError:没有我无法解决的AccoutService错误提供程序。

我尝试使用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个服务。在该项目中,它们的行为与预期的一样,只创建了一次。

与同事交谈后,他们建议创建一个具有相同文件结构的新测试项目,并将代码复制/粘贴到新项目中,这可能会解决问题,因为可能未创建某些文件。但是,此操作无法解决,因此可能还有其他问题。

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

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

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

预期结果:当我加载account.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加载到add-accounts.page,我得到另一个构造函数console.log消息,this.testService.authenticationState.value仍然是false

由于我还是Ionic和Angular的新手,所以我没有其他选择。每个服务应该都可以单例运行,但是某种程度上是行不通的。让我知道您是否需要其他代码来帮助我解决此问题。

编辑:

我发现解决方案太愚蠢了。 VS Code在某一时刻生成了我的服务在account.page.ts中的导入,由于某种未知原因,它在文件位置之后添加了.js,我的意思是看一下这种比较。

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

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

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

4 个答案:

答案 0 :(得分:1)

我找到了解决方法

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

之前:

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

之后:

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

删除此扩展名后,它应该可以正常工作。我不知道为什么VS Code一次只在我的account.page.ts中做到这一点,但这是另一个不再出现的问题。

答案 1 :(得分:0)

对于想要在任何地方获得主题价值的地方,都需要通过可观察的方式订阅主题。

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

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

然后在您的组件中,您需要在Init上订阅它。

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.

答案 2 :(得分:0)

如果使用延迟加载模块,则该服务可能会出现在延迟加载的模块提供程序中。

答案 3 :(得分:0)

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 放在了许多组件的 providers 部分 - 这就是为什么 angular 创建多个单例服务实例的原因 - 在删除所有之后 - 它创建了一个 :)

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

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