Bind service method to my frontend - Angular - angularjs

I have a service in Angular that has one method in there - say GetName() that returns someones name
I want in my html to be able to do something like {{ $scope.GetName() }} and have this bound to my method result. My main issue is if the name in my service changes (through another controller) then I would like my {{ ..... }} change.
Hopefully this makes sense.
Regards,
Yannis

You can't bind dynamic service calls to a DOM element.
Problably the best way would be to $watch for the name changes within the service, and use a callback function on change. You'd also need a way to notify the controller that the name has changed, so an event listener should also be set up. The listener would call the function that will update the scope variable, say $scope.nameFromService. And then in your HTML you'd need only this inside the controller part:
{{nameFromService}}
Again, to make it clear, this is what happens:
another controller changes the value in the service
$watch gets triggered and calls a function that (among other things) triggers an event
the event listener in your controller receives the event and updates the scope variable
Angular automatically updates the DOM
UPDATE
If you can change your service, I'd actually avoid the whole $watch thing and just modify the name setter function so it triggers an event when called. That way, all your controllers can be aware of the name changes.

Related

Angular doesn't update the view

I have worked with angular for long time without facing many problems until now. I have a controller which calls a function when a ng-change event occurs. I have a set of different reports that are generated depending on some select filters defined in the main controller called Principal (PrincipalController).
Most of the controllers work fine and generate what they suppose, however with the last one (AnalisisEvaluacionController) I am struggling.
Its view is loaded correctly in the beginning. I print the name in the view but when I called the method from a ng-change event the name does not change. I also have a ng-repeat over $scope.notas which does not work. Actually that is the real issue. I have read about it and I have tried with
$scope.$apply() and $scope.$digest() but the error
angular.js:14800 Error: [$rootScope:inprog] $apply already in progress is thrown
I have created a snippet with the code https://bitbucket.org/snippets/jorguerra/Ee6G6y
I basically have a ng-repeat over evaluacion.notas which is reloaded when the function generarInforme is call, which call a factory that brings a JSON back which is assigned to $scope.evaluacion in the controller. The problem is that I can iterate or show the data I need to display
You don't need to call $scope.$apply (or $digest) in ng-change handler, as it's handler is already wrapped in $scope.$apply block. If you, however need to call $scope.$apply (or $digest) in some situations (e.g. your ng-change handler does some asynchronous operation after which you need to sync the view model), you can prevent the error this way:
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
You can also wrap your fn in $timeout block
$timeout(fn)
which safely applies $scope.$apply.
I could solve my problem by creating a function init inside of AnalisisEvaluacionController which calls the method generarInforme and changing set all the required variables.

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 does AngularJS implement its two-way data binding mechanism?

AngularJS allows you to implement two-way data binding. However, the interesting part is how it detects model changes? The model is usually a plain object like the code below. We can change the name property of $scope.user but how does AngularJS detect the model changed? Does AngularJS enum all the properties of the $scope object?
angular.module('myApp', [])
.controller('BusinessCardController', function($scope){
$scope.user = {
name: 'Tanay Pant'
}
});
<input type="text" ng-model="user.name" placeholder="Full Name" />
There is a digest cycle, where the scope examines all of the $watch expressions and compares them with the previous value. It looks at the object models for changes, if the old value isn't the same as the new value, AngularJS will update the appropriate places, a.k.a dirty checking.
In order for the digest cycle to be execute $apply(fn) has to be run, this is how you enter the Angular world from JavaScript. How does $apply(fn) get called (taken from AngularJs integration with browser):
The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.
Data Binding
Digest Cycle Explanation
In order to achieve two-way binding, directives register watchers. For a page to be fast and efficient we need to try and reduce all these watchers that we create. So you should be careful when using two-way binding - i.e. only use it when you really needed. Otherwise use one-way:
<h1> {{ ::vm.title }} </h1>
Here it is quite obvious that the title of the page probably won't be changed while the user is on the page - or needs to see the new one if it is changed. So we can use :: to register a one-way binding during the template linking phase.
The main issues I've seen with explosions of watchers are grids with hundreds of rows. If these rows have quite a few columns and in each cell there is two-way data binding, then you're in for a treat. You can sit back and wait like in modem times for the page to load!
Two-way binding is limited almost exclusively to elements that use ng-model. The direction going from view to model uses standard event handlers to detect changes that must be updated within the model (e.g., onchange). The direction going from the model back to the view is updated during a $digest. But we do not call $digest directly.
Every element that is on your page that is going to respond to the digest cycle will, somewhere, attach a listener and an expression to its scope using $watch. When you write {{ foo() }}, or when you use ng-model='user.name', internally there is a call to $watch made on your behalf with a Javascript expression that will be run every time a digest cycle is run. This registration might happen during the compile of the template (our first example), or it might happen during the link phase of a directive (our second).
There is no magic here. The listeners that are attached are regular functions -- in our example, the listener for the expression foo() is provided for you, and it will update the html text on the page, while the listener for the expression user.name will call setText, or setOption, or whatever is required by the particular input which ng-model has been attached.
While angular can handle most of the listening, you can attach your own watch expressions with your own listeners manually inside any function that has access to a scope (scope is important because we will tear down those watchers if the corresponding parts of the page are removed). Be mindful of excess. Bindings aren't free, and the more things that are bound, the slower the page will respond. One-time bindings are one way of reducing this cost. Using $on with $emit and $broadcast are another.
So when is digest called? It is certainly not automatic. If the digest cycle is running, it means someone somewhere called $apply on their scope or on the root scope. ng-model attaches handlers which will respond to regular html events and will make calls to $apply on your behalf. But foo(), on the other hand, will never get called until some other bit of script somewhere calls $apply. Fortunately, most functions that you fill out for angular wrap those functions with a call to $apply, so you don't often have to make the call yourself (e.g., $timeout is wrapped with $apply, which is why we use it instead of setTimeout). But if you were using something outside of the scope of angular (a 3rd party library that connects to events), you would need to remember to call $apply yourself, and just like above, you can do this manually by calling $apply anywhere you have access to a scope.
In order to make Data Binding possible, AngularJS uses $watch API's to observer the changes on the scope. AngularJS registered watchers for each variable on the scope to observe the value in it. If the value of the variable on the scope gets changes, then the view gets updated automatically.
It happens because of the $digest cycle is triggered. Hence, AngularJS processes all the registered watchers on the current scope and the children and check for the updates and call the dedicated watcher listeners until the model is stabilized and no more listeners are fired. Once the $digest loop finishes the execution, the browser re-renders the DOM and reflects the changes
By default, every variable on a scope is observed by the angular. In this way, unnecessary variable are also observed by the angular that is time consuming and as a result page is becoming slow.

