1. Introducción
Angular está en un momento de cambio. Desde hace algunas versiones se están introduciendo muchos cambios. Mientras escribía este artículo ha salido la versión 18. Quien ya tenga las bases de Angular puede ir asumiendo todos los cambios poco a poco mientras van saliendo. Pero a quien le toque aprender Angular desde cero se le puede hacer un poco difícil entender bien cuál es la manera correcta de hacer las cosas. Personalmente, me tocó aprender Angular cuando justo había salido la versión 17 y me encontré con varias formas para hacer “lo mismo”.
En la documentación está todo explicado, pero, para empezar de cero, la cantidad de información puede llegar a abrumar. Desde mi punto de vista, alguien que se introduce hoy a Angular necesita indagar mucho en la documentación, leer el blog de Angular e incluso leer/ver otros tutoriales para entender bien como se recomienda hacer las cosas, entender la evolución que está teniendo con las últimas novedades y en que estado se encuentran estas novedades exactamente.
En este artículo, haré una introducción a Angular, con la información que a mí me ha sido útil para dar los primeros pasos y entender desde dónde parte Angular y hacia dónde se dirige.
Índice
- 1. Introducción
- 2. Objetivo del artículo
- 3. Diferencias clave respecto a versiones anteriores
- 4. Requisitos previos
- 5. ¿Qué es Angular?
- 6. Command Line Interface (CLI)
- 7. Componentes
- 8. Directivas
- 9. Blocks
- 10. Servicios
- 11. Organización
- 12. Siguientes pasos
- 13. Referencias
2. Objetivo del artículo
El objetivo principal es dar una primera visión de lo que se necesita para empezar en Angular. No explicaré en profundidad cada apartado porque se podría hacer un artículo para cada uno de estos. El objetivo es tener un mapa mental de los principales conceptos de Angular, poniendo lado a lado lo nuevo con lo anterior.
Algo más a tener en cuenta es que explicaré únicamente los cambios que puedan ser más útiles para alguien que recién empieza en Angular. Se están produciendo más cambios, pero desde mi punto de vista se alejan de lo que sería una introducción a Angular.
3. Diferencias clave respecto a versiones anteriores
Hay muchas novedades en Angular en las últimas versiones. En esta sección comentaré las que para mí pueden dar la sensación que hay 2 formas para hacer lo mismo. Si es tu primera introducción a Angular, seguramente esta sección la entenderás mejor después de terminar el turorial.
Antes de empezar, es importante aclarar que varias de las funcionalidades que Angular introduce, primero tienen una fase de Developer Preview y poco a poco se van estabilizando. Esto quiere decir que estas funcionalidades ya están disponibles ser utilizadas, pero que aún podrían tener algún cambio, dependiendo del feedback que reciban de la comunidad.
3.1. Rebranding y documentación
El primer cambio no es sobre el uso de Angular en sí. Se trata del rebranding y la nueva documentación que salió en la versión 17 y se ha vuelto la web oficial en la versión 18. En estos momentos si buscáis Angular en google aparecen 2 páginas de documentación de Angular: la antigua (angular.io) y la nueva (angular.dev). Con la llegada de la versión 18, la antigua se ha deprecado y redirige a la nueva documentación, aunque aún la podemos visitar si usamos el prefijo v17.
3.2. NgModules vs Standalone APIs
Este cambio es el que menos confusión puede dar actualmente, ya que es el que lleva más tiempo. Antes de la versión 14/15, la manera de organizar una aplicación en Angular era mediante los NgModules, pero en la versión 14 se introdujeron las Standalone APIs en developer preview, en la 15 pasó a ser estable y a partir de la 17 es la forma por defecto en que se generan lo componentes, directivas y pipes. Pero aún hay proyectos en los que no se habrá podido migrar a versiones nuevas y será necesario trabajar con NgModules.
3.3. Programación reactiva con RxJS vs Signals
RxJS es una librería que permite hacer programación reactiva mediante el patrón Observer. RxJS está muy ligado a Angular y no desaparecerá de un día para otro por la llegada de las Signals en la versión 16. Angular está trabajando para que RxJS sea totalmente opcional, pero por ahora aún no es posible. Pero, de momento, sí hay algunos casos en los que las Signals permiten reemplazan el uso de RxJS. Además, hay un paquete de angular para trabajar entre Observables y Signals,
Text
|
|
---|---|
rxjs-interop
|
.
3.4. Decoradores vs Function-based APIs
Con la llegada de las Signals también llegan las function-based APIs, con las que se reemplazarán muchos decoradores. En la versión 17 se introdujeron en developer preview las
Text
|
|
---|---|
Signal Inputs
|
,
Text
|
|
---|---|
Signal Model
|
,
Text
|
|
---|---|
Signal Queries
|
y la función
Text
|
|
---|---|
output
|
, la cual no es una Signal, pero permite usarse de la misma forma que el resto.
3.5. Directivas estructurales vs Built-in control flow blocks
Para mejorar la experiencia de desarrollo, se introdujeron en la versión 17, los block template syntax, en developer preview, y en la versión 18 se han estabilizado. De modo que los bloques
Text
|
|
---|---|
@if
|
,
Text
|
|
---|---|
@switch
|
y
Text
|
|
---|---|
@for
|
reemplazan las directivas
Text
|
|
---|---|
*ngIf
|
,
Text
|
|
---|---|
*ngSwitch
|
, y
Text
|
|
---|---|
*ngFor
|
.
4. Requisitos previos
Hay algunos requisitos previos a tener en cuenta para poder introducirse a Angular sin más problemas que entender el propio framework:
-
-
- Las bases del frontend: HTML, CSS y JavaScript
- Las classes en Javascript
- Las bases de Typescript
- Los decoradores de Typescript
- Entender el concepto de componente en el frontend
-
5. ¿Qué es Angular?
Angular, más allá de un framework, es una plataforma de desarrollo web. Incluye todas las herramientas y librerías necesarias para desarrollar una aplicación web:
-
-
- Un framework de componentes.
- Librerías para el routing, la gestión de formularios, la comunicación cliente-servidor, el testing, etc.
- Un CLI para ayudar a la creación, actualización, testing y mantenimiento de las aplicaciones.
-
Se le pueden añadir y modificar algunas partes en función de lo que se necesite, pero para una introducción sería complicarlo aún más.
6. Command Line Interface (CLI)
Para desarrollar aplicaciones en Angular se recomienda utilizar su CLI. Que te permitirá gestionar un montón de cosas de la aplicación.
Para usarlo, primero necesitamos instalarlo:
Text npm install -g @angular/cli
Una vez lo tenemos instalado, podemos usar el comando
Text
|
|
---|---|
ng
|
para ejecutar todos sus comandos. Por ejemplo, podemos crear un proyecto nuevo:
Text ng new nombre-del-proyecto
cd nombre-del-proyecto
Generar la base de nuevos componentes:
Text ng generate component ruta-del-componente-(opcional)/nombre-del-componente
O, ejecutar la aplicación:
Text ng serve
Pero hay muchos otros comandos:
Text
|
|
---|---|
ng build
|
,
Text
|
|
---|---|
ng test
|
,
Text
|
|
---|---|
ng e2e
|
,
Text
|
|
---|---|
ng update
|
, etc.
7. Componentes
Los componentes son la pieza básica para crear aplicaciones en Angular. Usando el comando
Text
|
|
---|---|
ng generate component nombre-del-componente
|
se generará un componente con la estructura básica para que lo podamos modificar con nuestras necesidades.
Podemos identificar los componentes en Angular por su sufijo
Text
|
|
---|---|
component
|
, por ejemplo, si llamamos al componente “item”, el fichero principal del componente sería
Text
|
|
---|---|
item.component.ts
|
.
7.1. Estructura
Text @Component({
selector: 'app-item',
standalone: true,
imports: [],
templateUrl: './item.component.html',
styleUrl: './item.component.css'
})
export class ItemComponent {
// Aquí se define el comportamiento del componente
}
La estructura básica de un componente se compone por el decorador para definir configuración específica del component y por la clase para definir su comportamiento.
La configuración básica que tendremos en un componente es la siguiente:
-
-
- El
Textselector
es el nombre del tag de ese componente. Por ejemplo, si llamamos al selector
Textapp-item, usaremos el componente como
Text<app-item></app-item>en los templates.
- Si ponemos la propiedad
Textstandalone
a
Texttrue, estaremos configurando el componente como un un standalone component, en lugar de usar la configuración de los NgModules.
- En el array de
Textimports
, se declararán todos los módulos, componentes, directivas, etc. que se vayan a usar en ese componente.
- El template es el HTML de ese componente. Podemos definirlo mediante el campo
TexttemplateUrl
, en la que se indicará la url donde esta definido el template, o mediante el campo
Texttemplate, en el que se podrá definir directamente el template entre comillas
Text“”o comillas invertidas
Text``.
- Los estilos del componente se pueden definir en uno de estos 3 campos:
TextstyleUrl
,
TextstyleUrlso
Textstyles. La diferencia entre
TextstyleUrly
TextstyleUrlses que al segundo le deberemos pasar un array de urls, permitiendo tener los estilos divididos en distintos archivos. Y en el campo
Textstyles, podremos definir directamente los estilos en un string o en un array de strings.
- El
-
7.2. Estado y Métodos
El estado y los métodos es la forma de darle comportamiento a los componentes.
7.2.1. Campos normales
Se puede crear el estado sin usar nada más allá de lo que se define en Typescript.
Text @Component({...})
export class ItemComponent {
count = 0;
doubleCount = 0;
increment() {
this.count = this.count + 1;
this.doubleCount = this.count * 2;
}
}
7.2.2. Estado reactivo con Signals: signal, computed, effect
También se puede usar las Signals para definir el estado. Están disponibles estas 3 funciones:
Text
|
|
---|---|
signal
|
,
Text
|
|
---|---|
computed
|
y
Text
|
|
---|---|
effect
|
.
Text @Component({...})
export class ItemComponent {
count = signal(0)
doubleCount = computed(() => this.count() * 2)
constructor() {
effect(() => {
console.log(this.count())
})
}
increment() {
this.count.update(state => state + 1)
}
reset() {
this.count.set(0)
}
}
7.2.3. Estado reactivo usando RxJS
O se pueden crear estados usando la librería RxJS.
Text @Component({...})
export class ItemComponent {
count$ = new BehaviorSubject<number>(0)
doubleCount$ = this.count$.pipe(map(value => value * 2))
increment() {
this.count$.next(this.count$.value + 1)
}
}
7.3. Template
Cada componente tiene su template. Es el HTML con sintaxis extra añadida por Angular.
7.3.1. Renderizar datos dinámicos
Para renderizar datos dinámicos, hay que usar las llaves
Text
|
|
---|---|
{{ }}
|
.
Text <p>{{ count }}</p> <!-- Normal -->
<p>{{ count() }}</p> <!-- Signal -->
<p>{{ count$ | async }}</p> <!-- RxJS -->
7.3.2. Propiedades y atributos dinámicos
Para definir atributos HTML de forma dinámica a algún elemento, se usarán los corchetes
Text
|
|
---|---|
[ ]
|
. De esta forma, Angular detectará que el valor asignado se debe tratar como si fuera JavaScript, por lo que le podremos asignar algún estado del componente.
Text <button [disabled]="isDisabled">+</button>
7.3.3. Event Handling
Se pueden vincular event listeners poniendo el nombre del evento entre paréntesis
Text
|
|
---|---|
( )
|
e invocando un método al otro lado. Además, si se necesita enviar el objeto del evento a la función, Angular proporciona una variable
Text
|
|
---|---|
$event
|
.
Text <button (click)="increment()">+</button>
<button (click)="increment($event)">+</button>
7.3.4. Pipes
Algunas veces hace falta modificar al dato que queremos renderizar. Por ejemplo, mostrar las fechas en un formato concreto o el precio de algún producto con el símbolo de la moneda que corresponda. En estos casos Angular proporciona las Pipes, unas funciones que reciben un valor y lo devuelven transformado.
Para usar las Pipes se debe declarar el módulo de la Pipe en el campo
Text
|
|
---|---|
imports
|
del componente y usar el operador
Text
|
|
---|---|
|
|
en el template.
Text <p>
{{date | date: "d MMMM, y, h:mm a" : undefined : "es"}}
</p>
<p>
{{price | currency: "EUR" : "symbol" : undefined : "es"}}
</p>
Además de las Pipes que proporciona Angular, se pueden crear Pipes personalizadas para transformaciones propias. Se tendrá que crear una clase que implemente la interfaz
Text
|
|
---|---|
PipeTransform
|
y que tenga el decorador
Text
|
|
---|---|
@Pipe
|
.
7.3.5. Proyección de contenido: Slot y Multi-Slot
Alguna vez se necesita crear componentes flexibles y reutilizables, en la que hay una parte del contenido que es el mismo para todos los casos que se reutilice, pero otra parte del contenido cambia según el caso.
Para esto existe el elemento
Text
|
|
---|---|
ng-content
|
. Con el que se puede crear un slot o múltiples slots para proyectar contenido. Para tener multiples slots hay que definir un atributo
Text
|
|
---|---|
select
|
para poder indicar qué contenido va en cada slot.
Text <div>
<header>
<h3>Componente con multi ng-content</h3>
</header>
<ng-content></ng-content>
<h4>Question:</h4>
<ng-content select="[question]"></ng-content>
<h4>Answer:</h4>
<ng-content select="[answer]"></ng-content>
</div>
Text <app-component-with-multi-ng-content>
<p>Contenido proyectado por defecto</p>
<p question>¿Cómo puedo proyectar mas de un contenido a la vez?</p>
<p answer>Con multi-slot content projection!</p>
</app-component-with-multi-ng-content>
7.4. Styles
Lo más importante a tener en cuenta en los estilos de cada componente es que, por defecto, los estilos afectarán únicamente al template del propio componente. De esta forma se limitan los side effects. También se puede definir un fichero global de estilos que apliquen a todos los componentes de la aplicación.
7.5. Inputs: propiedades y atributos dinámicos
Para enviar información de un componente padre a un hijo se pueden declarar propiedades de la clase hijo como inputs. Se puede hacer mediante el decorador
Text
|
|
---|---|
@Input
|
o mediante las
Text
|
|
---|---|
Signal Inputs
|
. Y desde el template del componente padre se puede enviar la información al hijo como un atributo dinámico.
Text @Component({...})
export class ItemComponent {
// Usando el decorador.
@Input({ required: true }) title: string = '';
@Input() subtitle: string = '';
// Usando Signal inputs. A partir de la v17.1
title = input.required<string>()
subtitle = input<string>("")
}
Text <app-item-parent [title]="Título" [subtitle]="Subtítulo" />
7.6. Outputs: Event Handling
Para enviar información de un componente hijo a un padre se pueden declarar eventos personalizados. Se declara mediante el decorador
Text
|
|
---|---|
@Output
|
o la función
Text
|
|
---|---|
output
|
y cuando haya algún cambio se tiene que emitir el evento. Desde el template del componente padre es donde se vincula el evento.
Text @Component({...})
export class ItemComponent {
// Usando el decorador.
@Output() onNameChange = new EventEmitter<string>();
// Usando output function. A partir de la v17.3
onNameChange = output<string>();
// Método para lanzar el evento
updateName(value: string) {
this.onNameChange.emit(value);
}
}
Text <app-item-parent (onNameChanged)="handleNameChange($event)" />
7.7. Constructor vs Inject
Cuando se necesite inyectar algún servicio en el componente deberemos usar el
Text
|
|
---|---|
constructor
|
o la función
Text
|
|
---|---|
inject
|
.
Text @Component({...})
export class ItemComponent {
// Usando el constructor.
constructor(private http: HttpClient) {}
// Usando la función inject. A partir de la v16.
private http = inject(HttpClient)
}
7.8. Host element
Para crear un componente en Angular hay que especificar un
Text
|
|
---|---|
selector
|
. Este se usa para poder usar el componente en los templates de otros componentes. Pero, además, se genera un elemento del DOM con ese selector. Este elemento del DOM es el host element de ese componente.
En el siguiente ejemplo
Text
|
|
---|---|
<app-item>
|
es el host element del componente
Text
|
|
---|---|
ItemComponent
|
:
Text @Component({
selector: 'app-item',
template: "<h1>App Item</h1>"
})
export class ItemComponent {
// Aquí se define el comportamiento del componente
}
Text <!-- Usando el componente en el template de otro componente -->
<app-item></app-item>
Text <!-- DOM renderizado -->
<app-item>
<h1>App Item</h1>
</app-item>
7.8.1. Estilar el Host element
El host element se puede estilar, en el fichero css del componente, con el selector
Text
|
|
---|---|
:host
|
.
Text :host {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
7.8.2. Vincular propiedades, atributos y eventos al Host element
De la misma forma que se pueden vincular propiedades, atributos y eventos a los elementos de los templates, también se pueden vincular al host element. Para hacerlo hay que usar la propiedad
Text
|
|
---|---|
host
|
en el decorador
Text
|
|
---|---|
@Component
|
.
Text @Component({
selector: 'app-item',
host: {
'[class.small]': "size() === 'small'",
'[class.big]': "size() === 'big'",
'(click)': 'handleClick($event)',
},
})
export class ItemComponent {
size = input<'small' | 'big'>('small');
handleClick(event: MouseEvent) {/* ... */}
}
También se pueden usar los decoradores
Text
|
|
---|---|
@HostBinding
|
y
Text
|
|
---|---|
@HostListener
|
, pero actualmente se mantienen para compatibilidad con versiones anteriores.
8. Directivas
Las directivas son clases que añaden comportamiento extra a nuestras aplicaciones. Angular proporciona algunas directivas de atributos (NgClass, NgStyle y NgModel) y algunas directivas estructurales (NgIf, NgFor y NgSwitch).
Explicaré una de cada tipo para que veáis como funcionan. En los dos casos se debe declarar el módulo
Text
|
|
---|---|
CommonModule
|
en la propiedad
Text
|
|
---|---|
imports
|
del decorador del componente donde se vayan a usar.
8.1. NgClass
Para usar la directiva
Text
|
|
---|---|
NgClass
|
se debe añadir
Text
|
|
---|---|
[ngClass]
|
a un elemento del template y así poder declarar una expresión para añadir clases css en función de alguna condición.
Text <div [ngClass]="isActive ? 'active' : ''">Is active</div>
8.2. NgIf
La directiva
Text
|
|
---|---|
NgIf
|
permite mostrar o no un elemento en función de alguna condición. Para usarla se debe añadir
Text
|
|
---|---|
*ngIf
|
al elemento, seguido de la condición.
Text <div *ngIf="isActive; else isInactive">
Is active
</div>
<ng-template #isInactive>
Is inactive
</ng-template>
Pero, con la llegada de los blocks, hay una alternativa más sencilla para conseguir lo mismo.
8.3. Custom directives
Además de las directivas que proporciona Angular también se pueden crear directivas personalizadas. Para crearlas hay que añadir el decorador
Text
|
|
---|---|
@Directive
|
a la clase. En la documentación podéis ver como crear directivas de atributos o directivas estructurales.
9. Blocks
Los blocks son una nueva sintaxis para los templates en Angular. Aportan grandes funcionalidades con una sintaxis muy simple. Además, no hace falta añadir ningún módulo en los imports de los componentes para poder usarlos.
9.1. @if
El bloque
Text
|
|
---|---|
@if
|
viene juntamente con el bloque
Text
|
|
---|---|
@else
|
. Después de usar el bloque
Text
|
|
---|---|
@if
|
, se pueden usar los bloques
Text
|
|
---|---|
@else if
|
y
Text
|
|
---|---|
@else
|
.
Text @if (isActive) {
<div>Is active</div>
} @else (isInactive) {
<div>Is inactive</div>
}
9.2. @for
El bloque
Text
|
|
---|---|
@for
|
se puede usar junto al bloque
Text
|
|
---|---|
@empty
|
para mostrar algo en caso de que el listado este vacío.
Text @for (player of players; track player.id) {
<player-component/>
} @empty {
Empty list of players
}
9.3. @switch
El bloque
Text
|
|
---|---|
@switch
|
se usa juntamente con los bloques
Text
|
|
---|---|
@case
|
y
Text
|
|
---|---|
@default
|
.
Text @switch (role) {
@case ('player') { <player-component/> }
@case ('referee') { <referee-component/> }
@default { <viewer-component/> }
}
9.4. Deferrable views: @defer
Deferrable views, también conocidas como bloques
Text
|
|
---|---|
@defer
|
, son una potente herramienta que puede utilizarse para reducir el tamaño inicial del bundle de la aplicación o para aplazar la carga de componentes pesados que puede que no se carguen hasta más adelante o incluso no lleguen a renderizarse.
El bloque
Text
|
|
---|---|
@defer
|
tiene algunos bloques asociados que permiten gestionar diferentes etapas del proceso de carga diferida:
Text
|
|
---|---|
@placeholder
|
,
Text
|
|
---|---|
@loading
|
y
Text
|
|
---|---|
@error
|
. Además, hay múltiples triggers que pueden desencadenar la carga del bloque:
Text
|
|
---|---|
on idle
|
,
Text
|
|
---|---|
on hover
|
,
Text
|
|
---|---|
on interaction
|
, etc.
Text @defer (on hover) {
<app-large-component />
} @loading (minimum 2s) {
<p>Loading...</p>
} @placeholder {
<p>Pasa el cursor por encima</p>
}
10. Servicios
Los servicios permiten compartir lógica entre componentes. Se componen por un decorador
Text
|
|
---|---|
@Injectable
|
y una clase para definir el comportamiento deseado.
Text @Injectable({
providedIn: 'root',
})
export class CounterService {
increment(value: number) {
return value + 1
}
decrease(value: number) {
return value - 1
}
}
Text @Component({...})
export class ItemComponent {
private counterService = inject(CounterService);
}
11. Organización
Angular tiene dos formas de conseguir que los componentes estén disponibles en otros componentes: Standalone Components y NgModules.
Actualmente, se recomienda usar los Standalone Components. A no ser que el proyecto en el que se vaya a trabajar aún use los NgModules, no hace falta aprenderlos en una introducción a Angular.
Como hemos visto en la sección de estructura, para definir un Standalone Component hay que añadir la propiedad
Text
|
|
---|---|
standalone
|
con el valor a
Text
|
|
---|---|
true
|
, en el decorador
Text
|
|
---|---|
@Component
|
. Además, podremos añadir la propiedad
Text
|
|
---|---|
imports
|
, en la que se añadirán todas las dependencias que usará el componente.
12. Siguientes pasos
Hasta aquí he mostrado por encima los principales conceptos para que puedas introducirte a Angular. Los siguientes pasos que recomendaría para seguir aprendiendo Angular sería: (1) crear una pequeña aplicación de prueba, para ver todos estos conceptos en marcha; (2) leer la documentación oficial para profundizar en cada uno de los conceptos mostrados en el artículo; y (3) estaría atento a las nuevas actualizaciones para ver la evolución día a día.
Además de los conceptos mostrados hay muchos más. Si queréis ir más allá, dejo una lista de algunos conceptos que pueden ser interesantes aprender y encontrareis en la documentación:
-
-
- Component Lifecycle
-
Textng-template
y
Textng-container - Two-way binding vs model
- Queries y Signal queries
- Inyección de dependencias
- Routing y navegación
- Formularios
- HTTP Client
- Testing
- Angular SSR
- Detección de cambios: OnPush vs Default
- Zoneless
- Optimización de imágenes en Angular
- Internacionalización
- Animaciones en Angular
- Angular DevTools
-
13. Referencias
-
-
- https://v17.angular.io/docs
- https://angular.dev/
- https://v17.angular.io/guide/what-is-angular
- https://blog.angular.io/angular-v15-is-now-available-df7be7f2f4c8
- https://blog.angular.io/angular-v16-is-here-4d7a28ec680d
- https://blog.angular.io/introducing-angular-v17-4d7033312e4b
- https://blog.angular.io/signal-inputs-available-in-developer-preview-6a7ff1941823
- https://blog.angular.io/angular-v17-2-is-now-available-596cbe96242d
- https://blog.angular.io/meet-angulars-new-output-api-253a41ffa13c
- https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe
- https://www.youtube.com/watch?v=DK8M-ZFjaMw
-
Un comentario
Buen tutorial, muy útil, gracias