Buscar..


7 mejoras simples de rendimiento

1) Utilice ng-repetir con moderación

El uso de ng-repeat en las vistas generalmente produce un rendimiento deficiente, especialmente cuando hay ng-repeat anidadas.

Esto es super lento!

<div ng-repeat="user in userCollection">
  <div ng-repeat="details in user">
    {{details}}
  </div>
</div>

Trate de evitar las repeticiones anidadas tanto como sea posible. Una forma de mejorar el rendimiento de ng-repeat es usar track by $index (o algún otro campo de identificación). Por defecto, ng-repeat rastrea todo el objeto. Con track by , Angular mira el objeto solo por $index o id de objeto.

<div ng-repeat="user in userCollection track by $index">
  {{user.data}}
</div>

Use otros enfoques como paginación , pergaminos virtuales , infinitos o limitTo: comience siempre que sea posible para evitar iterar en grandes colecciones.


2) Atar una vez

Angular tiene enlace de datos bidireccional. Viene con el costo de ser lento si se usa demasiado.

Rendimiento más lento

<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>

Rendimiento más rápido (AngularJS> = 1.3)

<!-- Bind once is much faster -->
<div>{{ ::my.data }}</div>

<div ng-bind="::my.data"></div>

<!-- Use single binding notation in ng-repeat where only list display is needed  -->
<div ng-repeat="user in ::userCollection">
  {{::user.data}}
</div>

El uso de la notación "unir una vez" le dice a Angular que espere a que el valor se estabilice después de la primera serie de ciclos de digestión. Angular utilizará ese valor en el DOM, luego eliminará a todos los observadores para que se convierta en un valor estático y ya no esté vinculado al modelo.

El {{}} es mucho más lento.

Este ng-bind es una directiva y colocará un observador en la variable pasada. Por lo tanto, el ng-bind solo se aplicará cuando el valor pasado realmente cambie.

Por otro lado, los corchetes se verán sucios y se actualizarán en cada $digest , incluso si no es necesario.


3) Las funciones de alcance y los filtros toman tiempo

AngularJS tiene un bucle de digestión. Todas sus funciones están en una vista y los filtros se ejecutan cada vez que se ejecuta el ciclo de resumen. El bucle de resumen se ejecutará cada vez que se actualice el modelo y puede ralentizar la aplicación (el filtro se puede golpear varias veces antes de que se cargue la página).

Evita esto:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Mejor enfoque

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Donde el controlador puede ser:

app.controller('bigCalulations', function(valueService) {
    // bad, because this is called in every digest loop
    this.calculateMe = function() {
        var t = 0;
        for(i = 0; i < 1000; i++) {
            t += i;
        }
        return t;
    }
    // good, because this is executed just once and logic is separated in service to    keep the controller light
    this.preCalulatedValue = valueService.valueCalculation(); // returns 499500
});

4 observadores

Los observadores bajan tremendamente el rendimiento. Con más observadores, el bucle de resumen tardará más tiempo y la interfaz de usuario se ralentizará. Si el observador detecta un cambio, iniciará el bucle de resumen y volverá a representar la vista.

Hay tres formas de hacer una observación manual de cambios variables en Angular.

$watch() - observa cambios de valor

$watchCollection() : vigila los cambios en la colección (mira más de $watch regular)

$watch(..., true) : evite esto tanto como sea posible, realizará una "observación profunda" y disminuirá el rendimiento (observa más que watchCollection )

Tenga en cuenta que si está vinculando variables en la vista, está creando nuevos relojes: use {{::variable}} para evitar la creación de un reloj, especialmente en bucles.

Como resultado, debe realizar un seguimiento de cuántos observadores está utilizando. Puede contar a los observadores con este script (crédito para @Words Like Jared Number of watchers )

(function() {
    var root = angular.element(document.getElementsByTagName('body')),
        watchers = [],
        f = function(element) {
        angular.forEach(['$scope', '$isolateScope'], function(scopeProperty) {
            if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
                angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
                watchers.push(watcher);
                });
            }
        });

        angular.forEach(element.children(), function(childElement) {
            f(angular.element(childElement));
        });
    };
 
    f(root);
 
    // Remove duplicate watchers
    var watchersWithoutDuplicates = [];
    angular.forEach(watchers, function(item) {
        if(watchersWithoutDuplicates.indexOf(item) < 0) {
            watchersWithoutDuplicates.push(item);
        }
    });
    console.log(watchersWithoutDuplicates.length);
})();

