admin管理员组

文章数量:1334327

I have a list of objects. The user can click on one, which then loads a child ponent to edit that ponent.

The problem I have is that when the user goes back to the list ponent, the child ponent has to do some cleanup in the ngOnDestroy method - which requires making a call to the server to do a final 'patch' of the object. Sometimes this processing can be a bit slow.

Of course what happens is the user arrives back on the list, and that api call pletes before the database transaction from the ngOnDestroy pletes, and thus the user sees stale data.

  ngOnDestroy(){
    this.destroy$.next();
    this.template.template_items.forEach((item, index) => {
      // mark unpleted items for deletion
      if (!item.is_pleted) {
        this.template.template_items[index]['_destroy'] = true;
      };
    });
    // NOTE 
    // We don't care about result, this is a 'silent' save to remove empty items,
    // but also to ensure the final sorted order is saved to the server
    this._templateService.patchTemplate(this.template).subscribe();
    this._templateService.selectedTemplate = null;
  } 

I understand that doing synchronous calls is not remended as it blocks the UI/whole browser, which is not great.

I am sure there are multiple ways to solve this but really don't know which is the best (especially since Angular does not support sync requests so I would have to fall back to standard ajax to do that).

One idea I did think of was that the ngOnDestroy could pass a 'marker' to the API, and it could then mark that object as 'processing'. When the list ponent does its call, it could inspect each object to see if it has that marker and show a 'refresh stale data' button for any object in that state (which 99% of the time would only be a single item anyway, the most recent one the user edited). Seems a bit of a crap workaround and requires a ton of extra code pared to just changing an async call to a sync call.

Others must have encountered similar issues, but I cannot seem to find any clear examples except this sync one.

EDIT

Note that this child ponent already has a CanDeactive guard on it. It asks the user to confirm (ie. discard changes). So if they click to confirm, then this cleanup code in ngOnDestroy is executed. But note this is not a typical angular form where the user is really 'discarding' changes. Essentially before leaving this page the server has to do some processing on the final set of data. So ideally I don't want the user to leave until ngOnDestroy has finished - how can I force it to wait until that api call is done?

My CanDeactive guard is implemented almost the same as in the official docs for the Hero app, hooking into a general purpose dialog service that prompts the user whether they wish to stay on the page or proceed away. Here it is:

  canDeactivate(): Observable<boolean> | boolean {
    console.log('deactivating');
    if (this.template.template_items.filter((obj) => { return !obj.is_pleted}).length < 2)
      return true;

    // Otherwise ask the user with the dialog service and return its
    // observable which resolves to true or false when the user decides
    return this._dialogService.confirm('You have some empty items. Is it OK if I delete them?');
  }

The docs do not make it clear for my situation though - even if I move my cleanup code from ngOnDestroy to a "YES" method handler to the dialog, it STILL has to call the api, so the YES handler would still plete before the API did and I'm back with the same problem.

UPDATE

After reading all the ments I am guessing the solution is something like this. Change the guard from:

    return this._dialogService.confirm('You have some empty items. 
        Is it OK if I delete them?');

to

    return this._dialogService.confirm('You have some empty items.
        Is it OK if I delete them?').subscribe(result => {
      ...if yes then call my api and return true...
      ...if no return false...
      });

I have a list of objects. The user can click on one, which then loads a child ponent to edit that ponent.

The problem I have is that when the user goes back to the list ponent, the child ponent has to do some cleanup in the ngOnDestroy method - which requires making a call to the server to do a final 'patch' of the object. Sometimes this processing can be a bit slow.

Of course what happens is the user arrives back on the list, and that api call pletes before the database transaction from the ngOnDestroy pletes, and thus the user sees stale data.

  ngOnDestroy(){
    this.destroy$.next();
    this.template.template_items.forEach((item, index) => {
      // mark unpleted items for deletion
      if (!item.is_pleted) {
        this.template.template_items[index]['_destroy'] = true;
      };
    });
    // NOTE 
    // We don't care about result, this is a 'silent' save to remove empty items,
    // but also to ensure the final sorted order is saved to the server
    this._templateService.patchTemplate(this.template).subscribe();
    this._templateService.selectedTemplate = null;
  } 

