Is using $watch better than using $on and $broadcast? - angularjs

If I want to call a function in a controller when I make changes in a different controller, I usually use $on and $broadcast.
However, I've started using $watch more recently. Now, I have a variable in a service, for example this.shouldIReload = false. Whenever I make a certain change(for example, deleting an item in a modal), I change the value in the service, and using $watch, I can call a reload function in another controller. This works.
Which way is better? What are the pros and cons of each?

Related

Add class later request was completed with Angular loading bar

I'm working my frontend with angular and angular-loading-bar, in the controller I put this code.
$rootScope.$on("cfpLoadingBar:completed",function(){
$(".animated").addClass("fadeIn");
});
or
$scope.$on("cfpLoadingBar:completed",function(){
$(".animated").addClass("fadeIn");
});
When the all XHR requests have returned, I want to add a clase in my section content, but the code inside event don't run.
How is the correct way to achieve it?
Firstly, check that you use appropriate event name. For example, are you sure thet its name is cfpLoadingBar:completed? Maybe its a cfpLoadingBar::completed (its a very common pattern) or something else?
Second, ensure that you have to subscribe to this event using $rootScope. Maybe you have to subscribe for it in some concrete controller witj its own $scope?
And as a big suggestion: DO NOT USE JQUERY AND ANGULAR TOGETHER IN YOU CODE, DO NOT MESS IT UP!!! Angular has a built in possibility to work also as a jquery. All that you need is to call angular.element() which returns you an element as if would use jquery. In your case you can write angular.element(".animated").addClass("fadeIn"); and it will do the same thing, but in angular way
Yeah, I use both cfpLoadingBar::completed and cfpLoadingBar:completed but don't run this event.
In the other hand I only have one controller by one section, it ran but I needed add a main controller and registered this event and propagate up the event with $broadcast in my child controller.
This is code in MainController
$scope.$on('cfpLoadingBar:completed', function(event, data) {
angular.element(".animated").addClass("fadeIn");
});
And This is code in other Child Controller
$rootScope.$broadcast('cfpLoadingBar:completed');
it is the only way to achieve, I don't know why XD
Thanks Andrew this way is better angular.element()

Reach directives scope from another directive

I have two directives, one which takes care of a list and one which creates an item in the list. The problem is that the directive which creates an item can be created anywhere in the dom. When I have created an item, how do I tell the other directive to update the list so I can list the new item?
The create-directive has a function called createItem which should call the other list-directives function "updateList". I have a service which both can use but I'd rather not fiddle with the scope in the service.
Am I thinking wrong here? Both are really needed for reusability but since their scopes are at closest siblings it's hard to reach them from each other.
What you can do is use angular events to trigger the "updateList" function.
Make the list-directive listen to an event like this:
//Whenever the 'update-list' event is broadcasted, the updateList() function will be called
scope.$on('update-list', updateList);
To broadcast the event from the create-directive you need to inject the $rootScope. With that you can now trigger the event:
$rootScope.$broadcast('update-list');
While events work fine in that scenario, please use them cautiously. If you have too many events all across your application it might be difficult to figure out what happens where and why.
The alternative that you would have is completely manage the list in the service that both directives use. In the list-directive you could bind to the list from the service, so that it'll get automatically updated whenever the list in the service changes. With that you don't need to change something in the scope of the directive from the Service, which should be avoided.

How to detect variable change events between controllers

I'm making a fairly trivial notification system where I want a main menu (outside ng-view) to display the last activity of the user, from what I've read it looks like a service is best used to communicate between controllers. How should I detect when something happens in controller A immediately from controller B. Should I $watch the variable of the service? Or is there an easier way to do this? Or given should I just have a root scope variable and $watch and change that with the controllers?
You should use $broadcast and $on methods. Here's a good example found on the internet
Also, there is no more an issue with $broadcast over $emit in recent versions of angular, because $broadcast runs as fast as $emit.
Don't use $watch for this because it will create a lot of events ( angular has also a limit of 10 rerun iterations to prevent deadlock ).
The watch listener may change the model, which may trigger other listeners to fire.
This is achieved by rerunning the watchers until no changes are
detected. The rerun iteration limit is 10 to prevent an infinite loop
deadlock.
Read more here
$watch will trigger everytime your variable change. Your can use $emit or $broadcast with $on for a less regular event.
This can be done in a factory : how to emit events from a factory
Could store something in the global $window and create a watch expression on it, that is just off the top off my head, might be a better way to do it.
Also take a look at this as it is like the sort of thing you need to do:
Share data between AngularJS controllers

Angular Service to update all scopes using it

I've been reading that Services are great for sharing data. But I am having a hard time making all data be in sync...
For example, let's say you have
an ItemService with an array of items.
a ItemListController, which shows a list of items ($scope.items = ItemService.items)
a AddItemController, a form to add items to the array (using the service).
When I add a new item, it doesn't automatically shows in the list controller. My workaround so far is to use cache:false in the router so that it refreshes every time I look at it...
I can't use $scope.$apply() because the adding happens on one controller while I want to see it on another...
You need to use $rootScope.$broadcast('item.added'); in the controller that initiates changes (AddItemController), after it successfully updates the array in the service.
Then in the ItemListController you use
$rootScope.$on('item.added', function(event){
$scope.items = ItemService.items;
});
Check this question
#Sarpdoruk There is two-way data binding between views and controllers, but not between controllers and services. By using this line $scope.items = ItemService.items; you copy 'items' from service to items in controller but you copy by value, not by reference. You can change $scope.items in your view or controller and they will get updated between the view and controller but these changes will have no effect on the service. Same thing if you update items in the service - controller will not know that something has been changed - so you need to $broadcast or $emit that something has changed from service and listen for the changes in the controller. You can also $broadcast between controllers, directives and even services (but since services have no access to $scope, you have to use $rootScope and some people frown upon that). The bottom line is, if you really have to use a $rootScope in the service because otherwise you will have to overcomplicate parts of your app, then do it and never bother with "best practice evangelist" The first rule of the app is that it has to work, only after it works you should worry about clean code. I hope it makes it clearer now.

$on Not Communicating With $Scope When Used With ng-include

Basically, I am unable to update my controller information when I listen for the $on event if I loaded my html dynamically using ng-include. Plunker example.
If you click once, you'll see the view keeps the original $scope.name. If you click again it will update.
I put a setTimeout on the broadcast to make sure the ng-include was loaded. You can set that to as long as you want, and will never be able to update the $scope on the first try (at least in my example).
Thoughts?
EDIT:
I'm using <ng-include="template"></ng-template>
As an area I can load alternate content in. If there is a better way to do this, please let me know.
setTimeout() is a function out of the control of AngularJS, so AngularJS will not automatically run a digest after the callback runs. That means, your $rootScope.$broadcast() was run, but AngularJS didn't realize that. The next time when you use $rootScope.template = '....';, a digest runs, and the view was updated to the previous run's model.
To solve the problem, you will need to manually call $scope.$apply() at the end of your setTimeout() callback, or use the Angular-wrapped version of setTimeout(), which is $timeout(), that will automatically run a digest afterwards.
Please refer to the docs for more details about digest/apply:
It works for me if you use $timeout instead of setTimeout. Which you should be using for angular applications.
$timeout(function(){
$rootScope.$broadcast('BROADCAST', param);
}, 1000);
There is definitely something wrong with your design if you are trying to do something like this though. Perhaps someone could suggest an alternate solution if you better explained what you are trying to achieve. As you cannot possibly know how long the timeout should be.
A few things:
1) First, do not define the $scope.template in the broadcast function. The ngInclude will not have a file to display until that value is set; so from it makes sense--in my mind--that the template would not be able to make changes before it and the controller are loaded.
2) You never actually apply Controller C2 to the ngInclude. You can do that like:
<ng-include src="template" ng-controller="c2"></ng-include>
Once I do these two things, the code works and updates the first time without the use of the setTimeout() at all.
Plunker

Resources