toddmotto / angularjs-styleguide

AngularJS styleguide for teams
https://ultimateangular.com
5.96k stars 700 forks source link

Passing attributes through multiple components #136

Closed jasanst closed 8 years ago

jasanst commented 8 years ago

Great guide but the area I see most people (including myself) getting confused if how to pass attributes or events through multiple levels of component hierarchy.

https://www.reddit.com/r/angularjs/comments/59fjdf/angularjs_tutorial_building_quality_angular_15/

I feel a style guide should have an option on the cleanest way to reference a great-grandparent component or similar.

floriangosse commented 8 years ago

I think a possible solution is to use transclude if the nested components arn't hard coupled.

<outer-component
  attr-1="$ctrl.outerAttr1"
>
  <inner-component
    attr-1="$ctrl.innerAttr1"
    on-click="$ctrl.handleInnerClike($event)"
  ></inner-component>
</outer-component>

If the nested components are hard coupled I think it isn't bad design to pass the event handler through the components.

toddmotto commented 8 years ago

@jasanst Hey! So I replied just now to the Reddit comment:


Hey! So sorry for the delay replying, super busy ha :) thanks for joining the hangout, hope you enjoyed it. So...

1) Most of the time you'll be breaking your data down into specific Objects, I think there is some impression that you have to just pass every single Object down and every single callback down. This may be the case in some scenarios, however you can actually (for example) create a new reference / object copy in a child component, and then use that to pass information down. My rule really is that the deeper the components get in terms of parent -> child -> grand-child, the less data is being bound (or should be bound). Saying this, you can skip bindings altogether and not have to worry about re-binding in child component controllers as such and use the $locals property to simply pass a callback all the way through one or two components. I have an example (https://jsfiddle.net/toddmotto/hogcojv7/) which demonstrates using $locals to simply take a callback and pass it through another component - see "todoList" component template for this piece of info. I'd highly consider using something like $ngRedux alongside components, which you'll essentially move your data to a single datastore.

2) Use services for initial data fetching, copying that data and passing it down elsewhere. Don't use them for shared state, this becomes super messy. If you use bindings, you get the full benefit of one-way dataflow and lifecycle hooks, to which services aren't really needed for shared application logic/state - they also have issues with updating $$watchers as primitive values don't tend to bind correctly - which may be what you're seeing with $onChanges. The $onChanges hook runs before a component is ready, also runs before $onInit, so you can essentially run some logic (such as cloning local data, mutating it and sending it back up). From my experience, you can only push one-way dataflow so far in Angular 1.x due to the majority of pretty useful/common directives still relying on two-way databinding (I'm looking at you ng-model). It can be done but it's a little messy. I've been thinking about an ng-model-oneway (or whatever) type directive that handles model setters/getters for change events etc.

3) Components have inputs and outputs. Inputs is where data can be passed into them, outputs are where data is passed back. You can kind of think about outputs as a public API. For example if you check the todo app I linked above, you'll see "todo-form" has a public property called "on-add". The decision of how this method gets called is the responsibility of that component, not the parent component. If you want to "call" methods from inside another component, I would suggest using databinding and passing a specific Object (think perhaps a simple Boolean) that can say yes or no to a specific function that $onChanges or $doCheck is waiting to hear from. Dataflows downwards so that it's predictable, any state changes that are going to happen, that story is told from a child and information exposed to the parent. Notice in the todo app example only one component is actually mutating state (via immutable operations), the child components are simply passing some information back up to the parent via an event, changes are merged and then one-way dataflow will propagate back down the components and re-render where needed. I hope this comment helps anyways! Feel free to reach out via twitter/email should you have anything else I hope I can help with! :)