Иногда в системе может происходить какое-то событие, которое нас должно заставить перерисовать огромное количество несвязанных компонентов. Яркий пример - пользователь вошёл на сайт, и надо сменить и хедер, и сайтбар, и ещё черт знает что. Давайте посмотрим, как это делается неправильно и правильно.
Обычно для логина заводится какой-нибудь AuthSerivce, задача которого - дёрнуть апи, передав туда логин и пароль. Выглядеть это может, ну например, так:
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService{
constructor(private http: HttpService) {}
login(login: string, password: string) {
return this.http.post("http://api-domain/login", {login, password})
}
}
И так, пока эта служба вообще никого ни о чём не уведомляет, и уведомление должен сделать компонент, её использующий, например так:
/* ... */
@Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.scss']
})
export class AuthComponent implements OnInit, OnDestroy {
form = new FormGroup({
login: new FormControl,
password: new FormControl
})
/* Как мы знаем, компоненты, не состоящие в родстве дочерний/родительский, должны общаться через службы, в которых заводятся Subject для нужных событий */
constructor(private auth: AuthService, private headerService: HeaderService) {}
doLogin() {
this.auth.login(this.form.value.login, this.form.value.password).subscribe(
() => {
this.headerService.loggedIn();
}
)
}
}
И тут мы явно нарушаем SOLID, а именно принципы Single Responsibility (единой ответственности) и принцип Open/Close (открытости к расширениям и закрытости к изменениям). Почему у нас компонент, ответственный за аутентификацию, знает, что в системе есть компонент, ответственный за хедер? А если нужно будет обновить ещё сайтбар, тоже сюда? А если ещё какой-то компонент? Ответственностью компонента AuthComponent должно быть только получить логин и пароль и передать его в службу AuthService.
ОК, давайте попробуем перенести это в службу Auth:
login(login: string, password: string) {
return this.http.post("http://api-domain/login", {login, password}).pipe(
tap(() => {this.headerService.loggedIn(); })
)
}
Теперь мы сняли обязанность с компонента, но служба аутентификации теперь знает, какие компоненты есть в системе. Это её совсем не касается!!! Служба должна знать лишь одно: если клиенты, которых надо уведомить про то, что пользователь вошёл на сайт. Теперь приходим к тому решению, которое будет правильным. Выносим все уведомления в отдельную службу:
import {Injectable} from '@angular/core';
import {Subject} from "rxjs";
@Injectable({
providedIn: 'root'
})
export class LoginNotificationService {
readonly private loggedIn$ = new Subject();
readonly private loggedOut$ = new Subject();
constructor() {
}
notifyLogin() {
this.loggedIn$.next();
}
notifyLogout() {
this.loggedOut$.next();
}
get onLoggedIn$() { return this.loggedIn$.asObservable(); }
get onLoggedOut$() { return this.loggedOut$.asObservable(); }
}
Теперь код метода логина в службе меняется так:
login(login: string, password: string) {
return this.http.post("http://api-domain/login", {login, password}).pipe(
tap(() => {this.loginNotificationService.notifyLogin(); })
)
}
Какие мы получили преимущества? Теперь ни компонент ни служба аутентификации понятия не имеют, кого нужно уведомить о том, что логин произошёл. Они знают только, что кого-то надо уведомить, и поэтому остаются изолированы на своих задачах. А клиенты, которым надо как-то поменять своё состояние в ответ на логин пользователя, могут использовать LoginNotificationService для того, чтобы подписаться на событие логина.