Angular: Proposta: entrada como observável

Criado em 8 dez. 2015  ·  183Comentários  ·  Fonte: angular/angular

Desculpe, não sou bom em inglês.

@Input valores de propriedade são fornecidos pelo componente pai. As mudanças vêm de forma assíncrona.
E se a propriedade Input foi alterada no componente filho (ele tem a propriedade como propriedade própria), seu detector de alterações nunca notará isso.

Meta

  • Os dados de entrada do pai e a propriedade de entrada do filho devem ser sincronizados.
  • Os desenvolvedores devem entender que as propriedades de entrada são alteradas de forma assíncrona.

    Proposta

@Component({ selector: "child" })
class Child {
  @Input("input") inputValue: Observable<T>;

  ngOnInit() {
    this.inputValue.map((value)=>...);
  }
}

@Component({
  template: `
  <child [input]="valueToChild"></child>
  `
})
class Parent {
  valueToChild: T;
}

O código acima não funciona. Atualmente, para receber entrada como Observable<T> , devo escrever como abaixo.

@Component({ selector: "child" })
class Child {
  @Input("input") inputValue: Observable<T>
}

@Component({
  template: `
  <child [input]="valueToChild"></child>
  `
})
class Parent {
  valueToChild: Observable<T> = new Observable<T>((observer)=>{
    ...
    observer.next(val);
  });
}

Exemplo: http://plnkr.co/edit/BWziQygApOezTENdTVp1?p=preview

Isso funciona bem, mas não é essencial. Os dados de entrada do pai são originalmente dados simples.

Acho que essa proposta nos deixa felizes.

Obrigado.

core inputs / outputs feature Needs Design

Comentários muito úteis

Caro time Angular. Dê-nos algo pelo que esperar em 2020 :-)

Além disso, a solução @ViewChild tem { read } . por exemplo:

@Input({ observable: true }) 
@Input({ asObservable: true }) 
@Input({ asSubject: true })

Todos 183 comentários

Olá @ laco0416 - seu inglês está bom, não se preocupe!

Gosto muito dessa ideia e é algo que já discutimos antes. Ele também combina perfeitamente com https://github.com/angular/angular/issues/4062 (eventos de visualização de observação) e https://github.com/angular/angular/issues/5467 (eventos filhos observáveis ​​dos pais)

É importante lembrar que nem todo mundo vai querer usar Observáveis ​​(essas pessoas estão perdendo!), Portanto, devemos fornecer opções para ambos os casos de uso e, portanto, é improvável que façamos @Input() diretamente em um Observável. Eu realmente acho que ter algo como @ObserveInput() pode funcionar e teremos uma discussão _após_ o lançamento do beta sobre alguns desses recursos mais interessantes, eu acho.

Nesse ínterim, aqui está uma implementação básica (e _muito_ experimental !!! NÃO faça isso de verdade) para essa ideia. É isso conceitualmente o que você estava pensando? http://plnkr.co/edit/Nvyd9IPBZp9OE2widOcW?p=preview

Acho que é uma péssima ideia alterar as propriedades de entrada em um componente filho. As propriedades de entrada devem ser "somente leitura". Seus dados devem sempre fluir de pai para filho (e nunca na direção inversa).

@alexpods eu acredito que a ideia aqui é exatamente essa - é _ouvir_ a mudança nas propriedades de entrada _como_ um observável, não emitindo valores upstream, o que é absolutamente bom no que me diz respeito.

@robwormald

seu inglês está bom, não se preocupe!

Obrigada! Estou tão aliviada.

Seu @ObserveInput é exatamente o que eu quero!
Além disso, @Input não tem alterações importantes. Acho que é uma solução muito boa.

@alexpods Eu também.

é ouvir a mudança nas propriedades de entrada como um Observable, não emitindo valores upstream, o que é absolutamente bom no que me diz respeito.

Eu penso da mesma forma que Rob.

@ laco0416 Ooh, desculpe pelo mal-entendido. A frase "se a propriedade de entrada foi alterada no componente filho" me confundiu.

Não sei se devo comentar aqui ou se devo abrir uma nova edição. Informe se estou adicionando uma solicitação ao lugar errado.

Tenho tentado (mas, até agora, falhado) escrever um decorador, e então me deparei com o plunkr de @robwormald , que funciona _quase_ perfeitamente (mas não muito).

O que me deixou animado com essa abordagem foi o fato de que ela está se aproveitando do gancho do ciclo de vida ngOnChanges .
O que eu gostaria de ver é uma maneira de _todos_ os ganchos de ciclo de vida serem expostos como Observáveis, ou seja, onChanges$: Observable<{[key: string]: SimpleChange}> , onInit$: Observable<{}> , etc.

Ter todos os ganchos do ciclo de vida disponíveis como Observáveis ​​me ajudará a Rx todas as coisas ;-)

Alguma atualização sobre isso?

AFAIK, No.
@robwormald você tem novidades?

Eu sei que isso é antigo, mas isso seria ótimo! @robwormald Alguma palavra sobre isso?

Eu adoraria fornecer valores variáveis ​​de @Input property como um Observable, assim como o @ObserveInput decorator de @robwormald faz. Nem sempre é viável fazer com que os componentes pais sejam aprovados nos Observáveis, especialmente quando você está migrando um aplicativo existente (Angular 1). Não ser capaz de "conter" Observáveis ​​dentro de um único componente torna o aproveitamento do poder e elegância do RxJS muito mais difícil.

Infelizmente, Rob não estava brincando quando disse para não usar esta versão de @ObserveInput . O problema que estou encontrando é que os Observáveis ​​são criados em um "nível de classe" (se essa terminologia fizer sentido) e, portanto, são compartilhados por todas as instâncias do componente. Isso não é bom, obviamente. Criar os Observáveis ​​no momento da instanciação também não funcionou para mim. Parece que o Angular não conecta corretamente a detecção de alterações nesse caso.

Alguém conseguiu uma implementação melhor de @ObserveInput ou há alguma notícia sobre o suporte oficial?

@lephyrus Embora um decorador @ObserveInput oficial seja muito bom, não é estritamente necessário para obter um Observável de alteração dos valores de @Input propriedade. O decorador seria simplesmente um "açúcar" muito elegante.

Já existe um evento de ciclo de vida ngOnChanges . Dentro de ngOnChanges podemos usar um rxjs Subject , para criar um observável de mudanças, ou seja:

<strong i="13">@Input</strong> inputString: string;
private Subject<string> inputString$ = new Subject<string>;

ngOnChanges(changes: { [key: string]: SimpleChange }) {
    if (changes.hasOwnProperty('inputString')) {
        this.inputString$.next(changes['inputString'].currentValue);
    }
}

constructor() {
    inputString$.subscribe(x => {
        console.log('inputString is now', x);
    });
}

Ou, se quiser algo mais reutilizável, você pode criar uma classe base que seu componente extends :

import { SimpleChange } from '@angular/core';
import { Observable, ConnectableObservable, Observer } from 'rxjs';

export interface TypedSimpleChange<T> {
    previousValue: T;
    currentValue: T;
}

export class ReactiveComponent {
    private changesObserver: Observer<{ [key: string]: SimpleChange }>;
    private changes$: ConnectableObservable<{ [key: string]: SimpleChange }>;

    constructor() {
        this.changes$ = Observable.create((observer: Observer<{ [key: string]: SimpleChange }>) => this.changesObserver = observer).publishReplay(1);
        this.changes$.connect();
    }

    public observeProperty<T>(propertyName: string): Observable<TypedSimpleChange<T>> {
        return this.changes$
            .filter(changes => changes.hasOwnProperty(propertyName))
            .map(changes => changes[propertyName]);
    }

    public observePropertyCurrentValue<T>(propertyName: string): Observable<T> {
        return this.observeProperty<T>(propertyName)
            .map(change => change.currentValue);
    }

    ngOnChanges(changes: { [key: string]: SimpleChange }) {
        this.changesObserver.next(changes);
    }
}

... que pode ser usado da seguinte forma:

@Component({
    ...
})
export class YourComponent extends ReactiveComponent {
    @Input() inputString: string;

    constructor() {
        super();
        this.observePropertyCurrentValue<string>('inputString')
            .subscribe(x => console.log('inputString is now', x));
    }
}

Estou usando essa abordagem até que um decorador oficial @ObserveInput esteja disponível.

Obrigado, @wmaurer. Seu ReactiveComponent é muito bem-vindo, e usando um código impecável e uma digitação segura para iniciar - muito bom! É importante ressaltar que ele também se comporta bem durante o teste e ainda permite o uso da estratégia de detecção de alterações OnPush . Agora estou feliz em esperar pelo "açúcar oficial". (Também tenho certeza de que vou aprender algo quando descobrir por que você teve que usar a lógica Observable.create() - ainda não encontrei tempo para investigar isso.) Mais uma vez: merci gäll! :piscar:

@lephyrus de nada, gärn gescheh ;-)

Usei Observable.create() para conseguir Observer para poder fazer next() . Eu poderia ter usado Subject que é Observable e Observer , mas acredito que geralmente é uma prática ruim 'expor' Subject ( Observer ).

@ laco0416 fecha em favor de https://github.com/angular/angular/issues/13248 ?

@DzmitryShylovich Não. O recurso proposto nesta edição é a passagem de dados somente leitura e baseada em eventos.

@ObserveInput vai ser super legal! : +1:

Obrigado @wmaurer por um bom exemplo. Ainda tenho uma dúvida. Eu gostaria de poder usar um objeto em vez de uma string como o observável.

Por exemplo

@Input() chartConfig: ChartConfig;

constructor(private _reportService: ReportService) {
        super();
             this.observePropertyCurrentValue<string>('chartConfig')
            .subscribe(changedConfig => this.updateChart(changedConfig));
 }

export class ChartConfig {
    public id: string;
    public type: any;
    public data: any;
    public labels: any;
}

No entanto, this.updateChart e ngOnChanges não são chamados. Como pode expandir sua amostra de uma string simples para observar um objeto em vez disso?

@ChrisWorks também deve funcionar com objetos:

this.observePropertyCurrentValue<ChartConfig>('chartConfig')
            .subscribe(changedConfig => console.log(changedConfig));

Faço isso com frequência, então, se não funcionar, acho que há um problema com a entrada de seu componente em algum lugar.

Olá @wmaurer , obrigado pela resposta. Você seria capaz de expandir sua amostra com uma versão de trabalho em que usa um objeto "config"? Eu simplesmente não consigo fazer o meu funcionar. Um exemplo de repositório Git? :)

@ChrisWorks realmente deve funcionar do jeito que está. observePropertyCurrentValue não diferencia entre uma entrada de string e um objeto.
Aqui está um projeto ng2 beta 0 muito antigo que fiz, em que as entradas são de todos os tipos diferentes, não apenas strings:
https://github.com/wmaurer/todomvc-ng2-reactive
por exemplo, https://github.com/wmaurer/todomvc-ng2-reactive/blob/master/src/app/todo-item/todo-item.component.ts

+1 Um caso de uso tão fundamental, não acredito que ainda não foi resolvido!

Finalmente encontrei a melhor solução e funciona sem repetição e com compilação AOT :) O segredo é combinar @Input() com um segundo decorador.

O decorador:

import { ReplaySubject } from 'rxjs/ReplaySubject'                                                                                                 

const subjects = new WeakMap()                                                                                                                     

export function ObservableInput() {                                                                                                
  return (target, propertyKey) => {                                                                                                                
    delete target[propertyKey]                                                                                                                     

    Object.defineProperty(target, propertyKey, {                                                                                                   
      set(value) {                                                                                                                                 
        this[propertyKey].next(value)                                                                                                              
      },                                                                                                                                                                                                                            
      get() {                                                                                                                                      
        let subject = subjects.get(this)                                                                                                           
        if (! subject)  {                                                                                                                          
          subject = new ReplaySubject<any>(1)                                                                                                      
          subjects.set(this, subject)                                                                                                              
        }                                                                                                                                          
        return subject                                                                                                                             
      },                                                                                                                                           
    })                                                                                                                                             
  }                                                                                                                                                
}                                                                                                                                                  

Uso:

class SomeComponent {
  @Input() @ObservableInput()                                                                                                                    
  public index: Observable<number>
}                                                                                                               

