Angular per Front-End Design

Tutorial pratico completo per creare applicazioni moderne con Angular. Dalla configurazione iniziale fino al deployment, con focus su design contemporaneo e user experience.

01

Landing & Cards

Crea la tua prima landing page con componenti riutilizzabili

02

Routing & Layout

Implementa navigazione fluida e struttura modulare

03

Galleria & Filtri

Costruisci gallerie interattive con filtri dinamici

04

Form & API

Integra form avanzati con mock API

Requisiti e Setup

Prima di iniziare, assicurati di avere tutti gli strumenti necessari per sviluppare con Angular.

Strumenti Essenziali

  • Node.js LTS (v18.x o v20.x) con npm incluso
  • Angular CLI per la gestione dei progetti
  • Editor moderno come VS Code
  • Connessione internet per installare dipendenze

Verifica Installazione

node -v
npm -v

Installa Angular CLI

npm install -g @angular/cli

Avvia il Primo Progetto

ng new nome-progetto --routing --style=scss
cd nome-progetto
ng serve --open
💡 Tip: Vuoi testare subito senza installare nulla? Usa StackBlitz o CodeSandbox per creare progetti Angular direttamente nel browser!

Opzioni di Hosting

Capitolo 1 – Prima Landing con Card Design

Creiamo subito una landing page visualmente accattivante con una griglia di card per mostrare progetti di design.

Setup Iniziale

ng new portfolio-design --routing --style=scss
cd portfolio-design
ng serve --open

Genera i Componenti

ng generate component header
ng generate component home
ng generate component project-card

app.component.html

<app-header></app-header>
<router-outlet></router-outlet>

header.component.html

<nav class="navbar">
  <div class="nav-brand">F.E. Design</div>
  <ul class="nav-menu">
    <li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a></li>
    <li><a routerLink="/designs" routerLinkActive="active">Designs</a></li>
    <li><a routerLink="/portfolio" routerLinkActive="active">Portfolio</a></li>
  </ul>
</nav>

home.component.html

<section class="hero-section">
  <h1>Design Portfolio</h1>
  <p>Scopri i nostri progetti e creazioni più innovative</p>
</section>

<section class="projects-grid">
  <app-project-card
    *ngFor="let item of projects"
    [title]="item.title"
    [image]="item.image"
    [tags]="item.tags"
    [description]="item.description">
  </app-project-card>
</section>

project-card.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-project-card',
  template: `
    <div class="project-card">
      <div class="card-image">
        <img [src]="image" [alt]="title" loading="lazy">
        <div class="card-overlay">
          <button class="view-btn">Visualizza</button>
        </div>
      </div>
      <div class="card-content">
        <h3>{{ title }}</h3>
        <p>{{ description }}</p>
        <div class="tags">
          <span class="tag" *ngFor="let tag of tags">{{ tag }}</span>
        </div>
      </div>
    </div>
  `,
  styleUrls: ['./project-card.component.scss']
})
export class ProjectCardComponent {
  @Input() title!: string;
  @Input() image!: string;
  @Input() tags: string[] = [];
  @Input() description!: string;
}
🎨 Design Tip: Usa immagini di alta qualità e assicurati che abbiano proporzioni consistenti per un look professionale.

Capitolo 2 – Routing e Layout Avanzato

Implementiamo un sistema di routing robusto per creare un'esperienza di navigazione fluida e moderna.

Configurazione Routing

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DesignsComponent } from './designs/designs.component';
import { PortfolioComponent } from './portfolio/portfolio.component';
import { ContactComponent } from './contact/contact.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'designs', component: DesignsComponent },
  { path: 'portfolio', component: PortfolioComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', redirectTo: '' } // Wildcard route
];

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled',
    anchorScrolling: 'enabled'
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Features Avanzate del Routing

  • Route Guards: Proteggi rotte sensibili
  • Lazy Loading: Carica moduli solo quando necessario
  • Route Parameters: Passa dati tra componenti
  • Query Params: Gestisci filtri e stato dell'app

designs.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-designs',
  templateUrl: './designs.component.html',
  styleUrls: ['./designs.component.scss']
})
export class DesignsComponent implements OnInit {
  designs = [
    {
      id: 1,
      title: 'UI Design System',
      image: 'https://picsum.photos/seed/ui1/600/400',
      tags: ['UI', 'Design System', 'Figma'],
      description: 'Sistema completo di componenti UI modulari'
    },
    {
      id: 2,
      title: 'Mobile App Interface',
      image: 'https://picsum.photos/seed/mobile1/600/400',
      tags: ['Mobile', 'UX', 'Prototyping'],
      description: 'Interfaccia intuitiva per app mobile'
    }
  ];

  ngOnInit(): void {
    // Animazioni di entrata
    this.animateCards();
  }

  private animateCards(): void {
    // Implementa animazioni staggered
  }
}

Capitolo 3 – Galleria Interattiva e Filtri

Creiamo una galleria dinamica con filtri avanzati e micro-interazioni che migliorano l'esperienza utente.

Funzionalità Implementate

  • Filtri dinamici per categoria
  • Ricerca live nei progetti
  • Animazioni fluide per transizioni
  • Layout responsive con CSS Grid

gallery-filter.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

@Component({
  selector: 'app-gallery-filter',
  template: `
    <div class="filter-controls">
      <div class="search-box">
        <input [formControl]="searchControl" 
               placeholder="Cerca progetti..."
               class="search-input">
      </div>
      
      <div class="filter-buttons">
        <button 
          *ngFor="let filter of filters"
          [class.active]="activeFilter === filter.value"
          (click)="setFilter(filter.value)"
          class="filter-btn">
          {{ filter.label }}
        </button>
      </div>
    </div>
    
    <div class="gallery-grid">
      <div *ngFor="let project of filteredProjects; trackBy: trackByProjectId" 
           class="gallery-item"
           [@slideIn]>
        <app-project-card 
          [title]="project.title"
          [image]="project.image"
          [tags]="project.tags">
        </app-project-card>
      </div>
    </div>
  `,
  animations: [
    // Definisci animazioni personalizzate
  ]
})
export class GalleryFilterComponent implements OnInit {
  searchControl = new FormControl('');
  activeFilter = 'all';
  
  filters = [
    { label: 'Tutti', value: 'all' },
    { label: 'UI Design', value: 'ui' },
    { label: 'Branding', value: 'branding' },
    { label: 'UX Research', value: 'ux' }
  ];

  projects = [
    {
      id: 1,
      title: 'Dashboard Analytics',
      image: 'https://picsum.photos/seed/dash1/600/400',
      tags: ['ui', 'dashboard'],
      category: 'ui'
    },
    {
      id: 2,
      title: 'Brand Identity',
      image: 'https://picsum.photos/seed/brand1/600/400',
      tags: ['branding', 'logo'],
      category: 'branding'
    }
  ];

  filteredProjects = [...this.projects];

  ngOnInit(): void {
    // Implementa ricerca live
    this.searchControl.valueChanges
      .pipe(debounceTime(300), distinctUntilChanged())
      .subscribe(searchTerm => {
        this.filterProjects(searchTerm || '');
      });
  }

  setFilter(filter: string): void {
    this.activeFilter = filter;
    this.filterProjects(this.searchControl.value || '');
  }

  private filterProjects(searchTerm: string): void {
    let filtered = [...this.projects];

    // Filtra per categoria
    if (this.activeFilter !== 'all') {
      filtered = filtered.filter(p => p.category === this.activeFilter);
    }

    // Filtra per termine di ricerca
    if (searchTerm) {
      filtered = filtered.filter(p => 
        p.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
        p.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
      );
    }

    this.filteredProjects = filtered;
  }

  trackByProjectId(index: number, project: any): number {
    return project.id;
  }
}
⚡ Performance Tip: Usa trackBy functions nelle *ngFor per migliorare le performance quando filtri grandi liste di elementi.

Capitolo 4 – Form Avanzati e Mock API

Implementiamo form reattivi con validazione avanzata e integrazione con API mock per simulare interazioni real-world.

Setup Moduli

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [
    // componenti...
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    HttpClientModule,
    BrowserAnimationsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

contact-form.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ContactService } from '../services/contact.service';
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  selector: 'app-contact-form',
  template: `
    <div class="contact-container">
      <div class="contact-header">
        <h2>Inizia il tuo progetto</h2>
        <p>Raccontaci la tua idea e trasformiamola in realtà</p>
      </div>

      <form [formGroup]="contactForm" (ngSubmit)="onSubmit()" class="contact-form">
        <div class="form-grid">
          <div class="form-field">
            <label for="name">Nome *</label>
            <input 
              id="name"
              formControlName="name" 
              placeholder="Il tuo nome"
              [class.error]="isFieldInvalid('name')">
            <div *ngIf="isFieldInvalid('name')" class="error-message">
              Nome è obbligatorio
            </div>
          </div>

          <div class="form-field">
            <label for="email">Email *</label>
            <input 
              id="email"
              type="email"
              formControlName="email" 
              placeholder="[email protected]"
              [class.error]="isFieldInvalid('email')">
            <div *ngIf="isFieldInvalid('email')" class="error-message">
              Inserisci un'email valida
            </div>
          </div>

          <div class="form-field">
            <label for="company">Azienda</label>
            <input 
              id="company"
              formControlName="company" 
              placeholder="Nome azienda (opzionale)">
          </div>

          <div class="form-field">
            <label for="budget">Budget</label>
            <select id="budget" formControlName="budget">
              <option value="">Seleziona budget</option>
              <option value="5k-10k">€5.000 - €10.000</option>
              <option value="10k-25k">€10.000 - €25.000</option>
              <option value="25k+">€25.000+</option>
            </select>
          </div>
        </div>

        <div class="form-field full-width">
          <label for="message">Messaggio *</label>
          <textarea 
            id="message"
            formControlName="message" 
            rows="5" 
            placeholder="Descrivi il tuo progetto..."
            [class.error]="isFieldInvalid('message')"></textarea>
          <div *ngIf="isFieldInvalid('message')" class="error-message">
            Messaggio è obbligatorio
          </div>
        </div>

        <div class="form-actions">
          <button 
            type="submit" 
            class="submit-btn"
            [disabled]="contactForm.invalid || isSubmitting"
            [@buttonState]="isSubmitting ? 'loading' : 'idle'">
            <span *ngIf="!isSubmitting">Invia Richiesta</span>
            <span *ngIf="isSubmitting">Invio...</span>
          </button>
        </div>

        <div *ngIf="submitMessage" 
             class="submit-message"
             [class.success]="submitSuccess"
             [class.error]="!submitSuccess"
             [@fadeIn]>
          {{ submitMessage }}
        </div>
      </form>
    </div>
  `,
  animations: [
    trigger('buttonState', [
      state('idle', style({ transform: 'scale(1)' })),
      state('loading', style({ transform: 'scale(0.95)' })),
      transition('idle <=> loading', animate('200ms ease'))
    ]),
    trigger('fadeIn', [
      transition(':enter', [
        style({ opacity: 0, transform: 'translateY(20px)' }),
        animate('300ms ease', style({ opacity: 1, transform: 'translateY(0)' }))
      ])
    ])
  ]
})
export class ContactFormComponent implements OnInit {
  contactForm!: FormGroup;
  isSubmitting = false;
  submitMessage = '';
  submitSuccess = false;

  constructor(
    private fb: FormBuilder,
    private contactService: ContactService
  ) {}

  ngOnInit(): void {
    this.initForm();
  }

  private initForm(): void {
    this.contactForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.email]],
      company: [''],
      budget: [''],
      message: ['', [Validators.required, Validators.minLength(10)]]
    });
  }

  isFieldInvalid(fieldName: string): boolean {
    const field = this.contactForm.get(fieldName);
    return !!(field && field.invalid && (field.dirty || field.touched));
  }

  async onSubmit(): Promise<void> {
    if (this.contactForm.valid) {
      this.isSubmitting = true;
      this.submitMessage = '';

      try {
        const formData = this.contactForm.value;
        await this.contactService.submitInquiry(formData).toPromise();
        
        this.submitSuccess = true;
        this.submitMessage = '✅ Grazie! Il tuo messaggio è stato inviato con successo.';
        this.contactForm.reset();
      } catch (error) {
        this.submitSuccess = false;
        this.submitMessage = '❌ Errore durante l\'invio. Riprova più tardi.';
      } finally {
        this.isSubmitting = false;
      }
    } else {
      this.markAllFieldsAsTouched();
    }
  }

  private markAllFieldsAsTouched(): void {
    Object.keys(this.contactForm.controls).forEach(key => {
      this.contactForm.get(key)?.markAsTouched();
    });
  }
}

contact.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { delay, catchError } from 'rxjs/operators';

export interface ContactForm {
  name: string;
  email: string;
  company?: string;
  budget?: string;
  message: string;
}

@Injectable({
  providedIn: 'root'
})
export class ContactService {
  private apiUrl = 'http://localhost:3000/api/contacts';

  constructor(private http: HttpClient) {}

  submitInquiry(formData: ContactForm): Observable<any> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json'
    });

    // Simula chiamata API reale
    return this.mockApiCall(formData);
    
    // Per API reale, decommenta questa riga:
    // return this.http.post(this.apiUrl, formData, { headers });
  }

  private mockApiCall(formData: ContactForm): Observable<any> {
    // Simula ritardo di rete
    const mockResponse = {
      success: true,
      message: 'Messaggio inviato con successo',
      id: Date.now(),
      data: formData
    };

    // Simula errore casuale 10% delle volte
    const shouldFail = Math.random() < 0.1;
    
    if (shouldFail) {
      return of(null).pipe(
        delay(1500),
        catchError(() => {
          throw new Error('Errore simulato');
        })
      );
    }

    return of(mockResponse).pipe(delay(1500));
  }

  // Metodo per recuperare i contatti (admin)
  getContacts(): Observable<ContactForm[]> {
    return this.http.get<ContactForm[]>(this.apiUrl);
  }
}

Setup JSON Server (Opzionale)

Per testare con una API reale in locale:

# Installa JSON Server globalmente
npm install -g json-server

# Crea db.json nella root del progetto
{
  "contacts": [],
  "projects": [
    {
      "id": 1,
      "title": "Design System",
      "category": "ui",
      "image": "https://picsum.photos/seed/ds1/600/400"
    }
  ]
}

# Avvia il server
json-server --watch db.json --port 3000
🚀 Pro Tip: Usa interceptors HTTP per gestire loading states, errori globali e autenticazione in modo centralizzato.

Risorse e Best Practices

Tutto quello che ti serve per portare il tuo progetto Angular al livello successivo.

🎯 Checklist Pre-Deploy

  • Build Production: ng build --prod
  • Test Routing: Verifica tutte le rotte
  • Ottimizzazione Immagini: Comprimi le immagini
  • SEO: Meta tags e Open Graph
  • PWA: Service Workers e Manifest

Hosting Platforms

📦

Netlify

Drag & drop deploy, automatico da Git

Vercel

Ottimizzato per framework frontend

🔥

Firebase

Google Cloud con database integrato

🐙

GitHub Pages

Gratuito per progetti open source

Comandi Essenziali

# Sviluppo
ng serve --open                    # Avvia dev server
ng generate component nome         # Crea componente
ng generate service nome           # Crea servizio
ng test                           # Esegui test

# Build e Deploy  
ng build --prod                   # Build per produzione
ng add @angular/pwa              # Aggiungi PWA support
ng update                        # Aggiorna dipendenze

# Analisi Bundle
ng build --prod --stats-json
npx webpack-bundle-analyzer dist/app-name/stats.json
📱 Mobile First: Testa sempre su dispositivi mobili. Usa Chrome DevTools per simulare diverse dimensioni schermo e connessioni lente.

Performance Tips

Risorse Utili