Angular 2+/4/5: Smart, dumb and deeply nested component communication

匿名 (未验证) 提交于 2019-12-03 02:49:01

问题:

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:

  1. Anti-pattern(?): Pass data down and up the component chain via @Input/@Output bindings. This is what some refer to as the 'extraneous properties' or 'custom event bubbling problem' problem (eg: here and here.) problem. No go.
  2. Anti-pattern: Smart component access to dumb (grand)children via @ViewChildren or @ContentChilden. This again hardwires the children and still doesn't create a clean mechanism for the (grand)children to pass data UP to the smart component.
  3. Shared message service as described in the angular.io cookbook here and an excellent post here.
  4. ?

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!

回答1:

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 pass @Input/@Output bindings up, down, and throughout the nested component chain (i.e. deal with the problems of 'custom event bubbling' or 'extraneous properties')

OR

  • Use a messaging/subscription service to communicate between this family of components (great description here) and inject that service for each component in the chain.

I'm personally a proponent of utilizing smart and presentational ('dumb') components. 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 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.



回答2:

Input() and Output() bindings are also a perfectly legitimate way to handle this. Let the smart component handle the logic of generating the values, and then use Input() and Output() to simply pass and receive the values along the component chain.

Of course, this points to one of the downsides of the smart/view approach: more files; more boilerplate. That's why I wouldn't argue for a single approach that's one-size-fits-all. Rather, choose an approach that makes sense in your current context (both for the app and for your organization).



回答3:

Why is #1 an anti-pattern? The grandparent component owns the data and passes it down to the dumb child components via @Input parameters. The dumb child components simply invoke callbacks when an event occurs (via @Output event emitters), causing the grandparent component to manipulate the data. Seems clean to me.

Edit: I see your point about repeatedly passing values like a submit handler through many intermediate layers. Maybe a nested structure which represents your component tree could be created in the parent component. Then each component can be passed the properties it needs, plus an object to pass down to the next component. Each component then only knows about the one below it:

// Parent component builds this object (or gets a service to do it)  viewModelForChildComponent: {      property1NeededForChildComponent,      property2NeededForChildComponent,      viewModelForGrandChildComponent: {         property1NeededForGrandChildComponent,          property2NeededForGrandChildComponent,          viewModelForGrandGrandChildComponent: {             property1NeededForGrandGrandChildComponent,              submitHandlerNeededForGrandGrandChildComponent         }     } } 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!