EDIT: Agora fiz disso uma biblioteca. Fico feliz em receber ideias para melhorá-lo. Adicionando um novo método que complementa um @Input com um Observable next. Veja https://github.com/ohjames/observable-input

Se eu tivesse que fazer algo assim, preferiria um único Assunto análogo a ngOnChanges: Subject<SimpleChanges>
Você ainda pode filtrar e mapear para apenas uma entrada específica, se quiser.

1 Assunto por entrada parece muito sem falar sobre deletar uma propriedade de classe para criar getter & setter e que o tipo de sua entrada está errado e fazer input = value irá de fato emitir o valor no observável e você não completando seu assunto.

Você pode encontrar um exemplo de implementação da minha ideia aqui . A abordagem do decorador é experimental, mas acho que deve ser bastante seguro usar a abordagem de herança (lembre-se de que acabei de fazer isso para mostrar aqui, ela não é usada).

@ghetolay não importa se o assunto está concluído, quando a view é fechada não há mais inscrições no stream e ele pode ser descartado pelo GC. Emitir o valor no observável é basicamente o objetivo disso. O compilador angular atualmente não verifica os tipos de propriedade de entrada, esperançosamente quando ele fizer algo mais oficial estará disponível;)

changes$ vaza a interface BehaviorSubject para o componente, idealmente deve ser digitado como Observable<...> , exceto pelo que parece razoável, embora um pouco mais prolixo.

Quanto à questão de muitos assuntos ... quer você esteja usando n assuntos ou 1 você ainda tem n assinaturas. Quando você tem 1 assunto, acaba adicionando um operador de mapa e um operador de filtro para cada assinatura, a sobrecarga de desempenho disso não seria muito menor do que apenas usar n assuntos, poderia ser ainda pior.

Ignorando a digitação dos próprios parâmetros de entrada, a versão baseada no decorador fornece uma API mais segura de tipos aos consumidores dos observáveis ​​do que os SimpleChange tipos baseados em any .

@ohjames

quando a visualização é fechada, não há mais assinaturas para o fluxo e ele pode ser descartado pelo GC

Você está assumindo que todas as assinaturas serão vinculadas à visualização e terão o mesmo ciclo de vida da visualização. Não estou fazendo nenhuma suposição sobre como o Observable será consumido.

Emitir o valor no observável é basicamente o objetivo disso. O compilador angular atualmente não verifica os tipos de propriedade de entrada, esperançosamente quando ele fizer algo mais oficial estará disponível;)

Eu quis dizer para o usuário, o usuário ainda pode acessar e definir a propriedade. Portanto, ele definirá a propriedade como se fosse um número, exceto se for digitado como um Observable e isso nunca definirá a propriedade, mas fará com que seja emitida. Isso é muito confuso para mim.

Quanto ao assunto de muitos assuntos ... se você está usando n assuntos ou 1 você ainda tem n assinaturas. Quando você tem 1 assunto, acaba adicionando um operador de mapa e um operador de filtro a cada assinatura, o overhead de desempenho disso não seria muito menor do que apenas usar n assuntos, poderia ser ainda pior.

Não entrarei nesse tipo de debate aqui porque logo perderemos o foco sobre o propósito principal.

Eu atualizei para não expor o Subject e construí algo para adicionar digitação às mudanças.
Não estou usando um BehaviorSubject mas um simples Subject porque não estou vendo aqueles Observáveis ​​como detentores de valor, mas como mudanças ao longo do tempo.
Acabei de encontrar uma falha: ao usar o pipe assíncrono, o primeiro valor não emite porque o pipe assíncrono será assinado após a primeira emissão. Vou atualizar meu código mais tarde para lidar adequadamente com esse caso.

Aqui está o link novamente: https://stackblitz.com/edit/angular-observableinput?file=observablechanges%2Fimpl.ts

Você está assumindo que todas as assinaturas serão vinculadas à visualização e terão o mesmo ciclo de vida da visualização. Não estou fazendo nenhuma suposição sobre como o Observable será consumido.

Ok, entendo seu ponto, completar os observáveis ​​forçará o fechamento de todas as assinaturas, mas não é algo que realmente me preocupa. Em nosso aplicativo, 99% das assinaturas acontecem por meio do pipe assíncrono e nos poucos que não precisam ser fechados manualmente, já que costumam se originar de observáveis ​​externos fornecidos por Redux / etc por meio de combineLatest / switchMap.

Não entrarei nesse tipo de debate aqui porque logo perderemos o foco sobre o propósito principal.

Bem, você levantou uma crítica inválida, então foi necessário contra-atacar.

~ Também usar sua classe por meio de herança não funcionará, o código gerado pelo compilador AOT não chama métodos de ciclo de vida em classes pai, consulte https://github.com/angular/angular/issues/12756#issuecomment -260804139, você precisarei adicionar ngOnDestroy() { super.ngOnDestroy() } e outro por ngOnChanges para todos os consumidores da classe. ~ <- parece ter sido corrigido no Angular 4.4 com certas limitações.

Revisei minha preocupação sobre o uso do pipe assíncrono e, como você ainda tem acesso à entrada, não acho que o uso estava fazendo sentido. Se você deseja apenas vincular o valor de entrada no modelo, pode vincular diretamente à entrada, sem a necessidade de usar qualquer observável e assíncrono aqui. E se você deseja compor um novo observável a partir dele, você deve ser capaz de obter exatamente o que deseja facilmente (provavelmente usando startWith algum lugar).

Então, acho que minha declaração anterior está ok:

Não estou usando um BehaviorSubject, mas um Assunto simples, porque não estou vendo aqueles Observable como portadores de valor, mas como mudanças ao longo do tempo. Se você precisar acessar um valor de entrada, a propriedade ainda existirá e você poderá fazer referência a ela de forma síncrona.

Este código é altamente experimental, é apenas algum tipo de código e eu só brinquei com ele naquele stackblitz.
Portanto, não deveria ter dito que era quase seguro e pode não funcionar com AOT, como você afirmou.
Apenas tentei e ambas as abordagens funcionam atualmente com AOT (angular v4.3.6, cli v1.4.1).

Meu ponto principal aqui é que ir de um observável changes contendo todas as mudanças de entrada dá a você a possibilidade de fazer qualquer coisa que você precisa usando operadores: reaja quando qualquer entrada muda, quando apenas 1 entrada muda, quando um número arbitrário de entrada mudou, quando mudou de um valor específico para outro etc ...
Você pode compor o observável que desejar.

Além disso, há apenas 1 assunto para gerenciar para a implementação e 1 propriedade para usar para o usuário versus 1 por entrada.

O que eu poderia mudar seria apenas emitir novos valores e não SimpleChanges object, pois o usuário provavelmente pode usar pair e map para obter o mesmo tipo de estrutura de SimpleChange . Mas atualmente o que temos é SimpleChanges então seria bobagem decompor apenas para recompor algo semelhante mais tarde.

PS:

Bem, você levantou uma crítica inválida, então foi necessário contra-atacar.

inválido para você .

Não é um ponto de vista pessoal quando nos referimos ao desempenho, são fatos mensuráveis ​​objetivos. Você sente que 3 assuntos equivalem a mais sobrecarga do que 6 operadores adicionais, talvez você esteja certo, mas não há base racional para acreditar nisso até que você faça algumas medições.

Quanto a reagir a muitas entradas ... claro, você pode apenas usar switchMap ou combineLatest ou qualquer um dos outros operadores projetados para trabalhar com vários observáveis. Você está fazendo afirmações muito ousadas e abrangentes sobre isso, se realmente deseja continuar esta conversa, vamos retirá-la deste rastreador de problemas.

Esta é uma versão com diferentes desvantagens e vantagens, mas requer uma classe base:

import { OnChanges, OnDestroy, SimpleChanges } from '@angular/core'
import { BehaviorSubject } from 'rxjs/BehaviorSubject'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/distinctUntilChanged'

export class ChangeObserver implements OnChanges, OnDestroy {
  protected ngChanges = new BehaviorSubject<object>({})

  ngOnChanges(changes: SimpleChanges) {
    const props = { ...this.ngChanges.value }
    for (const propName in changes) {
      props[propName] = changes[propName].currentValue
    }
    this.ngChanges.next(props)
  }

  ngOnDestroy() {
    this.ngChanges.complete()
  }

  changes<K extends keyof this>(key: K): Observable<this[K]>
  changes<V>(key: string): Observable<V>
  changes(key: string) {
    return this.ngChanges.map(props => this.ngChanges.value[key]).distinctUntilChanged()
  }
}

e usar:

class MyComponent extends ChangeObserver {
  @Input() public field: string
  // typescript knows this is Observable<string>
  field$ = this.changes('field')

  @Input() private privField: number
  // typescript can't access private stuff from outside the class so we need to help it out
  privField$ = this.changes<number>('privField')
}

Não tenho certeza se entendi mal o problema, mas o pipe assíncrono não resolve o problema?
[inputVar]="observableValue | async" ..

@najibla, você entendeu mal, as entradas não são observáveis, então o pipe assíncrono não ajuda.

@ohjames bem no meu caso, funcionou usando o pipe assíncrono para um observável. E quando eu atualizo o valor observável do pai, o valor é refletido no componente filho.

@najibla, este problema do github é sobre transformar @Input propriedades em observáveis, isso está tenuamente relacionado ao caso que você está descrevendo.

ou seja, você não deve precisar converter propriedades de entrada em um observável na visualização, se um componente responde à alteração da propriedade de entrada usando um observável ou não deve ser isolado em um componente em si. Caso contrário, você terá que alterar a interface do seu componente se decidir consumir uma propriedade como um observável que está em desacordo com a abordagem usual de "primeiro observável" do angular.

@icepeng Na verdade, você não entendeu bem, trata-se de transformar uma entrada padrão em um observável. Portanto, o novo padrão seria realmente:

<app-child [prop]="prop$ | async"></app-child>

... então ele fornece uma maneira de acessar prop dentro de AppChildComponent como um observável mesmo que não seja um observável (ou se fosse um observável você obteria um observável de observáveis ) Agora, é claro, se você tivesse prop$ em primeiro lugar como em seu exemplo, então pode não parecer sensato (você já pode simplesmente passar o observável com entradas agora). No entanto, ele fornece muito valor quando a entrada passada ainda não é um observável (por exemplo, um index de um ngFor ).

O problema em transmitir um observável com uma entrada (o que é totalmente possível agora) é que não funcionaria muito bem com OnPush .

@fxck Bem, você está certo que ngOnChanges não será chamado quando o observável emite (porque a entrada permanece a mesma), mas contanto que você use o pipe assíncrono para se inscrever em um observável passado por @Input then OnPush funcionará bem porque o pipe assíncrono aciona manualmente o detector de alterações do componente filho.

Sim, foi direcionado para @icepeng.

@ohjames @fxck Eu não sabia que era possível transmitir observáveis ​​agora, desculpe pela informação errada.

Uma vez que a passagem direta observável é possível, não tenho certeza se @ObserveInput seria útil.
Acho que se o valor que pode ser alterado não for observável, já é um problema.
Apenas uma opinião pessoal.

@icepeng Considere o seguinte:

<ng-container *ngFor="value in (values$ | async); let i = index">
  <child-component [value]="value" [index]="i"></child-component>
</ng-container>

Como fazer index consumível como um observável aqui sem pular aros?

Ou considere também que você está escrevendo uma biblioteca de terceiros e não quer forçar os consumidores de sua biblioteca a passar observáveis ​​para seu componente (muitos usuários não se sentem confortáveis ​​com streams e evitam rxjs).

Existem muitos outros casos em que isso é útil.

@ohjames index seria um valor estático para cada child-component , então não acho que deva ser observável. No entanto, agora eu entendo porque as pessoas querem esse recurso. E sua solução parece boa para escrever bibliotecas de terceiros.

BehaviorSubject abordagem @NgChanges() como esse.

{
  @NgChanges() ngChanges$: BehaviorSubject<{ prop: string }>
  @Input() prop: string;

  ngOnInit() {
    this.ngChanges$.subscribe(changes => console.log(changes.prop));
  }
}

Isso é possível implementar?

Um ReplayObservable com tamanho de buffer de 1 seria preferível, não há necessidade da semântica de valor de BehaviourSubject quando você também tem acesso à propriedade.

Para saber se é possível implementar, verifique o histórico de problemas para muitas implementações e opções diferentes. Nenhum é ótimo e alguns estão completamente quebrados, para fazê-lo corretamente, precisamos do PR vinculado ou algo semelhante.

meus 2 centavos: como object.observe foi depreciado em favor dos proxies, minha solução foi estender um proxy (sim, você não pode saber, por que retornei do construtor) e então escutar qualquer chave pertencente a este objeto como um observável por meio do método $ get (keyName). Você pode usá-lo com qualquer objeto, não apenas a classe angular um.

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';

/**
 * Extends this class to be able to observe any key affectation
 */
class ObservableProxy {
    private changes: Subject<[PropertyKey, any]> = new Subject<[PropertyKey, any]>();

    constructor() {
        return new Proxy(this, {
            set: (object, key, value) => {
                this.changes.next([key, value]);
                object[key] = value;
                return true;
            }
        });
    }

    public $get<K extends keyof this>(key: K): Observable<this[K]> {
        const value: this[K] = this[key];

        const startWith: Observable<this[K]> = Observable.of(value)
            .filter((initialValue) => // remove this filter if you dont mind receiving an undefined value on subscription
                initialValue !== undefined;
            );

        return this.changes
            .filter(([changedKey]) => {
                return changedKey === key;
            })
            .map(([changedKey, nextValue]) => nextValue)
            .merge(startWith);
    }
}

Exemplo:

class User extends ObservableProxy {
public name: string;
}

const user: User = new User();
user.$get("name").subscribe((value)=> {
console.log(value);
})

user.name = "toto"; // prints console.log("toto")

@robwormald , há alguma tração sobre esse assunto? Certamente seria um longo caminho para tornar as coisas mais fáceis de usar.

@bryanrideshark A melhor chance de isso ser suportado é através de https://github.com/angular/angular/issues/10185 Eu acho, surpreso que o PR não esteja ligado a este problema.

Isso não vai acontecer até depois de enviarmos Ivy
Na quarta-feira, 21 de fevereiro de 2018 às 7h56, James Pike [email protected] escreveu:

@bryanrideshark https://github.com/bryanrideshark A melhor chance de
isso sendo suportado é por meio de # 10185
https://github.com/angular/angular/issues/10185 Eu acho, surpreso que
PR não está ligado a este problema.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/angular/angular/issues/5689#issuecomment-367372931 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAgpkvtGA4w8uHgiz0QsdYwBgqgM2EpAks5tXDyWgaJpZM4Gwr8f
.

Seu inglês é ótimo e seu angular é melhor.

@ pldin601 Seu método não suporta código compilado AOT, então levará a enormes vazamentos de memória para o código de produção da maioria das pessoas. Verifique meu pacote observable-input , que funciona bem com AOT.

@ pldin601 o problema com seu código é que ele adiciona dinamicamente o método ngOnDestroy lifecycle. O código compilado AOT apenas chama ngOnDestroy se o compilador puder inferir estaticamente sua existência. Portanto, seu método só funcionará se cada componente que usa o decorador também definir um ngOnDestory (ele pode estar vazio se não for necessário). Em todos os outros casos, ele vazará assinaturas, evitando que os componentes sejam coletados como lixo.

Acho que isso acontecerá depois que a seção 3.2 for concluída (e espero que essa necessidade seja levada em consideração).
https://is-angular-ivy-ready.firebaseapp.com/#/status

Ivy fará parte do 7.0.0?

Implementei um decorador que pode vincular uma propriedade a uma propriedade complementar observável. Pode ser usado em propriedades de entrada angulares, mas também em qualquer outra propriedade. Ele é inspirado no decorador @ObservableInput() de @ohjames , mas evita possíveis problemas causados ​​por incompatibilidades de tipo entre getters e setters de propriedade usando uma propriedade diferente para fornecer o Observable.

https://github.com/PSanetra/bind-observable

Exemplo:

class MyClass {

  @BindObservable()
  public myProp: string = 'initialValue';
  public myProp$!: Observable<string>;

}

const myInstance = new MyClass();

myInstance.myProp$.subscribe(console.log);

myInstance.myProp = 'newValue'

Este código imprime os valores 'initialValue' e 'newValue' no console.

Qualquer atualização sobre quando isso será lançado até agora, o pacote observable-input parece o mais elegante sem quebrar nada + sem subclasses. @ohjames Quão estável / bem testado é isso neste ponto?

Acho que o suporte oficial seria mais maduro, garantirá suporte contínuo em todas as versões e uma experiência de desenvolvimento mais suave / incentivará os desenvolvedores a usar o rxjs * completo e otimizar com detecção de alterações no push (este deve ser um recurso inovador central, na minha opinião)

* sem comprometer a compatibilidade com entradas não observáveis ​​ou a necessidade de clichê extra para entradas híbridas, ou seja: setter chamando a seguir no assunto

Entrada observável usada em angular 4, 5 e 7 com e sem aot. Isso parece funcionar bem. Tenho quase certeza de que sabia vagamente o que estava fazendo quando o escrevi.

espero que eles o adicionem no framework e encorajem seu uso 2 downloads semanais npm não é suficiente em comparação com as vantagens que oferece xD se eles o adicionarem, aposto que as pessoas verão que é simples seguir essa abordagem

Proponho a seguinte API para entrada assíncrona com observer definido pelo usuário

decalre function AsyncInput(bindName?:string) : <T extends Observer>(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => any

os usuários podem apenas usá-lo como

@AsyncInput()
asynchronusProperty1 = new Subject();

@AsyncInput()
asynchronusProperty2 = new ReplySubject(1);

@AsyncInput()
asynchronusProperty3 = new UserObserver(); // where UserObserver implement `Observer` interface

Angular interno pode simplesmente chamar observer.next(newValue) sempre que o valor mudar.

Não se esqueça do suporte de @HostBinding também!

Isso deve ser combinado com a injeção do construtor @Input para torná-lo ainda mais incrível. Dessa forma, poderíamos corrigir "strictPropertyInitialization" de uma forma muito elegante:

class MyComponent {
  inputData$: Observable<Data>;
  constant: string;

  constructor(
    @Input() inputData$: Observable<Data>,
    @Input() constantString: string
  ) {
    this.inputData$ = inputData$;
    this.constant = constantString;
  }

}

@benneq, vale a pena notar que pode ser tão pequeno quanto:

class MyComponent {
  constructor(
    @Input() private inputData$: Observable<Data>,
    @Input() private constantString: string,
  ) {}
}

@ maxime1992 sim, embora você precise de um decorador separado para entradas como observáveis ​​para distinguir entre o caso de observáveis ​​sendo passados ​​de um componente para outro.

@benneq @ maxime1992 Os nomes dos parâmetros não fazem parte dos metadados do decorador. Como você os

@trotyl , acho que você poderia usar @Input('paramName') private myParam: Observable<Data>

@trotyl @benneq sim, mal. Não importa: zipper_mouth_face:

Não sei se alguém já postou esta solução, mas esta é uma solução bastante boa

@ElianCordoba Não se trata de HTML <input> . É sobre angular @Input()

Esse é um problema diferente, mas igualmente doloroso, @ElianCordoba. https://github.com/angular/angular/issues/13248

Acho que é um pseudo-requisito. Em primeiro lugar, o decorador de entrada pode aceitar um valor do tipo Observável; em segundo lugar, se o componente exigir um valor observável como entrada, ele deve lançar explicitamente um erro ao receber um valor não observável em vez de convertê-lo silenciosamente em um valor do tipo Observável.

É bastante semelhante à conversão implícita de tipo em javascript, pode ser confundido até mesmo causar bug e difícil de encontrar.

Não se trata de silenciar a entrada errada. Trata-se de expor uma interface comum entre componentes (que é uma entrada não observável). Isso lhe dá a liberdade de introduzir a funcionalidade rx sem quebrar sua interface com o mundo externo. Outro benefício é principalmente o levantamento da mutabilidade e do estado interno, uma vez que tudo será uma transformação da entrada, o que torna a detecção de mudança push a partir desse ponto um acéfalo.

Pelo contrário, expor uma interface que requer observáveis ​​soa desajeitada e forçando as pessoas a fornecer (valor) só porque seu componente disse soa estranho para mim.

Além disso, não se trata de conversão silenciosa, é uma API para assinar as alterações via rxjs. Dado que angular.http e o roteador fornecem observáveis, é muito estranho que o tratamento de alterações de entrada não forneça, unificar os mundos de retornos de chamada e RxJs requer muito boiler plate.

Não que eu não ame algumas das soluções inteligentes acima, mas às vezes apenas uma pequena reorganização da maneira 'padrão' de fazer isso é suficiente para resolver o problema real aqui que é a falta de clareza / falta de jeito. Além disso, ainda estou esperando por uma maneira 'oficial' pós-hera de fazer isso algum dia.

@Input('isOuterPanel')
set isOuterPanel(value: CheckoutPanelHeaderSettings)
{
    this.inputs.outerPanel$.next(value);
}

@Input('config')
set config(value: CheckoutPanelHeaderSettings)
{
    this.inputs.config$.next(value);
}

// observables for <strong i="6">@Inputs</strong>
inputs = {
    config$: new BehaviorSubject<CheckoutPanelHeaderSettings>(1),
    outerPanel$: new BehaviorSubject<CheckoutPanelHeaderSettings>(1)
};

Então você pode simplesmente colocar algo como combineLatest(this.service.magicInput$, this.inputs, config$)...... para combinar entradas com RxJS.

Tanto BehaviorSubject quanto ReplaySubject funcionam. BehaviorSubject é geralmente mais seguro e previsível.

  • BehaviorSubject - use se você tiver um padrão
  • ReplaySubject - observável não emitirá se você se esquecer de definir o valor de entrada

Isso não apenas ajuda com a clareza do código, mas como estou agrupando todas as entradas, posso ver facilmente o que é uma entrada observável 'pura' digitando apenas this.inputs. e completando automaticamente.


Você pode ir mais longe com a segurança de tipo com isso (na maioria das vezes, é apenas para diversão).

// define this globally to 'unwrap' a property 'inputs' with an object of ReplaySubject / BehaviorSubject
export type InputTypes<T extends { inputs: { [key: string]: ReplaySubject<any> | BehaviorSubject<any> } }> = {
    [P in keyof T['inputs']]: T['inputs'][P] extends Observable<infer X> ? X : unknown;
}
// define a local 'InputType' helper above each component
type InputType = InputTypes<CheckoutSmartHeaderComponent>;

Então você não precisa especificar explicitamente o tipo da propriedade @Input .

@Input('config')
set config(value: InputType['config$'])
{
    this.inputs.config$.next(value);
}

Eu poderia ter feito uma interface ObservableInputs para o componente para aplicar inputs mas decidi não fazê-lo, uma vez que não vai compilar de qualquer maneira se você estragar tudo.

@simeyla Muito clichê.

Decidi colocar meu próprio decorador lá fora. É meu primeiro pacote npm, então tenho certeza de que está faltando algo, mas aqui está: https://www.npmjs.com/package/@ng-reactive/async -input

Instalação

npm install @ng-reactive/async-input --save

Uso

import { Component, Input } from '@angular/core';
import { AsyncInput } from '@ng-reactive/async-input';
import { BehaviorSubject } from 'rxjs';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})
export class HelloComponent {
  @Input() name: string;
  @AsyncInput() name$ = new BehaviorSubject('Default Name');

  constructor() {
    this.name$.subscribe(name => console.log('from async input', name));
  }
}

@ mfp22 realmente bom, não vejo razão para que algo assim não deva ser integrado. Para sua informação, o link do github no npm para o pacote está desatualizado, vai para aqui:

https://github.com/mfp22/async-input/tree/master/projects/async-input

Vejo que este é um problema antigo, mas o recurso é incrível e estou realmente empolgado para vê-lo no Angular.

O que é ainda mais legal é que, com entradas observáveis, você não precisa de OnChanges hook - você pode usar o operador pairwise rxjs para obter os valores anteriores e atuais da entrada observável:

@Component({...})
class MyReactiveComponent {
  @ObservableInput() prop: Observable<string>; // Whatever syntax may be...

  // emits [prevValue, currValue] and no OnChanges hook yay!!
  propChanges$ = this.prop.pipe(pairwise());
}

https://github.com/rx-ts/ngrx/blob/master/src/utils/decorators.ts#L56 -L124

É minha implementação pessoal, funciona perfeitamente e muito bem, se pudermos integrá-la, isso é muito legal.

Publiquei a solução que usei pessoalmente em meus projetos como um pacote npm:

https://github.com/futhark/ngx-observable-input

Instalação

npm install ngx-observable-input

Uso

...
<image-item [url]="currentImageUrl"></image-item>
import { Component, Input } from "@angular/core";
import { ObservableInput } from "ngx-observable-input";
import { Observable } from "rxjs";

@Component({
    selector: "image-item",
    template: `<img [src]="url$ | async" />`
})
export class GalleryComponent {
    @ObservableInput() @Input("url") public url$: Observable<string>;

    ...
}

Vejo que este é um problema antigo, mas o recurso é incrível e estou realmente empolgado para vê-lo no Angular.

O que é ainda mais legal é que, com entradas observáveis, você não precisa de OnChanges hook - você pode usar o operador pairwise rxjs para obter os valores anteriores e atuais da entrada observável:

@Component({...})
class MyReactiveComponent {
  @ObservableInput() prop: Observable<string>; // Whatever syntax may be...

  // emits [prevValue, currValue] and no OnChanges hook yay!!
  propChanges$ = this.prop.pipe(pairwise());
}

Esse é um problema que também me incomoda há algum tempo, então investiguei um pouco e descobri a mesma solução que você mencionou aqui (@gund).

Implementei um pequeno auxiliar de decorador ( @ObservableInput ) que estende internamente o decorador @Input e permite esse tipo de composição. Você pode conferir aqui (também como pacote npm ). Tenho testado com exemplos simples, mas não tive tempo de usá-lo para projetos estendidos. Qualquer feedback é bem-vindo :)

Aqui está um exemplo de uso:

@Component({
  selector: 'app-test',
  template: `<span>{{ message$ | async }}</span>`
})
class TestComponent {
  @ObservableInput<number>('price') price$: Observable<number>;
  @ObservableInput<string>('name') name$: Observable<string>;

  message$ = combineLatest(this.price$, this.name$).pipe(
    map(([price, name]) => `${name} costs {price}`)
  );
}

E este componente pode ser usado assim:

<app-test [price]="price"
          [name]="name">
</app-test>

@aleics Tem certeza de que funcionará com o compilador AOT?

@aleics Tem certeza de que funcionará com o compilador AOT?

Não, não fora da caixa, é claro, uma vez que a propriedade @Input não é definida explicitamente. Mas você pode definir seu módulo com CUSTOM_ELEMENTS_SCHEMA , como ao usar Web Components, e então ele deve compilar.

Esta edição está aberta há 5 anos 🤦‍♂. Eu acho que é necessário ter isso para facilitar uma abordagem de programação reativa

Ivy está finalmente aqui, então acho que alguém tem tempo para trabalhar nisso? Por favor por favor por favor!

Meu Deus, eu concordo que isso é muito necessário. Os dados de entrada devem ser emitidos como uma fonte reativa, e você deve usar uma solução alternativa para que isso funcione em um ngOnChanges

Estou realmente ansioso por isso

Sei que o Angular é de código aberto, mas é errado ou ingênuo de minha parte pensar que um projeto executado pelo Google deve ser capaz de resolver problemas com muitas estrelas e solicitados em menos de 5 anos? Existem alguns problemas financeiros dos quais não estou ciente que estão impedindo o Google de contratar mais desenvolvedores para trabalhar no Angular?

@ etay2000 Não sei, acho meio engraçado como você pode fazer 75% das coisas em Angular com rxjs e então para algo tão fundamental e comumente usado como entradas de componentes, de repente você é forçado a seguir os procedimentos mundo. Você sempre tem essa parte significativa de seu projeto que não pode funcionar com o mundo dos observáveis, então é sempre forçado a usar clichês para colá-los novamente. Você nunca pode alcançar uma abordagem totalmente funcional para seu projeto. Então, essa questão estando aberta há 4 anos me lembra da falta de pragmatismo que me afastou do Angular e me direcionou para outros frameworks, por isso é muito útil. Se você quiser usar rxjs para tudo, talvez cycle.js seja a melhor solução, e se você não quiser ser forçado a usar rxjs, talvez Vue seja. Ter que usar observáveis ​​metade do tempo e não ser capaz de usá-los na outra metade simplesmente não faz sentido. Integrar uma abstração muito poderosa com uma alta curva de aprendizado e depois deixar de integrá-la totalmente é simplesmente desconcertante.

Eu sinto que a cadeia de priorização é assim

requisitos de usuários internos do Google -> qualquer coisa a ver com o compilador ser mais rápido -> qualquer coisa a ver com o tamanho do aplicativo ser menor -> qualquer coisa que não incomode os recém-chegados -> o que os desenvolvedores realmente querem

E embora os líderes de tecnologia digam que concordam que isso é importante, o fato é que Igor e Misko tendem a pensar sobre a maioria dos problemas em termos de compilação de modelos, vinculações de visualização e renderização. Não importa o que os consumidores do Angular estejam dizendo que é importante para eles, a solução é sempre mudar o sistema de modelos, ou escrever um novo renderizador, ou tornar o compilador melhor.

fonte: https://medium.com/@jeffbcross/jeffs-letter-to-the-angular-team-and-community-5367934a16c9)

Basta olhar para algumas das questões mais votadas.

Proposta: entrada como observável # 5689

Criado 5 anos atrás, última resposta de um membro relevante da equipe - 5 anos atrás, status atual desconhecido.

Suporte a streams de evento frio nos modelos # 13248

Criado há 4 anos, última resposta de um membro relevante da equipe - 1 ano atrás, status atual - esperando Ivy pousar.

Proposta: Fornecer Ganchos de Ciclo de Vida de Componentes como Observáveis ​​# 10185

Criado 4 anos atrás, última resposta de um membro relevante da equipe - 3 anos atrás, status atual desconhecido.

Proposta: Capacidade necessária para adicionar diretivas aos elementos do host na declaração do componente. # 8785

Criado 4 anos atrás, última resposta de um membro relevante da equipe - 2 anos atrás, status atual desconhecido.

as local-val sem * ngIf # 15280

Criado 4 anos atrás, última resposta de um membro relevante da equipe - 2 anos atrás, status atual desconhecido.

Suporte para Projeção de Conteúdo Dinâmico # 8563

Criado 4 anos atrás, última resposta de um membro relevante da equipe - 3 anos atrás, status atual desconhecido.

conteúdo padrão ng-content # 12530

Criado 4 anos atrás, última resposta de um membro relevante da equipe - nunca, status atual desconhecido.

Depois, há coisas como recursos de componentes, que permitiriam componentes de ordem superior, então, é claro, há muitos outros problemas mais votados, mas esses eu esperava que fossem resolvidos (isto é, resolvidos, não necessariamente esperando que eles sejam implementados imediatamente após Ivy ), porque eles têm a ver com o uso diário da estrutura AND reatividade, que, eu acho que ninguém diria, é o que a maioria das pessoas está usando (dado que ngrx é a biblioteca de gerenciamento de estado mais popular).

Meu aplicativo está cheio de "polyfills" e hacks pela metade (simplesmente devido a limitações técnicas, alguns só podem ser feitos corretamente no núcleo) para a maioria desses problemas, na esperança de um dia ser capaz de inserir um nativo angular substituição. Mas de alguma forma isso não parece estar chegando.

Então, sim, digamos que uma postagem de blog abordando o estado de alguns dos maiores problemas relacionados à experiência de reatividade / desenvolvedores seria realmente apreciada.

Que tal tentarmos codificar nós mesmos uma primeira versão desse recurso? Eu estou
morrendo de tédio durante a coisa obscura, quem quer se juntar a mim?

Várias pessoas já o fizeram, por exemplo. https://github.com/insidewhy/observable-input

O problema com a maioria deles é que para fazê-los com eficiência, você precisa modificar o compilador ou outras partes internas do angular.

Isso não conta. Quero dizer, vamos fazer uma contribuição adequada para o ângulo
testemunho. Afinal, temos o código-fonte disponível. Na verdade, não acho que precisaremos nem mesmo mexer no compilador neste caso.

@ganqqwerty Tentei implementá-lo dentro do Angular antes de escrever essa biblioteca. Angular é extremamente complicado. Para ser justo, o código é muito mais legível e compreensível do que React e suas funções malucas de quinhentas linhas, mas é "sofisticado". E ainda teria ficado feliz em me esforçar para fazer essa implementação se achasse que valeria a pena. Mas não acho que valha a pena precisamente pelas razões que @fxck iterou acima. A equipe Angular não dá ouvidos à comunidade, eles estão constantemente obcecados por otimizações e desempenho sem se preocupar com a usabilidade do Angular em geral. Não me importo com um framework que funciona bem quando me força a implementar clichês para problemas simples que foram demonstrados cinco anos atrás. Então decidi parar de me preocupar com o Angular, implementar o mínimo esforço que eu precisava para apoiar os projetos em que já estava trabalhando e evitá-lo onde e quando possível para todos os projetos futuros.

Este não é apenas um problema com o Angular, eu acho, mas com muitas ofertas do Google. Este problema e os outros que @fxck listados acima são apenas mais exemplos de problemas endêmicos no Google. Se você olhar para o Dart ou GoLang, você também verá que os desenvolvedores levantam problemas muito conhecidos e comumente enfrentados e os problemas permanecem abertos por mais de 5 anos.

Concordo que isso deve ser adicionado, mas uau, o ódio neste tópico é um pouco demais, quero dizer, estamos falando sobre adicionar algo que economize algumas linhas de código. Você pode ter entradas observáveis ​​agora com um setter decorado com @Input e seu próprio assunto para o qual enviar valores:

https://github.com/angular/angular/issues/5689#issuecomment -507001686

Se isso for muito clichê, já temos algumas opções simples de terceiros:

https://github.com/Futhark/ngx-observable-input

Estou feliz com o desenvolvimento do Angular e certamente não abandonaria a estrutura incrivelmente completa e confiável por causa de um recurso tão pequeno (embora desejável)

O problema geral é principalmente da perspectiva de como as pessoas o veem. Para alguém, são algumas linhas de código e parece muito simples alterar / adicionar qualquer coisa, mas na realidade, há muito mais escopo e muitos efeitos colaterais. Todos eles devem ser levados em consideração e, como sempre, a linha de temas é longa e tudo tem algum preço de custo, valor agregado e prioridade.

Gostaria de salientar que, pessoalmente, não espero que todos esses problemas sejam resolvidos imediatamente.

Mas eu espero, que agora, que Ivy está fora do caminho e está solta por um tempo, algum tipo de atualização oficial sobre, digamos, as 25 principais questões votadas, seja publicada.

Quer dizer, não acho possível para um projeto desse tamanho não ter em gerenciamento de projetos considerado prioridade e algum tipo de plano para todas as 25 questões mais votadas (ou qualquer outra coisa). A maioria deles possui até mesmo uma tag effort . Basta fazer uma postagem no blog, informar a comunidade que você sabe sobre isso e que tem algum tipo de plano para eles.

Eu certamente não abandonaria o Angular também, apesar de todas as suas deficiências, ainda gosto dele muito mais do que todas as outras alternativas. Mas eu acho que é justo dizer que falta "gerenciamento de relações com a comunidade" (especificamente no github).

// editar

Vocês, pessoas boas, se importariam de preencher esta enquete? Dada a sua situação atual e todas as coisas consideradas, quais desses problemas afetam mais você / melhoraria mais a sua experiência de desenvolvedor https://forms.gle/cprhx239kuqwWd5M8

@fxck Obrigado por escrever isso. Eu estava começando a me perguntar se todos haviam aceitado o fato de que a equipe Angular fará o que quiser, quando quiser. Embora isso seja certamente um direito deles, também acho que é necessário que as pessoas os chamem quando eles continuamente ignoram as vozes dos desenvolvedores. Concordo que não acho que ninguém espera a resolução imediata dos problemas, mas ignorá-los por 4-5 anos ou apenas esperar até que o problema seja bloqueado automaticamente parece muito pouco profissional para qualquer projeto, muito menos um projeto executado pelo Google. Eles ganharam algum tempo adicionando tags 'Fixed by Ivy' a um monte de questões abertas e, em seguida, não fizeram nada.

Eu certamente não abandonaria o Angular também, apesar de todas as suas deficiências, ainda gosto dele muito mais do que todas as outras alternativas. Mas eu acho que é justo dizer que falta "gerenciamento de relações com a comunidade" (especificamente no github).

Acho que essa frase resume meus pensamentos perfeitamente.

@chriszrc Eu realmente não interpretei nada neste tópico como 'ódio'. Mais como pessoas finalmente extravasando suas frustrações com o Angular, ignorando os problemas regularmente por anos a fio. Não é nem mesmo sobre esse assunto especificamente, é mais sobre o princípio de manter alguma forma de interação com a comunidade. A equipe Angular é considerada tão superior a qualquer outra pessoa que sente que pode ignorar as solicitações do desenvolvedor porque sabe o que é melhor para todos?

@chriszrc Não vejo nenhum ódio neste tópico, apenas um monte de frustração compreensível. Acho que igualar essa solicitação e a decepção relacionada com "salvar algumas linhas de código" é um exagero. Não é apenas esse problema que está frustrando a comunidade, mas a coleção significativa de questões populares que @fxck levantou acima que foram negligenciadas.

Olá a todos, desculpem-nos pelo silêncio neste tópico. Há duas solicitações ortogonais que recebemos há algum tempo:

  1. Melhor suporte RxJS de primeira classe na estrutura
  2. Menos RxJS em Angular, tornando-o potencialmente opcional

Ambos os pedidos estão em nosso radar e ambos têm seus prós e contras. Por exemplo, se formos na primeira direção, tornaremos a estrutura menos acessível para engenheiros novos para a API expressiva e semanticamente rica do RxJS. Muitas pessoas se preocupam em usar o RxJS em todos os lugares em seus aplicativos Angular, tornando o código-fonte difícil de ler entre os membros da equipe com diferentes níveis de experiência.

Ao mesmo tempo, já temos várias APIs reativas, mas algumas das primitivas oferecidas pelo Angular são totalmente obrigatórias e as coisas não funcionam muito bem. Dito isso, faz sentido melhorar o suporte de reatividade, mas isso deveria ser parte do núcleo do Angular ou delegado a um projeto da comunidade (ngrx - extensão reativa do Angular, por exemplo) com o qual estamos colaborando?

Há muitas maneiras de resolver esse problema, mas precisamos levar em consideração:

  • Compatibilidade com versões anteriores - não apenas para o código-fonte, mas também para recursos de treinamento
  • Curva de aprendizado
  • Consistência com APIs atuais
  • Consistência entre projetos
  • Desempenho - tempo de execução e tempo de carregamento inicial

Estamos no processo de priorizar alguns dos principais problemas do GitHub, observando as melhorias mais solicitadas e impactantes que podemos abordar. Eu diria que este é um dos mais desafiadores, então é difícil se comprometer com um encontro.

@mgechev Obrigado por responder. Se os rxjs e as apis reativas forem divididos em outra extensão (não meu voto pessoal), faria mais sentido não ter isso junto com uma implementação de armazenamento de estado / redux particular? Passei rapidamente do ngrx para o ngxs (que parecia bem menos clichê e muito mais "angular" com decoradores) e, finalmente, para Akita, que é meu favorito atual e o que mais venho desenvolvendo. Ou se for agrupado com ngrx, pelo menos torne realmente fácil apenas adicionar as partes de que precisamos, para que não estejamos presos a usar ngrx ou incluir essas dependências-

Se os rxjs e as apis reativas forem divididos em outra extensão (não meu voto pessoal)

Não estou dizendo que é isso que vai acontecer. No meu comentário, estou apenas compartilhando uma lista incompleta de alternativas para que eu possa mostrar o escopo do projeto e as diferentes considerações que temos.

@mgechev de fato, obrigado pela resposta.

Eu me pergunto, entretanto, se a parte reativa do Angular get for de alguma forma "rebaixada" para o projeto da comunidade, isso não significaria que o reativo se tornará um cidadão de segunda classe do Angular?

@mgechev Eu sinto sua dor

Aqui estão nossos requisitos para tal divisão:

  • Usando tanto a necessidade imperativa quanto a reativa para se sentir nativo e nenhum dos dois deve se sentir apegado
  • Sem vínculo com a solução de gerenciamento de estado específico para permitir reativo (divulgação completa, migramos todos os mais de 50 projetos para usar o Akita até agora)
  • Ambas as opções precisam ser lançadas em uma única versão ao mesmo tempo (sem lag / atraso)
  • Não pode haver "favorito", qualquer coisa que o angular faça precisaria funcionar em ambos os mundos enquanto se sentisse nativo
  • Suporte oficial completo para ambas as abordagens apoiado pela equipe angular (com base em nossa experiência, este é um requisito do cliente de parada bruta)

Com base no acima:

  • Existem 2 opções principais:

    • core imperativo + addon reativo

    • núcleo reativo + complemento imperativo

  • O empacotamento reativo para fazer com que pareça imperativo é bastante simples, não para o contrário
  • Portanto, votaríamos a favor do uso de reativo no núcleo e fazer com que o complemento contenha as APIs imperativas
  • Devido ao atraso zero + nenhum requisito de favoritos, bem como suporte oficial necessário, não vejo como o addon pode ser um projeto comunitário

Eu entendo que o acima é a nossa visão do mundo e existem muitas outras. A razão pela qual estou escrevendo isso é:

  • Se você pousar na extração reativa para um projeto comunitário, teremos que migrar mais de 50 soluções para outra estrutura (mais uma vez, isso será direcionado ao cliente, não nossa escolha)
  • Então, eu gostaria de pedir para que nós (a comunidade) saibamos o mais rápido possível onde você chega nisso, para que tenhamos tempo de retreinar nossos desenvolvedores em outro framework e migrar esses projetos

adicione o recurso por favor

@mgechev Não entendo suas preocupações. Esta proposta é sobre combinar os dois mundos com uma interface uniforme e direta para desenvolvedores iniciantes. Não se trata de escolher um ou outro, mas sim habilitar a combinação. Por exemplo, o desenvolvedor x decide construir um componente reativo incrível. E o desenvolvedor decide usá-lo em seu projeto não tão reativo. Por que não? Por que ele precisa depender de qualquer cola ngrx?
A diferença entre os dois mundos é se você armazena / gerencia o estado em seu componente inteligente final versus o armazenamento de estado central e é disso que se trata o ngrx, mas para os componentes, sinto que a própria estrutura deve ser o facilitador.

Há duas solicitações ortogonais que recebemos há algum tempo:

  1. Melhor suporte RxJS de primeira classe na estrutura
  2. Menos RxJS em Angular, tornando-o potencialmente opcional

Eu ficaria bem de qualquer maneira, mas às vezes isso dá uma sensação desconexa ao Angular, que é a última coisa que você faria, exceto de uma estrutura incluída com baterias.

Pessoalmente, estou curioso para ver como você teria versões não-RxJS para todos os pacotes Angular (HTTP, Roteador, Formulários). Eu suspeito que não existam boas versões não-RxJS desses, e é por isso que eles têm RxJS em primeiro lugar.

Isso quer dizer ... a menos que esteja faltando alguma coisa, (2) não é viável.


PS: Eu não descreveria esses pedidos como "ortogonais"; mais como "oposto".

Menos RxJS em Angular, tornando-o potencialmente opcional

Por curiosidade, @mgechev , há algum problema no Github para isso? (Eu não vi nenhum.) Ou isso é mais uma coisa do Googler?

@pauldraper Eu vi pelo menos um problema no github onde alguém está reclamando sobre como o rxjs é terrivelmente complexo e quer que seja completamente opcional. Pessoalmente, adoro rxjs, mas trabalhei com duas equipes agora, onde isso trouxe medo e repulsa para os desenvolvedores juniores e seniores. Além disso, ortogonal é o termo melhor do que oposto, uma vez que é possível implementar ambas as solicitações ao mesmo tempo (dado que cada solução pode ser trabalhada sem uma prejudicar a outra, fornecendo várias opções de API para consumir coisas de forma reativa e imperativa).

Para cada comentário reclamando sobre rxjs no github, provavelmente existem milhares de desenvolvedores usando e aproveitando sem problemas.

image

image

Aqui estão os resultados desse formulário que postei acima (quase 200 pessoas responderam), enquanto eu os estava discutindo no Angular discord.

Sem surpresa, as formas são a maior dor de cabeça, mas aparentemente @brandonroberts e @MikeRyanDev podem estar cozinhando alguma coisa, finalmente.

Para cada comentário reclamando sobre rxjs no github, provavelmente existem milhares de desenvolvedores usando e aproveitando sem problemas.

Apesar de adorar rxjs, não sou tão otimista. Eu trabalhei com muitos desenvolvedores que estão tão esgotados que pensar em ter que dedicar tempo para aprender algo com uma curva de aprendizado íngreme é um pesadelo. E muitos que se preocupam muito mais em ganhar dinheiro do que programar: D Ou os desenvolvedores juniores agressivos que pensam que tudo o que não conseguem aprender no espaço de um dia deve ser lixo.

Eu trabalhei com muitos desenvolvedores que estão tão esgotados que pensar em ter que dedicar tempo para aprender algo com uma curva de aprendizado íngreme é um pesadelo. E muitos que se preocupam muito mais em ganhar dinheiro do que programar

Esses são os desenvolvedores que o Angular está tentando atender na época, esgotados e sem vontade de aprender? :) E quem se importa neste momento de qualquer maneira. O Angular já existe há um tempo, não vai a lugar nenhum e absolutamente não vai perder qualquer tração se você adicionar ciclo de vida observável, entrada observável, eventos observáveis. Basta adicionar essas três pequenas coisas e as pessoas ficarão felizes por um tempo, é isso (e siga isso com https://indepth.dev/component-features-with-angular-ivy/ para completar o pacote básico).

angular_rxjs

Este diz tudo. O IMHO Angular deve permanecer consistente e usar mais RxJS: sem RxJS, não seria Angular.

Esses são os devs que o Angular está tentando atender? :)

Sim, seria pretensioso atender apenas a desenvolvedores que desejam escrever código usando rxjs. Embora eu prefira usar rxjs, quero que o Angular atenda a ambos os conjuntos de desenvolvedores. Por que o Angular deveria atender apenas a nós e não àqueles que pensam de forma diferente? Principalmente quando acredito que somos a minoria, não a maioria.

Novamente, ir "totalmente reativo", como adicionar ciclos de vida observáveis, entradas, eventos não afetaria de forma alguma aqueles sobre os quais você está falando, mas deixaria o outro grupo muito feliz.

E os recursos dos componentes são úteis, quer você goste de programação reativa ou não.

@fxck Nunca discordei disso por um segundo. Eu estava respondendo ao seu comentário anterior para apontar que há muitas pessoas que não querem usar o rxjs e muitas dessas abriram problemas no github para reclamar disso.

Apesar de o angular decidir desenvolver o núcleo - reativamente ou não, no mundo ideal haveria o pacote oposto construído no topo para atender às necessidades opostas (por exemplo, @angular/reactive ou @angular/imperative - com nomes melhores esperançosamente).

Qualquer um desses dois ir para a comunidade é para mim um rebaixamento para algumas pessoas e para mim angular ter uma perspectiva de ser uma ótima estrutura reativa foi a razão para ir com ele em primeiro lugar.


Por outro lado, acho que seria mais fácil manter o angular reativo e ter extensões imperativas, pois é mais fácil fazer a ponte, em vez de fazer a ponte entre o imperativo e o reativo - falando pela simplicidade do núcleo e do pacote de extensão.

Isenção de responsabilidade: o último ponto que formulei com base em suposições - não ficaria surpreso se fosse muito mais fácil escrever o imperativo central - a extensão reativa seria ainda mais complexa.

Por curiosidade, @mgechev , há algum problema no Github para isso? (Eu não vi nenhum.) Ou isso é mais uma coisa do Googler?

Não é uma coisa interna do Google. Você ficaria surpreso (assim como eu fiquei quando entrei para a equipe), como é grande a interseção entre os requisitos externos e internos. A principal diferença está no sistema de compilação e no gerenciamento de dependências, além de que os Googlers têm praticamente os mesmos requisitos que os não Googlers.

Estamos em contato com milhares de desenvolvedores - muitas empresas externas, equipes internas e colaboradores individuais. Em ambos os casos, há pessoas que adorariam usar o RxJS e outras que não acham que é a escolha certa para eles. Ambos os campos têm argumentos excelentes.

Esta não é uma decisão que queremos apressar, especialmente considerando que existem soluções alternativas / soluções da comunidade bem estabelecidas. O trabalho feito na comunidade nos ajuda a coletar pontos de dados extras e estabelecer uma solução ideal para o conjunto atual de variáveis.

Acho que ter uma abordagem unificada reativa ou imperativa (sendo uma escolha por projeto) ao desenvolver um aplicativo angular seria altamente benéfico. A capacidade de misturar e combinar não ajuda a experiência de desenvolvimento e criar código não DRY (por exemplo, entradas não reativas).

Nada acontece isoladamente, então posso entender totalmente os ambientes corporativos com dificuldade para treinar novamente grandes quantidades de desenvolvedores para a programação reativa. Portanto, descartar o imperativo realmente moveria o angular para fora do ponto ideal corporativo, o que, para ser realista, não vai acontecer, pois constitui uma grande base de usuários-chave para um framework como o angular.

Dito isso, o que não vejo como realista é construir uma interface reativa em cima de uma imperativa. A outra direção é bastante direta, pois você sempre pode escrever a saída de um observável em uma variável que pode ser usada imperativamente. Na verdade, é isso que o angular já está fazendo em muitos lugares sob a capa.

Portanto, voto para tornar o angular totalmente reativo em seu núcleo e, em seguida, crio "invólucros" imperativos na parte superior.

O principal problema com essa abordagem provavelmente seriam etapas de migração maiores para usuários obrigatórios. O que não é nada em ambientes corporativos e pode ser exatamente o que a equipe angular está tentando evitar, daí a sugestão de todo imperativo no núcleo.

@mgechev Já que isso foi levantado algumas vezes, seria interessante ouvir seus pensamentos sobre extensão nuclear / imperativa reativa e se ela tem alguma base para se apoiar?

Em ambos os casos, há pessoas que adorariam usar o RxJS e outras que não acham que é a escolha certa para eles. Ambos os campos têm argumentos excelentes.

Ótimo, é bom saber. Definir ir full-in com RxJS? Como adicionar ciclo de vida observável, entrada observável e eventos observáveis ​​se encaixa nisso? E não, nenhum deles realmente tem uma "solução alternativa / comunidade bem estabelecida".

@fxck nenhum destes realmente tem uma "solução alternativa / comunidade bem estabelecida".

“É verdade. Eu escrevi uma dessas soluções e ela só funciona devido a um grande hack que depende de modelos de verificação de tipo pobres. Eu verifiquei algumas das outras soluções postadas neste tópico também e cada uma delas tem pelo menos um dos seguintes problemas:

  • vazamentos de assinatura
  • requisitos clichê para o usuário
  • hacks tão ruins, senão piores, do que o que usei em minha biblioteca.

No que me diz respeito, é impossível fazer isso bem.

Os eventos observáveis ​​são ainda piores, não existem muitas soluções de comunidade, eles geralmente requerem uma etapa de construção adicional (que em alguns casos quebra SSR em configurações monorepo) e hacks em cima disso mesmo (se você estiver usando extensão de classe, por exemplo, para obter eventos observáveis ​​de ciclo de vida, porque ei, o angular não suporta isso nativamente) https://github.com/typebytes/ngx-template-streams/issues/8

Os eventos de ciclo de vida são indiscutivelmente os "mais fáceis", mas ainda precisam contar com uma extensão de classe ou decoradores personalizados, nenhum dos quais é ideal.

@fxck Até onde eu sei, não é possível limpar assinaturas de eventos de ciclo de vida sem ter que usar uma classe pai (eugh) ou um método que o usuário tem que chamar de seu próprio evento de ciclo de vida destruído (pior ainda).

Os eventos de ciclo de vida são indiscutivelmente os "mais fáceis", mas ainda precisam contar com uma extensão de classe ou decoradores personalizados, nenhum dos quais é ideal.

Eu tentei implementar eventos de ciclo de vida (para torná-los reativos) com decoradores personalizados também e é uma dor. ~ Sem extensão de classe, não sei se é possível sem 100 hacks ~. A possibilidade do usuário de estender o angular é até agora muito pobre e depende de hacks ou muitos clichês para fazer isso.

@ tonivj5 Se o recurso do componente for uma API pública, você vê isso como uma forma potencial de implementar ciclos de vida sem herança?

@ntziolis hah engraçado você mencionar os recursos, na verdade estou trabalhando na parte do ciclo de vida do sub-formulário ngx e os recursos são exatamente algo que acho que ajudaria. Nesse ínterim, é muito possível remendar a fábrica de componentes com ivy. Eu vejo isso como sendo muito útil fora do sub-formulário ngx, então acho que vou dividir essa lógica em uma biblioteca separada

@ntziolis , neste ponto, não tenho certeza se algum dia veremos as promessas de Ivy realmente sendo cumpridas.

Você verificou o efeito angular? (não ngrx / effects) https://dev.to/stupidawesome/reactive-adventures-in-angular-introducing-angular-effects-1epf Com esta ferramenta você pode escrever aplicativos totalmente reativos.
Estado observável bidirecional entre os componentes pai e filho incluídos
https://dev.to/stupidawesome/exploring-the-angular-effects-api-2gol
A versão 9.1.0 apresentará um modelo de composição / ganchos baseado na API de composição do Vue 3.
Eu acho que é o próximo passo para o Angular trabalhar com reatividade.

@mgechev

Esta não é uma decisão que gostaríamos de apressar, especialmente considerando que existem soluções alternativas / soluções comunitárias bem estabelecidas

Só por curiosidade, quem somos nós e quantos Googlers contribuem ativamente para o processo de tomada de decisão do Angular?

Você indica que esta não é uma decisão que deve ser apressada, mas eu me pergunto qual é a sua definição de não apressada? Este é um daqueles colocar uma tag 'Fixed by Ivy' nele e ignorá-lo até que mais desenvolvedores comecem a fazer barulho novamente, digite situações? Existe uma versão direcionada em mente para qualquer trabalho reativo ou há outras versões mais recentes do compilador que precisam ser lançadas primeiro?

O fato de geralmente haver 2.500 questões em aberto em determinado momento surge em discussões internas?

@ntziolis , como @zakhenry disse, acho que features ajudaria

@ntziolis Eu fiz outra tentativa com o ciclo de vida e tenho um POC funcionando (sem extensão de classe nem implementa interfaces de ciclo de vida). Ele se baseia em API privada (: exclamação :)

@ tonivj5 hah Eu não tive a chance de ler o seu, mas também quase https://github.com/cloudnc/ngx-observable-lifecycle

A ideia com a biblioteca na qual estou trabalhando é ter uma base comum para a qual outras bibliotecas possam se conectar para criar sua própria funcionalidade ciente do ciclo de vida. Deve ser capaz de funcionar de forma que, se houver várias funções que desejam acessar o mesmo gancho, haja apenas um gancho decorado no componente def.

Eu tenho tudo funcionando, só preciso classificar CI e testes de unidade.

O fato de geralmente haver 2.500 questões em aberto em determinado momento surge em discussões internas?

Lol, não houve 2500 questões abertas desde meados de 2019.

$ github_issue_stats history -i2m -n20 -sangular/angular

+-------------------------+--------------------+
|         period          |       issues       |
+-------------------------+--------------------+
| This month (2020-05)    | 2855 (+137, -112)  |
| Last month (2020-03)    | 2830 (+495, -550)  |
| 2 months ago (2020-01)  | 2885 (+601, -575)  |
| 3 months ago (2019-11)  | 2859 (+437, -352)  |
| 4 months ago (2019-09)  | 2774 (+411, -305)  |
| 5 months ago (2019-07)  | 2668 (+441, -369)  |
| 6 months ago (2019-05)  | 2596 (+488, -349)  |
| 7 months ago (2019-03)  | 2457 (+425, -373)  |
| 8 months ago (2019-01)  | 2405 (+428, -330)  |
| 9 months ago (2018-11)  | 2307 (+425, -391)  |
| 10 months ago (2018-09) | 2273 (+515, -466)  |
| 11 months ago (2018-07) | 2224 (+641, -541)  |
| 12 months ago (2018-05) | 2124 (+690, -624)  |
| 13 months ago (2018-03) | 2058 (+605, -444)  |
| 14 months ago (2018-01) | 1897 (+773, -679)  |
| 15 months ago (2017-11) | 1803 (+815, -979)  |
| 16 months ago (2017-09) | 1967 (+671, -431)  |
| 17 months ago (2017-07) | 1727 (+664, -518)  |
| 18 months ago (2017-05) | 1581 (+854, -548)  |
| 19 months ago (2017-03) | 1275 (+987, -796)  |
| 20 months ago (2017-01) | 1084 (+671, -505)  |
+-------------------------+--------------------+

Para meu gosto, prefiro projetos de código aberto que mantenham os problemas abertos para discussão até que haja uma discussão / consenso claro da comunidade, em vez de projetos que fechem cada problema aberto sem discussão. É também uma etapa para os mantenedores explicarem suas preocupações à comunidade ao longo do tempo, antes de fecharem qualquer possibilidade de aceitar uma proposta.

@agalazis Este problema está aberto há 4 anos e meio e é praticamente ignorado nos últimos 3.

@agalazis Este problema está aberto há 4 anos e meio e é praticamente ignorado nos últimos 3.

Houve um membro da equipe Angular explicando por que ele ainda está aberto apenas 5 dias atrás, então você simplesmente não está certo.

@ etay2000 Algumas

@beeman

Houve um membro da equipe Angular explicando por que ele ainda está aberto apenas 5 dias atrás, então você simplesmente não está certo.

Você realmente gosta daqueles emojis polegares para baixo, hein? Isso foi realmente uma explicação? Após 4 anos ou mais, ele disse que não quer apressar a decisão.

@agalazis
Concordo que algumas decisões são difíceis, mas o que deve ser considerado um prazo aceitável para adiar a tomada dessas decisões difíceis? Nem todas as 2855 questões em aberto envolvem essas decisões difíceis, mas muitas delas já existem há bastante tempo.

@ etay2000 Aceite ou não, este é o código aberto, as pessoas que abrem questões são muito mais do que os mantenedores. Um projeto datilografado, por exemplo, tem quase o dobro de questões abertas e outra proposta que fiz lá também demorou um pouco. Não me importo, pois a discussão trouxe muitas alternativas que não são tão sofisticadas quanto ter o recurso, mas conseguiram ver muitas outras perspectivas

@agalazis Você está certo, acho que esperava (incorretamente) mais de um projeto de código aberto mantido pelo Google do que de um projeto menor.

@beeman Insira um emoji de polegar para baixo aqui: -------->

@ etay2000 Você deve sempre se lembrar de que cada decisão importante afeta centenas de milhares de coisas, projetos e pessoas. E o que é ainda mais importante, uma decisão não perfeita e a introdução de APIs públicas, que levaria a algumas mudanças bruscas em pouco tempo depois disso, é apenas um pesadelo. Você pode ver quase em cada PR, como cada passo é revisado, e se não for perfeito, ele simplesmente espera, semanas, meses ou até anos.

@agalazis Você está certo, acho que esperava (incorretamente) mais de um projeto de código aberto mantido pelo Google do que de um projeto menor.

Não sei, olhe para Go and dart também. Na verdade, o Google é muito famoso por ignorar solicitações extremamente populares da comunidade por meia década: D Em geral, sua abordagem para linguagens / frameworks / etc. é muito conservador . Você pode ver novas linguagens e ideias surgindo que revolucionam áreas e o Google ainda não as adota há anos, por exemplo, a segurança nula atingiu Kotlin, TypeScript e Scala muito antes do Dart. Acho que há benefícios e déficits nesse tipo de abordagem, mas, pelo menos para mim, tendo a evitar coisas desenvolvidas pelo Google sempre que possível, porque isso realmente não se encaixa no meu ethos.

