Angular ARCHITECTURES

Michele Stieven

Michele Stieven

Front-End developer from Bassano del Grappa

@MicheleStieven

I NOSTRI OBIETTIVI

  • Approccio modulare
  • Divisione delle responsabilità
  • Ottimizzazione delle performance
  • Riusabilità
  • Codice pulito e testabile

ARGOMENTI

  • DEPENDENCY INJECTION

  • MODULES

  • ROUTING

DEPENDENCY INJECTION

export class CounterService {
    counter = 0;
}
@Component({ ... })
export class MyComponent {
    constructor(c: CounterService) {
        console.log(c.counter)
    }
}

PROVIDERS

@NgModule({
  ...
  providers: [ CounterService, ... ]
})
export class AppModule { }

A COSA SERVE UN MODULO?

Estendere con librerie

Organizzare un'applicazione

ESTERNE

  • Angular Material
  • Ionic
  • AngularFire2
  • ...

INTERNE

  • FormsModule
  • HttpModule
  • RouterModule
  • ...
@NgModule({
  imports: [ ...moduli ],
  declarations: [ ...componenti/dir/pipe ],
  providers: [ ...servizi ],
  exports: [ ...moduli, c/d/p ],
  bootstrap: [ ...componente ],
  entryComponents: [ ...componenti ]
})
export class MyModule {}

BUILT-IN MODULES

  • BrowserModule
  • CommonModule
  • FormsModule
  • ReactiveFormsModule
  • HttpModule
  • RouterModule
  • BrowserAnimationsModule
  • ...

ANGULAR CLI

ng new pippo

  • --prefix=pp

  • --routing

  • --style=SCSS|sass|less...

Feature Modules

AppModule

views

DashboardModule

ContactModule

ROUTING

ROUTING

ROUTING

Reducers

Reducers

Actions

Actions

 

ReduxModule    

 

RootReducer

ContactModule

  contact.module.ts

  contact-routing.module.ts

views

components

store

reducers

actions

services

utils

import { CommonModule } from '@angular/core';
import { ContactRoutingModule } 
  from './contact-routing.module';

@NgModule({
  imports: [
    CommonModule,
    ContactRoutingModule,
    ...
  ],
  declarations: [ ... ]
})
export class ContactModule { }

BrowserModule vs commonmodule

  • CommonModule contiene le direttive comuni di Angular (ngIf, ngFor...)
  • BrowserModule serve all'app per essere eseguita su browser
  • BrowserModule ri-esporta CommonModule

AppModule

Feature Modules

BrowserModule

CommonModule

MODULE SCOPING

Module-scoped

APP-scoped

Componenti, direttive e pipe sono

I servizi dichiarati in un modulo sono

I servizi accessibili globalmente vanno dichiarati in AppModule!

O no?

?

Introduciamo il routermodule

Definire le rotte della nostra applicazione

Passaggio di dati da una rotta all'altra

Ottimizzare le performance

Restringere l'accesso ad alcune aree dell'applicazione

LAZY LOADING MODULES

{
  path: 'contacts',
  loadChildren: () => import('./contact.module')
  			.then(m => m.ContactModule)
}

LAZY MODULE

Possiamo usare loadChildren anche per moduli non lazy-loaded con una normale funzione!

TIP!

function contactsEntrypoint() {
    return ContactsModule;
}

...

{
  path: 'contacts',
  loadChildren: contactsEntrypoint
}

LOADCHILDREN WITHOUT LAZY-LOADING

PRE-LOADING MODULES

import { PreloadAllModules } from '@angular/router';

...

RouterModule.forRoot(
    ROUTES, 
    { preloadingStrategy: PreloadAllModules }
)

CUSTOM PRE-LOADING STRATEGY

{
    path: 'admin',
    loadChildren: './admin.module#AdminModule',
    data: {preload: true}
}

export class SelectivePreload implements PreloadingStrategy {
  preload(route: Route, load: Function): Observable<any> {
    return route.data && route.data.preload ? load() : of(null);
  }
}

RouterModule.forRoot(
    ROUTES, 
    { preloadingStrategy: SelectivePreload }
)
{
    path: 'admin',
    loadChildren: './admin.module#AdminModule',
    data: {whatever: true}
}

export class Pippo implements PreloadingStrategy {
  preload(route: Route, load: Function): Observable<any> {
    return route.data && route.data.whatever ? load() : of(null);
  }
}

RouterModule.forRoot(
    ROUTES, 
    { preloadingStrategy: Pippo }
)

MODULE SCOPING PER I LAZY MODULE

  • NON sono inclusi nel global scope dell'app
  • I componenti vanno importati come sempre
  • Ogni lazy module ha un suo injector, quindi:

 

 

 

  • I servizi dichiarati nel modulo sono disponibili solo nel modulo
  • Possiamo accedere ai servizi globali, ma se importiamo un modulo che ha servizi in "providers", per quei servizi viene generata un'altra istanza!

Attenzione ai servizi!

/app

AppModule

DashboardModule

ContactModule

COREModule

SHAREDModule

VIEWS

Lazy?

Lazy?

Auth, user...

components, pipes, directives...

CORE

SHARED

CORE MODULE

Utilizziamo CoreModule per fare il provide dei servizi globali: viene importato UNA sola volta SOLO da AppModule.

E se per sbaglio un altro modulo lo importasse?

constructor(@Optional() @SkipSelf() core: CoreModule) {
  if(core) {
    throw new Error('You shall not run!');
  }
}

TYPESCRIPT IMPORTS

{
  "compilerOptions": {
    ...,
    
    "baseUrl": "src",
    "paths": {
      "@app/*": ["app/*"],
      // oppure...
      "@app/core/*": ["app/core/*"],
      "@app/shared/*": ["app/shared/*"],
      "@env/*": ["environments/*"]
    }
  }
}

tsconfig.json

SHARED MODULES

Usiamo gli Shared Module per dichiarare componenti/direttive/pipe in un unico modulo, che verrà importato da altri moduli.

import { CustomButton } from '...';

@NgModule({
    declarations: [ CustomButton ],
    exports: [ CustomButton ]
})
export class SharedModule {}

?

SHARED MODULES & SERVICES

Uno Shared Module non dovrebbe fare il provide di servizi!

I Lazy Module, importandolo, ne genererebbero un'altra istanza...

Ma se dobbiamo, possiamo farlo!

MODULEWITHPROVIDERS

interface ModuleWithProviders { 
  ngModule: Type<any>
  providers?: Provider[]
}

Con MWP possiamo prendere un modulo esistente ed "agganciarci" dei servizi!

@NgModule({
    declarations: [...],
    imports: [...],
    exports: [...]
})
export class SharedModule {
    
    static forRoot() {
        return {
            ngModule: SharedModule,
            providers: [...]
        }
    }
}

ROUTERMODULE!

RouterModule.forRoot()

  • va utilizzato solo in AppModule
  • aggiunge rotte
  • fa il provide del servizio Router

 

RouterModule.forChild()

  • va utilizzato nei feature module
  • aggiunge solamente le rotte
class RouterModule {
  static forRoot(routes, config) {
    return {
      ngModule: RouterModule,
      providers: [
        {
          provide: ROUTES,
          multi: true,
          useValue: routes
        },
        ...RouterProviders
      ]
    }
  }
}

MULTI-PROVIDER!

sOLO NEL FORROOT

ROUTER GUARDS

Possiamo definire dei servizi che verranno usati dal router per stabilire se una rotta può essere utilizzata

  • CanActivate

  • CanActivateChild

  • CanDeactivate

  • CanLoad

export class AuthGuard 
       implements CanActivate {

    canActivate() {
        return true;
    }
}
{
    path: 'admin',
    component: AdminComponent,
    canActivate: [ AuthGuard ]
}

{
    path: '',
    canActivateChild: [ AuthGuard ],
    children: [ ... ]
}

{
    path: 'admin',
    loadChildren: () => import(...).then(...),
    canLoad: [ AuthGuard ]
}
{ 
  path: '',
  component: MyComponent,
  canDeactivate: [ DeactivateGuard ]
}

CanDeactivate

interface CanDeactivate<T> { 
  canDeactivate(
    component: T, 
    currentRoute: ActivatedRouteSnapshot, 
    currentState: RouterStateSnapshot, 
    nextState?: RouterStateSnapshot)
  : Observable<boolean>|Promise<boolean>|boolean
}

<T>





export class DeactivateGuard implements CanDeactivate<MyComponent>{

  canDeactivate(component: MyComponent) {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

@Component({...})
export class MyComponent {
    canDeactivate() { return true; }
}

...

{ 
  path: 'my-component', 
  canDeactivate: [ DeactivateGuard ],
  component: MyComponent 
}

export interface CanComponentDeactivate {
    canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean
}

export class DeactivateGuard implements CanDeactivate<CanComponentDeactivate>{

  canDeactivate(component: CanComponentDeactivate) {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

@Component({...})
export class MyComponent {
    canDeactivate() { return true; }
}

...

{ 
  path: 'my-component', 
  canDeactivate: [ DeactivateGuard ],
  component: MyComponent 
}

ROUTE PARAMETERS

{
    path: 'contact/:id',
    component: ContactComponent
}
@Component({...})
class ContactComponent {
  constructor(route: ActivatedRoute) {
    route.paramMap.subscribe(params => {
        // params['id']
    });
  }
}

ROUTE RESOLVERS

Immaginiamo la rotta:

path: 'contact/:id',

component: ContactComponent

 

ContactComponent dovrebbe richiedere un servizio e chiedere i dati del contatto passandogli l'id. 

Vogliamo passargli direttamente i dati, evitandogli la dipendenza dal servizio...

{
    path: 'contact/:id',
    component: ContactComponent,
    resolve: {
        contact: ContactResolver
    }
}
@Injectable()
class ContactResolver implements Resolve<any> {
  constructor(private svc: MyService) {}
 
  resolve(route: ActivatedRouteSnapshot) {
    return this.svc.getContact(route.params.id);
  }
}
@Component()
export class ContactComponent {

  constructor(route: ActivatedRoute) {
      route.data.subscribe(...)
  }
}

MODULES RECAP

CORE MODULE

- Servizi globali, per snellire AppModule

- Importato SOLO da AppModule

SHARED MODULE

- Raggruppare componenti/direttive/pipe

- Servono servizi? Utilizziamo forRoot()

FEATURE MODULES

- Possono importare SharedModule

- Possono essere Lazy Loaded

- ...Possono avere i propri servizi!

THANKS!

SLIDES.COM/MICHELESTIEVEN/ANGULAR-ARCHITECTURES

Angular Architectures

By Michele Stieven

Angular Architectures

Understanding Angular modules, DI & routing

  • 1,418