Create onboarding and its guard

I would push back on this.

Think of it this way. Let’s say you’re playing Hangman, and the game can be paused and resumed. What you would need to write to and read back from Storage is really just:

  • the target word
  • what guesses have been made so far

Everything else can be deduced from that. Now imagine that you have a service that keeps track of guesses. Every time it hears of a new one it adds it to its list, and writes that out to Storage.

The key part: even before that write finishes, the service still has up-to-date data. So as long as everybody else in the app is just asking the service what’s been guessed, you have no problems.

The trouble starts to happen when other parts of the app bypass the service and read directly from Storage. I think this is a categorically bad choice for a myriad of reasons: it makes changing storage methods harder, it makes testing and maintenance harder, the code is harder to follow, etc. So as long as you are disciplined about not either reading or writing to Storage outside of your services, you can avoid bugs like this, even without waiting on Storage writes.

I’m not familiar with the term “resolver” in this context. I’m guessing it’s from some object mapping library. I can’t really improve on the TypeScript documentation I linked earlier, but you can use arrays as generic parameters:

interface Spieler { ... }
class NamenseingabeResolver implements Resolve<Spieler[]> {
  resolve(): Promise<Spieler[]> {...}
}

…or (I’m having a tough time thinking of a real-world situation in which I would do something this abstract, but…):

class NamenseingabeResolver<T> implements Resolve<T[]> {
  resolve(): Promise<T[]> {...}
}

You made a good point with not accessing storage outside a service, I dindn’t think about this yet. This means that services are kind of static so everything else can access it and changes are saved globally.

Resolvers are used to load data for a page/component before it is actually loaded. They are added in the routing module:

{
  path: 'index-namenseingabe',
  loadChildren: () => import('../pages/namenseingabe/namenseingabe.module').then( m => m.NamenseingabePageModule),
  resolve: {
   spieler: NamenseingabeResolver
  }
},

And if I use this:

export class NamenseingabeResolver<T> implements Resolve<T[]> {

  constructor(private storageService: StorageService) { }

  resolve>(): Promise<T[]> {
    return this.storageService.get(Constants.SPIELER)
    .then(res => {
      if(res)
        return res;
      else
        return [];
    }).catch(() => {
      return [];
    });   
  }

I get this error:

ERROR in src/app/resolvers/namenseingabe.resolver.ts:14:5 - error TS2322: Type 'Promise<unknown>' is not assignable to type 'Promise<T[]>'.
[ng]       Type '{}' is missing the following properties from type 'T[]': length, pop, push, concat, and 26 more.```

I found the solution for the error: I had to change

if(res)

to

if(res && Array.isArray(res))

Exactly.

Ah, OK. I have not found lazy loading anywhere near worth the mountain of hassle, so I don’t bother with it.

I think the first example (using a concrete Spieler[T] parameter) is much more readable, anyway. The example here of a Resolve<Hero> could just as easily be a Resolve<Hero[]> to return an array of Heros.

I found the solution for the error: I had to change

if(res)

to

if(res && Array.isArray(res))

Is This also a good solution or should I create my own data-type Spieler for that?

I think what you have is fine.

So I now updatet my system so that storage service is only accessed by the services. I created Behaviour Subjects for my data and here comes the next problem: My guards need access to the data in these subjects but they don’t wait until this data is there. Before, I got the data directly from the storage so it was no problem.

spieler$ = new BehaviorSubject<any>('');

  constructor(private storageService: StorageService) { }

  init() {
    this.storageService.get(Constants.SPIELER).then( async res => {
      if(Array.isArray(res)) {
        this.spieler$.next(res);
        this.spieler = res;
      } else {
        this.spieler$.next([]);
      }
    });
  }

init is called in a resolver, right at the begiining, but still my guard does not wait for the data:

canActivate(): boolean {
    this.spielerService.spieler$.subscribe(async res => {
      if(res && Array.isArray(res) && res.length > 1) {
        this.router.navigate(['/home/spieleauswahl']);
        return false;
      } else {
        return true;
      }
    }).unsubscribe();
  }

Moreover, there is an error, because there is no return-path outside the subscription

Two big problems here, and I bet you know what the first one is going to be: get rid of that any.

The larger one is that the contract of BehaviorSubject says that it is always valid, so your subscription will be called as soon as it is registered. If that’s a problem, don’t use BehaviorSubject. It is possible to have a BehaviorSubject<Spieler[] | undefined>, and then a check for truthiness in the subscription will tell you whether there is a valid Spieler[] there.

Finally, especially given that you’re relatively new to all of this, I would urge you to avoid async and await like the plague. They obscure what is actually happening, and should only be used when you understand completely what they do (and, frankly, I would argue, not even then).

Okay I did all that and to make it easier I get the data in my guards directly from the storage - there I know how to do it

You’re in charge, but I still think that is going to be a decision you will regret, because of everything I said earlier about how you have introduced a breeding ground for race conditions.

mhm… Do you have a tutorial for me where I can learn all of this in combination? So using the IonicStorage, connceting it with my services and using this data then in guards?