5) ng-if / ng-show

Estas funciones son muy similares en comportamiento. ng-if elimina elementos del DOM, mientras que ng-show solo oculta los elementos pero conserva todos los controladores. Si tiene partes del código que no desea mostrar, use ng-if .

Depende del tipo de uso, pero a menudo uno es más adecuado que el otro.

  • Si el elemento no es necesario, use ng-if

  • Para activar / desactivar rápidamente, use ng-show/ng-hide

    <div ng-repeat="user in userCollection">
      <p ng-if="user.hasTreeLegs">I am special<!-- some complicated DOM --></p>
      <p ng-show="user.hasSubscribed">I am awesome<!-- switch this setting on and off --></p>
    </div>
    

Si tiene dudas, use ng-if y pruebe!


6) Deshabilitar la depuración

De forma predeterminada, las directivas y los ámbitos de enlace dejan clases y marcas adicionales en el código para ayudar con varias herramientas de depuración. Deshabilitar esta opción significa que ya no procesa estos diversos elementos durante el ciclo de resumen.

angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
    $compileProvider.debugInfoEnabled(false);
}]);

7) Usa la inyección de dependencia para exponer tus recursos.

La inyección de dependencia es un patrón de diseño de software en el que un objeto recibe sus dependencias, en lugar del objeto que las crea. Se trata de eliminar las dependencias codificadas y hacer posible cambiarlas cuando sea necesario.

Podría preguntarse sobre el costo de rendimiento asociado con el análisis de cadenas de todas las funciones inyectables. Angular se encarga de esto almacenando en caché la propiedad $ inject después de la primera vez. Así que esto no ocurre cada vez que una función necesita ser invocada.

CONSEJO PROFESIONAL: Si está buscando el enfoque con el mejor rendimiento, vaya con el enfoque de anotación de propiedad de $ inyectar. Este enfoque evita por completo el análisis de la definición de la función porque esta lógica se ajusta dentro de la siguiente comprobación en la función de anotación: if (! ($ Inject = fn. $ Inject)). Si $ inyectar ya está disponible, ¡no se requiere análisis!

var app = angular.module('DemoApp', []);

var DemoController = function (s, h) {
    h.get('https://api.github.com/users/angular/repos').success(function (repos) {
        s.repos = repos;
    });
}
// $inject property annotation
DemoController['$inject'] = ['$scope', '$http'];

app.controller('DemoController', DemoController);

PRO TIP 2: puede agregar una directiva ng-strict-di en el mismo elemento que ng-app para optar al modo DI estricto, que generará un error cada vez que un servicio intente usar anotaciones implícitas. Ejemplo:

<html ng-app="DemoApp" ng-strict-di>

O si usas bootstrapping manual:

angular.bootstrap(document, ['DemoApp'], {
    strictDi: true
});

Atar una vez

Angular tiene reputación de tener un enlace de datos bidireccional impresionante. De forma predeterminada, Angular sincroniza continuamente los valores enlazados entre los componentes del modelo y de la vista en cualquier momento en que los datos cambian en el componente del modelo o en la vista.

Esto conlleva el costo de ser un poco lento si se usa demasiado. Esto tendrá un mayor impacto de rendimiento:

Mal rendimiento: {{my.data}}

Agregue dos dos puntos :: antes del nombre de la variable para usar el enlace de una sola vez. En este caso, el valor solo se actualiza una vez que se define my.data. Usted está apuntando explícitamente a no observar los cambios en los datos. Angular no realizará ninguna verificación de valores, lo que dará como resultado que se evalúen menos expresiones en cada ciclo de resumen.

Buenos ejemplos de rendimiento utilizando un enlace de una sola vez

{{::my.data}}
<span ng-bind="::my.data"></span>
<span ng-if="::my.data"></span>
<span ng-repeat="item in ::my.data">{{item}}</span>
<span ng-class="::{ 'my-class': my.data }"></div>

Nota: Sin embargo, esto elimina el enlace de datos bidireccional para my.data , por lo que siempre que este campo cambie en su aplicación, el mismo no se reflejará en la vista automáticamente. Por lo tanto, úselo solo para valores que no cambiarán a lo largo de la vida útil de su aplicación .

Funciones de alcance y filtros.

AngularJS tiene un bucle de resumen y todas sus funciones en una vista y filtros se ejecutan cada vez que se ejecuta el ciclo de resumen. El bucle de resumen se ejecutará cada vez que se actualice el modelo y puede ralentizar la aplicación (el filtro se puede golpear varias veces, antes de que se cargue la página).

Debes evitar esto:

<div ng-controller="bigCalulations as calc">
  <p>{{calc.calculateMe()}}</p>
  <p>{{calc.data | heavyFilter}}</p>
</div>

Mejor enfoque

<div ng-controller="bigCalulations as calc">
  <p>{{calc.preCalculatedValue}}</p>
  <p>{{calc.data | lightFilter}}</p>
</div>

Donde la muestra del controlador es:

.controller("bigCalulations", function(valueService) {
  // bad, because this is called in every digest loop
  this.calculateMe = function() {
    var t = 0;
    for(i = 0; i < 1000; i++) {
      t = t + i;
    }
    return t;
  }
  //good, because it is executed just once and logic is separated in service to    keep the controller light
  this.preCalulatedValue = valueService.caluclateSumm(); // returns 499500
});

Vigilantes

Los observadores necesitan ver algún valor y detectar que este valor ha cambiado.

Después de llamar a $watch() o $watchCollection nuevo observador se agrega a la colección de observadores internos en el alcance actual.

Entonces, ¿qué es el observador?

Watcher es una función simple, que se llama en cada ciclo de resumen, y devuelve algo de valor. Angular verifica el valor devuelto, si no es el mismo que en la llamada anterior: se ejecutará una devolución de llamada que se pasó en el segundo parámetro para funcionar $watch() o $watchCollection .

(function() {
  angular.module("app", []).controller("ctrl", function($scope) {
    $scope.value = 10;
    $scope.$watch(
      function() { return $scope.value; },
      function() { console.log("value changed"); }
    );
  }
})();

Los observadores son asesinos de rendimiento. Cuantos más observadores tengas, más tiempo tardarán en hacer un bucle de resumen, más lenta será la IU. Si un observador detecta cambios, iniciará el ciclo de resumen (recálculo en todas las pantallas)

Hay tres formas de hacer una observación manual para cambios variables en Angular.

$watch() - solo observa los cambios de valor

$watchCollection() : vigila los cambios en la colección (mira más de $ regular)

$watch(..., true) : evite esto tanto como sea posible, realizará una "observación profunda" y eliminará el rendimiento (observa más que watchCollection)

Tenga en cuenta que si está vinculando variables en la vista, está creando nuevos observadores; use {{::variable}} no para crear observadores, especialmente en bucles

Como resultado, debe realizar un seguimiento de cuántos observadores está utilizando. Puede contar a los observadores con este script (crédito para @Words Like Jared - ¿Cómo contar el número total de relojes en una página?

(function() {
  var root = angular.element(document.getElementsByTagName("body")),
      watchers = [];

  var f = function(element) {

    angular.forEach(["$scope", "$isolateScope"], function(scopeProperty) {
      if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
        angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
          watchers.push(watcher);
        });
      }
    });

    angular.forEach(element.children(), function(childElement) {
      f(angular.element(childElement));
    });

  };

  f(root);

  // Remove duplicate watchers
  var watchersWithoutDuplicates = [];
  angular.forEach(watchers, function(item) {
    if(watchersWithoutDuplicates.indexOf(item) < 0) {
      watchersWithoutDuplicates.push(item);
    }
  });

  console.log(watchersWithoutDuplicates.length);

})();

