ezzabuzaid / react-context-in-angular

1 stars 1 forks source link

The Angular Way #9

Open ezzabuzaid opened 3 years ago

ezzabuzaid commented 3 years ago

Angular does have Dependency Injection framework that provides different approaches to handle a situation where something like React Context API is needed.

In the "The Problem" section, you saw that to pass down a value to the descendants' components you have to declare an @Input at every component even though that a component might merely act as a wrapper for another component. This actually can be changed by providing an InjectionToken to the ancestor component and inject that token at any descendant component to utilize the value.

Change the root component to include the InjectionToken

const FamilyNameToken = new InjectionToken('FamilyName');
@Component({
  selector: 'app-root',
  template: `<app-grandchild> </app-grandchild>`,
  providers: [{provide: FamilyNameToken, useValue: 'The Angulars'}]
})
export class AppComponent { }

And for the component that needs the value to inject the InjectionToken

@Component({
  selector: 'app-grandchild',
  template: `Family Name: {{familyNameValue}}`
})
export class GrandchildComponent {
  constructor(@Inject(FamilyNameToken) public familyNameValue: string) { }
 }

That might look easy and simple at first, but the catch is when you want to update the value you need to have a kind of RxJS Subject because Angular will hard inject the value that corresponds to the InjectionToken into the GrandchildComponent. The other way is to use a class provider to act as a state holder.

class FamilyName {
  private state = new ReplaySubject(1);
  public setName(value: string) {
    this.state.next(value);
   }
  public getName() {
    return this.state.asObservable();
  }
}

The root component will inject the class and sets the value.

@Component({
  selector: 'app-root',
  template: `<app-grandchild> </app-grandchild>`,
  providers: [FamilyName]
})
export class AppComponent {
  constructor(public familyName: FamilyName) {
    $familyNameState = this.familyName.setName('The Angulars');
  }
}

And for the component that needs the value to inject the FamilyName class and subscribe to the changes.

@Component({
  selector: 'app-grandchild',
  template: `Family Name: {{$familyNameState|async}}`
})
export class GrandchildComponent {
  $familyNameState = this.familyName.getName();
  constructor(public familyName: FamilyName) { }
 }

Also, you can re-provide the FamilyName class at any component level so it can act as the ProviderComponent.

With that said, having a way to pass down a value within the component template it self can reduce the amount of class you will need.