Angular 1 directive slow to update view - javascript

I have a screen which has a loading spinner at start up. On this screen, I have a controller that passes a property to a directive. The directive has an ng-show based upon this property.
Once I have set the above property to true, I want the directive to show the content and I want the controller to hide the loading spinner.
What is happening is, the spinner is being hidden before the directive renders the ng-show. This causes a flicker. I would expect the directive to completely loaded by the time I have hidden the spinner.
This is in the controller:
showSpinner();
$scope.showContent = true;
hideSpinner();
This is the directive initializer:
<div content-directive show-content="showContent "></div>
This is me passing in the property into my directive:
return {
scope:
{
showContent : "="
}
};
this is my directive markup:
<div ng-show="showContent ">
<div style="width: 100px; height: 100px; background-color:dodgerblue"></div>
</div>

Take a look at using ng-cloak on your div of content you want to be shown. From the docs:
The ngCloak directive is used to prevent the AngularJS 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.
<div ng-cloak ng-show="showContent ">
<div style="width: 100px; height: 100px; background-color:dodgerblue"></div>
</div>
Then, also from the docs:
When this css rule is loaded by the browser, all html elements
(including their children) that are tagged with the ngCloak directive
are hidden. When AngularJS encounters this directive during the
compilation of the template it deletes the ngCloak element attribute,
making the compiled element visible.
https://docs.angularjs.org/api/ng/directive/ngCloak

Related

Is it possible to Angularjs ng-switch does not re render view?

In some page of my site, i have some directives inside ng-switch, like this:
<div ng-switch="contentMenuObj.model">
<div ng-switch-when="calendar">
// Some directive
</div>
<div ng-switch-when="history">
// Some directive
</div>
</div>
Every time that I change the "view" (calendar to history) and go back (history to calendar) the angular re-render the calendar view and new queries are make in the server.
My question is about this behavior, is it possible to angular does not re-render the views? If impossible, what is the best way to solve this problem?
If I understand correctly. You would like to not rerender view when contentMenuObj.model change - right? If so apply one way binding
<div ng-switch="::contentMenuObj.model">
<div ng-switch-when="calendar">
// Some directive
</div>
<div ng-switch-when="history">
// Some directive
</div>
</div>
Model in that case will be loaded only once.
Or try to use ng-show / ng-hide directives if you would like to load directives only once.
<div ng-show="contentMenuObj.model == 'calendar'">
// Some directive
</div>
<div ng-show="contentMenuObj.model == 'history'">
// Some directive
</div>
Angular re-render your directives because ng-switch remove div when ng-switch-when condition is not satisfied, which makes directive is destroy and next time must be re-render.
The ng-show directive in contrast to ng-switch directive not remove element, but only hides.
So, if you want to hide and show content without re-rendering, try:
<div>
<div ng-show="contentMenuObj.model === 'calendar'">
// Some directive
</div>
<div ng-show="contentMenuObj.model === 'history'">
// Some directive
</div>
</div>

Angularjs Directive -> child directive variables with transclusion

