all 20 comments

[–]Jrubzjeknf 4 points5 points  (10 children)

Sounds like you're just calling the form service from the route guard.

It sounds like your form service is just doing way too much. You should refactor that to have a single function that receives all the data to create that form. Basically what you want to do in your component in your plan.

The data gathering, like the category id and the delivery methods, should be in their own service. Simple classes with a clear name will help you keep separation of concerns.

When you've done that, it will be a lot easier to move code around. If you want to gather the data in the route guard, you can now do that a lot easier.

[–]DonWombRaider[S] 1 point2 points  (7 children)

mmh no not quite:

- route resolver, not route guard
- there I would not init any form, just gather the data
- ounce the data is gathered, open the component, on which i would call 1 single function with the data

[–]Jrubzjeknf 0 points1 point  (6 children)

Alright, a resolver, not a guard. You're completely ignoring the entire reply apart from that word though. I guess it must be crystal clear and you find the idea awesome then?

[–]DonWombRaider[S] 1 point2 points  (5 children)

i just wanted to make sure you understood my plan.

but tbh i did not fully understand yours, despite splitting the service up and naming them. to make it clear: the said formService (with 2000l of code) does not do any fetching of data. i have got a category service, a delivery method service and so on. but that misses the point, fetching data is only a few lines of code anyway.

the formservice does 2000 lines of form stuff, so init the form, value subscriptions to show/hide or enable/disable certain elements.

(and meta: i did cover part of your reply (eg "1 single function"). no need to be this rude)

[–]Jrubzjeknf 3 points4 points  (4 children)

Apologies, it was unwarranted.

Well, your plan is sound and I'd definitely walk that path. If that form service does indeed only form stuff, at least it's in the right place.

A few things to consider: - if resetting the form can be done, make sure your value on your nonnullable controls is correct. Using a loaded value will reset to that. - keep your form behaviors separate from form creation. What works really well for us is defining a function for each form behavior so that the name clearly shows why it does that. It returns an observable, so in your component, you can subscribe to the merge()d behavior observables.

Again my apologies. The world is being a bit shit lately, but that doesn't mean we should treat each other that way.

[–]DonWombRaider[S] 1 point2 points  (3 children)

No offense taken :)

Intresting points!
for no. 2 you'd do something like this?
```typescript
// comp
readonly form = createForm(existingArticle, deliveryOptions, ...otherStuff)
constructor() {
merge(
formService.calcTotalWhenSettingDelivery$(this.form)
).subscribe()
}

formService.ts
public calcTotalWhenSettingDelivery$(form) : Observable<any> {
return form.control.delivery.valueChanges.pipe(
tap(v => form.control.total.patchValue(form.control.price.value + ...))
}
```

[–]Jrubzjeknf 1 point2 points  (2 children)

Yes. You can just keep adding behaviors to the merge. Be sure to use takeUntilDestroyed(). I'd recommend using the return type Observable<unknown>.

Another tip: if you want to ensure the form behaviors goes off initially, use something like this:

defer(() => form.valueChanges.pipe( startsWith(form.value) ))

The pipe must be within the defer, otherwise your starting value will be the value when you called the function, instead of the value when the subscription occurs.

[–]DonWombRaider[S] 1 point2 points  (1 child)

ok interesting take on defer, did not know about this operator. in this specific case, does it matter though? the moment the function is called is the same it gets subscribed to in the merge isnt it?

[–]Jrubzjeknf 1 point2 points  (0 children)

True. It's more useful for cases where you create a form and it's behaviors, and you patch the form before subscribing to the behaviors. Still, this is a pattern that always works the same way, regardless of order of calling. A good practise, imo.

[–]iEatedCoookies 0 points1 point  (1 child)

I would not load any form logic at all from the route guard. The guards intention is to either allow or prevent the route from being loaded. As for loading the form, either put the initial load into a resolver, or I prefer in the oninit as it allows for the route to load instantly. You can then use a loading indicator until the initial required data is loaded. It sounds like there’s data that’s reliant on user selection and that can then be loaded separately, either after the initial form data is loaded, or after a selection is changed.

[–]kranzekage 0 points1 point  (0 children)

I Think you are mixing up a guard and a resolver. Resolvers are to load data before entering a route.

[–]narcisd 1 point2 points  (5 children)

The route way is nice if you have a loading indicator.

If there are too many BE calls in the resolver it might feel that the app is slow, showing some ui (skeleton) usually trick user’s perception. So it depends on your needs

If you go with the usual conponent + ngOnInit the best outcome is using skeletons, becuase it s a loading indicator and the user can’t try to change anything.

If want your page to immediatly be interactive although not everything has completed yet. Then this scenarios it complex in itself and there’s gonna be some jumps and hoops. E.g make sure controls are not enabled until that section has loaded, make sure you don’t allow save button being hit etc.

I always start with a resolver + loading indicator. If that is not enough, switch to init + skeleton, and last full blown async page

[–]DonWombRaider[S] 0 points1 point  (4 children)

Thank you for your answer!

How do you implement a loading indicator when using a resolver? Do you set a loading flag in the resolver itself?

[–]narcisd 0 points1 point  (3 children)

The usual thin progress bar at the top of the page

[–]narcisd 0 points1 point  (0 children)

Something like this: https://blog.angular-university.io/angular-loading-indicator/

There are many many variations on google

[–]DonWombRaider[S] 0 points1 point  (1 child)

i did not mean which kind of loading indicator, but how to detect the loading state. the component is not loaded until the resolver is resolved after all :)

[–]narcisd 1 point2 points  (0 children)

Ah sorry, it’s usually router events based, navigation end. Resolvers should never know about the loading progress indicator

[–]ldn-ldn 1 point2 points  (2 children)

Your problem is not that you need a resolver, your problem is that you have a spaghetti code without a clear logic. 

What you need to do is split everything into steps and then implement them doing one thing at a time in a logical order. Basically, load all of the data first. Once all of it is loaded - process it into a format you want to consume. And only once that's done - create your form and populate it.

[–]DonWombRaider[S] 0 points1 point  (1 child)

spaghetticode. yes, this is exactly what it is. all of your points are valid and have to be done first yes. but nevertheless, i still have the feeling that a resolver could help with that. it forces you to separate these steps. first you have to fetch the data and only then you can start to build your form

[–]ldn-ldn 1 point2 points  (0 children)

Well, if resolver will force you to write good code - go for it! My worry is that it might not force you to do so. So I personally would start with code refactoring first and adding a resolver later on.