(Desculpe por isso, todos, mas eu tenho que fazer isso.)

@beeman Insira um emoji de polegar para baixo aqui: -------->

@ etay2000 Não sei como esse tipo de comentário ajuda em nada, senão uma violação de conduta aqui. Tenho quase certeza de que todo mundo sabe como abaixar o polegar, então qual é o sentido do sarcasmo?

Só para lembrar, ninguém é forçado a usar o Angular, você pode tentar encontrar outro framework onde as pessoas lhe agradem mais.

@brunojcm Acho que a festa acabou agora que a polícia de comentários está aqui. A questão é que ele ignorou todos os meus outros comentários, mas não percebi que sarcasmo era uma violação de conduta.

Só para lembrar, ninguém é forçado a usar o Angular, você pode tentar encontrar outro framework onde as pessoas lhe agradem mais.

A maneira como eu realmente quero responder a isso definitivamente violaria o código de conduta aqui.

Terminei, todos podem retornar à sua programação agendada regularmente. Voltarei em 4-5 anos para ver como as coisas aconteceram.

@ tonivj5 @zakhenry Ótimo saber que isso pode ser uma avenida.

@mgechev Eu entendo que esta não é nem mesmo uma discussão preliminar, mas adoraria ouvir sua opinião sobre:

  • núcleo reativo + extensão não reativa vs o contrário.
  • e mais específico para esse problema, há qualquer chance realista de expor a API de recursos de alguma forma.

    • mesmo que seja apenas com a ressalva de interromper mudanças no futuro, desde que a função geral ainda esteja acessível.

    • Como @zakhenry fez para eventos de ciclo de vida, vejo um ou um número muito limitado de projetos que

    • Você poderia, então, trabalhar com essa (s) equipe (s) de wrapper para informá-los sobre as próximas mudanças importantes no início (muito semelhante à sua sugestão sobre extensões reativas)

Acho que vocês podem aplicar diretamente o módulo reativo do Vue 3.0 (ref ou reativo) e resolver esse problema.

@AnonymousArthur se você está falando sobre o pacote @vue/reactivity então ele não tem nenhuma relação com o que estamos discutindo aqui, pois fornece reatividade às propriedades e não tem nada a ver com fluxos reativos (que é o que RxJs é).

@gund Sim, eu concordo. No entanto, essa conversa leva um bom tempo (4,5 anos) e ainda não terminou, então eu apenas tive uma ideia (pode não ser) muito rapidamente para quem não quer escrever subscribe / unsubscribe ou ngOnChange Handler. RxJS fornece recurso de reatividade e esta conversa (não li tudo) está falando sobre envolver o valor de entrada com o Observable como um açúcar sintático para torná-lo assistível e eu acho que é uma ideia incrível.

Acho que essa ideia é realmente útil e o protótipo de @robwormald no início parece ótimo, mas acho que subestimou os benefícios potenciais e quão pequeno o impacto precisaria ser.

Deixe-me propor um pequeno ajuste nessa solução para corrigir algumas deficiências, mas basicamente a mesma abordagem:

Angular definiria um decorador @OInput() e um tipo EventReceiver<T> que estende Observable<T> mas também adiciona um .value getter (como BehaviorSubject<T> ).

No lado do componente pai , como no exemplo de @robwormald , nada muda. Se você quiser passar um valor Observável lá, ainda precisará de | async . Ou você pode passar um tipo não observável, tudo bem (como sempre) também.

No lado do componente filho , aqui está o que proponho (um ligeiro desvio dessa proposta):

@Component({ selector: "child" })
class Child {
  // Notice, this parallels the patterns of `@Output()`.
  @OInput() foo = new EventReceiver<MyType>();

  constructor() {
    // The reactive way to listen to input changes in the class.
    this.foo.subscribe((v) => {
      console.log('foo changed', v);
    });
    // Or you can bind to `foo | async` in the template.
  }

  // But this would also support less reactive patterns in parallel,
  // both for ease of migration and for cases where developers don't
  // want to migrate fully. 
  ngOnChanges(changes: SimpleChanges) {
    if (changes['foo']) {
      console.log('foo changed', changes['foo'].currentValue); // Unchanged
      console.log('foo changed', this.foo.value);  // Previously this would have been just `this.foo`
    }
  }
}

Agora, aqui estão os benefícios dessa abordagem, que acho que foram um pouco subestimados:

  • Isso permite designs totalmente reativos nos componentes pai e filho, se desejado.
  • Nos componentes pais, mantém os padrões de projeto de entrada existentes do Angular, de modo que nenhum componente seja forçado a isso ou precise projetar em torno dele.
  • O contexto pai e filho são totalmente independentes, o pai não precisa saber se o filho está usando Input ou OInput , eliminando o risco de bugs de componentes cruzados com essa mudança.
  • É relativamente fácil migrar parcialmente um único componente filho, se desejado, porque o Angular mantém todos eles conectados aos eventos de ciclo de vida existentes como antes.
  • Como o Angular implementa esse recurso, todos os EventReceiver s podem incluir internamente um tubo através de takeUntil(ngOnDestroyEvent) , portanto, a maioria dos casos não precisará se lembrar de cancelar a inscrição quando seu componente for destruído, porque esses observáveis ​​serão concluídos automaticamente . (Viva vazamentos de memória reduzidos!)
  • Neste componente filho, isso parece e funciona muito semelhante ao padrão de @Output() hoje, então oferece um bom paralelismo e capacidade potencial de conectá-los bem.

    • Observação lateral: como acompanhamento, acho que seria legal propor um @InputOutput() que usa um ReplaySubject<T>(1) para conectar os comportamentos para uma ligação bidirecional de forma mais uniforme e consistente. Mas isso tem seus próprios desafios e argumentos, então NÃO estou propondo isso aqui.

  • Nenhum novo tipo de clichê para fazer isso funcionar, isso é tão simples quanto @Output() .
  • Angular seria perfeitamente switch() sobre as alterações de Observable sendo inseridas pelo pai, então padrões reativos mais fracos como aquele que não aderem à mesma instância Observable não precisam ser especiais ( switch() ed) em componentes filhos.

Observação lateral sobre o nome OInput que escolhi: Obviamente, isso está aberto a debates. No entanto, eu pessoalmente gosto desse nome porque é a abreviação de "Entrada observável" E porque se parece com "Saída", que é o que isso reflete.

Caro time Angular. Dê-nos algo pelo que esperar em 2020 :-)

Além disso, a solução @ViewChild tem { read } . por exemplo:

@Input({ observable: true }) 
@Input({ asObservable: true }) 
@Input({ asSubject: true })

Tanto para ter algo pelo qual ansiar em 2020.: D A menos, é claro, que isso (e outros problemas listados acima) conte como um bug e não um novo recurso. :)

https://twitter.com/ThomasBurleson/status/1283902827467886592
image

@fxck Posso ter uma opinião impopular aqui, mas honestamente, se 2020 é o ano em que a equipe Angular decide eliminar muitos bugs nos formulários, roteador, etc., eu não ficaria bravo com 0 recursos : encolher de ombros :

image

Dito isso, talvez seja mais um problema relacionado aos eventos recentes e suponho que haja coisas complicadas acontecendo no momento na equipe Angular. Espero que eles consigam manter as grandes pessoas que vêm construindo o Angular ativamente até agora: coração :

Desculpe pelo fora do tópico. Voa para longe

@ maxime1992 Seria bom também para mim.

Posso ter uma opinião impopular aqui, mas honestamente, se 2020 é o ano em que a equipe Angular decide eliminar muitos bugs em formulários, roteador, etc., eu não ficaria bravo com 0 recursos

Mas o que me deixa nervoso é o fato de que há processos de RH pouco saudáveis ​​a longo prazo dentro da equipe Angular que nos afetam, sem nós. Principalmente do ponto de vista de investimentos massivos nesta área.

Do meu ponto de vista, eles estão corrigindo problemas principalmente fechando-os via BOT - saindo sem resposta e fechando por bot., ..: - /

@ montella1507 Não, isso não é exatamente verdade. O bot bloqueia apenas questões já fechadas. E já é verdade que os membros da equipe Angular se esforçam muito em muitos casos quando tentam explicar o que é necessário descrever e reproduzir quando novos problemas são inseridos. É um processo muito demorado e requer muita paciência.

Para aqueles que estão culpando a equipe Angular e reclamando de não obter recursos e mudanças rapidamente:
Eu entendo sua dor. Eu também adoraria ver esse recurso no Angular, mas também mudaria muitas coisas que pessoalmente não gosto.

Mas talvez o Angular não tenha o objetivo de ser uma estrutura que tem todos os brinquedos novos e brilhantes e recebe toneladas de recursos a cada lançamento. E por mim tudo bem. Deve haver estruturas que visem a um nível mais empresarial e forneçam estabilidade e confiabilidade. E o Angular faz isso muito bem, pelo menos por experiência própria.

Eu gosto mais do Vue. Eu o uso para meus projetos pessoais. Mas meus projetos podem rapidamente começar a ficar desatualizados e sinto a necessidade de atualizar para um novo estilo de componente. Além disso, quando faço atualizações, as coisas sempre quebram da maneira mais estranha. O ecossistema também parece ter mais dificuldade em manter a compatibilidade.

Em contraste, o ângulo é maduro e estável. Isso não muda com frequência, mas isso também significa que você terá menos trabalho para refatorar seu código à medida que mantém suas dependências atualizadas, e isso é valioso para produtos comerciais. Seu conhecimento também permanece relevante por mais tempo e, em vez de tentar acompanhá-lo, você pode se aprofundar.

As pessoas da novinho em folha , elas estão pedindo uma correção para um descuido no design da estrutura. Atualmente, às vezes somos forçados a usar observáveis, e às vezes incapazes de usar observáveis, para a funcionalidade principal. Aqueles que odeiam os objetos observáveis ​​ficam frustrados. Aqueles que amam os objetos observáveis ​​ficam frustrados. E muitos de nós ficamos tão desapontados com a situação que desistimos e mudamos para outros frameworks. Eu adoraria continuar usando o Angular, um pouco de direção e poderia ter sido um framework realmente ótimo. Mas as postagens em blogs sobre má gestão, a falta de pragmatismo, os descuidos, são todos objetivamente decepcionantes.

@insidewhy Seja paciente, sempre há alguns picos e vales na estrada, mas isso não significa que outros também não os tenham ou que haja alguns erros principais. Significa apenas que as coisas podem avançar com mais rapidez e eficácia do que realmente aconteceram.

Não houve menção a qualquer questão mencionada anteriormente no roteiro, o que achei triste, eu realmente esperava que houvesse algo, menção, reconhecimento ... essa discussão seguiu em discórdia angular -

image

Dei a eles o benefício da dúvida e perguntei no comentário daquele artigo do roadmap médio , a resposta de Minko:

image

Ele praticamente reiterou o que disse aqui alguns meses atrás, nenhum desses problemas relacionados ao rxjs será corrigido tão cedo, então sim.

Parei de tentar transmitir meu ponto de vista, mas deixe-me tentar uma última vez. Não estou pedindo que o Angular seja transformado em Cycle.js , substituindo as APIs atuais, tudo o que estou pedindo são três pequenas cerejas reativas além das APIs atuais.

  • ciclo de vida observável
  • entrada observável
  • eventos de modelo observáveis

Nenhum deles quebra a compatibilidade com versões anteriores, aumenta a curva de aprendizado, não é consistente com outras APIs e posso imaginar que dificilmente afeta o desempenho , então como eles são tão complicados e requerem um planejamento mais longo e cuidadoso, em comparação com outras tarefas que estão por vir. Basta adicionar um de cada vez, mostrar que você se importa. Por favor.

@fxck : +1: com certeza.

  • ciclo de vida observável
  • entrada observável
  • eventos de modelo observáveis

@insidewhy Seja paciente

Pedir às pessoas por mais de 5 anos de paciência não é tão pragmático quando há muitos competidores correndo na corrida, todos competindo por atenção.

@ insidewhy Era mais geral.