Si no desea crear su propio script, hay una utilidad de código abierto llamada ng-stats que utiliza un gráfico en tiempo real integrado en la página para darle una idea del número de relojes que Angular está administrando, así como el Frecuencia y duración de los ciclos de digestión a lo largo del tiempo. La utilidad expone una función global llamada showAngularStats que puede llamar para configurar cómo desea que funcione el gráfico.

showAngularStats({
  "position": "topleft",
  "digestTimeThreshold": 16,
  "autoload": true,
  "logDigest": true,
  "logWatches": true
});

El código de ejemplo anterior muestra el siguiente cuadro en la página automáticamente ( demostración interactiva ).

captura de pantalla del gráfico ng-stats

ng-if vs ng-show

Estas funciones son muy similares en comportamiento. La diferencia es que ng-if elimina elementos del DOM. Si hay partes grandes del código que no se mostrarán, entonces ng-if es el camino a seguir. ng-show solo ocultará los elementos pero conservará todos los manejadores.

ng-si

La directiva ngIf elimina o vuelve a crear una parte del árbol DOM en función de una expresión. Si la expresión asignada a ngIf se evalúa como un valor falso, entonces el elemento se elimina del DOM, de lo contrario, se reinserta en el DOM un clon del elemento.

ng-show

La directiva ngShow muestra u oculta el elemento HTML dado en función de la expresión proporcionada al atributo ngShow. El elemento se muestra u oculta al eliminar o agregar la clase de CSS ng-hide al elemento.

Ejemplo

<div ng-repeat="user in userCollection">
  <p ng-if="user.hasTreeLegs">I am special
    <!-- some complicated DOM -->
  </p>
  <p ng-show="user.hasSubscribed">I am aweosme
    <!-- switch this setting on and off -->
  </p>
</div>

Conclusión

Depende del tipo de uso, pero a menudo uno es más adecuado que el otro (por ejemplo, si el 95% del tiempo no se necesita el elemento, use ng-if ; si necesita cambiar la visibilidad del elemento DOM, use ng-show ).

En caso de duda, utilice ng-if y pruebe!

Nota : ng-if crea un nuevo alcance aislado, mientras que ng-show y ng-hide no lo hacen. Use $parent.property si no se puede acceder directamente a la propiedad del ámbito principal.

Rebota tu modelo

<div ng-controller="ExampleController">
    <form name="userForm">
        Name:
        <input type="text" name="userName"
           ng-model="user.name"
           ng-model-options="{ debounce: 1000 }" />
            <button ng-click="userForm.userName.$rollbackViewValue();   user.name=''">Clear</button><br />
    </form>
    <pre>user.name = </pre>
</div>

En el ejemplo anterior, estamos configurando un valor de rebote de 1000 milisegundos que es de 1 segundo. Esto es un retraso considerable, pero evitará que la entrada golpee repetidamente el ng-model con muchos ciclos de $digest .

Al usar rebotar en sus campos de entrada y en cualquier otro lugar donde no se requiera una actualización instantánea, puede aumentar el rendimiento de sus aplicaciones Angular de manera sustancial. No solo puede retrasar el tiempo, sino que también puede retrasarse cuando se desencadena la acción. Si no desea actualizar su modelo ng con cada pulsación de tecla, también puede actualizar el desenfoque.

Siempre anular el registro de los oyentes registrados en otros ámbitos distintos del alcance actual

Siempre debe anular el registro de otros ámbitos que no sean su alcance actual como se muestra a continuación:

//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);

No tiene que anular el registro de las listas en el alcance actual, ya que angular se haría cargo de ello:

//no need to deregister this
$scope.$on(...);

$rootScope.$on escuchas permanecerán en la memoria si navega a otro controlador. Esto creará una pérdida de memoria si el controlador queda fuera del alcance.

No hacer

angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];

function badExample($scope, $rootScope) {
    $rootScope.$on('post:created', function postCreated(event, data) {});
}

Hacer

angular.module('app').controller('goodExampleController', goodExample);
goodExample.$inject = ['$scope', '$rootScope'];

function goodExample($scope, $rootScope) {
    var deregister = $rootScope.$on('post:created', function postCreated(event, data) {});

    $scope.$on('$destroy', function destroyScope() {
        deregister();
    });
}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow