Why does my $watch only ever fire once? - javascript

I'm factoring out some widget and the $watch expression works perfectly having all in one file but now I moved the relevant controller part into a new controller and the markup into a new html and the $watch fires exactly once after initialization but not when editing typing in the associated input.
JS:
app.controller('getRecipientWidgetController', [ '$scope', function($scope) {
console.log("controller initializing")
var testReceivingAddress = function(input) {
console.log("change detected")
}
$scope.$watch("addressInput", testReceivingAddress)
} ])
HTML of wrapper:
<ng-include
src="'partials/getRecipientWidget.html'"
ng-controller="getRecipientWidgetController"
ng-init="recipient=cert"> <!-- ng-init doesn't influence the bug. -->
</ng-include>
HTML of partials/getRecipientWidget.html:
<md-text-float ng-model="addressInput"></md-text-float>
I suspect there is some scope voodoo going on? I left the ng-init in to make clear what I want to achieve: build an obviously more complex, reusable widget that in this instance would work on $scope.cert as its recipient.

That is probably because ng-include will create a new inherited scope on the included HTML, hence $scope.addressInput in your controller is not the same reference as $scope.addressInput in getRecipientWidget.html
Well it's not easy to explain, but you should either put ng-controller within the HTML of getRecipientWidget.html (and not on the div above that includes it), OR you can use an object such as something.addressInput instead of the raw addressInput which avoids references issues on raw types (number/string).

ng-include creates new scope.
Try this
<md-text-float ng-model="$parent.addressInput"></md-text-float>
Plunker example

Related

angular - append and execute script inside ng-view before append the template content within it

Since script can't be loaded inside templates, due to Angular's jQLite wasn't written to achieve it, I decided to add jQuery library before Angular since it checks for jQuery existence, and voila!, it works. But, the fact that I'm here asking a question, means that there's a 'but', so, the script doesn't execute before content loads. Of course, I made a little trick with routes.
In module's config section, I made this:
$routeProvider
.when("Business/:Context/:View?", {
templateUrl: function (url) {
return "Contexts/" + url.Context + "/" + (url.View || url.Context) + ".html";
}
});
Then let's say we set the route to "#/Business/Test" he most locate a file called Test.html on "/Contexts/Test", right eh!. Let's say Test.html content is this.
<script>
(function(ng){
console.log(ng)
ng.module('MyApp').controller('TestController', function ($scope, $route, $routeParams, $location, $http, $mdDialog) {
$scope.name = "TestController";
$scope.params = $routeParams;
$scope.name = "John";
});
})(angular)
</script>
<div ng-controller="TestController">
Hola {{ name }}
</div>
And finally the real question: why is this happening? It's like the is executed after or I don't know, because, looking the console:
Angular exists but the controller isn't added in time.
Am I doing wrong? It this behavior allowed? Can anyone lead me in this trip?
Are you trying to include the controller script with the view? Don't understand why and what the logic behind it? You have the template and the controller and the reason is to separate the view/DOM and the business logic behind it.
In angular, the script tag Load the content of a element into $templateCache, so that the template can be used by ngInclude, ngView, or directives so it doesn't interpulated the same as a <script> tag would be normally loaded.
They also state that The type of the element must be specified as text/ng-template, and a cache name for the template must be assigned through the element's id, which can then be used as a directive's templateUrl.
So you're just not using the script tag as intended by angular.
Why not including the controller in a js file and load it from the root HTML, or by using requirejs or a similar library.

How does ng-cloak work?

I've been looking at the ng-cloak source code
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngCloak.js
It looks like it strips away the ng-cloak attribute during the compile phase of the directive. But when when I try
console.log(element.html())
during the compile function of a directive, the expressions have still not been evaluated, so I get an output like
<my-directive ng-cloak> {{foo}} </my-directive>
Given that ng-cloak will remove the ng-cloak attribute and the corresponding display:none, wouldn't it show {{foo}}? I'm confused here. Whend do Angular expressions get evaluated? It doesn't look like it gets evaluated in the compile function. When is the DOM updated?
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but the preferred usage is to apply multiple ngCloak directives to small portions of the page to permit progressive rendering of the browser view.
ngCloak works in cooperation with the following css rule embedded within angular.js and angular.min.js. For CSP mode please add angular-csp.css to your html file (see ngCsp).
https://docs.angularjs.org/api/ng/directive/ngCloak
Example index.html
<div id="template1" ng-cloak>{{ 'hello' }}</div>
<div id="template2" class="ng-cloak">{{ 'world' }}</div>
things.js
it('should remove the template directive and css class', function() {
expect($('#template1').getAttribute('ng-cloak')).
toBeNull();
expect($('#template2').getAttribute('ng-cloak')).
toBeNull();});
Or you can use in other way
it might not be enough to add the display: none; rule to your CSS. In cases where you are loading angular.js in the body or templates aren't compiled soon enough, use the ng-cloak directive and include the following in your CSS:
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;}
Angularjs - ng-cloak/ng-show elements blink
Angular will interpolate the bindings at the end of the $digest cycle so that all of the other modifications have already completed. If they were processed earlier, a directive might later change the binding or scope and cause the DOM to be out-of-date.
You can decorate the $interpolate service so that you can log when Angular interpolates a binding:
.config(function($provide){
$provide.decorator("$interpolate", function($delegate){
function wrap() {
var x = $delegate.apply(this, arguments);
if (x) {
var binding = arguments[0];
return function() {
var result = x.apply(this, arguments);
console.log('binding:', binding, result);
return result;
}
}
}
angular.extend(wrap, $delegate);
return wrap;
});
})
You can then see that all of the directives have been processed and finally, the $interpolate service is called and the DOM is modified accordingly.
Click here for live demo.
ng-cloak also fixes the two times click bug for ng-click reverse. Once listed this instruction will fix the issue in witch the button with ng-click reverse instruction needs two clicks in order to execute.

How to Dynamically add/remove directive AngularJS

I'm using the Angular Ellipsis directive (here: https://github.com/dibari/angular-ellipsis) to put some ellipsis on text that overflows. Here is the code that does this for the text contained in the scope variable 'fullText'.
<div data-ng-bind="fullText" data-ellipsis></div>
I'd like also, to have the ability to show the full text, un-ellipsised (if that is a word...) when I click on a button, say. This directive doesn't give me a easy way to do that, as far as I can tell.
What is the best AngularJS way to do this? I am pretty new to AngularJS and haven't yet written any directives yet - is there a non directive way to do this elegantly?
You can use ng-if or ng-show/ng-hide :
<div data-ng-bind="fullText" data-ellipsis ng-if="condition"></div>
<div data-ng-bind="fullText" ng-if="!condition"></div>
<button ng-click="toggle()">Toggle</button>
// In controller :
$scope.toggle = function() {
$scope.condition = !$scope.condition;
}
But the best way is to have the directive handle it directly.

AngularJS directive only called once?

I'm trying to shove mixitup inside my angular page and in order to do so I made a directive module for it
angular.module('MainCtrl', [])
.controller('MainController', function($scope) {
$scope.tagline = 'To the moon and back!';
})
.directive('mixitContainer', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).mixItUp(scope.$eval(attrs.mixitContainer));
}
};
});
Don't worry about the simplicity of the main controller, it is simply a test.
Now, the issue is that the directive only get's called once! If I go to another page and ask angular to load another controller and then go back to the home page and ask angular to load MainCtrl again, the directive isn't loaded!
Heres the with the directive:
<div id="Container" class="mixit-container" mixit-container="{load: {sort: 'order:asc'}, controls: {toggleFilterButtons: true, toggleLogic: 'and'}}">
Anyone got any ideas?
AngularJS doesn't include routing facilities. Those are provided either by ngRoute (a core but optional module), ui-router (ngRoute made super-awesome-amazing), or another replacement. You don't say which you use, and each has different behaviors.
Whichever it is, this is going to come down to the router, not the directive. The directive will get called whenever necessary. It doesn't control that itself - 'necessary' means Angular is compiling a portion of the DOM, usually from a template file, and has run into the directive. It will call the directive and ask it "what now?"
The above-named routers have different behaviors, both from each other and also based on how you configure them internally. In all of them you can arrange things so that templates do, or do not, get re-rendered. For example, in ui-router you can have 'child' states. If you have a child state active, the parent is also active... so going from the child to the parent will not re-render a template because it was already done earlier. And to make matters more complex, you can even override this by hooking the $stateChangeStart event and force the router to redraw that view even if it didn't think it needed to.
All this means... set your attention to your directive aside. It will do what you want as soon as the higher level does what you want. Get your router behaving the way you expect and you will be happy!

Using Angular, how can I show a DOM element only if its ID matches a scope variable?

I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});

Categories

Resources