I want to directly access the DI system. I cant use the typical @inject method because they require a constructor and I am working with abstract classes.
In v1 I had direct DI working like this:
import {Container} from "aurelia-dependency-injection";
export abstract class DrfListController<TListItem> {
private router: Router = Container.instance.get(Router);
abstract paramsChanged(newParams): Promise<Array<TListItem>;
}
I can figure out how to do this in V2. The doc mentions using container.get(Router) but its not clear what container it is referring to. Any tips?
even though you could do that in Aurelia v1 it wasnt really a best practice as you tightly couple your class to a singleton root container. Instead what about creating a ctor and in your derrived classes injecting whats necessary and passing to super()?
I understand the tight coupling issue. The use case is the following though:
I have many List Components in my App with Sorting, Filtering, and Pagination. The state of the filter needs to be represented in the url as query parameters so the user can share links to a specific state ( i.e. limit=50&page=3&zip=8003 ). In the derrived classes I inject the api controller, schemas, and models. But I want the generic routing, url watching, reloading, and state management to be abstracted out of sight.
It’s worked well in the 20+ list views the app implements and the trade off of tightly coupling vs simple abstraction has payed off so far. Extending the abstract controller has the added benefit that the IDE offers to stub the abstract methods for you and thus guides the implementation.
I’ve gone and reimplmented using window.location and the history api, but having access to the Router would be more practical.
so if you’re already injecting things in derived classes you’re essentially just an router inject and calling the abstracts ctor with the router as param via super away right? is there something else causing issues?
If I define a ctor in the abstract def with the Router being injected, then I have to provide the Router as an argument in the derived classes as well, and pass it down in super(). Then I need to inject Router in all the derived classes which is what I am trying to avoid.
well this would be the somewhat “proper way” as you keep things clean and testable. you can always go the dirty route with a global var holding the router and grabbing it in the abstract class
@aGustafson I think that it might be better to treat the common functionalities as a (singleton?) service class. That way you can inject whatever you want to the service class and thereafter inject the service class also to your other classes. The inheritance way of code sharing gets messy super quick. Also a thumb rule is to avoid inheritance for UI components.
perhaps it helps to think about your v1 approach as service locator pattern, which is regarded somewhat as an anti pattern as it hides complexity but makes testing the target without knowing the hidden deps impossible. DI on the other hand builds upon the concept of injecting deps via ctor arguments. wrapping deps in a manager class, what @Sayan751 proposed, is a mean to reduce the amount of injected deps while maintaining ctor injection.
Hmmm, that is sort of what I am doing. I realized I explained it incorrectly above. The DrfListController is not a component. It is an abstract service class who’s instances must override the paramsChanged method which retrieves new data when the url’s queryparams have changed. (There are other methods in the real code, above is just a simplified example ).
The Components relying on this service are the Pagination, Sortable Headers, Filters, and List Components. The service observes and sets the queryparams, and and helps the components manage their state.
Maybe I can use a factory to create the service, or split the service into a generic singleton service that manages the queryparams and a separate service that talks to the data API.
Alternatively you can also use the event aggregator and publish a event when the query parameter changes. The components/listeners can handle the event as per their need.
Although it might be easier to encapsulate this change handling logic to an instance of a “list model” class and bind that instance to your pagination, filter, list components etc. Here I am assuming that when the query parameter changes you somehow want to load your chunk of data accordingly.