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,425