javascript - nganimate - ngshow angular 4
AngularJS: muestra la animación de carga para el script lento, por ejemplo, mientras se filtra (8)
En angularjs 1.2 las operaciones como filtrar una ng-repeat
con muchas filas (> 2,000 filas) pueden volverse bastante lentas (> 1 segundo).
Sé que puedo optimizar los tiempos de ejecución usando limitTo
, pagination, filtros personalizados, etc. pero aún me interesa saber si es posible mostrar una animación de carga mientras el navegador está ocupado ejecutando largos guiones.
En el caso de angular, creo que podría invocarse siempre que se ejecute $digest
porque esa parece ser la función principal que ocupa más tiempo y podría llamarse varias veces.
En una pregunta relacionada, no se dieron respuestas útiles. Cualquier ayuda muy apreciada!
Aquí hay un ejemplo de trabajo:
angular
.module("APP", [])
.controller("myCtrl", function ($scope, $timeout) {
var mc = this
mc.loading = true
mc.listRendered = []
mc.listByCategory = []
mc.categories = ["law", "science", "chemistry", "physics"]
mc.filterByCategory = function (category) {
mc.loading = true
// This timeout will start on the next event loop so
// filterByCategory function will exit just triggering
// the show of Loading... status
// When the function inside timeout is called, it will
// filter and set the model values and when finished call
// an inbuilt $digest at the end.
$timeout(function () {
mc.listByCategory = mc.listFetched.filter(function (ele) {
return ele.category === category
})
mc.listRendered = mc.listByCategory
$scope.$emit("loaded")
}, 50)
}
// This timeout is replicating the data fetch from a server
$timeout(function () {
mc.listFetched = makeList()
mc.listRendered = mc.listFetched
mc.loading = false
}, 50)
$scope.$on("loaded", function () { mc.loading = false })
})
function makeList() {
var list = [
{name: "book1", category: "law"},
{name: "book2", category: "science"},
{name: "book1", category: "chemistry"},
{name: "book1", category: "physics"}
]
var bigList = []
for (var i = 0; i <= 5000; i++) {
bigList = bigList.concat(list)
}
return bigList
}
button {
display: inline-block;
}
<html>
<head>
<title>This is an Angular Js Filter Workaround!!</title>
</head>
<body ng-app="APP">
<div ng-controller="myCtrl as mc">
<div class = "buttons">
<label>Select Category:- </label>
<button ng-repeat="category in mc.categories" ng-click="mc.filterByCategory(category)">{{category}}</button>
</div>
<h1 ng-if="mc.loading">Loading...</h1>
<ul ng-if="!mc.loading">
<li ng-repeat="ele in mc.listRendered track by $index">{{ele.name}} - {{ele.category}}</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
</body>
<html>
El problema es que mientras JavaScript se esté ejecutando, la UI no tendrá oportunidad de actualizarse. Incluso si presenta una ruleta antes de filtrar, aparecerá "congelada" mientras Angular esté ocupada.
Una forma de superar esto es filtrar en fragmentos y, si hay más datos disponibles, filtrar de nuevo después de un pequeño $timeout
. El tiempo de espera le da al subproceso UI la oportunidad de ejecutar y mostrar cambios y animaciones.
Un violín que demuestra el principio está here .
No usa los filtros de Angular (son sincrónicos). En su lugar, filtra la matriz de data
con la siguiente función:
function filter() {
var i=0, filtered = [];
innerFilter();
function innerFilter() {
var counter;
for( counter=0; i < $scope.data.length && counter < 5; counter++, i++ ) {
/////////////////////////////////////////////////////////
// REAL FILTER LOGIC; BETTER SPLIT TO ANOTHER FUNCTION //
if( $scope.data[i].indexOf($scope.filter) >= 0 ) {
filtered.push($scope.data[i]);
}
/////////////////////////////////////////////////////////
}
if( i === $scope.data.length ) {
$scope.filteredData = filtered;
$scope.filtering = false;
}
else {
$timeout(innerFilter, 10);
}
}
}
Requiere 2 variables de soporte: $scope.filtering
es true
cuando el filtro está activo, dándonos la oportunidad de mostrar el spinner y deshabilitar la entrada; $scope.filteredData
recibe el resultado del filtro.
Hay 3 parámetros ocultos:
- el tamaño del fragmento (
counter < 5
) es pequeño a propósito para demostrar el efecto - el retraso de tiempo de espera (
$timeout(innerFilter, 10)
) debe ser pequeño, pero suficiente para que el subproceso de la interfaz de usuario tenga tiempo de responder - la lógica de filtro real, que probablemente debería ser una devolución de llamada en escenarios de la vida real.
Esto es solo una prueba de concepto; Sugeriría refactorizarlo (a una directiva probablemente) para uso real.
Estos son los pasos:
- Primero, debes usar animaciones CSS. No se deben utilizar animaciones y GIF controlados por JS dentro de los procesos pesados bec. del límite de hilo único. La animación se congelará. Las animaciones de CSS están separadas del subproceso de interfaz de usuario y son compatibles con IE 10+ y todos los principales navegadores.
- Escribe una directiva y colócala fuera de tu
ng-view
confixed
posicionamientofixed
. - Enlace a su controlador de aplicación con alguna bandera especial.
- Alterne la visibilidad de esta directiva antes y después de procesos largos / pesados. (Incluso puede vincular un mensaje de texto a la directiva para mostrar información útil al usuario). - Interactuar con esto o cualquier otra cosa directamente dentro de un ciclo de proceso pesado tomará más tiempo para terminar. Eso es malo para el usuario!
Plantilla de directiva:
<div class="activity-box" ng-show="!!message">
<img src="img/load.png" width="40" height="40" />
<span>{{ message }}</span>
</div>
Directiva de activity
:
Una directiva simple con un solo message
atributo. Tenga en cuenta la directiva ng-show
en la plantilla de arriba. El message
se usa para alternar el indicador de actividad y también para establecer el texto de información para el usuario.
app.directive(''activity'', [
function () {
return {
restrict: ''EA'',
templateUrl: ''/templates/activity.html'',
replace: true,
scope: {
message: ''@''
},
link: function (scope, element, attrs) {}
};
}
]);
SPA HTML:
<body ng-controller="appController">
<div ng-view id="content-view">...</div>
<div activity message="{{ activityMessage }}"></div>
</body>
Tenga en cuenta que la directiva de activity
coloca fuera de ng-view
. Estará disponible en cada sección de su aplicación de una sola página.
Controlador de aplicaciones:
app.controller(''appController'',
function ($scope, $timeout) {
// You would better place these two methods below, inside a
// service or factory; so you can inject that service anywhere
// within the app and toggle the activity indicator on/off where needed
$scope.showActivity = function (msg) {
$timeout(function () {
$scope.activityMessage = msg;
});
};
$scope.hideActivity = function () {
$timeout(function () {
$scope.activityMessage = '''';
}, 1000); // message will be visible at least 1 sec
};
// So here is how we do it:
// "Before" the process, we set the message and the activity indicator is shown
$scope.showActivity(''Loading items...'');
var i;
for (i = 0; i < 10000; i += 1) {
// here goes some heavy process
}
// "After" the process completes, we hide the activity indicator.
$scope.hideActivity();
});
Por supuesto, puedes usar esto en otros lugares también. por ejemplo, puede llamar a $scope.hideActivity();
cuando una promesa se resuelve O alternar la actividad a request
y la response
del httpInterceptor
es una buena idea.
Ejemplo de CSS:
.activity-box {
display: block;
position: fixed; /* fixed position so it doesn''t scroll */
z-index: 9999; /* on top of everything else */
width: 250px;
margin-left: -125px; /* horizontally centered */
left: 50%;
top: 10px; /* displayed on the top of the page, just like Gmail''s yellow info-box */
height: 40px;
padding: 10px;
background-color: #f3e9b5;
border-radius: 4px;
}
/* styles for the activity text */
.activity-box span {
display: block;
position: relative;
margin-left: 60px;
margin-top: 10px;
font-family: arial;
font-size: 15px;
}
/* animating a static image */
.activity-box img {
display: block;
position: absolute;
width: 40px;
height: 40px;
/* Below is the key for the rotating animation */
-webkit-animation: spin 1s infinite linear;
-moz-animation: spin 1s infinite linear;
-o-animation: spin 1s infinite linear;
animation: spin 1s infinite linear;
}
/* keyframe animation defined for various browsers */
@-moz-keyframes spin {
0% { -moz-transform: rotate(0deg); }
100% { -moz-transform: rotate(359deg); }
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(359deg); }
}
@-o-keyframes spin {
0% { -o-transform: rotate(0deg); }
100% { -o-transform: rotate(359deg); }
}
@-ms-keyframes spin {
0% { -ms-transform: rotate(0deg); }
100% { -ms-transform: rotate(359deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
Espero que esto ayude.
Lo que puede hacer es detectar el final de ngRepeat
como dice esta publicación .
Haré algo así como, en el controlador:
$scope.READY = false;
Y en la directiva, como dice la publicación anterior, haré algo como:
if (scope.$last) {
$scope.READY = true;
}
Y puedes tener un cargador / spinner basado en css con
<div class="loader" ng-show="!READY">
</div>
Por supuesto, también puede tener animaciones basadas en CSS que son independientes de la ejecución de js.
Promesa / aplazamiento se puede utilizar en este caso, puede llamar a notificar para ver el progreso de su código, documentación de angular.js: https://code.angularjs.org/1.2.16/docs/api/ng/service/ $ q Aquí hay un tutorial sobre el procesamiento pesado de JS que usa ng-Promise: http://liamkaufman.com/blog/2013/09/09/using-angularjs-promises/ , espero que sea útil.
// aplicación Fábrica para mantener el modelo de datos
app.factory (''postFactory'', función ($ http, $ q, $ timeout) {
var factory = {
posts : false,
getPosts : function(){
var deferred = $q.defer();
//avoiding the http.get request each time
//we call the getPosts function
if (factory.posts !== false){
deferred.resolve(factory.posts);
}else{
$http.get(''posts.json'')
.success(function(data, status){
factory.posts = data
//to show the loading !
$timeout(function(){
deferred.resolve(factory.posts)
}, 2000);
})
.error(function(data, status){
deferred.error(''Cant get the posts !'')
})
};
return deferred.promise;
},
getPost : function(id){
//promise
var deferred = $q.defer();
var post = {};
var posts = factory.getPosts().then(function(posts){
post = factory.posts[id];
//send the post if promise kept
deferred.resolve(post);
}, function(msg){
deferred.reject(msg);
})
return deferred.promise;
},
};
return factory;
});
Puede ejecutar el filtro en otra WebWorkers usando WebWorkers , por lo que su página angularjs no se bloqueará.
Si no utiliza webworkers, el navegador podría obtener un tiempo de espera de ejecución de javascript y detener por completo su aplicación angular, e incluso si no obtiene ningún tiempo de espera, su aplicación se congelará hasta que se complete el cálculo.
ACTUALIZACIÓN 23.04.14
He visto una importante mejora en el rendimiento en un proyecto a gran escala que usa scalyr y bindonce
Puede usar este código tomado de esta url: http://www.directiv.es/angular-loading-bar
allí puedes encontrar una demostración de trabajo también.
Aquí está el código de thei:
<!DOCTYPE html>
<html ng-app="APP">
<head>
<meta charset="UTF-8">
<title>angular-loading-bar example</title>
<link rel="stylesheet" type="text/css" href="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.css"/>
<style>
body{
padding:25px;
}
</style>
</head>
<body ng-controller="ExampleController">
<button id="reloader" ng-click="getUsers()">Reload</button>
<ul ng-repeat="person in data">
<li>{{person.lname}}, {{person.fname}}</li>
</ul>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-animate.min.js"></script>
<script src="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.js"></script>
<script>
angular.module(''APP'', [''chieffancypants.loadingBar'', ''ngAnimate'']).
controller("ExampleController",[''$scope'',''$http'',function($scope,$http){
$scope.getUsers = function(){
$scope.data=[];
var url = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&delay=3&callback=JSON_CALLBACK"
$http.jsonp(url).success(function(data){
$scope.data=data;
})
}
$scope.getUsers()
}])
</script>
</body>
</html>
¿Como lo uso?
Instalarlo a través de npm o bower
$ npm install angular-loading-bar
$ bower install angular-loading-bar
Para usar, simplemente inclúyalo como una dependencia en tu aplicación y ¡listo!
angular.module(''myApp'', [''angular-loading-bar''])
Utilice spin.js y el sitio http://fgnass.github.com/spin.js/ muestra el paso que es bastante fácil. la animación de carga está en CSS, que se separó del subproceso de la interfaz de usuario y, por lo tanto, se cargó sin problemas.