My constructor has optional parameters and they seem to mess with the predominant way of doing DI.
constructor(public name:string, public age?:number, private _service:Service);
Typescript understandably doesn't like that I put a non optional parameter behind an optional one, furthermore the service doesn't get injected when the optional parameter isn't set. How do I solve that? I can't put it somewhere else in the constructor since I would be expected setting the service manually.
Is there something like field injection?
#Inject() private _service:Service;
constructor(public name:string, public age?:number);
Should I replace the optional parameters with default values? Any other suggestions?
EDIT:
As discussed below, I tried to inject a service into an object that isn't created by Angular's DI. This doesn't work. Since I can't create this class (model) using DI I now pass the service manually from the class that instantiates this objects.
Just add the #Optional() decorator before the constructor parameter that should only be injected if there was a provider registered.
import { Optional } from '#angular/core';
constructor(public name:string, #Optional() public age:number)
If I understand correctly what you are trying to do, you need a service factory. See here: https://angular.io/docs/ts/latest/guide/dependency-injection.html#factory-provider
Basically,
class MyClass {
constructor(public name:string, private _service:Service, public age?:number){}
}
And then
let myFactory = (_service: Service) => {
return isAgeOk ? new MyClass(name, _service, age) : new MyClass(name, _service);
};
And then you should provide your service like this:
providers: [{ provide: MyClass, useFactory: MyFactory, deps: [Service]}]
Related
is there a way to access metadata from controller methods?
For example, I add metadata to a controller class with SetMetadata() - e.g. from a decorator.
I know how to access metadata in a guard. You need to inject reflector and guard.canActivate() has ExecutionContext parameter.
canActivate(context: ExecutionContext): boolean {
metadata: SomeType = this.reflector.get<EnabledFeatures>(SOME_METADATA_KEY, [context.getClass()]);
}
To get metadata I need 2 components: Reflector and ExecutionContext.
I can inject Reflector into controller, but how can I access ExecutionContext from a controller?
Assuming we set some metadata on Controller with #SetMetadata :
#Controller({...})
#SetMetadata('roles', ['admin'])
We can have access to it, by creating our custom param decorator:
export const Roles = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
// get roles metadata from #Controller class
const roles = Reflect.getMetadata('roles', ctx.getClass());
return roles;
},
);
And then we can use it on controller's method :
#Get()
getInfo(#Roles() roles): string {
// roles = ['admin']
//...
}
Some notes
#SetMetadata not a good practice
Usage of #SetMetadata directly is not really a good practice. Prefer to create a specific decorator (for maintenance and readability of code) :
export const SetRoles = (...roles: string[]) => SetMetadata('roles', roles);
...
#Controller({...})
#SetRoles('admin')
export class MyController {...}
Reflect.getMetadata API vs Injector
Even if Reflect.getMetadata is in fact called by Reflector API of NestJS, it could be changed in the future.
So if we want to deal with only public/documented API of NestJS, we can:
use a global guard, which will inject Injector,
get metadata with ExecutionContext
and then set result in Request instance.
An other custom param decorator will retrieve data from Request and return it.
More complicated, but without using direct call to Reflect.getMetadata API.
I am building an authorization service for my nestjs app.
For every protected resource on my app (Media, Game, ...), I have a *RoleEntity associated (MediaRoleEntity, GameRoleEntity, ...) that defines what a user can do with a specific resource. Each one of this *RoleEntity implements RoleEntityInterface:
export interface RoleEntityInterface<T extends ResourceEntity> {
resource: T;
user: UserEntity;
role: string;
}
Each protected entity (MediaEntity, GameEntity, ...) extends ResourceEntity.
Now I want to build a generic provider RoleService, responsible for database interaction:
#Injectable()
export class RoleService<T extends ResourceEntity> {
readonly roleRepository: Repository<RoleEntityInterface<T>>;
async read(roleDto: Partial<RoleDto<T>>): Promise<RoleEntityInterface<T>> {
return this.roleRepository.findOne({
where: { ...roleDto },
});
}
async create(roleDto: RoleDto<T> | RoleDto<T>[]): Promise<void> {
await this.roleRepository.insert(roleDto);
}
}
And I want inject this service in guards, interceptors...
Problem, I don't know how to do that, more precisely:
How can I dynamically inject the roleRepository ? (I imagine some kind of factory has to be involved.)
REAL USE CASE
I want to be able to protect resources with a guard:
#Injectable()
export class RoleGuard<T extends ResourceEntity> implements CanActivate {
constructor(
private authService: AuthService,
private roleService: RoleService<T>,
private readonly reflector: Reflector,
) {}
...
}
Now in a controller, when I use
#Role('Admin')
#UseGuards(RoleGuard<MediaEntity>)
Get()
...
It would be perfect if the whole thing magically works :), ie correct roleService with correct roleRepository are properly injected.
I am completely new to nestjs and typescript (and never played with angular neither) so maybe the whole approach is wrong..
I'm currently setting up an application with Symfony 3 and Doctrine 2.5 and I'm trying to inject an entity repository into a service and I keep getting the following error:
Type error: Argument 1 passed to UserService::setUserRepository() must
be an instance of UserRepository, instance of
Doctrine\ORM\EntityRepository given, called in
appDevDebugProjectContainer.php on line 373
This is how wire things up in my services.yml:
service_user:
class: UserService
calls:
- [setUserRepository, ["#service_user_repository"]]
service_user_repository:
class: UserRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments: [Entity\User]
This is my UserService:
<?php
class UserService {
protected $userRepository;
public function setUserRepository( UserRepository $userRepository )
{
$this->userRepository = $userRepository;
}
}
And this is my UserRepository:
<?php
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository {
}
I have checked and double checked my namespaces and class names, all seem to check out fine.
How do I inject an entity repository into a service with Symfony 3 service wiring?
As mentioned in comment, everything you've showed looks fine.
But since you're getting from entity manager an EntityRepository instance instead of UserRepository, it means that you didn't configured User entity to have custom (UserRepository) repository class.
If you use YAML mapping, it should be something like:
Entity\User:
repositoryClass: UserRepository
# rest of mapping
I am trying to wire up a basic Angular2 app that uses the Http service. (Most of the tutorials I've seen do this by having a Component consume the Http service, which seems wrong unless the basic philosophy of thin controllers has changed – but that's a different question.)
I would like to create a service that uses Angular's Http service. But I can't figure out how to inject the Http service other than this:
boot.ts:
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {HTTP_PROVIDERS } from 'angular2/http';
bootstrap(AppComponent, [HTTP_PROVIDERS]);
myService.ts:
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class aService{
constructor(http:Http){
}
/** do some stuff *//
}
This works, but it seem very wrong to require the user of the service to know the service's dependencies and be required to inject them into the bootstrap process. It seems like there should be a way to directly hand a providers array to a service the same way you can a component, but I can't find it. Am I just missing something?
Update
This way if a parent injector provides an implementation for OtherService this one is used, otherwise OtherServiceImpl is used (default).
#Injectable()
class SomeService {
OtherService _other;
SomeService(Injector injector) {
_other = injector.getOptional(OtherService);
if (_other == null) {
_other = injector.resolveAndCreateChild([
provide(OtherService, useClass: OtherServiceImpl)
]).get(OtherService);
}
_other.doSomething();
}
}
If you provide another one like
bootstrap(AppElement, [
provide(OtherService, useClass: OtherServiceImpl2)
]);
OtherServiceImpl2 is used.
See also https://github.com/angular/angular/issues/5622
Original
You could just make the http service optional (using the #Optional() annotation) and if none is provided just create an instance inside the constructor with new Http().
This way the user doesn't need to know about the services dependencies, but is able to pass alternative implementations if necessary (for example for testing).
If creating the dependeny inside the service requires DI itself, you can inject an injector and use it to get dependencies.
See also optional dependencies in http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
What also could work (not tried myself yet) is just to create a child injector and instruct it to skip self
From the SkipSelfMetadata documentation
class Dependency {
}
#Injectable()
class NeedsDependency {
dependency;
constructor(#SkipSelf() dependency:Dependency) {
this.dependency = dependency;
}
}
var parent = Injector.resolveAndCreate([Dependency]);
var child = parent.resolveAndCreateChild([NeedsDependency]);
expect(child.get(NeedsDependency).dependency instanceof Depedency).toBe(true);
var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
expect(() => inj.get(NeedsDependency)).toThrowError();
I don't know yet if this still resolves from "self" if parent can't provide the requested type.
have two services that require an XPathDocument. I want to be able to define named instances of XPathDocumnet to use in the configuration of the two services. I also want to be able to tell StuctureMap which constructor of XPathDocument to use. When I try to get an instance of XPathDocument it tells me that it can't find the plugged type for XmlReader. I want to use the constructor that requires a string uri for the xml file. I cannot seem to get this to work. Here is the StructureMap configuration code.
public class Service1 : IService1 {
public Service1(XPathDocument document) {}
}
public class Service2 : IService2 {
public Service2(XPathDocument document) {}
}
public class Registry1 : Registry {
ForRequestedType<IService1>().TheDefault.Is.OfConcreteType<Service1>()
.CtorDependency<XPathDocument>().Is(x => x.TheInstanceNamed("XPathDocument1"));
ForRequestedType<IService2>().TheDefault.Is.OfConcreteType<Service2>()
.CtorDependency<XPathDocument>().Is(x => x.TheInstanceNamed("XPathDocument2"));
ForRequestedType<XPathDocument>().AddInstances(x => {
x.OfConcreteType<XPathDocument>()
.WithCtorArg("uri").EqualToAppSetting("XmlFile1")
.WithName("XPathDocument1");
x.OfConcreteType<XPathDocument>()
.WithCtorArg("uri").EqualToAppSetting("XmlFile2")
.WithName("XPathDocument2");
});
}
Since XPathDocument is a framework type that is not under your control, you should register it with a factory delegate.
container.Configure(r => r.For<XPathDocument>()
.Use(() => new XPathDocument("XmlFile1"));
But since you need a dofference instance per service, you might be better of not registering this class itself, but only configure your services with a constructor argument where you specify the lambda delegate.