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();
  }

Похожие записи

Отладка Angular в VS Code

Настройка Visual Studio Code используя расширение Debugger for Chrome для отладки Angular приложений
03 декабря 2017 г. в Angular

let-* $implicit in Angular template

Синтаксис let-* позволяет объявить переменную в шаблоне , использования ключа $implicit позволяет устанавливать значение по-умолчанию для объявленной переменной.

29 августа 2018 г. в Angular

Angular. Can't set breakpoints in VS Code

Вариант решения проблемы, когда не срабатывают точки остановки при разработке Angular приложений в редакторе VS Code
10 апреля 2018 г. в Angular

@Attribute() декоратор

Аналогично @Input() позволяет получить значение атрибута с хоста компонента/директивы, но не отслеживает дальнейшее изменение атрибута.

15 сентября 2019 г. в Angular