Angular Resolver
Цель Resolver - получить асинхронные данные перед отображением компонента. Важный момент в том, что не надо в резолвер помещать всё подряд, особенно если это некритичные долго загружаемые данные. В нём должно быть то, без чего нельзя отобразить компонент. Официальная документация даёт следующий пример:
@Injectable({ providedIn: 'root' })
export class HeroResolver implements Resolve<Hero> {
constructor(private service: HeroService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
// функция получения данных определяется в отдельной сервисе
return this.service.getHero(route.paramMap.get('id'));
}
}
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'detail/:id',
component: HeroDetailComponent,
// использование сервиса в секции resolve
resolve: {
hero: HeroResolver
}
}
])
],
exports: [RouterModule]
})
export class AppRoutingModule {}
@Component({
selector: "app-hero-detail",
templateUrl: "./hero-detail.component.html",
})
export class HeroDetailComponent implements OnInit {
readonly hero$ = this.route.data.pipe(
map((data: { hero: Hero }) => data.hero)
);
constructor(
private route: ActivatedRoute,
) {}
}
Альтернативный и более простой способ - получать данные сразу в компоненте.
@Component({
selector: "app-hero-detail",
templateUrl: "./hero-detail.component.html",
})
export class HeroDetailComponent implements OnInit {
readonly hero$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) => this.service.getHero(params.get("id")))
);
constructor(
private route: ActivatedRoute,
private router: Router,
private service: HeroService
) {}
}
Однако привидённый пример весьма абстрактен. В реальном приложении UX требует показывать пользователю обратную связь, обычно это spinner во время загрузки. В альтернативном варианте опуская нюансы spinner показать несложно:
<div *ngIf="hero$ | async as hero; else loading">
<h3>"{{ hero.name }}"</h3>
</div>
<ng-template #loading><spinner></spinner></ng-template>
В случае с resolver простой вариант показать загрузку я вижу в универсальном индикаторе
На днях вышла статья от ребят из жёлтого банка, где предложен механизм, позволяющий показывать spinner во время загрузки данных, используя резолверы.
Ещё одна частная плюшка
В случае использования hash навигации решается проблема смены id "руками" в адресной строке браузера.
type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);
Сталкивался с кодом, когда в OnInit
компонента была инициализация, которая не срабатывала т.к. Angular переиспользовал существющий компонент. В итоге получился костылик с *ngIf="!needReload"
в родительском компоненте на <router-outlet>
, который в нужный момент для пересоздания компонента делал:
private reloadChildComponent(): void {
this.needReload = true;
this.cdr.detectChanges();
this.needReload = false;
this.cdr.markForCheck();
}