Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the new inject function #198

Closed
NetanelBasal opened this issue Jun 3, 2022 · 10 comments
Closed

Use the new inject function #198

NetanelBasal opened this issue Jun 3, 2022 · 10 comments

Comments

@NetanelBasal
Copy link
Member

Since Angular 14 was released, as I mentioned in my previous blog post, we can now create something like:

export function untilDestroyed() {
  const subject = new Subject<void>();

  const viewRef = inject(ChangeDetectorRef) as ViewRef;

  viewRef.onDestroy(() => {
    subject.next();
    subject.complete()
  });

  return takeUntil(subject.asObservable())
} 

@Component({
  selector: 'app-todo-page',
  templateUrl: './todo-page.component.html'
})
export class TodoPageComponent {
  destroy$ = untilDestroyed();

  ngOnInit() {
    interval(1000).pipe(
      this.destroy$
    ).subscribe(console.log)
  }
}

Should we remove the decorator approach or provide both solutions? @arturovt

@arturovt
Copy link
Collaborator

arturovt commented Jun 3, 2022

Hey, I’ll reply a bit later, I’m onto phone for next the week.

@NetanelBasal
Copy link
Member Author

Sure, take your time.

@arturovt
Copy link
Collaborator

arturovt commented Jun 3, 2022

Considering the above example, how the operator will be used for non-component classes? Services, NgModules, pipes, etc (since they all may implement the OnDestroy interface)?

@NetanelBasal
Copy link
Member Author

It should work the same.

@NetanelBasal
Copy link
Member Author

NetanelBasal commented Jun 5, 2022

@Injectable()
export class BarService {
  destroy$ = untilDestroyed();

  init() {
    interval(1000).pipe(
      this.destroy$
    ).subscribe(console.log)
  }
}
@Component({
  selector: 'app-foo',
  templateUrl: './foo.component.html',
  providers: [
    BarService
  ],
})

@arturovt
Copy link
Collaborator

arturovt commented Jun 6, 2022

There're some cases I've noticed where it doesn't work compared to the existing behavior:

@NgModule()
export class SomeModule {
  destroy$ = untilDestroyed(); // No provider for ChangeDetectorRef!
}

@Injectable({ providedIn: 'root' })
export class RootService {
  destroy$ = untilDestroyed(); // No provider for ChangeDetectorRef!
}

Embedded views:

@Pipe({ name: 'impure', pure: false })
export class ImpurePipe implements PipeTransform {
  destroy$ = untilDestroyed();

  constructor() {
    new Subject()
      .pipe(
        this.destroy$,
        finalize(() => console.log('Finalized')) // Not called
      )
      .subscribe();
  }

  transform(value: string) {
    return 'Hey';
  }

  ngOnDestroy(): void {
    console.log('Called when `shown` becomes `false`.');
  }
}

@Directive({ selector: '[myDirective]' })
export class MyDirective {
  destroy$ = untilDestroyed();

  constructor() {
    new Subject()
      .pipe(
        this.destroy$,
        finalize(() => console.log('Finalized')) // Not called
      )
      .subscribe();
  }

  ngOnDestroy(): void {
    console.log('Called when `shown` becomes `false`.');
  }
}

@Component({
  selector: 'app-root',
  template: `
    <button (click)="shown = !shown">Toggle</button>
    <ng-template [ngIf]="shown">
      <div myDirective></div>
      {{ "" | impure }}
    </ng-template>
  `
})
export class AppComponent {
  shown = true;
}

@NetanelBasal
Copy link
Member Author

NetanelBasal commented Jun 6, 2022

  • NgModule - I ignored it on purpose because it's a rare use case, IMO.
  • providedIn: root - That's make sense. It only works when used with component/directive providers.
  • Embedded views - That's a nice catch. For some reason, it seems to use the VCR of the host component. I wonder if this is by design or a bug.

@NetanelBasal
Copy link
Member Author

Actually, it makes sense because we are injecting ChangeDetectorRef.

@NetanelBasal
Copy link
Member Author

Hmm what about doing something like this (quick pseudo code):

const symbol = Symbol('untilDestroyed');
const patched = Symbol('patched');

export function untilDestroyed(instance: any) {
  const proto = Object.getPrototypeOf(instance);

  if (!proto[patched]) {
    proto[patched] = true;
    const original = proto.ngOnDestroy;

    proto.ngOnDestroy = function () {
      original?.apply(this, arguments);
      this[symbol].next();
      this[symbol].complete();
    }
  }

  instance[symbol] = new Subject<void>();

  return takeUntil(instance[symbol].asObservable())
} 

@NetanelBasal
Copy link
Member Author

This code works, but I don't see any benefit over our current approach. I'm closing the issue for now.

@NetanelBasal NetanelBasal unpinned this issue Jun 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants