El autor del artículo, cuya primera parte de la traducción estamos publicando, dice que ha estado trabajando en una aplicación angular a gran escala en 
Trade Me durante aproximadamente dos años. En los últimos años, el equipo de desarrollo de aplicaciones ha estado mejorando constantemente el proyecto, tanto en términos de calidad de código como de rendimiento.
Esta serie de materiales se centrará en los enfoques de desarrollo utilizados por el equipo de Trade Me, que se expresan en forma de más de dos docenas de recomendaciones sobre tecnologías como Angular, TypeScript, RxJS y @ ngrx / store. Además, se prestará atención a las técnicas de programación universal destinadas a hacer que el código de la aplicación sea más limpio y preciso.
1. Acerca de trackBy
Usando 
ngFor para recorrer matrices en plantillas, use esta construcción con la función 
trackBy , que devuelve un identificador único para cada elemento.
▍ Explicaciones
Cuando la matriz cambia, Angular vuelve a representar todo el árbol DOM. Pero si usa 
trackBy , el sistema sabrá qué elemento ha cambiado y realizará cambios en el DOM que se aplican solo a ese elemento en particular. Los detalles sobre esto se pueden encontrar 
aquí .
▍To
 <li *ngFor="let item of items;">{{ item }}</li> 
▍Después
 //   <li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li> //   trackByFn(index, item) {     return item.id; //  id,   } 
2. Palabras clave const y let
Si va a declarar una variable cuyo valor no planea cambiar, use la palabra clave 
const .
▍ Explicaciones
El uso apropiado de las palabras clave 
let y 
const aclara la intención con respecto al uso de entidades declaradas que las usan. Además, este enfoque facilita el reconocimiento de los problemas causados por sobrescribir accidentalmente valores constantes. En esta situación, se produce un error de compilación. Además, mejora la legibilidad del código.
▍To
 let car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) {  myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) {  yourCar = `${youCar}s`; } 
▍Después
 //  car  ,     car  const car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) {  myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) {  yourCar = `${youCar}s`; } 
3. Operadores de transportadores
Cuando trabaje con RxJS, use operadores canalizados.
▍ Explicaciones
Los operadores transportados admiten el algoritmo de vibración de árbol, es decir, cuando se importan, solo se incluirá en el proyecto el código que se planea ejecutar. Esto también facilita la identificación de declaraciones no utilizadas en los archivos.
Tenga en cuenta que esta recomendación es relevante para Angular versión 5.5 y superior.
▍To
 import 'rxjs/add/operator/map'; import 'rxjs/add/operator/take'; iAmAnObservable   .map(value => value.item)   .take(1); 
▍Después
 import { map, take } from 'rxjs/operators'; iAmAnObservable   .pipe(      map(value => value.item),      take(1)    ); 
4. Aislamiento de correcciones de API
No todas las API son completamente estables y sin errores. Por lo tanto, a veces es necesario introducir algo de lógica en el código destinado a solucionar problemas de API. En lugar de colocar esta lógica en componentes donde se usan API parcheadas, sería mejor aislarla en algún lugar, por ejemplo, en un servicio, y referirse desde el componente ya no es a la API problemática, sino al servicio correspondiente.
▍ Explicaciones
El enfoque propuesto permite mantener las correcciones "más cerca" de la API, es decir, lo más cerca posible del código desde el que se realizan las solicitudes de red. Como resultado, se reduce la cantidad de código de aplicación que interactúa con API problemáticas. Además, resulta que todas las correcciones están en un solo lugar, como resultado será más fácil trabajar con ellas. Si tiene que corregir errores en la API, entonces es mucho más fácil hacer esto en un solo archivo que distribuir estas correcciones en toda la aplicación. Esto facilita no solo la creación de correcciones, sino también la búsqueda del código apropiado en el proyecto y su soporte.
Además, puede crear sus propias etiquetas, como 
API_FIX (que se parece a la etiqueta 
TODO ), y marcar las correcciones con ellas. Esto facilita encontrar tales soluciones.
5. Suscripción en la plantilla.
Evite suscribirse a observables desde componentes. En cambio, suscríbase a ellos en plantillas.
▍ Explicaciones
Los operadores canalizados asíncronos se anulan automáticamente, lo que simplifica el código y elimina la necesidad de una administración de suscripción manual. Esto, además, reduce el riesgo de que el desarrollador se olvide de darse de baja del componente, lo que puede provocar pérdidas de memoria. Es posible reducir la probabilidad de pérdidas de memoria utilizando reglas de interinterruptor destinadas a identificar objetos observables de los que no se dieron de baja.
Además, la aplicación de este enfoque lleva al hecho de que los componentes dejan de ser componentes con estado, lo que puede generar errores cuando los datos cambian fuera de la suscripción.
▍To
 //  <p>{{ textToDisplay }}</p> //  iAmAnObservable   .pipe(      map(value => value.item),      takeUntil(this._destroyed$)    )   .subscribe(item => this.textToDisplay = item); 
▍Después
 //  <p>{{ textToDisplay$ | async }}</p> //  this.textToDisplay$ = iAmAnObservable   .pipe(      map(value => value.item)    ); 
6. Eliminar suscripciones
Al suscribirse a objetos supervisados, asegúrese siempre de que las suscripciones a ellos se eliminen correctamente utilizando operadores como 
take , 
takeUntil etc.
▍ Explicaciones
Si no se da de baja del objeto observado, esto provocará pérdidas de memoria, ya que la secuencia del objeto observado permanecerá abierta, lo que es posible incluso después de que se destruya el componente o después de que el usuario vaya a otra página de la aplicación.
Aún mejor sería crear una regla linter para detectar objetos observados con una suscripción válida para ellos.
▍To
 iAmAnObservable   .pipe(      map(value => value.item)        )   .subscribe(item => this.textToDisplay = item); 
▍Después
Use el operador 
takeUntil si desea observar los cambios de un objeto hasta que otro objeto observado genere un cierto valor:
 private destroyed$ = new Subject(); public ngOnInit (): void {   iAmAnObservable   .pipe(      map(value => value.item)      
Usar algo como 
this es un patrón utilizado para controlar la eliminación de suscripciones a muchos objetos observados en un componente.
Use 
take si solo necesita el primer valor devuelto por el objeto observado:
 iAmAnObservable   .pipe(      map(value => value.item),      take(1),      takeUntil(this._destroyed$)   )   .subscribe(item => this.textToDisplay = item); 
Tenga en cuenta que aquí usamos 
takeUntil con 
take . Esto se hace para evitar pérdidas de memoria causadas por el hecho de que la suscripción no condujo a obtener el valor hasta que se destruyó el componente. Si la función 
takeUntil no se usara 
takeUntil , la suscripción existiría hasta que se recibiera el primer valor, pero como el componente ya se habría destruido, este valor nunca se habría recibido, lo que provocaría una pérdida de memoria.
7. Utilizando operadores adecuados
Usando operadores de suavizado con objetos observables, aplique aquellos que correspondan a las características del problema que se está resolviendo.
- Use switchMapcuando necesite ignorar la acción programada anterior cuando llegue una nueva acción.
- Use mergeMapen caso de que necesite procesar todas las acciones despachadas en paralelo.
- Utilice concatMapcuando las acciones deban procesarse una tras otra, en el orden en que se reciben.
- Use exhaustMapen situaciones en las que, en el proceso de procesamiento de acciones recibidas anteriormente, necesite ignorar otras nuevas.
Los detalles sobre esto se pueden encontrar 
aquí .
▍ Explicaciones
Usar, si es posible, un operador, en lugar de lograr el mismo efecto combinando varios operadores en una cadena, reduce la cantidad de código de aplicación que debe enviarse al usuario. El uso de un operador seleccionado incorrectamente puede conducir a un comportamiento incorrecto del programa, ya que diferentes operadores procesan los objetos observados de manera diferente.
8. Carga perezosa
Luego, siempre que sea posible, intente organizar la carga diferida de los módulos de la aplicación angular. Esta técnica se reduce al hecho de que algo se carga solo si se usa. Por ejemplo, un componente se carga solo cuando debe mostrarse.
▍ Explicaciones
La carga diferida reduce el tamaño de los materiales de la aplicación que el usuario tiene que descargar. Esto puede mejorar la velocidad de descarga de la aplicación debido al hecho de que los módulos no utilizados no se transfieren del servidor a los clientes.
▍To
 // app.routing.ts { path: 'not-lazy-loaded', component: NotLazyLoadedComponent } 
▍Después
 // app.routing.ts { path: 'lazy-load', loadChildren: 'lazy-load.module#LazyLoadModule' } // lazy-load.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { LazyLoadComponent }   from './lazy-load.component'; @NgModule({ imports: [   CommonModule,   RouterModule.forChild([        {            path: '',            component: LazyLoadComponent        }   ]) ], declarations: [   LazyLoadComponent ] }) export class LazyModule {} 
9. Acerca de las suscripciones dentro de otras suscripciones
A veces, para realizar alguna acción, es posible que necesite datos de varios objetos observables. En tal situación, evite crear suscripciones a dichos objetos dentro de los bloques de 
subscribe de otros objetos observables. En su lugar, utilice operadores adecuados para encadenar comandos. Entre tales operadores, uno puede notar con 
withLatestFrom y 
combineLatest . Considere los ejemplos y luego comente sobre ellos.
▍To
 firstObservable$.pipe(  take(1) ) .subscribe(firstValue => {   secondObservable$.pipe(       take(1)   )   .subscribe(secondValue => {       console.log(`Combined values are: ${firstValue} & ${secondValue}`);   }); }); 
▍Después
 firstObservable$.pipe(   withLatestFrom(secondObservable$),   first() ) .subscribe(([firstValue, secondValue]) => {   console.log(`Combined values are: ${firstValue} & ${secondValue}`); }); 
▍ Explicaciones
Si hablamos de legibilidad, de la complejidad del código o de los signos de código incorrecto, cuando el programa no utiliza las funciones de RxJS en su totalidad, esto indica que el desarrollador no conoce bien la API de RxJS. Si tocamos el tema del rendimiento, resulta que si el objeto observable necesita algo de tiempo para inicializarse, se suscribirá a 
firstObservable , entonces el sistema esperará a que se complete la operación, y solo después de eso comenzará el trabajo con el segundo objeto observable. Si estos objetos son solicitudes de red, se verá como una ejecución síncrona de solicitudes.
10. Sobre escribir
Siempre trate de declarar variables o constantes con un tipo diferente a 
any .
▍ Explicaciones
Si se declara una variable o constante en TypeScript sin especificar un tipo, el tipo se deducirá en función del valor que se le asigne. Esto puede llevar a problemas. Considere un ejemplo clásico de comportamiento del sistema en una situación similar:
 const x = 1; const y = 'a'; const z = x + y; console.log(`Value of z is: ${z}` //  Value of z is 1a 
Se supone que 
y es un número aquí, pero nuestro programa no lo sabe, por lo que muestra algo que parece incorrecto, pero no genera ningún mensaje de error. Se pueden evitar problemas similares asignando tipos apropiados a variables y constantes.
Reescribimos el ejemplo anterior:
 const x: number = 1; const y: number = 'a'; const z: number = x + y; //    : Type '"a"' is not assignable to type 'number'. const y:number 
Esto ayuda a evitar errores de tipo de datos.
Otra ventaja de un enfoque sistemático para escribir es que simplifica la refactorización y reduce la probabilidad de errores durante este proceso.
Considere un ejemplo:
 public ngOnInit (): void {   let myFlashObject = {       name: 'My cool name',       age: 'My cool age',       loc: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: any): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`);   console.log(`Location: ${myObject.loc}`); } //  Name: My cool name Age: My cool age Location: My cool location 
Supongamos que queremos cambiar, en 
myFlashObject , el nombre de la propiedad 
loc a la 
location y cometimos un error 
myFlashObject editar el código:
 public ngOnInit (): void {   let myFlashObject = {       name: 'My cool name',       age: 'My cool age',       location: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: any): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`);   console.log(`Location: ${myObject.loc}`); } //  Name: My cool name Age: My cool age Location: undefined 
Si no se utiliza la 
myFlashObject al crear el objeto 
myFlashObject , en nuestro caso, el sistema supone que el valor de la propiedad 
loc de 
myFlashObject undefined está 
undefined . Ella no cree que 
loc pueda ser un nombre de propiedad no válido.
Si se usa la escritura al describir el objeto 
myFlashObject , en una situación similar veremos, al compilar el código, un maravilloso mensaje de error:
 type FlashObject = {   name: string,   age: string,   location: string } public ngOnInit (): void {   let myFlashObject: FlashObject = {       name: 'My cool name',       age: 'My cool age',       //         Type '{ name: string; age: string; loc: string; }' is not assignable to type 'FlashObjectType'.       Object literal may only specify known properties, and 'loc' does not exist in type 'FlashObjectType'.       loc: 'My cool location'   }   this.processObject(myFlashObject); } public processObject(myObject: FlashObject): void {   console.log(`Name: ${myObject.name}`);   console.log(`Age: ${myObject.age}`)   //     Property 'loc' does not exist on type 'FlashObjectType'.   console.log(`Location: ${myObject.loc}`); } 
Si está comenzando a trabajar en un nuevo proyecto, será útil establecer, en el archivo 
tsconfig.json , la opción 
strict:true para habilitar la verificación estricta de tipos.
11. Sobre el uso de linter
Tslint tiene varias reglas estándar como 
no-any , 
no-magic-numbers , 
no-console . El linter se puede personalizar editando el archivo 
tslint.json para organizar la verificación del código de acuerdo con ciertas reglas.
▍ Explicaciones
Usar un linter para verificar el código significa que si algo aparece en el código que está prohibido por las reglas, recibirá un mensaje de error. Esto contribuye a la uniformidad del código del proyecto, mejora su legibilidad. Vea otras reglas de tslint aquí.
Cabe señalar que algunas reglas incluyen medios para corregir lo que se considera inadmisible de acuerdo con ellas. Si es necesario, puede crear sus propias reglas. Si está interesado, eche un vistazo a 
este material, que analiza la creación de reglas personalizadas para el linter utilizando 
TSQuery .
▍To
 public ngOnInit (): void {   console.log('I am a naughty console log message');   console.warn('I am a naughty console warning message');   console.error('I am a naughty console error message'); }  
▍Después
 // tslint.json {   "rules": {       .......       "no-console": [            true,            "log", //  console.log             "warn" //  console.warn        ]  } } // ..component.ts public ngOnInit (): void {   console.log('I am a naughty console log message');   console.warn('I am a naughty console warning message');   console.error('I am a naughty console error message'); } // .      console.log and console.warn        console.error,         Calls to 'console.log' are not allowed. Calls to 'console.warn' are not allowed. 
Resumen
Hoy revisamos 11 recomendaciones que esperamos sean útiles para los desarrolladores de Angular. La próxima vez, espera 11 consejos más.
Estimados lectores! ¿Utiliza el marco angular para desarrollar proyectos web?
