Angularjs - Hide content until DOM loaded - angularjs

I am having an issue in Angularjs where there is a flicker in my HTML before my data comes back from the server.
Here is a video demonstrating the issue: http://youtu.be/husTG3dMFOM - notice the #| and the gray area to the right.
I have tried ngCloak with no success (although ngCloak does prevent the brackets from appearing as promised) and am wondering the best way to hide content until the HTML has been populated by Angular.
I got it to work with this code in my controller:
var caseCtrl = function($scope, $http, $routeParams) {
$('#caseWrap').hide(); // hides when triggered using jQuery
var id = $routeParams.caseId;
$http({method: 'GET', url: '/v1/cases/' + id}).
success(function(data, status, headers, config) {
$scope.caseData = data;
$('#caseWrap').show(); // shows using jQuery after server returns data
}).
error(function(data, status, headers, config) {
console.log('getCase Error', arguments);
});
}
...but I have heard time and time again not to manipulate the DOM from a controller. My question is how can I achieve this using a directive? In other words, how can I hide the element that a directive is attached to until all content is loaded from the server?

In your CSS add:
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
and just add a "ng-cloak" attribute to your div like here:
<div id="template1" ng-cloak>{{scoped_var}}<div>
doc: https://docs.angularjs.org/api/ng/directive/ngCloak

On your caseWrap element, put ng-show="contentLoaded" and then where you currently have $('#caseWrap').show(); put $scope.contentLoaded = true;
If the caseWrap element is outside this controller, you can do the same kind of thing using either $rootScope or events.

Add the following to your CSS:
[ng\:cloak],[ng-cloak],.ng-cloak{display:none !important}
The compiling of your angular templates isn't happening fast enough.
UPDATE
You should not do DOM manipulation in your controller. There are two thing you can do...
1. You can intercept changes to the value within the scope of the controller via a directive! In your case, create a directive as an attribute that is assigned the property you want to watch. In your case, it would be caseData. If casedata is falsey, hide it. Otherwise, show it.
A simpler way is just use ngShow='casedata'.
Code
var myApp = angular.module('myApp', []);
myApp.controller("caseCtrl", function ($scope, $http, $routeParams, $timeout) {
$scope.caseData = null;
//mimic a delay in getting the data from $http
$timeout(function () {
$scope.caseData = 'hey!';
}, 1000);
})
.directive('showHide', function () {
return {
link: function (scope, element, attributes, controller) {
scope.$watch(attributes.showHide, function (v) {
if (v) {
element.show();
} else {
element.hide();
}
});
}
};
});
HTML
<div ng-controller='caseCtrl' show-hide='caseData'>using directive</div>
<div ng-controller='caseCtrl' ng-show='caseData'>using ngShow</div>
JSFIDDLE:http://jsfiddle.net/mac1175/zzwBS/

Since you asked for a directive, try this.
.directive('showOnLoad', function() {
return {
restrict: 'A',
link: function($scope,elem,attrs) {
elem.hide();
$scope.$on('show', function() {
elem.show();
});
}
}
});
Stick (show-on-load) in your element, and in your controller inject $rootScope, and use this broadcast event when the html has loaded.
$rootScope.$broadcast('show');

I have used Zack's response to create a 'loading' directive, which might be useful to some people.
Template:
<script id="ll-loading.html" type="text/ng-template">
<div layout='column' layout-align='center center'>
<md-progress-circular md-mode="indeterminate" value="" md-diameter="52"></md-progress-circular>
</div>
</script>
Directive:
directives.directive('loading', function() {
return {
restrict: 'E',
template: 'll-loading.html',
link: function($scope,elem,attrs) {
elem.show();
$scope.$on('loaded', function() {
console.log("loaded: ");
elem.hide();
});
}
}
});
This example uses angular-material in the html

The accepted answer didn't work for me. I had some elements that had ng-show directives and the elements would still show momentarily even with the ng-cloak. It appears that the ng-cloak was resolved before the ng-show returned false. Adding the ng-hide class to my elements fixed my issue.

Related

AngularJS: Can't call onClick() when using ng-bind-html [duplicate]

I've included a Plunker here: http://plnkr.co/edit/4vqV8toHo0vNjtfICtzI?p=preview
I'm trying to add a button to the DOM and when clicked should execute the function bound to it. In this case it should alert "testing". Here is the code.
controller
app.controller('MainCtrl', function($scope, $sce) {
$scope.trustedHtml = $sce.trustAsHtml('<button ng-click="testAlert()">Submit</button>');
$scope.testAlert = function () {
alert('testing')
};
});
HTML
<body ng-controller="MainCtrl">
<div ng-bind-html="trustedHtml"></div>
</body>
$sce.trustAsHtml and ng-bind-html are not meant to build HTML with directives. This technique will not work.
This is because angular works by first compiling and then linking. See the conceptual overview for a good explaination.
In short, by the time you link the HTML defined in your trustAsHtml, it is too late for angular to compile (and therefore understand) the ng-click directive.
In order to dynamically add HTML, you should be looking at the $compile service (and/or directives). Docs are here.
For Angular 1.6.1, I found a solution that worked for me.
template:
<div ng-bind-html="trustAsHtml(content);" init-bind> </div>
In controller:
$scope.trustAsHtml = function(string) {
return $sce.trustAsHtml(string);
};
Directive:
.directive('initBind', function($compile) {
return {
restrict: 'A',
link : function (scope, element, attr) {
attr.$observe('ngBindHtml',function(){
if(attr.ngBindHtml){
$compile(element[0].children)(scope);
}
})
}
};
})

