Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: How to prevent "code flash" in page while loading

I have created a simple app using AngularJS. When I open the page for a second I see the screen below:

enter image description here

However, after the load is complete I see the loaded and styled content which is fine:

enter image description here

How do I prevent the flash of AngularJS code on my page ? Is this related to FOUC ?

Here is the HTML code:

<!doctype html>
<html class="no-js" lang="en" ng-app="MainApp">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Foundation | Welcome</title>
    <link rel="stylesheet" href="css/foundation.css" />
    <script src="js/vendor/modernizr.js"></script>

    <style>
    .row.full-width {
        width: 100%;
        margin-left: auto;
        margin-right: auto;
        max-width: initial;
    }
    </style>

</head>

<body ng-controller="MainCtrl">
    <div class="off-canvas-wrap" data-offcanvas>
        <div class="inner-wrap">
            <nav class="tab-bar">
                <section class="right-small">
                    <a class="right-off-canvas-toggle menu-icon" href="#"><span></span></a>
                </section>
                <section class="left tab-bar-section">
                    <h1 class="title">Salary Calculator</h1>
                </section>
            </nav>

            <aside class="right-off-canvas-menu">
                <ul class="off-canvas-list">
                    <li>
                        <label>Location</label>
                    </li>
                    <li><a href="#">United Kingdom</a>
                    </li>
                </ul>
            </aside>

            <section class="main-section">


                <div class="row full-width">
                    <div class="large-4 columns">

                        <ul class="tabs" data-tab>
                            <li class="tab-title active"><a href="#panel1">Annual Salary</a>
                            </li>
                            <li class="tab-title"><a href="#panel2">Monthly Expenses</a>
                            </li>
                        </ul>
                        <div class="tabs-content">
                            <div class="content active" id="panel1">
                                <div class="row">
                                    <div class="large-12 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Salary" ng-model="salary"/>
                                    </div>
                                </div>
                            </div>
                            <div class="content" id="panel2">
                                <div class="row">
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Rent" ng-model="rent" />
                                    </div>
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Wireless, TV, Home Phone" ng-model="telecom"/>
                                    </div>

                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="TV License" ng-model="tv" />
                                    </div>
                                </div>

                                <div class="row">
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Mobile Phone" ng-model="mobile"/>
                                    </div>
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Subscription" ng-model="subscription"/>
                                    </div>

                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Electricty" ng-model="electricity" />
                                    </div>
                                </div>

                                <div class="row">
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Food" ng-model="food"/>
                                    </div>
                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Transport" ng-model="transport" />
                                    </div>

                                    <div class="large-4 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Charity" ng-model="charity"/>
                                    </div>
                                </div>

                                <div class="row">
                                    <div class="large-12 columns">
                                        <input ng-change="calculate()" type="text" placeholder="Other" ng-model="other"/>
                                    </div>
                                </div>

                            </div>
                        </div>

                    </div>
                    <div class="large-8 columns"  ng-cloak >
                        <table >
                       <thead>
                            <tr>
                                <th width="200"></th>
                                <th width="250">Yearly</th>
                                <th width="250">Monthly</th>
                                <th width="250">Weekly</th>
                                <th width="250">Daily</th>
                            </tr>
                        </thead>
                        <tbody ng-repeat="val in results">
                            <tr>
                                <td>{{val.rowType}}</td>
                                <td>{{val.yearly}}</td>
                                <td>{{val.monthly}}</td>
                                <td>{{val.weekly}}</td>
                                <td>{{val.daily}}</td>

                            </tr>
                        </tbody>
                        </table>

                    </div>
                </div>



            </section>

            <a class="exit-off-canvas"></a>

        </div>
    </div>

    <script src="../bower_components/angularjs/angular.js"></script>


    <script src="js/app-service.js"></script>
    <script src="js/app-controller.js"></script>
    <script src="js/app-directives.js"></script>
    <script src="js/app.js"></script>

    <script src="js/vendor/jquery.js"></script>
    <script src="js/foundation.min.js"></script>

    <script>
    $(document).foundation();
    </script>
</body>

</html>

EDIT:

Please see my answer as well for an alternative solution in addition to the accepted one.

like image 267
Cemre Mengü Avatar asked Feb 14 '15 09:02

Cemre Mengü


4 Answers

It has been a long time but here is for my working solution for this one:

You need to use ng-cloak on the body tag of your html BUT the most important part is this CSS below:

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }

For me I had to add this for getting ng-cloak to work. This is probably not the only solution to this problem as can be seen in other answers. Hope this helps someone.

like image 40
Cemre Mengü Avatar answered Oct 06 '22 22:10

Cemre Mengü


ng-cloak will help to some extent, but you can fully prevent it using ng-bind directive instead of using {{ }}.

e.g.

<td ng-bind="val.monthly"> </td>

not

<td>{{val.monthly}}</td>
like image 136
Michael Low Avatar answered Oct 06 '22 22:10

Michael Low


Angular already gives you the tool to prevent this: ngCloak: https://docs.angularjs.org/api/ng/directive/ngCloak

Just put the directive on your body like <body ng-cloak> and it should work.

EDIT The Docs also advice you to actually not put it on the body, but on smaller portions of your page - wherever you see the need. Depending on the size of your page, that is a good idea. For smaller Pages I put it on the body and never had problems.

like image 30
David Losert Avatar answered Oct 06 '22 23:10

David Losert


Along with ng-cloak, you can use a resolve object in your router. This will prevent the controller from instantiating and the view from rendering until the data is there.

In the following example I am assuming you are using uiRouter. The same pattern applies for ngRouter.

Your state config:

$stateProvider
    .state('yourState',{
        templateUrl: 'yourTemplate.html',
        controller: 'YourController as vm',
        resolve: YourController.resolve
    })

As you can see, you have set the resolve property of the state to a static resolve object on your controller. Now the route will not resolve until this object is resolved.

To setup resolve object, lets assume you have a service yourService that has a method getData that returns a promise. This is very important. Because we don't want the route resolved until the promise is resolved.

So your controller may look something like this.

YourController.$inject = ['yourService'];
function YourController(yourService) {
    var self = this;
    yourService.getData().then((data) { self.data = data});
}

This is pretty standard. You can access the data from the view with vm.data but you will see a flash of {{vm.data}}. That is, if we remove the resolve we have added to the state config.

So now we change the controller to add a static resolve object to work with the resolve we have added to the state config.

YourController.resolve = {
    'yourService': 'yourService',
    'data': ['yourService', function(yourService) {
        return yourService.getData();    
    }]
}

YourController.$inject = ['data'];
function YourController(data) {
    this.data = data;
}

So now we have a resolve object. The yourService will resolve as a normal service, but the data property will resolve only when the promise returned by getData() is resolved. Then this data will be passed directly into the controller using Dependancy Injection.

In reality, you probably wont need to use ng-cloak if you use resolve.

Here is a working example:

angular.module('app', ['ui.router'])

.config(['$stateProvider',
  function($stateProvider) {

    $stateProvider
    
      .state('noDot', {
        controller: "NoDotController",
        template: "Using a old style $scope binding {{customers[0].CutomerName}}"
      })
             
      .state('noResolve', {
        controller: "NoResolveController as vm",
        template: "We are displaying things before the data is here {{vm.customers[0].CustomerName}}"
      })

    .state('withResolve', {
      controller: "WithResolveController as vm",
      template: "We are waiting for data before displaying anything {{vm.customers[0].CustomerName}}",
      resolve: WithResolveController.resolve
    })

    .state('empty', {
      template: ""
    })

  }
])

.controller('NoResolveController', NoResolveController)
  .controller('NoDotController', NoDotController)
  .controller('WithResolveController', WithResolveController)
  .service('northwind', Northwind);

NoDotController.$inject = ['$scope', 'northwind'];
function NoDotController($scope, northwind) {
  northwind.getCustomers().then(function(customers) {
    $scope.customers = customers});
}

NoResolveController.$inject = ['northwind'];
function NoResolveController(northwind) {
  var self = this;
  northwind.getCustomers().then(function(customers) {
    self.customers = customers;
  });
}

WithResolveController.resolve = {
  'northwind': 'northwind',
  'customers': ['northwind',
    function(northwind) {
      return northwind.getCustomers();
    }
  ]
}
WithResolveController.$inject = ['customers'];
function WithResolveController(customers) {
  this.customers = customers;
}

Northwind.$inject = ['$timeout', '$q'];
function Northwind($timeout, $q) {
  this.$q = $q;
  this.$timeout = $timeout;
}
Northwind.prototype.getCustomers = function() {
  var deferred = this.$q.defer();

  this.$timeout(function() {
    deferred.resolve([{CustomerName: "Name of Customer"}])
  }, 1000);

  return deferred.promise;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.js"></script>
<div ng-app="app">
  <a ui-sref="noDot" href="#">No Dot</a>
  <span> | </span>
  <a ui-sref="empty" href="#">Emtpy</a>
  <span> | </span>
  <a ui-sref="noResolve" href="#">No Resolve</a>
  <span> | </span>
  <a ui-sref="empty" href="#">Emtpy</a>
  <span> | </span>
  <a ui-sref="withResolve" href="#">With Resolve</a>
  <br>
  <br>
  <ui-view></ui-view>

</div>
like image 35
Martin Avatar answered Oct 06 '22 21:10

Martin