angular
March 23

Output(): новая функция в Angular 17.3

Angular v17.3 намекает, что декораторы скоро уйдут в прошлое. На этот раз в добавок к ранее введенным input() и model() была добавлена функция output().

import { output } from '@angular/core';

@Component({
  selector: 'app-foo',
  template: '',
  standalone: true,
})
class FooComponent {
  page = output<number>();
  // Or use an alias
  page = output<number>({ alias: 'currentPage' });
}

Далее, как и с обычным output остается прочитать полученное значение

import { output } from '@angular/core';

@Component({
  selector: 'app-main',
  template: '<app-foo (page)="doSomething($event)">',
  standalone: true,
})
class AppComponent {
  doSomething(data) { сonsole.log(data) }
}

Отклоняясь от EventEmitter, который расширяет класс Subject из RxJS, функция output возвращает OutputEmitterRef, у которого есть только два открытых метода — subscribe и emit. Этот функционал позволяет заменить EventEmitter любым другим наблюдаемым объектом внутри нашего декоратора Output.

Создание Output из Observable

Для поддержки совместимости с существующими рабочими процессами Angular вводит функцию outputFromObservable. Эта функция позволяет прокидывать вверх по дереву компонентов значения из Observalbe.

import { outputFromObservable } from '@angular/core';

@Component({
  selector: 'app-foo',
  template: '',
  standalone: true,
})
class FooComponent {
  form = new FormGroup({ ... })
  value = outputFromObservable(this.form.valueChanges);
  // You can also use an alias
  value = outputFromObservable(this.form.valueChanges, { alias: 'valueChange' });
}


Эта функция может принимать как Subject и EventEmitter. Изучим её реализацию:

export function outputFromObservable<T>(
    observable: Observable<T>, opts?: OutputOptions): OutputRef<T> {
  return new OutputFromObservableRef<T>(observable);
}
class OutputFromObservableRef<T> implements OutputRef<T> {
  private destroyed = false;

  destroyRef = inject(DestroyRef);

  constructor(private source: Observable<T>) {}

  subscribe(callbackFn: (value: T) => void): OutputRefSubscription {
    const subscription = this.source.pipe(
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: value => callbackFn(value),
    });

    return {
      unsubscribe: () => subscription.unsubscribe(),
    };
  }
}

Класс OutputFromObservableRef принимает наблюдаемый объект и реализует метод subscribe класса OutputRef. При вызове этого метода он подписывается на предоставленный Observable и передает отправленные значения в коллбек. Более того, он обеспечивает очистку при уничтожении связанного компонента или директивы.

Преобразование Output в Observable

Так же у нас есть возможность использовать outputToObservable, который преобразует выходные данные в Observable:

import { output, outputToObservable } from '@angular/core';

@Component({
  selector: 'app-foo',
  template: '',
  standalone: true,
})
class FooComponent {
  page = output<number>();
  page$ = outputToObservable(this.page);
}

Реализация довольно проста:

function outputToObservable<T>(ref: OutputRef<T>): Observable<T> {
  const destroyRef = ɵgetOutputDestroyRef(ref);
  
  return new Observable<T>(observer => {
    destroyRef?.onDestroy(() => observer.complete());
    const subscription = ref.subscribe(v => observer.next(v));
    return () => subscription.unsubscribe();
  });
}

Это вольный перевод материала