Angular $observe with $apply - angularjs

Im currently observing attribute values using $observe inside a directive, as I change the value of the attribute the callback is called, when I then change a $scope variable it isn't "updated" and I figured this is because I need to use $apply, when invoking $apply I get the error $rootScope:inprog which seems to be because of the $observe?
Simply, how do I update a $scope variable from inside an $observe?
My code (Coffee):
App.directive "reactiveButton", () ->
directive = {}
directive.restrict = "E"
directive.scope =
translateId: '#translateId'
loadingIndicator: '#loadingIndicator'
directive.templateUrl = App.Base.concat("directive/reactive-button.html")
directive.link = ($scope, $elements, $attrs) ->
$attrs.$observe 'translateId', (value) ->
$scope.$apply () ->
$scope.translateId = value
$attrs.$observe 'loadingIndicator', (value) ->
$scope.loadingIndicator = Boolean value
return directive

Your $observe block is redundant in this example. You've already got an isolate scope that's set up to assign translateId to the scope, so you can remove it.
What do you mean by "isn't updated"?
Inside reactive-button.html, you should be able to see the value of translateId by putting this somewhere: {{translateId}}
^ That should change to reflect your attribute.

directive.scope =
translateId: '#translateId'
loadingIndicator: '#loadingIndicator'
This part tells AngularJS to create an isolated scope. AngularJS will create watchers for the attributes of this directive for those properties. It will update the scope whenever those attributes change. You don't have to observe them.
App.directive "reactiveButton", () ->
directive = {}
directive.restrict = "E"
directive.scope =
translateId: '#translateId'
loadingIndicator: '#loadingIndicator'
directive.templateUrl = App.Base.concat("directive/reactive-button.html")
return directive
You're directive doesn't need a link function, because there isn't anything else for it to do (at this point). The template reactive-button.html will still work with the translateId and loadingIndicator bindings.

You don't need to $apply your scope in an $observe listener: it will already be triggered during a digect cycle.
Updating as you did should be enough, please reproduce and explain us the problem you have when doing so.

Related

When to declare function with 'scope' vs. 'var' in directive?

In this plunk I have a directive that is instantiated twice. In each case, the result of the directive (as defined by its template) is displayed correctly.
Question is whether getValue2() needs to be defined as scope.getValue2 or var getValue2. When to use each in the directive?
HTML
instance 1 = <div dirx varx="1"></div>
<br/>
instance 2 = <div dirx varx="2"></div>
Javascript
var app = angular.module('app', []);
app.controller('myCtl', function($scope) {
});
app.directive('dirx', function () {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
varx: '='
};
directive.template = '{{getValue()}}';
directive.link = function (scope, element, attrs) {
scope.getValue = function(){
return getValue2();
};
var getValue2 = function() {
return scope.varx * 3;
}
};
return directive;
});
The only time you need declare something as a property on the $scope object is when it is part of your application state.
Angular 1.x will "dirty check" the $scope and make changes to the DOM. Anything on the $scope object can be watched, so you can observe the variable and trigger functions. This is why Angular searching & filtering can be done with almost no JS code at all. That being said, it's generally good practice to keep the '$scope' free of anything that isn't needed.
So as far as getValue() is concerned, is it being called on render or in a directive in your HTML? if the answer is "no", then it doesn't need to be declared as a property on the $scope object.
Since you're using getValue() in the directive template, it is being rendered in the UI and needs to be in Angular's $scope.
You can also just do:
directive.template = '{{ varx * 3 }}';
docs: https://docs.angularjs.org/guide/scope
The first thing is that the code contains unnecessary nested calls. it can be:
var getValue2 = function() {
return scope.varx * 3;
}
scope.getValue = getValue2;
The second thing is that getValue2 isn't reused and isn't needed, it can be:
scope.getValue = function() {
return scope.varx * 3;
}
Since getValue is used in template, it should be exposed to scope as scope.getValue. Even if it wouldn't be used in template, it's a good practice to expose functions to scope for testability. So if there's a real need for getValue2, defining and calling it as scope.getValue2 provides small overhead but improves testability.
Notice that the use of link function and direct access to scope object properties is an obsolete practice, while up-to-date approach involves controllerAs and this.

Value of input field is being removed by directive