Change templateURL of directive dynamically after $http.get()

I'm working on 'skeleton' loading the UI in different components. I have a directive that I'm loading a template initially (this template has low opacity and looks like a mock table). Once I get the data I need in an http.get() then I want to change the templateUrl of the directive. Below is what I've tried so far.
function registers($log, $state, $templateCache, currentCountData, storeFactory) {
var directive = {
restrict: 'A',
scope: true,
templateUrl: '/app/shared/tableMock/tableMock.html',
link: link
};
return directive;
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$templateCache.put('/app/dashboard/registers/registers.html');
});
}
}
I'm not sure I'm on the right track. I can step through and see the storeFactory return the correct data from my factory. How can I then change the templateUrl of the directive?
For cases like this I usually do something like this in my directive template:
<div ng-switch="viewState">
<div ng-switch-when="gotRegisters">
Content for when you get the registers
</div>
<div ng-switch-default>
For when you don't have the registers
</div>
</div>
This way you could just change a variable to show your content ie scope.viewState = 'gotRegisters'; instead of waiting for it to download after you already downloaded your registers.
With a help from this question I was able to come up with this
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$http.get('/app/dashboard/registers/registers.html', { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
});
}
tplContent correlates to the response of the $http.get(). It's the html in the registers.html file. element represents the directive itself.

AngularJS postmessage to iframe

I am looking for a way to obtain an iframe contentWindow object and post a message to it after some action of the user. My current solution does not feel ok with angular at all (especially accessing the DOM from the controller).
I have created a plunker demonstrating the issue:
http://plnkr.co/edit/aXh4jydWGWfK3QQD4edd
Is the a more angular way to execute the postMessage?
controller:
app.controller('Main', function($scope) {
$scope.click = function() {
var iframe = document.getElementById("inner").contentWindow;
iframe.postMessage("Hello iframe", '*');
}
});
html:
<body ng-controller="Main">
<button ng-click="click()">send message</button>
<iframe id="inner" src="inner.html"/>
</body>
I realize your question is over a year old at this point but I've recently had a similar need so I thought I'd post my solution. Originally I had something like you posted but as you pointed out this doesn't feel very "Angular". It's also not easily testable which I supposed is also not very "Angular".
Instead I've refactored my code to implement the iframe as a directive. I then $broadcast() events from my app's controllers and have the directive listen for them. This code can probably be improved quite a bit but it's feels a little more "Angular" and avoids directly touching the DOM.
'use strict';
angular.module('app')
.directive('angularIframe', ['$rootScope', function($rootScope) {
return {
restrict: 'E',
replace: true,
template: '<iframe id="game" src="/iframe/index.html" width="100%" height="100%" frameboarder="0" scrolling="no"></iframe>',
link: function(scope, elem) {
var off = $rootScope.$on('app.postmessage', function(ev, data, targetOrigin) {
var str = JSON.stringify(data);
targetOrigin = targetOrigin || '*';
elem[0].contentWindow.postMessage(str, targetOrigin);
});
// See: http://stackoverflow.com/a/14898795/608884
elem.on('$destroy', function() {
off();
});
}
};
}]);
You can then use this directive by adding <game></game> somewhere in your application.
In a controller you can now broadcast the app.postmessage event along with some data to invoke postMessage() on the iframe:
var myData = { foo: 'bar' };
$rootScope.$broadcast('app.postmessage', myData);

AngularJS enable directive once $http get finishes

app.controller("MainController", function($scope, $http){
$scope.items= [];
console.log('hi');
$http.get('../assets/data/data.json').then(function(response) {
$scope.drinks =response.data.drinks;
});
});
app.directive('cycle', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).cycle({
fx: 'fade',
timeout: 10
});
}
};
});
I have the above AngularJS and when I run my page the Console.Log shows [cycle] terminating; too few slides: 0 and then it shows the Get Request after.
How would I be able to run the directive once the $http.get has finished?
try doing this
<div cycle ng-if="drinks"></div>
that way element that contains directive will be compiled in browser only when the drinks are available
There are two things you can use
ng-cloak
ng-show and ng-hide and custom scope property
Also consider using animation instead of creating manual animation within directive

JQuery UI Spinner is not updating ng-model in angular

Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});

Resources