Broadcast rootscope change

I'm trying to write a script to access the rootscope and change a variables value from the dev tools. I can get the scope and the rootscope, but my changes don't show up in my bindings. However if I use a bound input everything is fine.
Example:
<input ng-model="mode"></input>
<span ng-bind-html="mode"></span>
update each other fine but
angular.element("body").scope().$root.mode = 'test'
Updates the controller but has no effect on the bindings. I have compared my original controller and my console controller using === and they are equal.
So from what I read I need to broadcast or emit that I've changed the scope, however I can't seem to figure out what to broadcast , or if I am broadcasting correctly.
Broadcast code attempts:
scope.$apply(function() {root.$emit('change', {mode: 1})})
scope.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
root.$apply(function() {root.$emit('change', {mode: 1})})
root.$apply(function() {root.$emit('stateChangeSuccess', {mode: 1})})
Additionally it should be noted that changing the input will change the span but not my copy of the root scope.
root.mode = 'console'
type 'input text' in the input
root.mode returns 'console'
I don't think this is possible with events (perhaps I'm wrong). There is another way to do make changes from the console though.
The reason angular.element("body").scope().$root.mode = 'test' doesn't update your bindings is that Angular doesn't "know" that a change to the scope has been made. If you are making updates to your data model from "outside" the Angular world, you will need to wrap you changes in an $apply() function:
var $rootScope = angular.element("body").scope().$root;
$rootScope.$apply(function() {
$rootScope.mode = 'hi';
});
FYI, when sending an event from the $rooteScope, you'll want to use $broadcast instead of $emit. $broadcast broadcasts an event "downward" to all descendant scopes, while $emit sends the event "upwards" to ancestor scopes. If you $emit from the $rootScope, there are no ancestor scopes to pick up the event (though perhaps the $rootScope will?).
If I understand you correctly, scope() === $root, otherwise you're making some false assumptions here.
WHat I believe is happening is that you are changing value of root scope but your bindings are actually using another scope due to prototype inheritance(eg the string property is actually created in scope() when you bind against it).
Try either of these two tricks:
1) Either try scope.$apply(function(){scope.mode ='test'});
2) $root.mode={name:'test'}; and bind against mode.name in html
Notice that you should be able to fire a $digest cycle using $apply on root scope and it should propagate down to child scopes too. $root.$apply();

How to catch an event in controller which is thrown by an directive in Angular?

I'm having some trouble sending and catching events in Angular.
Consider this plunker: Plunk..
As you can see I have two controllers and a directive. The directive 'lives' in controller 1. When something happens in the directive, I want to catch this event in controller 2. As you can see in the plunk, nothing is logged to the console; the event is not catched.
I've also tried to use a service. In this scenario I created a method on the service and when this method is called, I throw the event. Just like in the Plunk, I listen for the event in Controller 2, but this also didn't work.
Where it all comes down to is that I want to invoke a method on another controller..
In you Plunker, second controller (SecondController) is registered, but it is never actually initialized. So that's why your listener never logs the event.
It's not clear how, where and when you are using using your second controller, but if you initialize it with either ng-view (through routes) or ng-controller, than its listener will observe the event.
PLUNKER
Each time you visit a route, the associated controller (and a $scope) are (re)created.
Since you mentioned that Controller 2 is associated with a route, it will only exist when you visit that route (as #Ajay and #Stewie already mentioned), so you can't catch an event in that controller.
I suggest you create a service with some API/methods to do the following:
record that an event triggered
check to see if an event triggered
clear an event
Your directive would call the method to record the trigger.
Your Controller 2 would call the check method when it is created (and then probably call the clear method if it finds the trigger set).

Resources