loading...
Cover image for It's something @angular injectors can solve

It's something @angular injectors can solve

julianobrasil profile image Juliano ・6 min read

The use case

Today I answered a question at StackOverflow and I'll describe it here just because the author is free to remove it from there breaking that link. The question was: How to Access Parent Component from Child Component in Angular 9.

The first point is that component interaction is very well documented on @angular official site. I'm not the hugest fan of what the author of the question wanted to do. I'm not gonna judge his reasons too because I really don't know his use case thoroughly though. And, most of all, angular provides the tools to do that, so why not learning how to use them? Someday they can be useful. Let's just, for a moment, forget that there are other ways of solving the problem we're taking as an excuse for our sleuthing and focus on this topic: how to use @angular injector directly.

So, in the description of the question, the author described this scenario:

@Component({
  template: '<child></child>'
}) export class Parent { pp: string; }

@Component({
  selector: 'child',
  template: '<grand-child></grand-child>'
}) export class Child { }

@Component({selector: 'grand-child', ...}) export class GrandChild { }

What he wanted to do was accessing, from the GrandChild, the Parent to change pp property. And he was asking why he wasn't able to do, in angular 9, what he was used to doing in angular 8. In his description, he describes what he was trying to do and I'll post it here as it was in the question text:

  this.viewContainerRef[ '_data' ].componentView.component.viewContainerRef[ '_view' ].component;

Don't mess with the library private properties

If you don't wanna be lectured because of one seriously bad coding practice, just jump to the next section

Don't be distracted by following the logic in the last snippet: it's not important if it works or not. The point is that in a serious typescript project like angular, you should believe when the developer uses the private modifier. What he is trying to say is: this property is part of the internal implementation of this class and I can change it without any warning.

Keep this abbreviation in mind: API. The API (Application Program Interface) is the thing you should rely on. As its name said, it is supposed to be the way of an application to use the features of a class/library. Changes in the API are usually avoided by the libraries' authors. And when they are inevitable, they first try to make it compatible with previous versions, and if not possible, publish a document listing that change as a breaking change (because some of the users of that library will have to change their codes in order to use that version).

The API is so important that some testers say that they are the only part of a library that must be tested. It cannot break.

So, if you find yourself in a situation where you need something that is not on the API, the best you can do is asking the authors to expose it. If the authors agree with that, let's consider as acceptable to temporarily use hacks while you wait for that implementation to be rolled out with the new API adding. If the authors aren't gonna change the API, find another way to do what you want. Keeping code that accesses private properties of 3rd party libraries, is having a bomb with a random timer in your project: sooner or later, without any warning, it'll give you headaches.

If my reasons didn't convince you to not use those private properties, do yourself a favor: backup the piece of code you're using it with good tests (and comments). Don't let your users find out for you that a part of your app isn't working anymore.

@angular Dependency Injection

There are great articles out there (and the official docs) diving into how @angular's dependency injection (DI) system works and describing how it uses a probabilistic search technique (Bloom Filters) to discover the injectable stuff in the angular component tree, like this one. It's not part of the scope of this article to go through all that information again.

You only have to know that the DI has multiple injectors, which are methods, associated with components, directives, pipes, and modules, that are responsible for look for objects based on tokens throughout the DI system. For example, if you ask a specific injector for a component and it cannot find it, it asks its parent injector for that component and so on. The injectors are distributed in a hierarchical tree.

How can the angular DI solve that stackoverflow problem

The most simple way

Angular allows for any parent component to be directly injected in any of its children. It's simple and effective. End of discussion.

@Component({
  template: '<child></child>'
}) export class Parent { pp: string; }

@Component({
  selector: 'child',
  template: '<grand-child></grand-child>'
}) export class Child {
  constructor(public parent: Parent) {}
}

@Component({selector: 'grand-child', ...})
export class GrandChild { 
  constructor(private _parent: Child) {
    this._parent.parent.pp = 'some value';
  }
}

Using ViewContainerRef service

All angular component can be used as a reference in the dom to create other components dynamically. The ViewContainerRef is a service associated a component that has methods to do that creation taking that component as a reference in the DOM (even though it may appear that these dynamic components are created inside the component that owns the ViewContainerRef, it is, in fact, created as a sibling - see this article for more info).

What we are really interested in here is the fact that the ViewConainerRef service has a public method to get the parent's injector of the component it is associated with. _And it can also be injected in the component:

@Component({
  template: '<child></child>'
}) export class Parent { pp: string; }

@Component({
  selector: 'child',
  template: '<grand-child></grand-child>'
}) export class Child {}

@Component({selector: 'grand-child', ...})
export class GrandChild { 
  constructor(private _viewContainerRef: ViewContainerRef) {
    const childInjector = this._viewContainerRef.parentInjector;
    const parent: Parent = childInjector.get<Parent>(Parent);
    parent.pp = 'some value';
  }
}

Notice that we don't need any property on Child component to get to the Parent.

Using Injector service

If you look carefully at the previous technique, you may connect some dots and think to your self that if there's a tree of injectors and if one injector doesn't know how to resolve a reference token, it asks for its parent injector... than why just ask the component's injector instead of asking the parent injector for that info? And it's a completely plausible question. Of course, you can do so:

@Component({
  template: '<child></child>'
}) export class Parent { pp: string; }

@Component({
  selector: 'child',
  template: '<grand-child></grand-child>'
}) export class Child {}

@Component({selector: 'grand-child', ...})
export class GrandChild { 
  constructor(private _injector: Injector) {
    const parent: Parent = this._injector.get<Parent>(Parent);
    parent.pp = 'some value';
  }
}

Notice that, once more, we don't need any property on Child component to get to the Parent.

Final Considerations

In the above cases, you can get a reference for the components, because they are hierarchically distributed. It wouldn't work, for example, if A and B components were siblings (you would not be able to inject a reference to A using A's injector => the search algorithm look for a token up in a tree from a given start point, it won't look for a token going up in the tree and, then, going down from upper nodes). You couldn't, also, inject B in A if B was a child of A (the injectors asks information up in the hierarchy, not down in it).

Knowing that you have an injector at your service sometimes solves some problems in a fast way (maybe not the best way, but in a fast way). Maybe you'll never use it, but it's there for you.

Posted on by:

julianobrasil profile

Juliano

@julianobrasil

I'm a full-stack web developer, passionate about all kinds of technologies. Formerly bachelor and MSc in Electrical Engineering.

Discussion

markdown guide