Uncategorized

Understanding Advanced Dependancy Injection (in Angular)

This series of blog posts which I have converted from Youtube videos. I personally have really enjoyed these videos so in order to textually document them, these are essentially some screenshots and rough notes organised. 

This particular post is based on the awesome Ward Bell NGVikings 2018 talk. It can be found on the link https://www.youtube.com/watch?v=PRRgo6F0cjs

There is an outer container represented here as the app animal container which is represented by app animals. Inside it, there could be any number of components which can be slid into the HTML.

Some of them are animals while others are not.

Sometimes, there are arbitrarily repeated items. Here, there are multiple dogs.

Each component is different

Export class CatComponent implements Animal ()

Export class DogComponent extends Animal ()

Export class FoxComponent implements Animal ()

Export class RockComponent ()

Export class Viking ()

Some of the above components implement an interface, which is called animals. Some of them extend- they sub-class the interface.

An abstract class can be an interface. This means, that there can be a class but it is not required burn it as the base class for anything, it could just be implemented or extended.

Export abstract class Animal {

Name: string;

Abstract speak(): void ;

Abstract clear(): void;

}

This is particularly useful.

Provide class instead of an interface

Export interface IAnimal {}

//Does not work

{provide: IAnimal, useClass: someService }

The interface IAnimal here did not work because the interface just disappears.

But, if it is done as a class;

Export abstract class Animal {}

//works great!

{provide : Animal, useClass: some Service}

As a class, it acts as an injection token and it gives shape to the things which are needed to be injected. The abstract classes can be used as interfaces- they occupy no space and they are free.

Viking implements the interface without saying so:

Export class VikingComponent {

Name= ‘Viking’;

Saying: string;

Speak() {

This.saying= ‘Aiiii!!!;

}

Clear() {

This.saying= ‘’;

}

}

Interfaces are required to find the interfaces. But the Viking component implements the interface but it does not say it does.

Export class RockComponent {

//a rock just sits

}

Component injector hierarchy

The decorator, @ContentChildren which helps in finding the projected content inside the container.

Find the component by its type. In the example below, it is the FoxComponent:

@contentChildren (FoxComponent) foxesQL: QueryList<FoxComponent>;

ngAfterContentInit() {

const foxes= this.foxesQL.toArray();

this.animals= foxes;

}
Speak() {

This.animals.forEach(animal=> animal.speak());

}

After one of the lifecycles hooks the content a knit. It is checked and now it is possible to speak to the foxes.

It only works for foxes. It is possible to do all the types and implement that in the container but the container should not know anything about the children.

Components can register with a parent component.

Each of the individual components which are projected in, can talk to the container.

The tab control works similarly. There is a tab container and the tabs inside it know that they have registered themselves with a tab container.

Here is the structure:

There is the app animals, the container.

Component injector hierarchy

Unlike Angular JS, there is not just one injector in Angular now.

In fact, there is a hierarchy of the injectors in angular. It starts from the platform when the app is started, then there is app root when it is bootstrapped. It is possible to bootstrap multiple apps on the screen so it is possible that a bunch of them are present, all of which are inherited from the platform.

Sometimes, only one of them is required on the page, so this separation is required.

Then there is an app module, which is an injector too. Then app component and app container component, which finally gets down to cats and dogs.

At any level of the hierarchy, some service can be asked and if it is not in the existing injector, it moves up the hierarchy and asks for the service.

Service shadowing

Here, there is a location service in HTTP. The app module is provided with the Foo service. Then, there is a different implementation of this service which is provided to the next level. Then Foo service is again provided to the next level and also with a different implementation.

The fox component wants a Foo service. It will get the third implementation of this service, say: Foo 3. Fox component would only see the implementation of Foo which it was provided with as the rest of them are shadowed. This feature is sometime undesirable, but sometimes, it is very helpful and exactly required.

Inject the parent AnimalContainer component

Export class FoxComponent implements Animal {

// container component is somewhere up the component injector tree

Constructor( container: AnimalConatinerComponent) {

Container. Register(this); // register with the container

}

}

It is possible to find it by having is bubble up. There is a FoxComponent injecting the animal container, the parent and it calls register on that container.

