2
  • I have popupservice that opens popup components for me like this:

    export class PopupService {
        alert() { this.matdialog.open(PopupAlertComponent); }
        yesno() { this.matdialog.open(PopupYesNoComponent); }
        custom() { this.matdialog.open(PopupCustomComponent); }
    }
    
  • Then I open my custom popup with this.popupservice.custom().

    export class HomeComponent {
        constructor(private popupservice: PopupService) {}
        openCustomPopup() {
             this.popupservice.custom();
        }
    }
    
  • Then, in my custom popup component I want to call my own alert popup (to report an error or something):

    export class CustomPopup {
        constructor(private popupservice: PopupService) {}
        doHttpCall() {
             ...do http calls...
             if (callFailed) this.popupservice.alert('Call Failed');
        }
    }
    

How can I solve this circular dependency problem?

Notes:

  • I have read the other questions but I feel that my problem is a specific "help me out" problem. Though you are still welcome to refer me to other questions.
  • The this.popupservice.alert() is not just a javascript alert, it is my own custom popup that has a theme and everything.
4
  • Can you post some code for DI Commented Nov 28, 2018 at 5:45
  • That 3rd party popup needs component reference to create the popup, doesn't it? Commented Nov 28, 2018 at 6:00
  • 1
    Yes exactly :) this.matdialog.open(PopupAlert); Commented Nov 28, 2018 at 6:01
  • Posted an answer, check it out. Commented Nov 28, 2018 at 6:39

2 Answers 2

2

What you can do is to take away popup creation logic from PopupService. Here is a little refactor for you.

Use PopupService to only create events from different parts of the application.

@Injectable()
export class PopupService {
    private _alert: Subject<any> = new Subject();
    private _yesno: Subject<any> = new Subject();
    private _custom: Subject<any> = new Subject();

    // since you didn't like the on- methods, you can do following
    public readonly alert$ = this._alert.asObservable();
    public readonly yesno$ = this._yesno.asObservable();
    public readonly custom$ = this._custom.asObservable();

    onAlert() { return this._alert.asObservable(); }

    onYesno() { return this._yesno.asObservable(); }

    onCustom() { return this._custom.asObservable(); }

    alert(payload) { this._alert.next(payload); }
    yesno(payload) { this._yesno.next(payload); }
    custom(payload) { this._custom.next(payload); }
}

So, we have a PopupService which only emits some events with some payloads. I used Subject here because later subscribers won't need to know if there was a alert or yesno event earlier. If you want to have such logic, you can change Subject to BehaviorSubject.

Create a component called PopupManager and use it in app.component

app.component.ts

@Component({
    selector: 'app-root',
    template: `
        <!-- some template -->
        <app-popup-manager></app-popup-manager>
    `
})
export class AppComponent {}
@Component({
   selector: 'app-popup-manager',
   template: '' // don't need to have template
})
export class PopupManagerComponent implements OnInit {
   constructor(private matDialog: MatDialog, private popupService: PopupService) {}

   ngOnInit() {
       this.popupService.onAlert().subscribe(payload => {
       //   this.matDialog.open...
       });

       this.popupService.onYesno().subscribe(payload => {
       //   this.matDialog.open...
       });

       this.popupService.onCustom().subscribe(payload => {
       //   this.matDialog.open...
       });
   }
}

With this way, you can use PopupService in any component you want since it is a singleton, standalone service now.

Why you should not expose Subjects to outside world?

You can think of this encapsulating class fields. You could indeed expose _alert to outside world but then you will have no control over who uses this subject in what way. Methods are always great way to provide functionality while maintaining some control over the fields. In the future, you may want to change internals of the service, maybe some of the subjects. If you let other parts of the application access directly on fields, you would have to refactor a lot. But this way, since you are giving them some methods, as long as you don't break those methods you'll be fine.

Sign up to request clarification or add additional context in comments.

8 Comments

Dude this is perfect thank you! But you can remove the onX() functions right? Then in PopupManagerComponent just use this.popupService._alert.subscribe(...)
You should not access to subject directly, I'll put an alternative way to do it.
Why should you not? You are returning the observable anyway so I feel like you can just skip the return function and access it directly. Why is this not a good idea?
It is like having private fields. You should encapsulate the subject and only let people emit events through methods.
I'll explain why you should hide Subjects.
|
2

Your service should not know about components, that is bad design if your services have knowledge of components. You should have an observable like a behaviour subject in your service and have your component subscribe to the observable to know when to popup a new message.

In your service

message$ = new BehaviourSubject<string>(null);

and a function to send a message down the pipeline.

nextMessage(message: string) {
  this.message$.next(message);
}

Then in your component you subscribe to the message$ observable and then do you popup.

this.messageSubscription = this.service.message$.subscribe(message => { this.popup(message); });

Making sure to takeUntil or unscbscribe on ngDestroy.

ngOnDestroy() {
  if (this.messageSubscription ) {
    this.messageSubscription.unsubscribe();
  }
}

1 Comment

Does the popup listen to the observable and then open it self? I don't fully understand this. Would you mind putting your code into the PopupService, HomeComponent & CustomPopup please. That would help a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.