@fxck O Google tem (pela minha última contagem) 4.000 aplicativos construídos de forma angular e de natureza altamente semelhante, alternar entre esses projetos e a revisão por pares é (imagino) bastante simples. Mesmo na minha empresa, um dos grandes benefícios do Angular é que, sem eu mesmo revisar o código de outra equipe, sei que ele está em um estado em que uma revisão por pares não dispara uma investigação inteira.

Agora, digamos que implementemos cada um desses atalhos úteis RxJS no núcleo, o que acontece então? É possível que acabemos com um viés de procedimento em vez de um viés reativo, sem uma orientação clara de qual viés é correto para qualquer caso específico. Depois de ler as discussões até agora, a resposta da equipe angular não é "Esta é uma prioridade baixa" (como seu fluxo sugere), na verdade parece mais perto de "Não queremos tomar partido".

Os usuários avançados de RxJS na comunidade Angular aqui são uma minoria vocal, porque depois de grok Typescript, DI, templates e várias bibliotecas do Angular, você tem quase 1 ano sob seu controle usando o framework diariamente, e agora está pronto para entender completamente o RxJS. Permitir que pessoas de nível sênior tenham preconceitos de reatividade nas avaliações por pares afasta ainda mais as pessoas, e isso é literalmente a última coisa de que a Angular precisa.

O objetivo do Angular (a meu ver) é reduzir e diminuir as conversas de "Use a tecnologia X porque é melhor, aqui está uma pequena novela por quê" e manter as conversas em revisões de colegas sobre a lógica de negócios que está sendo implementada. Queremos realmente estratificar nossos componentes entre angular "Avançado" e angular "Iniciante" mais do que já fizemos?

Esses são, sem dúvida, os mesmos argumentos que vimos com gerenciamento de estado, por que o Angular deveria determinar, a partir dos muitos padrões possíveis, qual é o correto?

Deixe-me apenas citar um dos membros da equipe angular para você.

image

A propósito, não há absolutamente nada avançado sobre qualquer um desses (ciclo de vida observável, entrada observável, eventos de modelo observáveis).

Esses são, sem dúvida, os mesmos argumentos que vimos com gerenciamento de estado, por que o Angular deveria determinar, a partir dos muitos padrões possíveis, qual é o correto?

A diferença entre a gestão estadual e esta, é que a gestão estadual poderia ser feita pela comunidade, não há nada dentro do núcleo angular que o impeça. Infelizmente, o Angular não fornece maneiras de implementar adequadamente nenhum dos três que mencionei. (editar: link corrigido para comentário)

Estou de todo o coração por deixar o máximo possível para a comunidade, que inclui pacotes como formulários, roteador, layout flexível, universal etc., deixe a equipe Angular se concentrar no núcleo, cli e componentes. Mas, para isso, eles precisam fornecer à comunidade pontos de entrada suficientes, como, por exemplo, permitir componentes de ordem superior . Mas eles falharam em fazer isso por anos (muitas vezes dizendo que Ivy é necessária primeiro) e se recusam a reconhecer / falar sobre isso em seus planos agora.

Depois de ler as discussões até agora, a resposta da equipe angular não é "Esta é uma prioridade baixa" (como seu fluxo sugere), na verdade parece mais perto de "Não queremos tomar partido".

Sim e é assustador.

@fxck Acho que estamos na mesma página sobre isso. Eu diria que toda vez que menciono o RxJS em fóruns ou mesmo com meus colegas engenheiros, a maioria das citações que posso citar é "Ainda não entendi isso, como posso saber mais?" ou "Acho impressionante quantos operadores existem." ou "Estou tendo problemas para ler este pipeline neste PR". Para cada 1 engenheiro RxJS bem versado, parece haver 15 engenheiros Angular bem versados ​​com sobrancelhas franzidas.

Sim e é assustador.

Isso é um pouco hiperbólico, não "precisamos" de @angular/core para ter esses padrões; na verdade, existem MUITAS variantes e bibliotecas dentro desse problema que podem ser usadas hoje em qualquer projeto. A única reticência em usar as opções mencionadas anteriormente é que possivelmente, no futuro, você terá que migrar para algo que o Angular fornece como uma substituição @angular/core padrão. É realmente tão difícil para nós npm uninstall e corrigir todos os erros de digitação?

Decisões arquitetônicas importantes, que afetam todos os projetos angulares e requerem inúmeras horas para documentar e migrar se não funcionarem, para mim isso parece ainda mais assustador, e já estivemos lá antes:

Isso é um pouco hiperbólico, não "precisamos" de @ angular / core para ter esses padrões; na verdade, existem muitas MUITAS variantes e bibliotecas neste problema que podem ser usadas hoje em qualquer projeto.

Não, não realmente, cada solução requer hacks ou depende de APIs internas que podem mudar. Esse é o ponto principal .. leia o que @insidewhy disse https://github.com/angular/angular/issues/5689#issuecomment -630661006 e o ​​que eu disse abaixo sobre fluxos de eventos de modelo.

// edite o link corrigido para comentar

Não, não realmente, cada solução requer hacks ou depende de APIs internas que podem mudar.

Quais APIs ou tipos internos você acha que deveriam ser tornados públicos / genéricos para permitir que isso seja abordado pela comunidade? Se este projeto está em um padrão de espera porque não há um caminho claro para a frente, então parece apropriado pedir que tenhamos permissão para projetar o nosso próprio em vez de bater constantemente neste mesmo tambor.

Leia o outro comentário de @insidewhy ele tentou fazer isso dentro do core.

image

@fxck Não vou discutir a história de tentativas de implementar isso no core ou como esse problema está aberto há 5 anos, o que está nos impedindo agora de abrir os internos para permitir que a comunidade desenvolva um design por conta própria?

Ei @insidewhy , eu me pergunto se este é o hack de verificação de tipo em que sua biblioteca confiava. Aparentemente, não funciona mais usando o TS 4.0

A propriedade 'serviceStackId $' é usada antes de seu initialization.ts (2729)

image

Ei @insidewhy , eu me pergunto se este é o hack de verificação de tipo em que sua biblioteca confiava. Aparentemente, não funciona mais usando o TS 4.0

Não, estava no compilador de modelo. Isso é outra coisa. Droga. Ah bem. Posso adicioná-lo à lista de mantenedores se quiser tentar consertá-lo.

@fxck Na verdade, você deve atribuir esse operador piped em ngOnInit, uma vez que as propriedades baseadas no decorador não serão injetadas até que o construtor seja executado, então este é realmente um problema legítimo com seu código.

@insidewhy eu definitivamente trabalhei antes do TS4. E verifique isso ,

function ObservableInput() {
  return (target: any, propertyKey: any) => {
    Object.defineProperty(target, propertyKey, {
      set(value: any) {
        console.log(target, propertyKey, value);
      },
      get() {
        return 'ObservableInput modified value';
      },
    })
  }
}

class Foo {
    @ObservableInput()
    bar = 'original bar value';

    baz = this.bar;

    constructor() {
        console.log('loggging this.baz', this.baz);
    }
}

new Foo();

isso registra

loggging this.baz ObservableInput valor modificado

então acho que ainda deve funcionar, só no TS4 o compilador reclama porque não percebe que há um get com um valor dentro desse decorador.

Estou usando um decorador para sentir a alteração da prop de entrada e fazendo uma versão observável da prop de entrada. Veja esta demonstração do codesandbox .

É assim que funciona:

// subjectize.ts
export function Subjectize(keyToWatch: string): PropertyDecorator {
  return (proto: any, propKey: string) => {
    const internalKey = Symbol(keyToWatch);
    Object.defineProperties(proto, {
      [keyToWatch]: {
        get() {
          return this[internalKey];
        },
        set(value) {
          this[internalKey] = value;
          this[propKey].next(value);
        }
      }
    });
  };
}
// counter.component.ts
import { Component, Input } from "@angular/core";
import { ReplaySubject } from "rxjs";
import { Subjectize } from "./subjectize";

@Component({
  selector: "app-counter",
  templateUrl: "./counter.component.html",
  styleUrls: []
})
export class CounterComponent {
  @Input()
  count: number;

  @Subjectize("count")
  count$ = new ReplaySubject(1);
}

Como @wmaurer apontou, @Subjectize pode ser tratado como um "açúcar" para fazer as coisas.

Vale a pena ler o guia oficial do angular Interceptar mudanças de propriedade de entrada com um setter , o que explica que podemos sentir as mudanças de entrada usando getter/setter .

@hankchiutw Essa solução é semelhante ao meu decorador @BindObservable : https://github.com/PSanetra/bind-observable#usage

Obrigado @hankchiutw

Como você está inicializando ReplaySubject com 1, optei por usar BehaviorSubject.
Além disso, inicializei o valor diretamente em Subjetivar.

export function Subjectize<T>(keyToWatch: string): PropertyDecorator {
  return (target: Object, propKey: string) => {
    const internalKey = Symbol(keyToWatch);
    Object.defineProperties(target, {
      [keyToWatch]: {
        get(): T {
          return this[internalKey];
        },
        set(value: T) {
          this[internalKey] = value;

          if (this[propKey]) {
            this[propKey].next(value);
          } else {
            this[propKey] = new BehaviorSubject(value);
          }
        }
      }
    });
  };
}

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'rol-bla',
    templateUrl: 'bla.html',
    styleUrls: [ 'bla.scss' ]
})
export class BlaComponent implements OnInit {
    @Input() world: World;
    @Subjectize<World>('world') world$: BehaviorSubject<World>;

    // ...
}

Eu gostaria de poder evitar escrever o tipo na propriedade "Subjectized", pois ele deve ser capaz de adivinhá-lo a partir da propriedade relacionada :( Alguma ideia de como fazer isso?

O sonho seria ser capaz de fazer isso e criar automaticamente o objeto Subject relacionado (mundo $ no meu caso) como BehaviorSubjecte inicializado corretamente.

export class BlaComponent implements OnInit {
    @Input() @Subjectize() world: World;

    // ...
}

@mparpaillon Seu design proposto é basicamente como é feito em https://github.com/PSanetra/bind-observable
A única diferença é que o decorador @BindObservable() usa um ReplaySubject sob o capô. Portanto, se quiser que o Assunto contenha um valor inicial, você também precisa inicializar a propriedade vinculada explicitamente (também se for apenas indefinida, pois pode ser possível que o tipo de propriedade não permita valores indefinidos ou nulos).

Exemplo Stackblitz

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponentComponent {
  @Input()
  @BindObservable()
  counter: number;
  counter$: ReplaySubject<number>;
}

Obrigado @PSanetra mas eu prefiro minha solução (que na verdade é a solução do Hank)

@mparpaillon Eu tentei algo para o seu sonho (ha) em outra demonstração .

O uso parece

export class CounterComponent {
  @Input()
  @Subjectize()
  count: number;

  @Input()
  @Subjectize("myCount$")
  anotherCount: number;
}

onde você pode escolher especificar o nome do objeto subjetivo.

Embora seja tecnicamente viável, temo que isso possa ser ambíguo para os desenvolvedores (já que um novo membro da classe é criado sob o título).

Obrigado @hankchiutw ! No entanto, o texto datilografado não me permite usar count $ ou myCount $.

Capture d’écran 2020-11-12 à 10 11 50

Além disso, você pode estar certo sobre a ambigüidade ... Obrigado novamente

No entanto, o texto datilografado não me permite usar count $ ou myCount $.

Você declarou os membros de sua classe como "count" e "anotherCount", é por isso que você não pode acessar "myCount $" e "count $". Eles simplesmente não existem, porque você não os declarou em lugar nenhum.

Eu sei ... esse é o ponto. Eu estava procurando uma maneira de declará-los do decorador. @hankchiutw ofereceu uma solução e só estou dizendo que não está funcionando como está

@mparpaillon, dê uma olhada na minha solução: https://github.com/Futhark/ngx-observable-input

@Futhark Oh, isso é quente! THX

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

pkozlowski-opensource picture pkozlowski-opensource  ·  3Comentários

gugamm picture gugamm  ·  3Comentários

mohsen1 picture mohsen1  ·  3Comentários

aquinum6 picture aquinum6  ·  3Comentários

escardin picture escardin  ·  3Comentários