Diretiva de carregamento AngularJS

O problema

Quando você tem uma visão em AngularJS com conteúdo que é carregado dinamicamente do servidor (usando o serviço $ http, por exemplo) você provavelmente terminará com um espaço em branco inicial na página, que será substituído pelo conteúdo quando o a resposta chega do servidor.

Seria melhor exibir um ícone de carregamento como um espaço reservado para o conteúdo, que seria substituído pelo conteúdo quando os dados terminassem de ser carregados pelo seu controlador. Idealmente, você não deseja implementar essa lógica em todos os controladores que carregam dados dinamicamente, então seria bom implementar uma solução apenas uma vez e então reutilizá-la em todas as suas visualizações que esperam aguardar o carregamento de algum conteúdo.

A solução

Eu criei uma diretiva chamada ng-loading que pode ser colocada como um atributo do div cujo conteúdo você deseja substituir por um ícone de carregamento enquanto o conteúdo real do div ainda está sendo preparado. O valor do atributo ng-loading é uma expressão que, quando avaliada como true, substituirá o ícone de carregamento pelo conteúdo original do div. Abaixo está o código da diretiva:

 angular.module('yourModule')
.directive('ngLoading', function (Session, $compile) {

var loadingSpinner = <LOADING_ICON_HTML_HERE>;

return {
restrict: 'A',
link
: function (scope, element, attrs) {
var originalContent = element.html();
element
.html(loadingSpinner);
scope
.$watch(attrs.ngLoading, function (val) {
if(val) {
element
.html(originalContent);
$compile
(element.contents())(scope);
} else {
element
.html(loadingSpinner);
}
});
}
};
});

Quando esta diretiva é inicializada, ela armazena o conteúdo original do div e, em seguida, substitui-o pelo html de um ícone de carregamento. Você pode usar qualquer ícone que quiser aqui, mas eu recomendo usar um do Spin Kit de Thobias Ahlin ( http://tobiasahlin.com/spinkit/ ) porque eles têm uma aparência incrível. Apenas certifique-se de incluir o CSS relacionado em sua folha de estilo.

Abaixo está um exemplo de como usar a diretiva ng-loading em sua visualização:

<div id="customerPanel" ng-loading="customer">
Customer's name: {{customer.name}}

</div>

O conteúdo do div customerPanel será substituído por um ícone de carregamento até que a variável do cliente do escopo seja avaliada como algo diferente de 0, nulo ou indefinido (você pode alterar “if (val)” no código da diretiva para “if (typeof val! == ‘undefined’) “se isso te incomoda).

Podemos melhorar ainda mais a diretiva, fornecendo outro atributo contendo uma mensagem a ser exibida quando o conteúdo que estava carregando não for encontrado:

link: function (scope, element, attrs) {
var originalContent = element.html();
element
.html(loadingSpinner);
scope
.$watch(attrs.ngLoading, function (val) {
if(val) {
if(val.notFound) {
element
.html(attrs.ngLoadingNotFound ? attrs.ngLoadingNotFound : "Not found.");
} else {
element
.html(originalContent);
$compile
(element.contents())(scope);
}
} else {
element
.html(loadingSpinner);
}
});
}

E o código na visualização se torna:

<div id="customerPanel" ng-loading="customer" ng-loading-not-found="Customer not found!">
Customer's name: {{customer.name}}

</div>

Desta forma, se a variável de escopo no atributo ng-loading for avaliada como um objeto contendo uma propriedade chamada ‘notFound’ com o valor true, o conteúdo da div será substituído pela mensagem definida no atributo ‘ng-loading- not-found ‘, ou por uma mensagem padrão caso este atributo não tenha sido definido.

Se desejar, você também pode adicionar classes CSS personalizadas adicionais aos divs onde o atributo ng-loading é aplicado para criar tamanhos diferentes para o ícone de carregamento. No meu projeto criei as classes pequeno, médio e grande para um dos spinners que tirei do SpinKit mencionado acima.