NOTE: for simplicity consider the component depths as:
- Smart (grand)parent level 0
- dumb child level 1
....
- dumb grandchild level 2
....)
There are various options and conditions on how smart/grand/parent/child components communicate and pass data up and down a MULTI-LEVEL (at least 3 levels) chain. We'd like to keep our 'smart' (grand)parent component as the only component that has access to our data service (or atomic/immutable store) and it will drive exchange of information with 'dumb' (grand)children. The options we see are:
Now in case of '3', the dumb (grand)children must have the message service injected. Which brings me to my questions:
Q1: It seems intuitively odd for each of the 'dumb' (grand)children to have a message service injected. Is best practice for the message service to be a dedicated service for this family OR does it piggy back on the data service the 'smart' grandparent is charged with mentioned above?
Q1A: Additionally, how is this much better than adding @Input/@Output bindings up and down the chain if all the components will have a service injected? (I see the argument that the 'dumb' component needs SOME way to get info)
Q2: What if the 'smart' grand parent were communicating with a redux-like store (ngrx for us)? Once again is the communication with the 'dumb' components best happen via an injected/dedicated messages service or is it best to inject the store into each 'dumb' component...or? Note, the inter-component communication is a combination of 'actions' (eg: form validation, disable button, etc) in addition to data (i.e. add data to/update store or service).
Thoughts greatly appreciated!
(UPDATE: 02-07-2019: This post was getting dated--added the 'store/ngrx' pattern)
So after looking into this further, when it comes to how best to communicate down and up a nested component chain, there seems to be really only two options -- a Faustian bargain between:
EITHER
OR
OR:
I'm personally a proponent of utilizing smart and presentational ('dumb') components. Adding a 'store' should also be done selectively as it significantly increases the costs of the process ranging from architecture, consistent implementation patterns, development, and maintenance to on-boarding of new personnel. Nominally, a 'dumb' component only needs @Inputs and @Outputs and that's it. It does not care how deep or shallow it is in a component tree--that's the applications problem. In fact it doesn't care what application uses it in the first place. Meanwhile, a deep down component isn't very dumb or transportable if an application specific service is injected into it. BTW, the counter-part 'smart' component is really providing intermediary services (via a first class @Injectable service or redux-like store) to whichever dumb component in its family tree that needs it. The smart component also doesn't care about components beyond its immediate child's @Inputs as long as the grandchildren somehow signal up a service/store action needs to be taken (again via the @Input/@Output chain). This way a smart component also becomes transportable across application lines.
Given this, the Faustian bargain, IMO, leans towards utilizing an @Input/@Output chain with all the mentioned issues it brings with it. That said, I'm keeping an eye on this and welcome clean and decoupled alternatives if anyone knows of any.