I'm making a complex view with a parent directive, and some sub-directives nested in. I've run into a problem where it seems like variables aren't being passed through the layers of directives properly.
Here's the setup:
<header>
<div expandable expand="functionFromHeader">
<div votable show-vote="variableFromHeader">
...
<!-- from votable template -->
<vote>
<!-- from vote template -->
<div class="vote" ng-show="showVote">vote</div>
</vote>
</div>
</div>
<div class="contentGettingExpanded" ng-show="variableFromHeader">
...
</div>
</header>
Both the expandable directive and the votable directive use transclusion.
The function from the header scope "functionFromHeader" toggles the variable "variableFromHeader".
The problem is the variable starts out false, but the votes show up anyway. (In the link function I inspected it an it is coming through as a string "variableFromHeader" rather than the value of the variable.
The content that is supposed to expand and collapse, starts collapsed as it should, but once it is expanded, it doesn't collapse. The content just flashes on the screen.
How do I properly pass the variables through the directives?

ng-include not working, seems to be delayed in requesting the files

Im having issues using ng-include for templating, I have a sign in page that shows a modal window, the content of which depends on the results of the attempted sign up.
I'm using ng-include to fill in the content of the modal window, like:
<div class="md-modal md-effect-10" id="loginModal">
<div class="md-content">
<h3 ng-include = "loginTemplate.header"></h3>
<div ng-include = "loginTemplate.body"></div>
</div>
</div>
the success function of my ajax sign in request looks like:
$scope.launchModal = function (respStr){
$scope.loginTemplate = {
header : "templates/loginModal/"+respStr+"/header.html",
body :"templates/loginModal/"+respStr+"/body.html"
};
$('#loginModal').addClass('md-show');
};
however whats happening is that the window is getting show with nothing in the templates, and only when I dismiss the window do the templates get requested. Then if i show the window again by typing:
$('#loginModal').addClass('md-show');
the templated content is there.
You can see it in action at the link below, youll need to click "sign up" the login logic isnt hooked up yet.
Any clue?
The problem is when your modal showing, your pages header.html and body.html are not available because there'll be little delay to GET the page from server.
Any kind of HTML manipulation you need a directive. So use a directive to show your modal
In HTML
<div my-modal class="md-effect-10" id="loginModal">
<div class="md-content">
<h3 ng-include = "loginTemplate.header"></h3>
<div ng-include = "loginTemplate.body"></div>
</div>
</div>
And myModal directive
myApp.directive('myModal', function() {
return {
link: function(scope, element) {
element.addClass('md-show');
}
}
})

Applying Angular directive ngCloak to a single Controller

I was playing with this toy Angular JS application and tried to use the ngCloak directive to hide the templating markup at startup. When applied to the tag where I declare the app module, everything works fine:
<div ng-app="EditorErrante" ng-cloak>
but when I try to cloak only the second Controller:
<div id="stats" ng-controller="Statistiche" ng-cloak>
nothing happens the directive has no effect: I can still see the markup in the "stats" div before it gets processed.
I tried adding the directive as a class and copying the CSS rule in the stylesheet. Still nothing.
Anybody know why this happens?
Try this:
Add a style in your stylesheet:
.ng-cloak { display:none; }
Then use ng-cloak as a class instead of an attribute:
<div id="stats" ng-controller="Statistiche" class="ng-cloak">
e.g. http://plnkr.co/edit/xWPW2i?p=preview
should work now.
Remember, you can use directives as classes as well, not just attributes and elements.
Edit, just tested this, and you dont have to put it into a class. So just add the .ng-cloak style into your stylesheet.

Conditional logic in AngularJS template

I have an angular template which looks like this...
<div ng-repeat="message in data.messages" ng-class="message.type">
<div class="info">
<div class="type"></div>
<div class="from">From Avatar</div>
<div class="createdBy">Created By Avatar</div>
<div class="arrowTo">
<div class="arrow"></div>
<div class="to">To Avatar</div>
</div>
<div class="date">
<div class="day">25</div>
<div class="month">Dec</div>
</div>
</div>
<div class="main">
<div class="content">
<div class="heading2">{{message.title}}</div>
<div ng-bind-html="message.content"></div>
</div>
</div>
<br />
<hr />
<br />
</div>
I have set up a JSfiddle to show the data being bound.
What I need to do is make the "from", "to" and "arrowTo" divs show conditionally, depending on the content of the data.
The log is is this...
If there is a "from" object in the data then show the "from" div and bind the data but don't show the "createdBy" div .
If there is no "from" object but there is a "createdBy" object then show the "createdBy" div and bind the data.
If there is a "to" object in the data then show the "arrowTo" div and bind it's data.
Or in plain English, if there is a from address, show it, otherwise show who created the record instead and if there is a to address then show that too.
I have looked into using ng-switch but I think I'd have to add extra markup which would leave an empty div if there was no data. Plus I'd need to nest switch directives and I'm not sure if that would work.
Any ideas?
UPDATE:
If I were to write my own directive (If I knew how!) then here is some pseudo code to show how I would want to use it...
<div ng-if="showFrom()">
From Template Goes Here
</div>
<div ng-if="showCreatedBy()">
CreatedBy Template Goes Here
</div>
<div ng-if="showTo()">
To Template Goes Here
</div>
Each of these would disappear if the function/expression evaluated to false.
Angular 1.1.5 introduced the ng-if directive. That's the best solution for this particular problem. If you are using an older version of Angular, consider using angular-ui's ui-if directive.
If you arrived here looking for answers to the general question of "conditional logic in templates" also consider:
1.1.5 also introduced a ternary operator
ng-switch can be used to conditionally add/remove elements from the DOM
see also How do I conditionally apply CSS styles in AngularJS?
Original answer:
Here is a not-so-great "ng-if" directive:
myApp.directive('ngIf', function() {
return {
link: function(scope, element, attrs) {
if(scope.$eval(attrs.ngIf)) {
// remove '<div ng-if...></div>'
element.replaceWith(element.children())
} else {
element.replaceWith(' ')
}
}
}
});
that allows for this HTML syntax:
<div ng-repeat="message in data.messages" ng-class="message.type">
<hr>
<div ng-if="showFrom(message)">
<div>From: {{message.from.name}}</div>
</div>
<div ng-if="showCreatedBy(message)">
<div>Created by: {{message.createdBy.name}}</div>
</div>
<div ng-if="showTo(message)">
<div>To: {{message.to.name}}</div>
</div>
</div>
Fiddle.
replaceWith() is used to remove unneeded content from the DOM.
Also, as I mentioned on Google+, ng-style can probably be used to conditionally load background images, should you want to use ng-show instead of a custom directive. (For the benefit of other readers, Jon stated on Google+: "both methods use ng-show which I'm trying to avoid because it uses display:none and leaves extra markup in the DOM. This is a particular problem in this scenario because the hidden element will have a background image which will still be loaded in most browsers."). See also How do I conditionally apply CSS styles in AngularJS?
The angular-ui ui-if directive watches for changes to the if condition/expression. Mine doesn't. So, while my simple implementation will update the view correctly if the model changes such that it only affects the template output, it won't update the view correctly if the condition/expression answer changes.
E.g., if the value of a from.name changes in the model, the view will update. But if you delete $scope.data.messages[0].from, the from name will be removed from the view, but the template will not be removed from the view because the if-condition/expression is not being watched.
You could use the ngSwitch directive:
<div ng-switch on="selection" >
<div ng-switch-when="settings">Settings Div</div>
<span ng-switch-when="home">Home Span</span>
<span ng-switch-default>default</span>
</div>
If you don't want the DOM to be loaded with empty divs, you need to create your custom directive using $http to load the (sub)templates and $compile to inject it in the DOM when a certain condition has reached.
This is just an (untested) example. It can and should be optimized:
HTML:
<conditional-template ng-model="element" template-url1="path/to/partial1" template-url2="path/to/partial2"></div>
Directive:
app.directive('conditionalTemplate', function($http, $compile) {
return {
restrict: 'E',
require: '^ngModel',
link: function(sope, element, attrs, ctrl) {
// get template with $http
// check model via ctrl.$viewValue
// compile with $compile
// replace element with element.replaceWith()
}
};
});
You can use ng-show on every div element in the loop. Is this what you've wanted: http://jsfiddle.net/pGwRu/2/ ?
<div class="from" ng-show="message.from">From: {{message.from.name}}</div>

Categories