I have a simple Angular Problem - I think it's probably a case of can't see the wood for the trees here.
I have an input field with a directive attached. The purpose is eventually to compare new with old data and show a popup. However, as soon as I add the directive attribute to the input field, the value disappears:
Plunk here: http://plnkr.co/edit/BQvKGe6kjuD0ThPBYJ4d?p=preview
HTML:
First Name:
<input type='text' ng-model='currentEditItem.strFirstName' name='strFirstName' id='strFirstName'
cm-popover="currentEditItem.personOldData.strFirstName"/>
<br><br>
ngModel: {{currentEditItem.strFirstName}} <br>
cmPopover: {{currentEditItem.personOldData.strFirstName}}
JS
var app = angular.module('app', []);
app.controller('Ctrl', function ($scope) {
$scope.currentEditItem = {};
$scope.currentEditItem.strFirstName = "Bob";
$scope.currentEditItem.personOldData = {};
$scope.currentEditItem.personOldData.strFirstName = "Roger";
});
app.directive("cmPopover", function () {
return {
scope: {
ngModel: "=",
cmPopover: "="
},
link: function (scope, elem, attrs) {
console.log("ngModel", scope.ngModel);
console.log("cmPopover", scope.cmPopover);
}
}
});
If you go to the Plunk and remove the cm-popover attribute, the input field is filled with the value from the model. When the attribute is added the value disappears although the model is still in the scope with the correct value.
In your directive you declare an isolate scope. This input's scope is now this isolate scope since it's the directive element. It's looking for the currentEditItem object which doesn't exist in the isolate scope
ngModel does not create a new isolated scope for itself so it can $watch without having to hardcode a $parent in it's internal code.
But then you add another directive on the same DOM node that creates an isolated scope for itself. Couple this with the fact that you can only have a single isolated scope on a DOM node and you basically force ngModel to use/work with the same scope cmPopover created.
So when writing ng-model="currentEditItem.strFirstName" you are actually addressing the $scope inside the cmPopover directive, no the one in the (parent) controller. You can check this is the case by using ng-model="$parent.currentEditItem.strFirstName" - and it will work.
There's quite a lengthy conversation here with a lot of possible workarounds and solutions that leads to an actual fix in release 1.2.0.
So long story short: update to at least AngularJS 1.2.0 and this will work.

Angular Directive Two-Way Binding Issue

I am creating a custom input directive for some added features.
app.directive('myTextInput', [
function () {
var directive = {};
directive.restrict = 'A';
directive.scope = {
textModel: '='
};
directive.link = function ($scope, element, attrs) {
// FUnctionality
};
directive.template = '<input ng-model="textModel" type="text">';
return directive;
}]);
and used the isolated scope property textModel for two way binding.
And the parent scope HTML is:
<div my-text-input text-model="myScopeVariable"></div>
<span>TYPED : {{myScopeVariable}}</span>
The controller scope has variable $scope.myScopeVariable=""; defined.
When i do this.
what ever typed in the directive input is able to print in the <span> tag.
Which tells it is updating.
The issue is.
in the parent scope. i have a method that will execute on a button click.
$scope.doSomething = function(){
console.log($scope.myScopeVariable);
};
On click on the button. the log is empty. which is supposed to be what is typed in the directive input.
THIS IS STRANGE
Now if define the scope variable as
$scope.myScopeVariable = {key:''};
and use in HTML as
<div my-text-input text-model="myScopeVariable.key"></div>
<span>TYPED : {{myScopeVariable.key}}</span>
then it is working every where. Even in the previously said function doSomething().
Any idea what happening here ?
This is happen because myScopeVariable contain value as String.
So if you change the value of from textbox of directive. It wont be reflected.
And in second method, you are referring Object. so whenever you change value of object , its relevant watch method is called. And value will be updated.
my-text-input is a directive, so he has his own scope, so the model myScopeVariable is not watch by the parent.
try with this :
<div my-text-input text-model="$parent.myScopeVariable"></div>
<span>TYPED : {{myScopeVariable}}</span>
It's not the best practice but it might works.

Why $rootScope.$new does not let template into the directive?

I'm building unit testing using Karma and Mocha. Testing my directives, and using html2js (It converts the htmls to cached strings in $templateCache).
Interestingly, when using $rootScope.$new() in my test, the template html will not get into the directive . Here's the code:
it('should show a thumb name', function() {
inject(function($compile, $rootScope,$controller) {
var scope = $rootScope;//.$new() ($new not working. Why?)
var linkFn = $compile('<thumb></thumb>');
var element = linkFn(scope);
scope.$digest(); // <== needed so that $templateCache will bring the html
// (that html2js put in it)
console.log(element.html());// correctly returns thumb's directive templateUrl content
}));
...
However, if I use scope = $rootScope.$new(), the element.html() will return an empty string
any ideas?
many thanks
Lior
According to the docs for $digest (http://docs.angularjs.org/api/ng.$rootScope.Scope), this will only process watchers etc for the current scope and its children.
This suggests to me that when you set scope = $rootScope and then $digest you will be processing watchers etc on the $rootScope, I think this is where promises will be resolved too, releasing your templates. When you do scope = $rootScope.$new() and call $digest on that, I expect anything that should happen from the $rootScope doesn't happen.
So, does this work if you change scope.$digest() to $rootScope.$digest() or scope.$apply()?

angularjs mocking element for directive to add features

I use angular js 1.0.3 and I try to test my directive.
we use jQuery that is loaded automatically by angular and is accessible as angular.element that is passed to directive.
how can I add properties to the element before directive is linked with scope???
var def = '<input data-my-directive="" />';
var scope = $rootScope.$new();
var linked = $compile(def);
// do something to add property something that jq is adding
var directive = linked(scope);
my directive is something like
return function(scope, element, attrs) {
element.jq-plugin-method();
}
and my target is element passed to directive after linkage.
thanks for help
To answer my own question. it is sufficient to add
var jqLite = angular.element;
jqLite.prototype.jq-plugin-method = function(c) {...};
before
linked = $compile(definition);
I was blind or something yesterday or maybe I was adding this line after compile and it was too late.

Resources