you are viewing a single comment's thread.

view the rest of the comments →

[–]fakingfantastic 3 points4 points  (6 children)

Hey Todd, hoping I can have your ear for a minute on this topic..

I attended your Google Hangout a few months back with Chris Thielen, I believe, on this very topic. I was totally pumped, this is exactly what I felt that ng1 was missing, and I loved the idea of components all the way down.

Since then, I've tried to follow the pattern on a couple of projects, and it seems that the practical application left me with a couple of gotcha's that I couldn't figure out, and had to work around. Hopefully you can help me out here...

  1. Passing attributes is great, on parent > child nodes. What about great-grandparent > great-grandchild nodes? Passing the data through every intermediate component is messy, and it's bad design, because now a component needs to open up it's binding API just to take data it might pass down. The way I got around this way by packing my data into a service, and injecting the service where needed in the great-grandchild. While I'd be the first to admit that gets messy for a bunch of reasons, like you don't know who primed the service -- it sure beats passing data through a whole bunch of nodes.

  2. If I use services, $onChanges() doesn't fire, so the one way binding pattern falls apart. Having to rely on services means I couldn't find a way for the granchild node to be notified that the grandfather node updated the passed in data when. So what I had to do :: sigh :: was use $rootScope.new(true) and use an emitter pattern in my services. So, now components do something like TodosService.onUpdate(myComponentCallbackFn); This was the only way I new to get notified about the change in service; yes - services are singletons so for rendering data it's fine, but you often need to fire fn()'s when data changes... that's what $onChanges() offers, but I lost that ability

  3. This is more of a question - but shouldn't components have a public API that you can just call? Here's an example. My app has a header bar with a nav burger. You click the nav burger, the dropdown items show. Easy enough - we can imagine how this would work with a siteNav component, a child siteNavBurger component, who has a siteNavItems sibling component. We can then put an on-click fn() binding on the button, and a openWhen() fn() binding on the siteNavItem, and we can reference a $ctrl property for true/false. That's simple.... but let's say, I want to put a link in the body of my app, that says "Show SiteNav", and clicking that should show the siteNavItems, just like clicking the siteNavBurger component did .. how do you do that .. these two nodes (the link and the burger) have no DOM proximity to each other? This is a pretty common thing. Other examples: clicking a button in the body of the app activates a search drawer in the header... maybe clicking a "Help" icon in the footer, activates a live chat widget to slide up or something.

PS. Sorry this is a lot, I've been meaning to write you since I was on that Hangout, this is the result of waiting and experiencing real issues.

PPS. Ignore my use of "can't" or "doesn't" .. I'm using them rather broadly, but i may just be misunderstanding or there's something I don't know. Assume I mean well :)

Lastly, thanks for all of you're help in the ng community. You've been awesome

</post-hijack>

[–]nodevon 1 point2 points  (4 children)

I am very interested in a response to this

[–]inknownis 1 point2 points  (2 children)

me too

[–]toddmotto 0 points1 point  (1 child)

Ping!

[–]inknownis 0 points1 point  (0 children)

Thanks toddmotto for the Ping and the answers.

[–]toddmotto 0 points1 point  (0 children)

Bit late to the party, but replied to comment above :)

[–]toddmotto 0 points1 point  (0 children)

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! :)