Alternate way to route without using injected component?

I am building an app and I’d like the user to have the ability to set their starting page via a settings-page. The idea is that the user can select a page from a list of options, the setting gets stored to local-storage and later, when the user logs back in, the user is automatically taken to that page first.

I have a page-service which contains a mapping of Id’s to page-components. This is what I use to find the page I want to use when I read in my user’s saved start-page data.

My issue is that I have developed a cyclic-dependency that I don’t think I can break without finding a way to route in Ionic that doesn’t involve using the injected component. As far as I can tell, the only way routing is achieved in Ionic is with the NavController.push(component) or Nav.setRoot(component).

PageService.ts

import {Injectable} from "@angular/core";
import {HomePage} from "../pages/home/home";
import {SettingsPage} from "../pages/settings/settings";
import {CartPage} from "../pages/cart/cart";

@Injectable()
export class PageService {
    public pages = [
        {
            id: "HOME",
            component: HomePage
        }, {
            id: "SETTINGS",
            component: SettingsPage
        }, {
            id: "CART",
            component: CartPage
        }
    ];

    constructor() {
    }

    getPageById(id: string) {
        return this.pages.find(page => (page.id === id));
    }
}

settings.ts:

My SettingsPage component has the PageService injected so that it can get access to get the list of pages. This is where my cyclical dependency occurs. The SettingsPage is injecting PageService which has a reference to SettingsPage in it.

import {Component} from "@angular/core";
import {PageService} from "../../providers/page-service";
import {UserService} from "../../providers/user-service";

@Component({
    selector: "page-settings",
    templateUrl: "settings.html",
})
export class SettingsPage {
    startPages = [];

    constructor(private pageService: PageService, private userService: UserService) {
        this.startPages = this.pageService.getStartPages();
    }
}

settings.html:

Just a simple list with a card to output the selection.

<ion-content padding>
    <ion-list>
        <ion-card padding>
            <ion-card-title>Starting Page</ion-card-title>
            <ion-item>
                <ion-select [(ngModel)]="userService.activeUser.startPage">
                    <ion-option *ngFor="let page of startPages" value="{{page.id}}">
                        {{page.id}}
                    </ion-option>
                </ion-select>
            </ion-item>
        </ion-card>
    </ion-list>
</ion-content>

…and finally, when the app starts up and I want to automatically go to my start page I execute the following:

const startPage = this.pageService.getPageById(this.userService.activeUser.startPage);
this.nav.setRoot(startPage.component);

Use Nav in ngAfterViewInit to set variable homepages. There’s sample code in the NavController API. I’ve tried it and it works fine.

1 Like

Of course! Sometimes you get so used to doing things a certain way you forget there are alternatives!

I changed how I loaded the PageService into the SettingsPage and everything works… thanks!!

ngAfterViewInit(){
   this.pageService = new PageService();
   this.startPages = this.pageService.getStartPages();
}

I disagree with the previous advice. All object lifecycle management that can be done by Angular’s DI system should be. You should not be instantiating things manually with new. Circular dependencies can be resolved with forwardRef.

1 Like

Interesting… so use forward ref in the constructor

	constructor(@Inject(forwardRef(() => PageService)) pageService: PageService) {
	}

then in ngAfterViewInit() I can get the service and use it…

ngAfterViewInit() {
    const injector = ReflectiveInjector.resolveAndCreate([PageService]);
    this.pageService = injector.get(PageService);
    this.startPages = this.pageService.getStartPages();
}

… I do like this answer better.

Way too complicated. You should be able to just inject it in the constructor as normal and use that. Shouldn’t need to be dealing with injectors manually at all.

1 Like

Can you show me the code you’re suggesting using my above example? Thanks!

Simply put an access qualifier (public or private) on the pageService parameter in the constructor, and DI should work as normal. You can call getStartPages() wherever you feel is appropriate, but should not have to do anything else.

1 Like

Yep - that did the trick. Thanks a bunch!