Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Malas prácticas en Angular: Avanzado
Angular Firebase profile imageAntonio Cardenas
Antonio Cardenas forAngular Firebase

Posted on • Edited on

     

Malas prácticas en Angular: Avanzado

Autor de el post original en inglesArmen Vardanyan publicado paraindepth.dev artículo original eninglés

Hace algún tiempo el señorArmen Vardanyan publicó un artículo en inDepth recopilando las malas prácticas usadas siempre por desarrolladores en aplicaciones Angular. el cual puedes ver en españolaquí.

El día de hoy, el enfoque será centrarnos en algunos otros patrones que hacen que nuestros componentes/directivas/servicios y otras partes de nuestras aplicaciones Angular sean menos legibles y más difíciles de razonar. Sin más preámbulos, ¡comencemos!

Contaminar el ngOnInit

ngOnInit puede ser el gancho del ciclo de vida(lifecycle hook) más importante en los componentes Angular; se utiliza para inicializar datos, configurar algunos listeners, crear conexiones, etc. Pero a veces esto puede hacer que nuestro ciclo de vida sea demasiado abrumador:

ngOnInit puede ser el gancho del ciclo de vida(lifecycle hook) más importante en los componentes Angular; se utiliza para inicializar datos, configurar algunos listeners, crear conexiones, etc. Pero a veces esto puede hacer que nuestro ciclo de vida sea demasiado abrumador:

@Component({selector:'alguna',template:'plantilla',})exportclassSomeComponentimplementsOnInit,OnDestroy{@ViewChild('btn')buttonRef:ElementRef<HTMLButtonElement>;form=this.formBuilder.group({nombre:[''],apellido:[''],edad:[''],ocupacion:[''],})destroy$=newSubject<void>();constructor(privatereadonlyservice:Service,privateformBuilder:FormBuilder,){}ngOnInit(){this.service.getSomeData().subscribe(res=>{// manejar respuesta});this.service.getSomeOtherData().subscribe(res=>{// Mucha lógica puede ir aquí});this.form.get('age').valueChanges.pipe(map(age=>+age),takeUntil(this.destroy$),).subscribe(age=>{if(age>=18){// Hacer otras cosas}else{// Hacer otras cosas}});this.form.get('ocupacion').valueChanges.pipe(filter(ocupacion=>['ingeniero','doctor','actor'].indexOf(occupation)>-1),takeUntil(this.destroy$),).subscribe(ocupacion=>{// Haz un poco de trabajo pesado aquí});combineLatest(this.form.get('nombre').valueChanges,this.form.get('apellido').valueChanges,).pipe(debounceTime(300),map(([nombre,apellido])=>`${nombre}${apellido}`),switchMap(nombreCompleto=>this.service.getUser(nombreCompleto)),takeUntil(this.destroy$),).subscribe(user=>{// Hacer Algo});fromEvent(this.buttonRef.nativeElement,'click').pipe(takeUntil(this.destroy$),).subscribe(event=>{// manejar evento})}ngOnDestroy(){this.destroy$.next();}}
Enter fullscreen modeExit fullscreen mode

Eche un vistazo a este componente. No tiene muchos métodos; en realidad, solo tiene dos ciclos de vida. Pero el métodongOnInit es, francamente hablando, aterrador. Se suscribe a diferentes eventos de cambio de formulario, desde flujos defromEvent, también carga una gran cantidad de datos. Tiene 40 líneas de código, pero en realidad hemos omitido el contenido de las devoluciones de llamada desubscribe; con ellos, puede que sean más de 100 líneas, lo que ya va en contra de la mayoría de las pautas de software. Además, normalmente trabajamos con otros métodos y no conngOnInit, por lo que necesitaremos un mejor acceso a los otros métodos, pero ahora tendríamos que desplazarnos por todo este lío para llegar a ellos (o cerrar / reabrir ngOnInit cada vez que necesitemos ver eso). Además, encontrar algo dentro del métodongOnInit en sí se vuelve más difícil porque hay muchos conceptos y tareas mezclados.

Ahora echemos un vistazo a esta versión revisada del mismo componente:

@Component({selector:'alguna',template:'plantilla =',})exportclassSomeComponentimplementsOnInit,OnDestroy{@ViewChild('btn')buttonRef:ElementRef<HTMLButtonElement>;form=this.formBuilder.group({nombre:[''],apellido:[''],edad:[''],ocupacion:[''],})destroy$=newSubject<void>();constructor(privatereadonlyservice:Service,privateformBuilder:FormBuilder,){}ngOnInit(){this.loadInitialData();this.setupFormListeners();this.setupEventListeners();}privatesetupFormListeners(){this.form.get('edad').valueChanges.pipe(map(edad=>+edad),takeUntil(this.destroy$),).subscribe(age=>{if(edad>=18){// hacer alguna cosa}else{// hacer alguna cosa}});this.form.get('ocupacion').valueChanges.pipe(filter(ocupacion=>['ingeniero','doctor','actor'].indexOf(occupation)>-1),takeUntil(this.destroy$),).subscribe(ocupacion=>{// Hacer un poco de trabajo pesado aquí});combineLatest(this.form.get('nombre').valueChanges,this.form.get('apellido').valueChanges,).pipe(debounceTime(300),map(([nombre,apellido])=>`${nombre}${apellido}`),switchMap(nombreCompleto=>this.service.getUser(nombreCompleto)),takeUntil(this.destroy$),).subscribe(user=>{// Do some stuff});}privateloadInitialData(){this.service.getSomeData().subscribe(res=>{// manejar respuesta});this.service.getSomeOtherData().subscribe(res=>{// Mucha de la logica va aqui});}privatesetupEventListeners(){fromEvent(this.buttonRef.nativeElement,'click').pipe(takeUntil(this.destroy$),).subscribe(event=>{// handle event})}ngOnDestroy(){this.destroy$.next();}}
Enter fullscreen modeExit fullscreen mode

La lógica del componente es la misma, pero la forma en que organizamos nuestro código es diferente. Ahora, el métodongOnInit llama a tres métodos diferentes para cargar los datos iniciales de los servicios, configurar oyentes de cambio de formulario y configurar oyentes de eventos DOM (si es necesario). Después de este cambio, leer el componente desde cero se vuelve más fácil (leangOnInit: comprenda lo que comienza de un vistazo y, si necesita detalles de implementación, visite los métodos correspondientes). Encontrar la fuente de los errores también es relativamente más fácil: si los escuchas de formulario no funcionan correctamente, vaya asetupFormListeners y así sucesivamente.

No contamine su método ngOnInit- divídalo en partes!

Escribir selectores de directivas inútiles

Las directivas Angular son una herramienta poderosa que nos permite aplicar lógica personalizada a diferentes elementos HTML. Al hacerlo, utilizamos selectores css, lo que en realidad nos da mucho más poder de lo que queremos darnos cuenta. Aquí hay un ejemplo: imagine una directiva que verifica si el formControl del elemento correspondiente tiene errores y le aplica algún estilo; llamémoslo ErrorHighlightDirective. Ahora digamos que le damos un selector de atributos, digamos [errorHighlight]. Funciona bien, pero ahora tenemos que encontrar todos los elementos de formulario con el atributo formControl y poner nuestro[errorHighlight]en ellos, lo cual es una tarea tediosa. Pero, por supuesto, podemos usar el selector de atributos de la directiva [formControl], por lo que nuestra directiva se verá así:

@Directive({selector:'[formControl],[formControlName]'})exportclassErrorHighlightDirective{// implementacion}
Enter fullscreen modeExit fullscreen mode

Ahora nuestra directiva se vinculará automáticamente a todos los controles de formulario en nuestro módulo.
Pero el uso no termina ahí. Imagine que queremos aplicar una animación inestable a todos losformControls del formulario que tienen una clasehas-error. Podemos escribir fácilmente una directiva y vincularla usando un selector de clases: .has-error.

Utilice mejores selectores para sus directivas para evitar saturar su HTML con atributos innecesarios

Lógica dentro de un constructor de servicios

Los servicios son clases y, como tales, tienen unconstructor, que generalmente se usa para inyectar dependencias. Pero a veces los desarrolladores también escriben algún código/lógica de inicialización dentro de él. Y, a veces, esta no es la mejor idea, y este es el motivo.

Imagine un servicio que crea y mantiene una conexión de socket, envía datos al servidor en tiempo real y los envía incluso desde el servidor. Aquí hay una implementación ingenua:

@Injectable()classSocketService{privateconnection:SocketConnection;constructor(){this.connection=openWebSocket();// detalles de implementación omitidos}subscribe(eventName:string,cb:(event:SocketEvent)=>any){this.connection.on(eventName,cb);}send<Textendsany>(event:string,payload:T){this.connection.send(event,payload);}}
Enter fullscreen modeExit fullscreen mode

Este servicio básico crea una conexión de socket y maneja interacciones con ella. ¿Notas algo fuera de lugar?

El problema es que cada vez que se crea una nueva instancia de este servicio, se abre una nueva conexión. ¡Y puede que este no sea el caso que queremos!

En realidad, muchas veces una aplicación usará una conexión de un solo socket, por lo que cuando usemos este servicio dentro de módulos cargados de forma diferida, obtendremos una nueva conexión abierta. Para evitar esto, necesitamos eliminar la lógica de inicialización de este constructor y encontrar otra forma de compartir la conexión entre los módulos cargados de forma diferida. Además, es posible que deseemos tener un método que nos permita recargar la conexión a voluntad (esencialmente, volver a abrirla, por ejemplo, si se cierra inesperadamente):

@Injectable()classSocketService{constructor(privateconnection:SocketConnection// la conexión de socket en sí se proporciona en la raíz de la aplicación y es la misma en todas partes){}// manejar para recargar un socket, implementación ingenuaopenConnection(){this.connection=openWebSocket();}subscribe(eventName:string,cb:(event:SocketEvent)=>any){this.connection.on(eventName,cb);}send<Textendsany>(event:string,payload:T){this.connection.send(event,payload);}}
Enter fullscreen modeExit fullscreen mode

Agregar un nuevo estado cuando puede derivarlo del estado existente

Cada componente tiene su estado: un conjunto de propiedades que contienen datos esenciales para representar la interfaz de usuario. El estado es la parte lógica más importante de nuestra aplicación, por lo que manejarlo correctamente tiene grandes beneficios.

El estado puede describirse comooriginal y_derivado _. El estado original se puede describir como datos independientes que existen en sí mismos, - por ejemplo, el estado de inicio de sesión. El estado derivado depende completamente de alguna parte del estado original, - por ejemplo, un aviso de texto que dice "Inicie sesión" si el el usuario está desconectado o "Desconectar" si el usuario ha iniciado sesión. Esencialmente, no necesitamos guardar ese valor de texto en ningún lugar; siempre que lo necesitemos, podemos calcularlo en función del estado de autenticación. Entonces esta pieza de código:

@Component({selector:'some',template:'<button>{{ text }}</button>',})exportclassSomeComponent{isAuth=false;text='Desconectar';constructor(privateauthService:AuthService,){}ngOnInit(){this.authService.authChange.subscribe(auth=>{this.isAuth=auth;this.text=this.isAuth?'Desconectar':'Iniciar Session';});}}
Enter fullscreen modeExit fullscreen mode

se convertira en esto:

@Component({selector:'some',template:`<button>{{ isAuth ? 'Desconectar' : 'Iniciar Session' }}</button>`,})exportclassSomeComponent{isAuth=false;constructor(privateauthService:AuthService,){}ngOnInit(){this.authService.authChange.subscribe(auth=>this.isAuth=auth);}}
Enter fullscreen modeExit fullscreen mode

Como puedes ver, la propiedad de text fue un estado derivado y era completamente innecesario. Eliminarlo hizo que el código fuera más fácil de leer y razonar.

No cree variables y propiedades independientes para almacenar el estado derivado; calcúlalo siempre que sea necesario

Este puede parecer un poco fácil de detectar, pero cuando se trata de datos cada vez más complejos, incluso los desarrolladores más experimentados a veces cometen este error, especialmente con las transmisiones RxJS. En este artículo, exploro cómo se debe manejar este concepto en las aplicacionesRxJS Angular.

Conclusión

Hay muchos errores que se pueden cometer al escribir una aplicación con Angular. Pero algunos errores son muy comunes y se convierten en patrones, que se re utilizan y abusan. Conocer los más comunes y cómo evitarlos puede ser muy beneficioso para nuestras aplicaciones Angular.

Autor de este postArmen Vardanyan publicado paraindepth.dev artículo original eninglés

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

web-dev

More fromAngular Firebase

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp