https://github.com/Rachnerd/Angular-10
User2: ...
User1: ...
User1
User2
User3
Chat
Sidebar
Form
Messages
Channel
npm i -g @angular/cli
ng new chat-app --prefix=ov --style=scss
ng generate component chat
ng g c chat/chat-sidebar
ng g c chat/chat-channel
ng g c chat/chat-messages
ng g c chat/chat-form
Chat
Sidebar
Channel
Messages
Form
Smart
Dumb
UI
Smart
Dumb
Data
Event
Chat
ChatForm
send($event)
Button
click($event)
sendMessage(...)
Smart
Dumb
UI
Chat
Sidebar
Channel
Form
Messages
Smart
Dumb
messages
channel
event
Chat
Sidebar
Channel
Form
Messages
Smart
Dumb
AppModule
App
AppModule
Chat
ChatModule
...
@NgModule({
declarations: [
ChatSidebarComponent,
ChatMessagesComponent,
ChatFormComponent,
ChatChannelComponent,
ChatComponent,
],
imports: [CommonModule],
exports: [ChatComponent]
})
export class ChatModule {}
declarations are (private) components, directives and pipes available within the module scope
imports are other modules that add more declarationsÂ
to this module's scope and services to the root injector
exports are components, directives and pipes that will be available for other modules that import this module
Chat
Sidebar
Channel
Form
Messages
ChatModule
Input
Button
Smart
Dumb
UI
App
AppModule
Chat
ChatModule
...
Button
UiModule
Input
Sidebar
AppModule
Chat
ChatModule
...
Button
UiModule
...
Injector (root)
Service
@Injectable({
providedIn: 'root'
})
export class ChatService {
// ...
}
Chat
ChatModule
...
Injector (root)
ChatService
"ReactiveX combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events."
import { of } from 'rxjs';
const number$ = of(123);
number$.subscribe(n => console.log(n));
// 123
"Invokable collection of future values or events"
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
const delayedNumber$ = of(123)
.pipe(delay(100));
delayedNumber$.subscribe(n => console.log(n));
delayedNumber$.subscribe(n => console.log(n));
// (100ms pass)
// 123
// 123
delayedNumber$
.subscribe(
noop, // Next
noop, // Error
() => console.log('Completed')
);
// (100ms pass)
// Completed
"Equivalent to an EventEmitter, and the only way of multicasting a value or event to multiple Observer"
import { Subject } from 'rxjs';
const numberSubject = new Subject<number>();
numberSubject.asObservable()
.subscribe(
n => console.log(n),
e => console.log('Error occurred!'),
() => console.log('Completed')
);
numberSubject.next(123);
// 123
numberSubject.next(456);
// 456
numberSubject.complete();
// Completed
numberSubject.next(456);
// (nothing)
this.httpClient.get('assets/messages.json')
.subscribe(
n => console.log('Messages'),
error => console.log('Error'),
() => console.log('Completed')
);
/**
* If the call succeeds
*/
// Messages
// Completed
/**
* If the call fails
*/
// Error
class Service {
number$: Observable<number>;
private numberSubject: Subject<number>;
constructor() {
this.numberSubject = new Subject<number>();
this.number$ = this.numberSubject
.asObservable();
}
}
@Component({
// ...
})
class Component implements OnInit, OnDestroy {
n: number;
private subscription: Subscription;
constructor(private service: Service) {}
ngOnInit() {
this.subscription = this.service.number$
.subscribe(n => this.n = n);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
@Component({
// ...
template: `
<p *ngIf="number$ | async as number">
{{ number }}
</p>
`
})
class Component implements OnInit {
number$: Observable<number>;
constructor(private service: Service) {}
ngOnInit() {
this.number$ = this.service.number$;
}
}
const ROUTES: Route[] = [
{
path: '',
component: HomeComponent,
},
{
path: 'chat',
component: ChatComponent,
}
];
@NgModule({
declarations: [],
imports: [
RouterModule.forRoot(ROUTES),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
RouterOutlet
Chat
Home
/
/chat
App
AppRouting
const CHAT_ROUTES: Route[] = [
{
path: 'chat',
component: ChatComponent,
}
];
@NgModule({
// ...
imports: [
RouterModule.forChild(ROUTES),
// ...
],
// ...
})
export class ChatModule {}
RouterOutlet
Chat
Home
App
AppRouting
RouterOutlet
Chat
Home
const ROUTES: Route[] = [
{
path: '',
component: HomeComponent,
},
{
path: 'chat',
loadChildren: () =>
import('./chat/chat.module')
.then((m) => m.ChatModule)
}
];
@NgModule({
declarations: [],
imports: [
RouterModule.forRoot(ROUTES),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
App
AppRouting
RouterOutlet
Chat
Home
const CHAT_ROUTES: Route[] = [
{
path: '',
component: ChatComponent,
children: [
{
path: '',
component: ChatChannelComponent,
},
{
path: 'user',
component: ChatUserComponent,
},
],
},
];
...
RouterOutlet
User
Channel
// auth.model.ts
interface BaseUser {
username: string;
}
export interface AnonymousUser extends BaseUser {
type: 'Anonymous';
}
export interface LoggedInUser extends BaseUser {
type: 'LoggedIn';
token: string;
image: string;
}
export type User = AnonymousUser | LoggedInUser;
@Injectable({
providedIn: 'root',
})
export class AuthService {
user$: Observable<User>;
private userSubject: BehaviorSubject<User>;
constructor(
private httpClient: HttpClient
) {
this.userSubject =
new BehaviorSubject<User>(ANONYMOUS_USER);
this.user$ = this.userSubject.asObservable();
}
getUser(): User {
return this.userSubject.getValue();
}
login(): void {
this.httpClient.get('assets/user.json')
.subscribe((user: LoggedInUser) => {
this.userSubject.next(user);
});
}
logout(): void {
this.userSubject.next(ANONYMOUS_USER);
}
}
@Component({
// ...
template: `
<p>Welcome {{ (user$ | async).username }}</p>
`
})
export class HomeComponent implements OnInit {
user$: Observable<User>;
constructor(private authService: AuthService) {}
ngOnInit(): void {
this.user$ = this.authService.user$;
}
}
RouterOutlet
Chat
Home
Logout
Login
Auth
App
AppRouting
@Injectable({
providedIn: 'root',
})
export class AuthService {
isLoggedIn$: Observable<boolean>;
user$: Observable<User>;
private userSubject: BehaviorSubject<User>;
constructor(
private httpClient: HttpClient
) {
this.userSubject =
new BehaviorSubject<User>(ANONYMOUS_USER);
this.user$ = this.userSubject.asObservable();
this.isLoggedIn$ = this.user$.pipe(
map(user => user.type !== 'Anonymous')
);
}
// ...
}
@Component({
// ...
})
export class LoginComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private authService: AuthService, private router: Router) {}
ngOnInit(): void {
this.subscription = this.authService.isLoggedIn$
.pipe(filter((isLoggedIn) => isLoggedIn))
.subscribe(() => this.router.navigate(['']));
}
login(): void {
this.authService.login();
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
@Component({
// ...
})
export class LogoutComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private authService: AuthService, private router: Router) {}
ngOnInit(): void {
this.subscription = this.authService.isLoggedIn$
.pipe(filter((isLoggedIn) => !isLoggedIn))
.subscribe(() => this.router.navigate(['']));
this.authService.logout();
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
@Component({
// ...
template: `
...
<ov-navigation [menu]="navigationMenu$ | async"></ov-navigation>
...
`
})
export class AppComponent implements OnInit {
navigationMenu$: Observable<NavigationMenu[]>;
constructor(private authService: AuthService) {}
ngOnInit(): void {
this.navigationMenu$ = this.authService.isLoggedIn$.pipe(
map(isLoggedIn => (isLoggedIn ? LOGGED_IN_MENU : LOGGED_OUT_MENU))
);
}
}
@Injectable({
providedIn: 'root',
})
export class LoggedInGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.authService.isLoggedIn$.pipe(
tap((isLoggedIn) => {
if (!isLoggedIn) {
this.router.navigate(['login']);
}
})
);
}
}
// chat.module.ts
const CHAT_ROUTES: Route[] = [
{
path: '',
component: ChatComponent,
canActivate: [LoggedInGuard],
// ...
},
];
@Injectable()
export class Interceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
/**
* Process request
*/
return next.handle(request)
.pipe(
map(response => {
/**
* Process response
*/
return response;
})
);
}
}
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(public authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const user = this.authService.getUser();
if (user.type === 'LoggedIn') {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.token}`,
},
});
}
return next.handle(request);
}
}
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true,
},
],
})
export class AuthModule {}
@Injectable({
providedIn: 'root',
})
export class AuthService {
// ...
constructor(
private httpClient: HttpClient
) {
const persistedUser = this.getPersistedUser();
this.userSubject =
new BehaviorSubject<User>(persistedUser);
// ...
this.user$.subscribe((user) =>
sessionStorage.setItem(
USER_STORAGE_KEY,
JSON.stringify(user)
)
);
}
private getPersistedUser(): User {
return JSON.parse(
sessionStorage.getItem(USER_STORAGE_KEY)
) || ANONYMOUS_USER
}
// ...
}
Chat
Storage
App
Injector (root)