Can a resolver use the same service instance form another module except the AppModule in Angular?

107 views Asked by At

I have a lazy loaded module with all it components declared and routes added in RouterModule.forChild()

but some of these routes have their resolver that will prefetch some data And call a service to change something in the parent component header like the following:

//Imports...

@Injectable({
    providedIn: 'root',
})
export class MyVendorHeaderResolver implements Resolve<Header> {
    constructor(private vendorHeaderService: VendorHeaderService) {}

    resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<Header> {
        const title: string = 'MainNav.NavMenu.Vendors.Action.MyVendor';

        const breadcrumbs: Breadcrumb[] = [
            { displayText: 'MainNav.NavMenu.Vendors.Title', link: '..' },
            {
                displayText: 'MainNav.NavMenu.Vendors.Action.MyVendor',
                link: route.routeConfig.path,
            },
        ];

        //this change the header of the main page component if
        this.vendorHeaderService.changeHeader({ title, breadcrumbs } as Header);

        // if the pervious line works with the same instance of the service provided in module,
        // i'd not need to return anything form this resolver
        return of({ title, breadcrumbs } as Header);
    }
}

so this VendorHeaderService:

//imports

@Injectable({
    providedIn: 'root',
})
export class VendorHeaderService {
    private header$ = new Subject<Header>();

    constructor() {}

    headerChange(): Subject<Header> {
        return this.header$;
    }

    changeHeader(header: Header) {
        this.header$.next(header);
    }
}

this is the routing module:

const routes: Routes = [
    {
        path: '',
        redirectTo: 'vendors/vendors-categories',
        pathMatch: 'full',
    },
    {
        path: 'vendors',
        component: VendorsComponent,
        children: [
            {
                path: 'vendors-categories',
                component: VendorsCategoriesComponent,
                canActivate: [VendorsCategoriesGuard],
                resolve: {
                    header: VendorCategoryHeaderResolver,
                    categories: VendorsCategoriesResolver,
                },
                children: [
                    {
                        path: 'new',
                        component: VendorCategoryDialogEntryComponent,
                    },
                    {
                        path: ':vendorCategoryId',
                        component: VendorCategoryDialogEntryComponent,
                        canActivate: [VendorCategoryGuard],
                        resolve: {
                            categoryDialogData:
                                VendorCategoryDialogInfoResolver,
                        },
                    },
                ],
            },
        ],
    },
];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule],
})
export class VendorsAndCustomersRoutingModule {}

I want it to be provided only in my Lazy Loaded Module but I can't cause it used in the resolver.

so my question:

Is it possible to make the resolver service instance is the same one of that used in the module components without exposing the service outside with this provideIn: 'root'?

I've made the services all provided in the 'root' Enviroment Injector and it's working fine, but the service is exposed to any place in the app

2

There are 2 answers

0
Ali Kasem On

if i understand your problem... in the module where you want to use the service, you can use the providers array in the module metadata like this:

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule],
    providers: [VendorHeaderService]
})

the service will become a singletone within the context of the module. in your resolver you can use the @skipSelf injection operator to skip inject the service from resolver and listen to it through the injection hierarchy -which is injected from the module - like this:

constructor(@SkipSelf() private vendorHeaderService: VendorHeaderService) {}

, so in this way, you use the same service instance in the resolver and any component in the module.

2
Peca021 On

If you provide both VendorHeaderService and MyVendorHeaderResolver within the lazily loaded feature module instead of at the root level, the service and resolver won't be available throughout the entire app, they will only be accessible within that specific feature module.

Then, in your resolver, if you're obtaining the service instance through the constructor, you'll be working with the same instance of the service.

If you're using Angular version 14 or later, an alternative approach can be to inject the same service instance without a constructor function like this:

@Injectable()
export class MyVendorHeaderResolver implements Resolve<Header> {
    private vendorHeaderService = inject(VendorHeaderService);
    // ...
}

Angular also allows you to provide your services and resolvers on the route level, in your case you can modify your routes config to look something like this:

const routes: Routes = [
    {
        path: 'vendors',
        providers: [
            VendorCategoryHeaderResolver, 
            VendorsCategoriesResolver,
            VendorCategoryDialogInfoResolver,
            VendorHeaderService,
            // ...
        ],
        component: VendorsComponent,
        children: [{ //... }],
    }
]