How does ng-cloak work? - javascript

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.

Related

Compile Angular directive from dynamic html

I can't seem to get this done: I have HTML that is compiled from a ng-repeat, and I'd like to compile the result of that as well. How would I go about that?
I have a dataset containing chunks of text, that have been given a display type. This type is set as the span class. Most types are just triggering CSS rules (for example, comment-style boxes, see screenshot), but others should invoke a directive.
For example, the chunk containing 'named Nicodemus, ' is of type-hidden. I have a directive that collapses the chunk and inserts a little button to expand it.
Code:
<span class="chunk type-{{chunk.type}}" ng-repeat="chunk in verse.chunks">{{chunk.text}}</span>
Results in something like
<span class="chunk type-hidden">named Nicodemus, </span>
If the second would be my source html, it would compile the typeHidden directive just fine. I guess I need to find a way to make angular compile a second time. I can't seem to get it done using $compile (though I guess I don't really understand how that works).
Hope you can help!
Thanks in advance!
Here's a plunker to show how you can get a directive to compile an element and then again.
The code for the lazy:
angular
.module('App')
.directive('compileTwice', compileTwiceFactory);
function compileTwiceFactory($compile) {
return {
restrict: 'AE', // Whatever you want
terminal: true, // Angular should not keep compiling the element
// by itself after encountering this directive!
compile: compile, // Instead, we tell Angular how to compile the rest of the element
priority: 1001, // This directive should get compiled before the others, obviously
};
function compile(element, attrs) {
element.removeAttr('compile-twice');
element.removeAttr('data-compile-twice');
return function postLink(scope, _element, _attrs) {
var compiledTwice = $compile($compile(_element)(scope)[0])(scope)[0];
// do something with compiledTwice
};
}
}
edit: And obviously you can generalize that to compile an arbitrary number of times that you could specify like this:
<div compile-n-times="420"></div>
edit: The plunker doesn't seem to work under Firefox?
I've actually been able to fix this with a workaround. Not as elegant, but it works if I nest my directive within the ngrepeat and hardcode the name, making it visible using ng-if.
<!-- special type hidden -->
<span ng-if="chunk.type=='hidden'">
<span class="type-hidden">
{{chunk.text}}
</span>
</span>

Why does my $watch only ever fire once?

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

Appending angular directives to the DOM after document is already loaded doesn't work with ngAnimate

I am working on creating an Angular service that will append a simple notification box to the DOM and display it, without having to add HTML code and write the logic to hide and show it when necessary.
The service is called $notify and is used as below:
$notify.error( "this is an error", {position: "bottom-left"} );
The service will use angular.element to build the notification box and add it to the DOM. All of this works great. However, I am also using ngAnimate and animate.css to have the notification smoothly slide in on show and slide out upon closing. I've verified that the animations work if I simply paste the notification HTML code into my page but will not work when the code is added dynamically via the service. Do items have to be in the DOM at document load for ngAnimate to work? I've verified that the Angular service is loaded and properly inserting the HTML code but no animations are being applied. Here's what the HTML and CSS look like.
HTML:
<div class="simple-notify simple-notify-top-left simple-notify-info" ng-if="toggle">
<simple-notify-header>Hello!<span class='simple-notify-dismiss pull-right' ng-click='doSomething()'>×</span></simple-notify-header>
<simple-notify-body>Some bogus text here!</simple-notify-body>
</div>
CSS/LESS:
.simple-notify {
&.ng-enter {
display:none;
animation: #animate-enter #animation-length;
-webkit-animation: #animate-enter #animation-length;
}
&.ng-enter-active {
display:block;
}
&.ng-leave {
animation: #animate-leave #animation-length;
-webkit-animation: #animate-leave #animation-length;
}
}
Thanks!!!
You should never modify elements on the page from a service, this is what a directive is for. You should create an attribute directive on your body HTML element and in that place your logic. You can use $rootScope.$broadcast from your $notify service and $scope.$on in your directive. Alternatively, you can scrap the $notify service altogether and just use $rootScope.$broadcast as it can accept as many arguments as you want.
Lastly, you'll need to use the $compile service to make your HTML run properly after you've added it to the body. The $compile is what turns raw DOM into code Angular will process.
From inside your directive's scope.$on handler in the link function, you'd have something like.
$compile('<div>template code here</div>')(scope, function(cloned, scope){
element.append(cloned);
});
But you'd also need a cleanup as well. You might just add the code once as the directive's template and show/hide it with different content instead of adding/removing the DOM.