I understand that doing synchronous calls is not remended as it blocks the UI/whole browser, which is not great.

I am sure there are multiple ways to solve this but really don't know which is the best (especially since Angular does not support sync requests so I would have to fall back to standard ajax to do that).

One idea I did think of was that the ngOnDestroy could pass a 'marker' to the API, and it could then mark that object as 'processing'. When the list ponent does its call, it could inspect each object to see if it has that marker and show a 'refresh stale data' button for any object in that state (which 99% of the time would only be a single item anyway, the most recent one the user edited). Seems a bit of a crap workaround and requires a ton of extra code pared to just changing an async call to a sync call.

Others must have encountered similar issues, but I cannot seem to find any clear examples except this sync one.

EDIT

Note that this child ponent already has a CanDeactive guard on it. It asks the user to confirm (ie. discard changes). So if they click to confirm, then this cleanup code in ngOnDestroy is executed. But note this is not a typical angular form where the user is really 'discarding' changes. Essentially before leaving this page the server has to do some processing on the final set of data. So ideally I don't want the user to leave until ngOnDestroy has finished - how can I force it to wait until that api call is done?

My CanDeactive guard is implemented almost the same as in the official docs for the Hero app, hooking into a general purpose dialog service that prompts the user whether they wish to stay on the page or proceed away. Here it is:

  canDeactivate(): Observable<boolean> | boolean {
    console.log('deactivating');
    if (this.template.template_items.filter((obj) => { return !obj.is_pleted}).length < 2)
      return true;

    // Otherwise ask the user with the dialog service and return its
    // observable which resolves to true or false when the user decides
    return this._dialogService.confirm('You have some empty items. Is it OK if I delete them?');
  }

The docs do not make it clear for my situation though - even if I move my cleanup code from ngOnDestroy to a "YES" method handler to the dialog, it STILL has to call the api, so the YES handler would still plete before the API did and I'm back with the same problem.

UPDATE

After reading all the ments I am guessing the solution is something like this. Change the guard from:

    return this._dialogService.confirm('You have some empty items. 
        Is it OK if I delete them?');

to

    return this._dialogService.confirm('You have some empty items.
        Is it OK if I delete them?').subscribe(result => {
      ...if yes then call my api and return true...
      ...if no return false...
      });
Share Improve this question edited Oct 30, 2017 at 9:49 rmcsharry asked Oct 30, 2017 at 9:10 rmcsharryrmcsharry 5,56211 gold badges72 silver badges115 bronze badges 4
  • Look at CanDeactivate or guards in general, see here for example. – Matt Commented Oct 30, 2017 at 9:20
  • Thanks, that's not the solution. I'll update the question to explain why. – rmcsharry Commented Oct 30, 2017 at 9:23
  • @rmcsharry that is a solution. your user confirmation in CanDeactivate is not a solution. – tlt Commented Oct 30, 2017 at 9:28
  • If that is the solution, then why doesn't it work? I already have a CanDeactivate guard. When the guard activates the user is prompted. When they 'proceed' away, ngOnDestroy is called to do the cleanup work. – rmcsharry Commented Oct 30, 2017 at 9:35
Add a ment  | 

2 Answers 2

Reset to default 4

As you said, there are many ways and they depend on other details how your whole app, data-flow and ux-flow is setup but it feels like you might want to take a look at CanDeactivate guard method which ensures user cannot leave route until your Observable<boolean>|Promise<boolean> are resolved to true.

So, its a way for async waiting until your service confirms things are changed on server.

[UPDATE]

it depends on your user confirmation implementation but something along these lines...

waitForServiceToConfirmWhatever(): Observable<boolean> {
    return yourService.call(); //this should return Observable<boolean> with true emitted when your server work is done
  }

canDeactivate(): Observable<boolean> {

    if(confirm('do you want to leave?') == true)   
      return this.waitForServiceToConfirmWhatever();
    else
      Observable.of(false)
  }

One "workaround" I can think of is to have your list based in client. You have the list as a JS array or object and show the UI based on that. After editing in the details screen, have a stale flag on the item which the service called on ngOnDestroy clears while updating the other related data.

本文标签: javascriptAngular4how to ensure ngOnDestroy finishes before navigating awayStack Overflow