Self, SkipSelf And Optional Decorators In Angular

Self, SkipSelf And Optional Decorators In Angular

The Angular Decorators @Self, @SkipSelf, @Optional, and @Host control how the DI Framework resolves dependencies. Because they change the behavior of injectors, these decorators are called Resolution Modifiers. In this article, We will learn about the @Self, @SkipSelf, and @Optional.

How Angular DI Framework Resolves Dependencies

The DI Framework resolves dependencies in two phases when a component requests them.

It starts looking for the Dependency in the current component's ElementInjector in the first step. It will look in the Parent Components ElementInjector if it does not supply the Dependency. The Request bubbles up until it reaches the root ElementInjector or finds an injector that delivers the service.

If ElementInjector fails to satisfy the request, Angular searches the ModuleInjector hierarchy for the Dependency. If Angular is still unable to locate the provider, an exception is thrown.

@Self, @SkipSelf & @Optional Example

To demonstrate the @Self, @SkipSelf, and @Optional directives, we developed an Angular project.

The code includes a RandomService that, when initialized, creates a Random Number. The Angular Service is added to the AppModule's Providers array. This service can be injected anywhere in our application.

@Injectable({
  providedIn: "root"
})
export class RandomService {

The project has three Angular Components (AppComponent, ChildComponent, and GrandChildComponent), each of which injects the RandomService and shows the Random Number obtained from it.

We also have testDirective, which is part of the GrandChildComponent template. It also displays the Service's Random Number.

In all components and directives, make sure the Providers array is empty. Start the app. The RandomService is only created once by Angular. As a result, the components and directives all display the same number.

Self SkipSelf Optional Host Example In Angular


Let's see how @Self, @SkipSelf, and @Optional can be used to change the above behavior.

First, let's look at @Self.

@Self

The @Self decorator tells Angular to only look at the local injector for the dependency. The injector that is part of the present component or directive is known as the local injector.

As seen below, open the GrandChildComponent and add the @Self() on randomService.
@Component({
  selector: "my-grandChild",
  template: `
    <div class="box">
      GrandChildComponent => {{ randomNo }}
      <div class="dirbox" testdirective="">fdf</div>
    </div>
  `,
  providers: []
})
export class GrandChildComponent {
  randomNo;
  constructor(@Self() private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

This instructs the Angular DI Framework to search for the Dependency associated with the current Component. It will throw the mistake if it finds one.

The problem is resolved by adding the RandomService to the GrandChildComponent's providers array.
@Component({
  selector: "my-grandChild",
  template: `
    <div class="box">
      GrandChildComponent => {{ randomNo }}
      <div class="dirbox" testdirective="">fdf</div>
    </div>
  `,
  providers: [RandomService]
})
export class GrandChildComponent {
  randomNo;
  constructor(@Self() private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

As you can see in the image, Angular produces two RandomService instances. One comes from the AppModule, while the other comes from the GrandChildComponent. Also, the RandomService offered by the GrandChildComponent, not the AppModule, is used by testDirective.

Self Example In Angular


@SkipSelf

The @SkipSelf decorator tells Angular to start searching for the dependency in the Parent Injector and work its way up.

It instructs Angular to start from the Parent instead of looking for the injector in the local injector. This decorator is the polar opposite of the @Self decorator.

Restart the GrandChildComponent. Instead of using the Self decorator, use SkipSelf.
import { Component, SkipSelf, Self, Optional, Host } from "@angular/core";
import { RandomService } from "./random-service";
 
@Component({
  selector: "my-grandChild",
  template: `
    <div class="box">
      GrandChildComponent => {{ randomNo }}
      <div class="dirbox" testdirective="">fdf</div>
    </div>
  `,
  providers: [RandomService]
})
export class GrandChildComponent {
  randomNo;
  constructor(@SkipSelf() private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

As you can see in the image, the GrandChildComponent uses the Module's RandomService instance rather than the one it provides.

However, the testDirective continues to use the GrandChildComponent's RandomService.

SkipSelf Example In Angular

@Optional

Optional designates a reliance as such. It returns null instead of throwing an error if the dependency isn't found.

Remove the RandomService from the Providers Array in the GrandChildComponent and replace it with the @Self decorator. The error "No provider for RandomService found in NodeInjector" will appear right away.

Along with the @Self decorator, add the @Optional decorator. Instead of returning an error, dependency injection will instead return null.

Also, don't forget to include the ? in randomService?, or you'll get an error saying "Cannot read property 'RandomNo' of null."
import { Component, SkipSelf, Self, Optional, Host } from "@angular/core";
import { RandomService } from "./random-service";
 
@Component({
  selector: "my-grandChild",
  template: `
    <div class="box">
      GrandChildComponent => {{ randomNo }}
      <div class="dirbox" testdirective="">fdf</div>
    </div>
  `,
  providers: []
})
export class GrandChildComponent {
  randomNo;
  constructor(@Optional() @Self() private randomService: RandomService) {
    this.randomNo = randomService?.RandomNo;
  }
}

GrandChildComponent does not receive any values, as shown in the image, whereas testDirective uses the RandomService provided by the AppModule.

Optional Example In Angular


The following is complete code of the app.component.ts file
import { Component, VERSION } from "@angular/core";
import { RandomService } from "./random-service";
 
@Component({
  selector: "my-app",
  providers: [],
  viewProviders: [],
  template: `
    <div class="box">
      <p>AppComponent => {{ randomNo }}</p>
      <my-child></my-child>
    </div>
  `
})
export class AppComponent {
  randomNo;
  constructor(private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

The following is complete code of the child.component.ts file
import { Component, SkipSelf, Self, Optional, Host } from "@angular/core";
import { RandomService } from "./random-service";
 
@Component({
  selector: "my-child",
  providers: [],
  viewProviders: [],
  template: `
    <div class="box">
      <p>ChildComponent => {{ randomNo }}</p>
 
      <my-grandchild></my-grandchild>
    </div>
  `
})
export class ChildComponent {
  randomNo;
  constructor(private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

The following is complete code of the grand-child.component.ts file
import { Component, SkipSelf, Self, Optional, Host } from "@angular/core";
import { RandomService } from "./random-service";
 
@Component({
  selector: "my-grandChild",
  template: `
    <div class="box">
      GrandChildComponent => {{ randomNo }}
      <div class="dirbox" testdirective="">fdf</div>
    </div>
  `,
  providers: []
})
export class GrandChildComponent {
  randomNo;
  constructor(private randomService: RandomService) {
    this.randomNo = randomService.RandomNo;
  }
}

The following is complete code of the test-directive.ts file
import {
  Directive,
  ElementRef,
  Input,
  OnInit,
  SkipSelf,
  Self,
  Optional,
  Host
} from "@angular/core";
import { RandomService } from "./random-service";
 
@Directive({
  selector: "[testDirective]",
  providers: []
})
export class testDirective implements OnInit {
  @Input() ttClass: string;
 
  constructor(private el: ElementRef, private randomService: RandomService) {}
 
  ngOnInit() {
    this.el.nativeElement.innerHTML =
      "Directive =>" + this.randomService.RandomNo;
  }
}

The following is complete code of the random-service.ts file
import { Injectable } from "@angular/core";
 
@Injectable({
  providedIn: "root"
})
export class RandomService {
  private _randomNo = 0;
 
  constructor() {
    console.log("RandomService Constructed");
    this._randomNo = Math.floor(Math.random() * 1000 + 1);
  }
 
  get RandomNo() {
    return this._randomNo;
  }
}

The following is complete code of the styles.css file
.box {
  margin: 5px;
  border: 1px;
  border-style: solid;
}
 
.dirbox {
  margin: 5px;
  border: 1px;
  border-style: dotted;
}

The following is complete code of the app.module.ts file
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
 
import { AppComponent } from "./app.component";
import { ChildComponent } from "./child.component";
import { GrandChildComponent } from "./grand-child.component";
import { testDirective } from "./test-directive";
 
@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [
    AppComponent,
    ChildComponent,
    GrandChildComponent,
    testDirective
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}


Conclusion

In this article, We learned about the @Self@SkipSelf, and @Optional. In the next article, we learn about the @Host.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post