AngularJS event which is triggered after all scopes are applied

I have a AngularJS application where I am loading data from a REST service.
Now what sometimes happens is that the brackets {{}} used to access values from scope are rendered and after that replaced by the real values. Now what I d like to do is add a ng-switch to the top DIV of the application and check whether a global var (e.g. pageLoaded (true|false)) is true or false. If its true, I d like to load the normal page, if its false, I d like to print something like "loading...". So my problem is how can I get notified (e.g. through a Angular Event) if all the data is ready, and is added to scope? Because after that I dlike to set pageLoaded to true.
Is there a way to do this in a generic way? I don't like to implement this per page.
Thanks a lot in advance.
Greets
Marc
You should use ng-cloak for that - http://docs.angularjs.org/api/ng.directive:ngCloak
For showing a loading panel, you can do something like:
<div ng-hide="true">Loading...</div>
So when angular finishes loading, ng-hide will occur and hide the loading panel.
Use ng-cloak to get rid of this sort of problems. And make sure you apply the ng-cloak directive to the body of the page so that this doesn't show up till the data is loaded properly. And use the following styling in your CSS file.
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none;
}
Note: you can even create some other element or div, thats something like a popup or notification bar, which shows "please wait till the data is comnpletely loaded". Set this div to display:none initially and in the Ajax call, change the display property to block/inline as needed and finally make it dispay:none after the success call back.. :)
One of the solutions is you can use ng-bind instead of using {{}} which will show ugly {{}} when the value is not rendered.
<div ng-bind="value">Loading ...</div>
For anyone who is having a problem more to do with the actual question than OP's specific scenario:
I had a fragment that was getting loaded-in after/by the main partial that came in via routing.
I needed to run a function after that subpartial loaded and I didn't want to write a new directive and figured out you could use a cheeky ngIf
Controller of parent partial:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML of subpartial
<element ng-if="subIsLoaded()"><!-- more html --></element>

Angularjs animate ngClick + ngShow with custom directive (mySlide)

I'm trying to get the same ultimate functionality as ng-click + ng-show, except that I want the show to slide in instead of suddenly appear by toggling display: block/none;. I've got the jQuery animate I need, and I've set up the ng-click. I've got 2 problems, but the second might be a result of the first:
Problem 1
ng-click does not change the value of aside_users. I saw SO#12202067 which seems to be a similar situation, but I don't understand how/why their custom directive works and the native ng-click doesn't.
I see the scope: { … } after restrict: 'A',, but that appears to make $scope values available within the newly-created DOM element (my elements already exist and show up just fine, but no triggers/events are happening).
infobox.html
<aside
class="users"
ng-include src="'views/users.html'"
my-slide={"direction":"left","condition":"aside_users"}
></aside>
<i
class="icon icon-user"
ng-click="aside_users=!aside_users"
ng-init="aside_users=false"
></i>
The above code is a $compile'd template and elsewhere within the template I print out the value of the $scope parameter aside_users (prints false).
Problem 2
my-slide doesn't seem to be initiated/triggered (the logging of 'elm: ', elm doesn't appear in Chrome's console). I verified that directives.js is linked in my index.html page.
EDIT I remembered to link directives.js in index.html, but I forgot to add it to the resources array in app.js.
Plunkr
P.S. I'm not sure if <aside attr={object}> is strictly valid, but legitimate browsers seem to accept it in test cases (didn't bother to check IE). My alternate plan is to use 2 attributes: <foo my-slide="direction" my-condition="boolean"></foo>

Categories

Resources