We know that when an application page or component needs to load data, both the browser and Angular will take a certain amount of time to render the page. The gap here may be so small that the difference is not noticeable; or it may be long, causing our users to see a page that has not been rendered.
This situation is called Flash Of Unrendered Content (FOUC) (K)? and is always unwanted. Below we'll go over a few different ways to prevent this from happening to our users.
1. ng-cloak
The ng-cloak directive is a built-in directive of Angular. Its function is to hide all elements contained by it:
<div ng-cloak> <h1>Hello {{ name }}</h1> </div>
The implementation principle of Ng-cloak is a directive. Page initialization is to add a line of CSS code to the header of the DOM, as follows:
<pre class= “prettyprint linenums”> [ng\:cloak],[ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak{ Display:none ! important; }
[ng\:cloak],[ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak{ Display:none ! important; }
Angular sets elements with ng-cloak to display:none.
When angular parses the node with ng-cloak, it will remove the ng-cloak attribute and calss on the element at the same time, thus preventing the node from flickering. As follows:
<script type =”text/ng-template” id =”scenario.js-150”> It(‘should remove the template directive and css class',function(){ Expect(element(‘.doc-example-live #template1').attr(‘ng-cloak')) not().toBeDefined(); Expect(element(‘.doc-example-live #template2').attr(‘ng-cloak')). Not().toBeDefined(); }); </script> <script type =”text/ng-template” id =”scenario.js-150”> It(‘should remove the template directive and css class',function(){ Expect(element(‘.doc-example-live #template1').attr(‘ng-cloak')) not().toBeDefined(); Expect(element(‘.doc-example-live #template2').attr(‘ng-cloak')). Not().toBeDefined(); }); </script>
2. ng-bind
ng-bind is another built-in instruction in Angular that is used to operate bound page data. We can use ng-bind instead of {{ }} to bind elements to the page;
Using ng-bind instead of {{ }} can prevent the unrendered {{ }} from being displayed to the user. It will be much friendlier to use empty elements rendered by ng-bind to replace {{ }}.
The above example can be rewritten as follows, so as to prevent {{ }} from appearing on the page
<div> <h1>Hello <span ng-bind="name"></span></h1> </div>
3. resolve
When using routes between different pages, we have another way to prevent the page from being rendered before the data is completely loaded into the route.
Using resolve in the route allows us to get the data we need to load before the route is fully loaded. When the data is loaded successfully, the route will change and the page will be presented to the user; if the data is not loaded successfully, the route will not change, and the $routeChangeError event will get fired. [The $routeChangeError event will (not) be activated? 】
angular.module('myApp', ['ngRoute']) .config(function($routeProvider) { $routeProvider .when('/account', { controller: 'AccountCtrl', templateUrl: 'views/account.html', resolve: { // We specify a promise to be resolved account: function($q) { var d = $q.defer(); $timeout(function() { d.resolve({ id: 1, name: 'Ari Lerner' }) }, 1000); return d.promise; } } }) });
The resolve item requires a key/value object. Key is the name of the resolve dependency. Value can be a string (as a service) or a method that returns the dependency.
resolve is very useful when the resolve value returns a promise that becomes resolved or rejected.
When the route is loaded, the keys in the resolve parameter can be used as injectable dependencies:
angular.module('myApp') .controller('AccountCtrl', function($scope, account) { $scope.account = account; });
We can also use the resolve key to pass the results returned by the $http method, as $http returns promises from it's method calls:
angular.module('myApp', ['ngRoute']) .config(function($routeProvider) { $routeProvider .when('/account', { controller: 'AccountCtrl', templateUrl: 'views/account.html', resolve: { account: function($http) { return $http.get('http://example.com/account.json') } } }) });
It is recommended to define an independent service to use resolve key, and use the service to return the required data accordingly (this method is easier to test). To handle it this way, we need to create a service:
First, take a look at accountService,
angular.module('app') .factory('accountService', function($http, $q) { return { getAccount: function() { var d = $q.defer(); $http.get('/account') .then(function(response) { d.resolve(response.data) }, function err(reason) { d.reject(reason); }); return d.promise; } } })
After defining the service, we can use this service to replace the direct call to $http in the above code:
angular.module('myApp', ['ngRoute']) .config(function($routeProvider) { $routeProvider .when('/account', { controller: 'AccountCtrl', templateUrl: 'views/account.html', resolve: { // We specify a promise to be resolved account: function(accountService) { return accountService.getAccount() } } }) });