0

How do I use a method starting a listener on an observable which it returns in an if statement?

I'm in an Angular 5 project, I have this sort of setup in one of my components with an timeline where double click opens up a modal and you can type in the name for the item you're creating into that modal.

for the modals I used a reworked version of this answer. (I needed more up to date syntax and imports).

I've got it all nearly working now, here's my setup,

(timeline component which opens the modals) :

@Component({
  selector: 'app-planning',
  templateUrl: './planning.component.html',
  styleUrls: ['./planning.component.css']
})
export class PlanningComponent implements AfterViewInit {

  options = {
    onAdd: (item, callback) => {
      if(this.timeline.getCurrentTime() > item.start){

        this.errorTimelineItemModal();
        callback(null);

      } else {
          if (this.createNewTimelineItemModal()) { // <-- currently I have no return but
                                                  //   having one would be meaningless
                                                 // anyways because the if wouldn't wait
                                                // for the observable response and as a
                                               // result, it would always assess false.
          callback(item);

        } else callback(null);
      }
    }
  }


  constructor(
      private _element: ElementRef,
      private modalService: BsModalService
  ) {}

  ngAfterViewInit(){
    this.container = this._element.nativeElement.querySelector('#timeline');
    if (!this.items) {
      this.items = new vis.DataSet(this.mydataset);
      this.timeline = new vis.Timeline(this.container, this.items, this.groups, this.options);
    }
  }

  createNewTimelineItemModal() {
    const initialState = {
      title: 'Ajouter',
      multipleChoice: 'Bid',
      choices: ['Bid', 'D.C.', 'Kickoff'],
      accceptBtnName: 'Ajouter',
      closeBtnName: 'Annuler',
    };
    this.bsModalRef = this.modalService.show(Modal, {initialState});
    this.bsModalRef.content.onClose.subscribe(result => {
        this.createItemResult = result;
        console.log(JSON.stringify(result));
    })
  }

  updateTimelineItemModal(name) {
    const initialState = {
      title: 'Nouveau Nom ?',
      itemCurrentName: name,
      accceptBtnName: 'Rennomer',
      closeBtnName: 'Annuler',
    };
    this.bsModalRef = this.modalService.show(Modal, {initialState});
    this.bsModalRef.content.onClose.subscribe(result => {
        this.createItemResult = result;
        console.log(JSON.stringify(result));
    })
  }

  deleteTimelineItemModal() {
    const initialState = {
      title: 'Êtes-vous sûr de vouloir supprimer cet element?',
      accceptBtnName: 'Supprimer',
      closeBtnName: 'Annuler',
    };
    this.bsModalRef = this.modalService.show(Modal, {initialState});
    this.bsModalRef.content.onClose.subscribe(result => {
        this.createItemResult = result;
        console.log(JSON.stringify(result));
    })
  }

  errorTimelineItemModal() {
    const initialState = {
      title: 'Erreur',
      list: ['Désolé, créer des éléments avant la date d\'aujourd\'hui est désactivé']
    };
    this.bsModalRef = this.modalService.show(Modal, {initialState});
    this.bsModalRef.content.onClose.subscribe(result => {
        this.createItemResult = result;
        console.log(JSON.stringify(result));
    })
  }
}

(modal component) :

export class Modal implements OnInit {

  onClose: Subject<Object>;

  constructor(
    private formBuilder: FormBuilder,
    public _bsModalRef: BsModalRef) {}

  ngOnInit(): void {
    this.onClose = new Subject();
  }

  public onConfirm(): void {
    this.onClose.next(true);
    this._bsModalRef.hide();
  }

  public onCancel(): void {
     this.onClose.next(false);
     this._bsModalRef.hide();
  }
}

As you can see I am getting an answer from validating or not the modal. I can console log it.

Now is where I'm stuck. How can I get the code execution to just halt until an observable has been received by that method so as to assess correctly within the if?

this is actually very important for the correct execution of my code because the callback(null); and callback(item); that you might have noticed are the syntaxe one must have to either finalize the item creation or prevent it.

see : http://visjs.org/docs/timeline/#Methods

I had this working with alerts but I'm trying to switch to something with more functionalities and cleaner.

tatsu
  • 2,316
  • 7
  • 43
  • 87

1 Answers1

1

If I can understand you correctly, you need to synchronize two separate events. It is usually a bad practice to do so.

Try to re-organise your code. It is an async process, so you should divide the process into sub-"transactions", that can happen separately.

  1. Separate the logic for opening up your modal.
  2. Wait for the user to enter the data
  3. Process the answer from the modal.

Something like this:

  createNewTimelineItemModal() {
    const initialState = {
    ... 

    this.bsModalRef.content.onClose.subscribe(result => {
        this.createItemResult = result;
        this.continueToCreateItem(result);
      });
    }

  private continueToCreateItem(result: any){
    <insert your code here>
  }

Or other solution can be to return observable objects and hande it within the onAdd

options = {
    onAdd: (item, callback) => {
    ...
          this.createNewTimelineItemModal().subscribe(result => {
             if(result is something){
              callback(item);
             } else callback(null);
         }
      }
    }
  }

To "halt" the process is a pretty bad practice, but can be achived with Promise object.

this.myPromiseReturningMethod(params).then(() => {

but this will block all your application for the time being (with the user being unable to do anything) so I recommend to alter the structure instead.

ForestG
  • 17,538
  • 14
  • 52
  • 86
  • yeah I had a feeling the code was starting to be backwards. but it's not my fault I got backed into this corner by VisJs and Angular.io's respective docs. I feel like everyone has his own way of coding and when trying to use good code from one person with good code from another person, nothing makes sense anymore. I agree with you I don't want to halt the application (but really everyone could do with RXJS inventing observable's cousin the "observingIF" where the stream's direction is reversed and your assertion only happens on result). In the meantime I'll try your first suggestion, thanks. – tatsu Mar 28 '18 at 11:54
  • the thing is I really don't see how calling a `this.continueToCreateItem` helps me. I still need to be able to tell my `onAdd` whether to `callback(item);` or `callback(item);` – tatsu Mar 28 '18 at 11:58
  • 1
    you know what I'm gonna use promise and `.then` for me the delay is somewhere in the milliseconds, and under 100 at that. since all I'm doing is calling a local modal it's never going to bother the user unless he has an APM of 10 000 – tatsu Mar 28 '18 at 12:16
  • 1
    @this.continueToCreateItem : I wanted to express that you can pass down the callback function call reference, and handle it when you have everything. So this.createNewTimelineItemModal(callback) ---> this.continueToCreateItem(callback) ---> and at the point where you need it, you can use it. – ForestG Mar 28 '18 at 12:42
  • uhm... i'm only 20% sure I follow you there. are you sure that the callback can actually be passed around like that? draft up an example and I'll mark you. for now I'm satisfied with my promise-then. (yes, I'm a dirty scrub) – tatsu Mar 28 '18 at 13:10
  • 1
    very basic example of [method passing](http://plnkr.co/edit/AbIG2vIyBR89rR4aixsi) – ForestG Mar 28 '18 at 13:25
  • Ohhhhhhhhhhhhhhh! – tatsu Mar 28 '18 at 13:36