AnimalContainer holds references

Export class AnimalContainerComponent{

Animals: Animla[] = [];

Register(animal: Animal) { this.animals.push(animal); }

/**Ask the animals to speak*/

Speak() {

This.animals.forEach(animal=> animal.speak());

}

}

The animal containers hold a bunch of references so that when the registration happens, it pushes a greeting message. It is then, iterated over when someone presses the speak button and every animal is told to speak. (Just like a tab control).

It is not desirable because it is really tight coupling. All of the individuals; the foxes, dogs and cats, they all have to know the name of their parent and the whole protocol.

The foxes, dogs, and cats cannot know which container they belong to or have to belong to a single one. Even though, it works, this root is not required.

viewChildren(the-interface)?

@contentChildren(Animal) animalsQL:QueryList<Animal>;

Using the content children to get the animals, the result is zero.

It is because contentChildren cannot find a base class or an interface that is underneath any of the classes. If it can find the fox, it can find dog and cat as well. But, it does not know if they implement something else as well.

Mark them with an attribute directive?

It is possible to mark all of the things which are being projected with an attribute directive.

Write an empty attribute directive.

Define empty attribute directive:

Import { Directive } from @angular/core’;

@Direcitve ({

Selector: [animal]

})

Export class AnimalDirective {}

It does not have to do anything. It just needs to be written.

Mark with directive

<app-animals>

<cat animal></cat>

<dog animal></dog>

<rock></rock>

<fox animal> #item</fox>

<vikinganimal></Viking>

<p #item></p>

</app-animals>

It is put down on all the ones which are possibly animals. When the directives are asked, they give directives. There is nothing in the directive.

Mark with #item ref variable

They give you reference to either the element or the component that they are attached to. If that is done, it works great. Except there are rocks in there and the paragraph in there because in the above image, it was not put in the right ones.

The contentchildren has a second parameter, {read:}

ContentChildren with the attached animal directive

The marking with directives returned the directive which was not useful.

Add read parameter to the earlier code.

The elementRef is useful.

It gives us a direct access to the element with which the directive is attached. This means, if something was required to be done in the container, where the element was manipulated, it would be possible to do so. This is how the black border appeared.

Like a lot of attribute directives, this is how they manipulate the element with which they were attached to.

Fun facts

So this works. Take the directive, put the read parameter on there and ask for the FoxComponent and, it will get the FoxComponent.

This has been done before when a direct approach was used. It did not do anything because it was only good for foxes.

Aliasing with useExisting

Aliasing:

Providers: [

someService,

{provide: MyAlias, useExisting: someService}

]

It is a way of aliasing. It is a way of getting a reference to some other service by its token. If useExisting is used with an alias, it is a pointer to some other token (in this case: someService) and that will give someService. MyAlias now refers to something. It is not required to inject some service now. myAlias can be injected now and it actually gave the reference to someService.

Beware

Providers: [

someService,

// MyAlias is a new second instance of SomeService

{provide: MyAlias, useClass: SomeService}

]

It is really easy to confuse useExisting with useClass. If useClass is used, the reference to the original someService is not achieved. It gives a new instance of someservice associated with MyAlias. Now, there are 2 instances of someservice in the injector.

Providers: [

SomeService,

// MyAlias points to the same instance of SomeService

{provide: MyAlias, useExisting: someService }

]

When the alias is required, use useExisting. Whichever myalias is injected, it would be pointing to the same instance.

Alias the component with its own interface

Put it right on the component. Because it is provided at the component level, there is going to be one instance of myAnimal in that injector. If another Foxcomponent is created and the same procedure is done, will it get confused? No, because there is a hierarchy of injectors, whichever level is provided at, it gets its own instances, so every instance of the fox, cat, dog, etc. has its own injector and animal, service referencing by aliasing the component itself.

Back to the directive thing; use the read again. The read looks inside the injector and there is an animal in there now. When a read animal is done, it finds them all. There is access to the components now through the directive using the read parameter.

Now when the contentchildren is implemented, it looks into the injectors of everything that is in there.

It finds things now.

queryList.changes

Demo:


Leave a Reply

Your email address will not be published. Required fields are marked *