In this post we will try together to build a form consisted of different components and we will try various implementations to achieve the best architecture. The final pattern that we will implement is the smart dumb.
We will try 4 different approaches and soon I will create a 5th one which will use the exportAs.
The post is quite long but it worth's diving in :)
Our form will have:
Each form has different validation rules and we want the status of each one to be propagated on a way to be able to know if the whole form is valid or not. To disable a button if the form is invalid. We do not to invoke the HTTP call if the form is invalid.
Furthermore, we want to unit test each component separately.
The smart dumb pattern says that a component (smart) will play the role of the wrapper, will have all the heavy functionality (service calls, component orchestration, etc), and other components (dumb) will play the role of presentation. The dumb components will communicate with smart with @Input @Output
Let's create a smart component which will have nested formGroup objects for each dumb component.
In this approach, the smart component is the single point of truth as it contains the form objects of all the components
Notice that we've created the basic and address formGroups, and creditCards formArray.
We now need to create the presentation components and pass on in the relevant formGroup or formArray.
Let's see first the orchestration of the components in smart component
The app-customer-basic, app-customer-address and app-customer-credit-cards are our nested components. Notice that we are pass on in the relevant formGroup or formArray. Let's see the code of those nested components
Credit Cards component
The dumb components are indeed very lean and they do only one thing. PRESENTATION. Let's examine the pros and cons
pros - cons Approach #1
Let's focus on the credit cards and try not to re-create the form model for the addCreditCard and removeCreditCard actions.
We will move the form initialization from the smart to dumb
"Notice that we still set the formArray in smart component."
This approach gives us the flexibility to implement the addCreditcard and removeCreditCard with out re-creating the form model. We did a mini refactoring of our code. Let's see what we've achieved
pros - cons Approach #2
Doh!!! We still have the same amount of cons.
We are not yet there. We should try more!!!
In this state, the smart dumb initializes the form model for basic and address, but not for creditCards.
Let's try to make this consistent in terms of form initialization.
We will move the basic and address formGroups in the relevant components.
This approach seems to be clean and consistent. The smart dumb initializes only the main formGroup (basic, address, creditCards), and each component is responsible for its own formGroup data.
Well, it has minor issue. It doesn't work at all :D. Do you remember the initial goal? We want the smart component to be updated of the status of each form change. This approach doesn't work as we broke the form references
pros - cons Approach #3
Close enough ehh??
We will remove completely from smart component the form initialization and we will leave it only on dumb components.
We need to find a solution though to propagate the form status from bottom to top. From dumb components to smart components. We will utilize the ViewChild decorator.
The only difference from the dumb components is that we will remove the @Input decorator. The variable remains, just delete the @input
The @ViewChild decorator is very handy as it queries DOM elements and gets the state of them. If we use it with the class name, we get the class instance and not the element reference. This is very useful for our case.
Notice that we use the combineLatest rxjs operator. Read more here https://www.learnrxjs.io/operators/combination/combinelatest.html
The combileLatest operator is best used when you have multiple, long-lived observables that rely on each other for some calculation or determination.
The reason we get the class instances of the components via @ViewChild in the ngAfterViewInit is because the view queries are set before the ngAfterViewInit callback is called.
pros - cons Approach #4
I hope you enjoyed the article :)
Here you will find the final approach https://github.com/profanis/angular-athens-reactive-forms
On the next post I will explain the use of exportAs and how to bound the form status in smart component's template