Search code examples
angularrxjs

takeUntilDestroyed() can only be used within an injection context


I'm trying to replace my AbstractComponent (all the boilerplate for unsubscribing) with Angular's new takeUntilDestroyed().

But am getting an error out of the gate.

NG0203: takeUntilDestroyed() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext. Find more at https://angular.io/errors/NG0203

The blog post says:

By default, this operator will inject the current cleanup context. For example, used in a component, it will use the component’s lifetime.

The docs confirm that injecting context is optional. And this indepth article shows its use in OnInit without context. Here's how I'm using it.

  public ngOnInit(): void {
    this.route.firstChild.paramMap.pipe(
      takeUntilDestroyed()
    ).subscribe((res: ParamMap) => {
      ...
    });

How can this be resolved?


Solution

  • You need to pass destroyRef whenever you use takeUntilDestroyed() outside of an injection context. You can think of injection context as a space in your code that runs before the instantiation of a class, in this case a component. The constructor of a class is an example of an injection context because the code inside the constructor runs before the class is instanciated. Another example are class field declarations, whenever you declare a field you must either asign it a value directly or assign it within the constructor, this is because the value must be known at instantiation, which tells us that this assignment happens within an injection context.

    This ones work fine:

    export class FooCmp implements OnInit {
        route = inject(ActivatedRoute);
        params$ = this.route.firstChild.paramMap.pipe(takeUntilDestroyed())
    
        ngOnInit() {
            this.params$.subscribe(res => {/* some operation */})
        }
    }
    
    export class BarCmp {
        constructor(private route: ActivatedRoute) {
            this.route.firstChild.paramMap
            .pipe(takeUntilDestroyed())
            .subscribe(res => {
                // some operation
            })
        }
    }
    export class BazCmp implements OnInit {
        route = inject(ActivatedRoute)
        destroyRef = inject(DestroyRef)
    
        ngOnInit() {
            this.route.firstChild.paramMap
            .pipe(takeUntilDestroyed(this.destroyRef))
            .subscribe(res => {/* some operation */})
        }
    
    }
    

    This one wont:

    export class FooCmp implements OnInit {
        route = inject(ActivatedRoute);
    
        ngOnInit() {
            this.route.firstChild.paramMap.pipe(takeUntilDestroyed())
        }
    }
    

    Edit: Link to Angular's documentation about injection context: https://angular.io/guide/dependency-injection-context