0

I have multiple modules in my angular app and each is storing some related data to my master data. Now, I want to delete my master data and I want to delete all related data too.

So I was thinking to have a service (service1) with delete method and an observable to let other services know this data is going to be deleted and other dependent services (service2, service3, ...) will subscribe and delete their related data

@Injectable({
  providedIn: "root"
})
export class Service1Service {
  deleteData$: Subject<string> = new Subject<string>();
  constructor() {
    console.log("Service1Service.constructor");
  }

  delete(id: string) {
    console.log("service1.delete");
    this.deleteData$.next(id);
  }
}
@Injectable({
  providedIn: "root"
})
export class Service2Service {
  constructor(private service1: Service1Service) {
    console.log("Service2Service.constructor");
    this.service1.deleteData$.subscribe(id => this.delete(id));
  }

  delete(id: string) {
    console.log("service2.delete");
  }
}

export class MyComponent {
  constructor(
    private service1: Service1Service,
    ) {}

  delete() {
    this.service1.delete("5");
  }
}

So in this way, Service2 will be dependents on Service1, and it will be Open/Closed if in future a new moudle is developed to store some other related data to mater data.

The issue is if I don't inject Service2 in the components that already loaded, angular doesn't create them, so they don't subscribe to service to delete data.

I tried by using providedIn: "root" or directly add them to app.module providers no difference.

Note: I don't want Service1 be depends on other services ie I don't want service1 calls other services delete method

Satckblitz

Miquel Canal
  • 992
  • 1
  • 17
  • 25
Reza
  • 18,865
  • 13
  • 88
  • 163

1 Answers1

1

Angular will not create a service until it is required the first time for optimization reasons. Also if your service is not used anywhere in the code the Tree-shaking will remove it from the production code. For more details check the documentation about Tree-shakable providers.

I think for this case a better solution would be to define a common interface for all services and use multi providers. This means you provide multiple services for the same interface. Then you can inject all the services at once to the component and loop trough them and call delete on all or add another intermediate service to do it if you don't want the logic in the component. Then when you add a new service you just provide it as another provider and everything will work automatically. This way the services are completely independent and the component or parent service talks only to an abstract interface.

Here is the sample code for such a solution.

export abstract class ServiceBase {
  public abstract delete(id: string): void;
}

@Injectable()
export class Service1Service extends ServiceBase {
  constructor() {
    super();
    console.log("Service1Service.constructor");
  }

  delete(id: string) {
    console.log("service1.delete");
  }
}

@Injectable()
export class Service2Service extends ServiceBase {
  constructor() {
    super()
    console.log("Service2Service.constructor");
  }

  delete(id: string) {
    console.log("service2.delete");
  }
}

export class AppComponent {
  constructor(
    @Inject(ServiceBase)private services: ServiceBase[],
    ) {}

  delete() {
    this.services.forEach((service) => service.delete());
  }
}

Then you register the services as multi providers in the application module.

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  bootstrap:    [ AppComponent ],
  providers: [
    { provide: ServiceBase, useClass: Service1Service, multi: true },
    { provide: ServiceBase, useClass: Service2Service, multi: true }
  ]
})
export class AppModule { }

Here is a link to a fork of your StackBlitz that implements this solution.

There is also a workaround for your solution to work which is not so clean so I don't recommend using this. To do it you need to force the creation of the child services. One way I found is to provide a custom application initializer with a dependency on the Service2Service which will force the creation. Here is a link to the post that suggests this solution.

Here is the sample for your case:

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  bootstrap:    [ AppComponent ],
  providers: [{
    provide: APP_INITIALIZER,
    useFactory: () => () => {},
    deps: [Service2Service],
    multi: true
  }]
})
export class AppModule { }

The link to a fork of your StackBlitz that implements this workaround.

Aleš Doganoc
  • 11,568
  • 24
  • 40