The accepted answer works well, but if you have several dependencies 'provided' by your component that depend on each other than things get a lot more complicated.
Another approach that may work if you're already heavily using observables is to provide a LAZY_ID token which is actually a ReplaySubject<number> (or whatever type you need it to be).
In your ngOnInit() you simply call this.lazyID.next(this.id) to update the ReplaySubject with the value passed in via @Input.
In addition you would then use this LAZY_ID with a provider factory to create whatever the primary dependency was.
Disclaimer: I don't think this is a good general solution to this issue. It can get clumsy but sometimes it may work!
Here's a simplified example - would welcome improvements:
export const LAZY_ID = new InjectionToken<ReplaySubject<number>>('LAZY_ID');
export const LazyIDFactory = () => 
{
    return new ReplaySubject<number>(1);
}
export const productDataFromLazyIDFactory = (productService: ProductService, id$: ReplaySubject<number>) =>
{
    // Create your 'ProductData' from your service and id$
    // So yes - the catch here is your ProductData needs to take an observable
    // as an input - which only really works if you're extensively using observables
    // everywhere. You can't 'wait' for the result here since the factory must return
    // immediately
}
Then in your @Component
providers: [ 
    // creates our ReplaySubject to hold the ID
    {
        provide: LAZY_ID,
        useFactory: LazyIDFactory
    },
    { 
        provide: ProductData,
        useFactory: productDataFromLazyIDFactory,
        deps: [ ProductService, LAZY_ID ]
    },