Okay, lets set up a scenario.
Scenario: You have a Content controller. This content might be a photo, a blog post, whatever. Now, within html for this content, you have a Comment controller. The CommentController's job is to load comments, allow comment submissions, etc. However, the functionality of this controller depends on it's parent. After all, if there is no parent, what are you commenting on?
Problem: So with that said, lets introduce a problem. The ContentController needs to request information from the server to populate itself, and part of this is a Unique ID. Now, there are other ways about this problem, such as using the url-state as the unique ID, but this is the best method i could come up with to explain this problem.
The unique id is loaded from the server into the ContentController. It then populates its $scope with this ID, and the real content. The CommentController on the otherhand, wants to load it's own resources, but its resource loading is all dependant upon the parent ContentController's loaded data. Since both controllers load at the same time, we need a way to signal, or delay, the execution of the child CommentController's execution. Otherwise it will try to request comments from the server about a unique-content-id that does not exist.
Question: This is a very basic problem, with many obvious answers. My question however, is how would angular like this done? What would the solution be to this that best fits the "angular way".
The most obvious way is to simply have an event creator/consumer. Angular has multiple ways to deal with this. Such as a service, a $scope.promise = promise
, a resolve promise, $scope.$broadcast
, and etc. To me though, these sort of evented designs feel.. clunky.. it also feels tightly coupled. I would prefer my ContentController not be bogged down by what may or may not be within it.
another way would be to $watch
the scope for the variable you want, such as $scope.$watch('content.uid', function(newValue) {})
. This feels better, and less coupled than the above solution. Still not perfect though.. to me at least.
I feel like the best solution would be one that i do not know how to achieve. I would like a controller to be able to .. delay, children controllers if it (the parent) is doing something that is asynchronous. I feel this is better than an event, because of it's implied simplicity. It is still essentially an event broadcast, but hopefully the syntax would be clean, due to the fact that there is only a single "event". It is either incomplete or complete. Though, i'm not sure if anything like this is even possible without using one of the above evented methods.. would there be anyway to achieve this?
Anyway, this has been an issue that has been rolling around in my head since i started learning Angular a couple weeks ago. Any insight on the subject would be greatly appreciated.
Thanks for reading!
Furthermore, we need to know that the child component uses the @Output() property to raise an event (by using an EventEmitter) to notify the parent of the change. @Output() is a decorator that marks a class field (that has to be named) as an output property. EventEmitter emits custom events.
$scope.$parent refers to the $scope of the parent element. E.g. if you have an element with a controller nested within another element with it's own controller: <div ng-controller="parentController"> ...
Child to Parent Two ways it can be done are through services and $emit. Services are used to share code across an app. $emit sends an event upwards from the current component to all parent components and any data associated with that event. To conclude, it is easy to move data between components in AngularJS.
Angular scopes include a variable called $parent (i.e. $scope. $parent ) that refer to the parent scope of a controller. If a controller is at the root of the application, the parent would be the root scope ( $rootScope ). Child controllers can therefore modify the parent scope since they access to it.
It's tempting with Angular to eschew evented designs, or the $watch
ing of a variable for purposes like you're describing, especially because Angular makes so many things so clean, but that doesn't mean you shouldn't ever use them.
That said, the more I use Angular, the more I find myself pulling data manipulation out of the controllers and into services and factories that expose "API"s for the controllers to use.
For example, say you have the classic list view/detail view scenario, where you select an item from a list and then display its information in the detail view.
I might have a $resource
backed factory for the list and a service just for managing the selected item:
app.factory('Thing', function($resource) {
return $resource('/things/:id')
});
app.service('Selected', function() {
this.thing = null;
this.select = function(thing) { this.thing = thing; }
this.clear = function(thing) { this.thing = null; }
});
By breaking things up this way, the controllers are only responsible for coordination and responding to the view. Yeah, I have to $watch
the Selected
services, but something like that's more or less unavoidable. It does, however, decouple you from watching something directly on your data, like a uid
:
app.controller('List', function($scope, Thing, Selected) {
$scope.things = Thing.query();
// assuming something like 'ng-click="select(thing)"' in the view
$scope.select = function(thing) {
Selected.select(thing);
};
});
app.controller('Detail', function($scope, Selected) {
$scope.Selected = Selected;
$scope.$watch('Selected.thing', function(thing) {
$scope.thing = thing;
});
});
And then, supposing, you need to load comments for the selected 'thing', you just enhance your Thing
factory with that functionality:
app.factory('Thing', function($resource) {
var Thing = $resource('/things/:id');
Thing.prototype.CommentResource = $resource('/things/:thingId/comments/:id');
return Thing.
});
And then use that functionality from your controller.
app.controller('Detail', function($scope, Selected) {
$scope.Selected = Selected;
$scope.$watch('Selected.thing', function(thing) {
$scope.thing = thing;
$scope.comments = thing.CommentResource.query({id: thing.id});
});
});
I know the icky feeling that you can get when doing what feels like "reverting to older way" when using Angular.
I think the trick is to pull out everything but coordination from your controller, and then even when you have to use some events or assign a bunch of $watches
, you're still doing so in a loosely-coupled way.
That's just my two cents, hope it's useful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With