New issue

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

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

Already on GitHub? Sign in to your account

Proposal: Support declarative binding from View events to Observables #4062

Closed
robwormald opened this Issue Sep 8, 2015 · 61 comments

Comments

@robwormald
Member

robwormald commented Sep 8, 2015

Not sure this is a good idea, or even possible, but thought I'd throw it out there.

In vanilla Rx, if I wanted to listen to a DOM event to do a typeahead or something I'd do something like:

//get DOM element
const input = document.getElementById('myInput');
//observable of click events
const inputs$ = Rx.Observable.fromEvent(input, 'keyup');
//map changes to requests
const responses$ = inputs$.flatMap(ev => myHttpService.get(ev.target.value)); 
//subscribe to responses
responses$.subscribe(json => console.log(json));

In current ng2, if I wanted to do the equivalent, I'd do something like:

@Component({
  selector: 'my-component'
})
@View({
  template: `
     <div [ng-form-model]="myForm">
       <input type="text" ng-control="query">
    </div>
    <ul>
     <li *ng-for="#result of results | async">{{result.text}}</li>
   </ul>
`
})
class MyComponent {
  constructor(public http: Http, fb: FormBuilder){
    //create form group
    this.myForm = fb.group({
      query: ""
    });
    //subscribe to value changes, map to responses, bind to view w/ pipe
    this.results = this.myForm.controls.query.valueChanges
      .toRx()
      .flatMap(query => this.http.get(`/foos?q=${query}`)
      .map(res => res.json());
  }
}

This all works great (or will, once Rx stuff settles).

However, if I wanted to do the same sort of thing from an arbitrary event (unrelated to forms, like a mousedrag or button click or whatever), there's not a good way (I can see) to get an Observable from a DOM event, so you have to do something like:

<button (click)="makeRequest($event)">Make Request</button>
//snip
class MyComponent {
  makeRequest($event){
    this.http.get($event.target.url)
      .map(res => res.json())
      .subscribe(results => this.results = results);
  }
}

which isn't nearly as clean.

Proposed idea would be to provide a way to delegate / bind an arbitrary DOM event to a Subject/Observable, so that it could be easily subscribed to and handled reactively.

Something like (totally made up syntax here, point being we're binding the click event to the Subject):

Edit: see #4062 (comment) for current proposal

<button [(click)]="clicks$">make request</button>
class MyComponent {
  clicks$ = new Rx.Subject();
  constructor(){
    this.results = this.clicks$
      .flatMap(ev => http.get('someurl.json'))
      .map(res => res.json())
  }
}

I saw #3992, but I believe this is for emitting events "out" from the component, whereas this idea is more for usage within a component.

Thoughts?

@gdi2290

This comment has been minimized.

Show comment
Hide comment
@gdi2290

gdi2290 Sep 9, 2015

Member

👍 it would be awesome to have Rx Subjects for two-way data-binding

github-tipe-logo

Member

gdi2290 commented Sep 9, 2015

👍 it would be awesome to have Rx Subjects for two-way data-binding

github-tipe-logo

@dsebastien

This comment has been minimized.

Show comment
Hide comment
@dsebastien

dsebastien Sep 9, 2015

+1, let's RX all the things :)

dsebastien commented Sep 9, 2015

+1, let's RX all the things :)

@jeffbcross

This comment has been minimized.

Show comment
Hide comment
@jeffbcross

jeffbcross Oct 29, 2015

Contributor

I was chatting with @vsavkin earlier today about this, but more from the perspective of putting the component in control instead of the template. He had the idea of extending the @ViewChild property decorator to something like @ObserveChildOrBetterName('.some-button', 'click'),

class MyComponent implements AfterViewInit {
  @ObserveChild('.search-button', 'click') searchClick:EventEmitter<MouseEvent>;

  afterViewInit () {
    this.searchClick.subscribe(evt => alert('clicked'));
  }
}

This could work for native and custom events, and, aside from setting the MouseEvent as the generic type, has no tight coupling to the DOM, so this component is more portable to different platforms.

Contributor

jeffbcross commented Oct 29, 2015

I was chatting with @vsavkin earlier today about this, but more from the perspective of putting the component in control instead of the template. He had the idea of extending the @ViewChild property decorator to something like @ObserveChildOrBetterName('.some-button', 'click'),

class MyComponent implements AfterViewInit {
  @ObserveChild('.search-button', 'click') searchClick:EventEmitter<MouseEvent>;

  afterViewInit () {
    this.searchClick.subscribe(evt => alert('clicked'));
  }
}

This could work for native and custom events, and, aside from setting the MouseEvent as the generic type, has no tight coupling to the DOM, so this component is more portable to different platforms.

@ericmartinezr

This comment has been minimized.

Show comment
Hide comment
@ericmartinezr

ericmartinezr Oct 30, 2015

Contributor

@jeffbcross that would be beautiful 👍. Although I would call it @ObserveOutput() to keep it aligned with the latest naming.

Contributor

ericmartinezr commented Oct 30, 2015

@jeffbcross that would be beautiful 👍. Although I would call it @ObserveOutput() to keep it aligned with the latest naming.

@flyingmutant

This comment has been minimized.

Show comment
Hide comment
@flyingmutant

flyingmutant Nov 16, 2015

It would be nice to also use the hypothetical @ObserveChild() to translate this:

@ng.Component({
  template: `<input [ng-form-control]="inputControl">`
})
class Something {
  inputControl = new ng.Control();
  constructor() {
    this.inputControl.valueChanges.subscribe(...)
  }
}

into

@ng.Component({
  template: `<input #my-input>`
})
class Something {
  @ng.ObserveChild('myInput', 'input') inputChanges: EventEmitter<Event>;
  constructor() {
    this.inputChanges.map(ev => ev.target.value).subscribe(...)
  }
}

Having to use [ng-form-control] to observe the values does not feel very natural currently, and clutters the template as well.

flyingmutant commented Nov 16, 2015

It would be nice to also use the hypothetical @ObserveChild() to translate this:

@ng.Component({
  template: `<input [ng-form-control]="inputControl">`
})
class Something {
  inputControl = new ng.Control();
  constructor() {
    this.inputControl.valueChanges.subscribe(...)
  }
}

into

@ng.Component({
  template: `<input #my-input>`
})
class Something {
  @ng.ObserveChild('myInput', 'input') inputChanges: EventEmitter<Event>;
  constructor() {
    this.inputChanges.map(ev => ev.target.value).subscribe(...)
  }
}

Having to use [ng-form-control] to observe the values does not feel very natural currently, and clutters the template as well.

@flyingmutant

This comment has been minimized.

Show comment
Hide comment
@flyingmutant

flyingmutant Nov 16, 2015

Aside: isn't it amusing how the things move from onclick-style (attach things right in the DOM), to jquery-style (attach things after the fact using selectors), to angular1-style (= onclick-style reborn), to angular2-style (@ViewChild, @ObserveChild etc. – jquery-style reborn)?

#CleanTemplates2015

flyingmutant commented Nov 16, 2015

Aside: isn't it amusing how the things move from onclick-style (attach things right in the DOM), to jquery-style (attach things after the fact using selectors), to angular1-style (= onclick-style reborn), to angular2-style (@ViewChild, @ObserveChild etc. – jquery-style reborn)?

#CleanTemplates2015

@PascalPrecht

This comment has been minimized.

Show comment
Hide comment
@PascalPrecht

PascalPrecht Nov 29, 2015

Contributor

Love @jeffbcross and @vsavkin proposal. Thanks @robwormald for bringing this up.

@ericmartinezr as a site note, I think @ObserveOutput doesn't really fit here, as "outputs" describe what goes out of the component. This pattern however, describes how to listen/subscribe to something inside the component as part of the component's view. So it's not really an output.

But i'm sure we're going to find a nice name here, as naming is sooo easy.

Contributor

PascalPrecht commented Nov 29, 2015

Love @jeffbcross and @vsavkin proposal. Thanks @robwormald for bringing this up.

@ericmartinezr as a site note, I think @ObserveOutput doesn't really fit here, as "outputs" describe what goes out of the component. This pattern however, describes how to listen/subscribe to something inside the component as part of the component's view. So it's not really an output.

But i'm sure we're going to find a nice name here, as naming is sooo easy.

@danculley

This comment has been minimized.

Show comment
Hide comment
@danculley

danculley Dec 22, 2015

Not to add to the naming soup, but I think @ObserveChildEvent() and @ObserveHostEvent() would make the most sense.

@ObserveChild() is not sufficiently generic, as the same concept makes sense for observing DOM events on the host element as well. The semantics also do not match exactly, because the bound variable is observing an event on the child, not the child itself.

@ObserveOutput() is (as @PascalPrecht describes above) confusing because it is not just an observable version of @Output().

@ObserveEvent() identifies the event, but not where it is coming from.

danculley commented Dec 22, 2015

Not to add to the naming soup, but I think @ObserveChildEvent() and @ObserveHostEvent() would make the most sense.

@ObserveChild() is not sufficiently generic, as the same concept makes sense for observing DOM events on the host element as well. The semantics also do not match exactly, because the bound variable is observing an event on the child, not the child itself.

@ObserveOutput() is (as @PascalPrecht describes above) confusing because it is not just an observable version of @Output().

@ObserveEvent() identifies the event, but not where it is coming from.

@thelgevold

This comment has been minimized.

Show comment
Hide comment
@thelgevold

thelgevold Dec 31, 2015

Contributor

I have been playing with Observables tied to a UI element and some event.

One issue I've had is passing data via the event - without storing and passing state from the DOM.
I have been using fromEvent, but I end up having to pull info from the DOM element to make it work. The use case is to pass an id to build a dynamic url when an element is clicked.

I would love to be able to just pass arbitrary model data to processes triggered by the event. I would also like to abstract the underlying element and perhaps group it with other elements with the same behavior, but with different data.

One idea I came up with was a concept of an Observable group

<div [ObservableGroup]=['group1',{country:'usa'}]>USA</div>
<div [ObservableGroup]=['group1',{country:'germany'}]>Germany</div>

//Made up function
Observable.fromGroupAndEvent('group1','click')
.switchMap((r:any) => 
    {  
        this.activeCountry = r.data.country;
        return this.http.get('./country-info/' + this.activeCountry + '.json')
    })
.map((res: Response) => res.json())
.subscribe(r => this.capitol = r.capitol);

I am new to Observables, so I am not sure this is the right direction, but it does seem to meet some of my goals

Contributor

thelgevold commented Dec 31, 2015

I have been playing with Observables tied to a UI element and some event.

One issue I've had is passing data via the event - without storing and passing state from the DOM.
I have been using fromEvent, but I end up having to pull info from the DOM element to make it work. The use case is to pass an id to build a dynamic url when an element is clicked.

I would love to be able to just pass arbitrary model data to processes triggered by the event. I would also like to abstract the underlying element and perhaps group it with other elements with the same behavior, but with different data.

One idea I came up with was a concept of an Observable group

<div [ObservableGroup]=['group1',{country:'usa'}]>USA</div>
<div [ObservableGroup]=['group1',{country:'germany'}]>Germany</div>

//Made up function
Observable.fromGroupAndEvent('group1','click')
.switchMap((r:any) => 
    {  
        this.activeCountry = r.data.country;
        return this.http.get('./country-info/' + this.activeCountry + '.json')
    })
.map((res: Response) => res.json())
.subscribe(r => this.capitol = r.capitol);

I am new to Observables, so I am not sure this is the right direction, but it does seem to meet some of my goals

@edispring

This comment has been minimized.

Show comment
Hide comment
@edispring

edispring Jan 18, 2016

👍 for @ng.ObserveChild()

edispring commented Jan 18, 2016

👍 for @ng.ObserveChild()

@valorkin

This comment has been minimized.

Show comment
Hide comment
@valorkin

valorkin Jan 19, 2016

Contributor

Observe all the things! 👍

Contributor

valorkin commented Jan 19, 2016

Observe all the things! 👍

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jan 27, 2016

Member

I think this is a good idea, post RC.

Member

mhevery commented Jan 27, 2016

I think this is a good idea, post RC.

@timkindberg

This comment has been minimized.

Show comment
Hide comment
@timkindberg

timkindberg Feb 9, 2016

  @ObserveChild('.search-button', 'click') searchClick:EventEmitter<MouseEvent>;

I'm not sure this syntax is consistent with other Angular 2 syntaxes. Regular events, e.g. (click), are controlled from the template. Why would we have this new thing be controlled in the controller?

I'm not saying it's bad overall, but if we are deciding that we no longer want to handle events from the template, then it should be consistent across the board.

We are just looking to sugar this:

<button (click)="clicks$.emit($event)">Make Request</button>
class MyComponent {
  private clicks$ = new Subject();
}

Honestly, I'm not sure sugar is needed, that's already pretty short. I'd also prefer to NOT have another new syntax, let's try to keep the (event) syntax the same.

timkindberg commented Feb 9, 2016

  @ObserveChild('.search-button', 'click') searchClick:EventEmitter<MouseEvent>;

I'm not sure this syntax is consistent with other Angular 2 syntaxes. Regular events, e.g. (click), are controlled from the template. Why would we have this new thing be controlled in the controller?

I'm not saying it's bad overall, but if we are deciding that we no longer want to handle events from the template, then it should be consistent across the board.

We are just looking to sugar this:

<button (click)="clicks$.emit($event)">Make Request</button>
class MyComponent {
  private clicks$ = new Subject();
}

Honestly, I'm not sure sugar is needed, that's already pretty short. I'd also prefer to NOT have another new syntax, let's try to keep the (event) syntax the same.

@e-oz

This comment has been minimized.

Show comment
Hide comment
@e-oz

e-oz Feb 9, 2016

@timkindberg I'm all for brevity and consistency, but selector in proposal will allow to catch events from nodes, not existing in template initially (they could be generated later).

e-oz commented Feb 9, 2016

@timkindberg I'm all for brevity and consistency, but selector in proposal will allow to catch events from nodes, not existing in template initially (they could be generated later).

@timkindberg

This comment has been minimized.

Show comment
Hide comment
@timkindberg

timkindberg Feb 10, 2016

@e-oz yes but what stops the regular events (like click) from using a similar syntax as well? Why wouldn't we do normal click events like this then?:

@Event('.search-button', 'click')
onClick($event) {}

I'm just saying it's a bit odd to all of a sudden put the onus on the controller to behave in a way completely unlike anything that's in the API yet. It's setting a very strong precedent that will go against what Angular has always been all about; declare your properties and events in your HTML.

timkindberg commented Feb 10, 2016

@e-oz yes but what stops the regular events (like click) from using a similar syntax as well? Why wouldn't we do normal click events like this then?:

@Event('.search-button', 'click')
onClick($event) {}

I'm just saying it's a bit odd to all of a sudden put the onus on the controller to behave in a way completely unlike anything that's in the API yet. It's setting a very strong precedent that will go against what Angular has always been all about; declare your properties and events in your HTML.

@e-oz

This comment has been minimized.

Show comment
Hide comment
@e-oz

e-oz Feb 10, 2016

@timkindberg it's happened already - @input and @output are key things in angular 2 and they are very handy to use, and simple to read.
About your example and first question: it's again different selector: for list of elements. With (click) in template you can point exact node without any ID or specific class.

e-oz commented Feb 10, 2016

@timkindberg it's happened already - @input and @output are key things in angular 2 and they are very handy to use, and simple to read.
About your example and first question: it's again different selector: for list of elements. With (click) in template you can point exact node without any ID or specific class.

@timkindberg

This comment has been minimized.

Show comment
Hide comment
@timkindberg

timkindberg Feb 10, 2016

@e-oz @input and @output are completely different. I agree they are awesome, while also keeping the separation between view and controller.

@ObserveChild('.search-button', 'click') is different because it is finding a child by css selector and listening to an event... it's way too close to jquery's $('.search-button').click(). I don't even see how that is safe across various platforms as was said by @jeffbcross.

timkindberg commented Feb 10, 2016

@e-oz @input and @output are completely different. I agree they are awesome, while also keeping the separation between view and controller.

@ObserveChild('.search-button', 'click') is different because it is finding a child by css selector and listening to an event... it's way too close to jquery's $('.search-button').click(). I don't even see how that is safe across various platforms as was said by @jeffbcross.

@e-oz

This comment has been minimized.

Show comment
Hide comment
@e-oz

e-oz Feb 10, 2016

@timkindberg yes, it was wrong example, agree :) Maybe you are right and jQuery-programming style should not be proposed by Angular

e-oz commented Feb 10, 2016

@timkindberg yes, it was wrong example, agree :) Maybe you are right and jQuery-programming style should not be proposed by Angular

@cburgdorf

This comment has been minimized.

Show comment
Hide comment
@cburgdorf

cburgdorf Feb 10, 2016

I'm with @timkindberg here. In fact, I've been meaning to raise the same concern here but then forgot about it.

Considering we have a component with a template

<div>
    <h1>Some Component</h1>
    <button></button>
</div>

Why would I use <button (click)="handleClick()"></button> declaratively from the template to bind to the click event vs @ObserveChild('button', 'click') from within the component to bind to the click observable? After all it's just a matter of if I want to consume the event or the observable of the click. It shouldn't turn the semantics completely up side down I think.

Note, that I didn't bother to create a class for that button and used the element selector which actually may be a bad idea with this proposal. But then again, this is something that I would normally not have to care about.

However, I have another concern which is unrelated to the previous. I'm not feeling too well with exposing Subjects as public properties of our components either. Exposing a Subject leaks a whole lot of power to the outside world because next, complete and error could be called from anywhere.

We use to protect a deferred for the same reason and only expose a read only thing with deferred.promise instead. The idea is similar to not expose a Subject directly but call mySubject.asObservable() instead.

I'm not sure what's the best way to address these concerns so I'm just thinking out loud here. What if the framework would add a ngEmit method to the component which I could call from the template like this.

<button (click)="ngEmit('myButtonClicks', $event)"></button>

It would also add a ngObserve method to the component that I could use to subscribe to the Observable from within the component like this.

this.ngObserve('myButtonClicks').subscribe(e => { /*do something with click event */})

Regarding my privacy concerns, this would still leave up the possibility to misuse ngEmit as it could be called from the outside world,too. It may be a little clearer though since it's one clear API, marked for component's internal use exclusively, instead of exposing lots of Subjects at each and every component.

cburgdorf commented Feb 10, 2016

I'm with @timkindberg here. In fact, I've been meaning to raise the same concern here but then forgot about it.

Considering we have a component with a template

<div>
    <h1>Some Component</h1>
    <button></button>
</div>

Why would I use <button (click)="handleClick()"></button> declaratively from the template to bind to the click event vs @ObserveChild('button', 'click') from within the component to bind to the click observable? After all it's just a matter of if I want to consume the event or the observable of the click. It shouldn't turn the semantics completely up side down I think.

Note, that I didn't bother to create a class for that button and used the element selector which actually may be a bad idea with this proposal. But then again, this is something that I would normally not have to care about.

However, I have another concern which is unrelated to the previous. I'm not feeling too well with exposing Subjects as public properties of our components either. Exposing a Subject leaks a whole lot of power to the outside world because next, complete and error could be called from anywhere.

We use to protect a deferred for the same reason and only expose a read only thing with deferred.promise instead. The idea is similar to not expose a Subject directly but call mySubject.asObservable() instead.

I'm not sure what's the best way to address these concerns so I'm just thinking out loud here. What if the framework would add a ngEmit method to the component which I could call from the template like this.

<button (click)="ngEmit('myButtonClicks', $event)"></button>

It would also add a ngObserve method to the component that I could use to subscribe to the Observable from within the component like this.

this.ngObserve('myButtonClicks').subscribe(e => { /*do something with click event */})

Regarding my privacy concerns, this would still leave up the possibility to misuse ngEmit as it could be called from the outside world,too. It may be a little clearer though since it's one clear API, marked for component's internal use exclusively, instead of exposing lots of Subjects at each and every component.

@cristinecula

This comment has been minimized.

Show comment
Hide comment
@cristinecula

cristinecula Feb 10, 2016

I also think that adding the API in the component would cause dissonance with the rest of the framework.
I support @timkindberg's proposal of simply documenting this use case. The (event)="_subject.emit($event)" syntax is sweet enough :)

@cburgdorf Regarding your concern about exposing Subjects: you are right that enforcing private access would be best, but I do not think a level of indirection on top of the api is the best way to go.
The same concern is applicable to any stuff you put on this to make available in the template.

I guess that simple conventions go a long way (marking privateParts with a prefixed ___ or suffixed "$"). As a conscientious developer I steer clear of abusing private apis, even if this limit is not enforced.

cristinecula commented Feb 10, 2016

I also think that adding the API in the component would cause dissonance with the rest of the framework.
I support @timkindberg's proposal of simply documenting this use case. The (event)="_subject.emit($event)" syntax is sweet enough :)

@cburgdorf Regarding your concern about exposing Subjects: you are right that enforcing private access would be best, but I do not think a level of indirection on top of the api is the best way to go.
The same concern is applicable to any stuff you put on this to make available in the template.

I guess that simple conventions go a long way (marking privateParts with a prefixed ___ or suffixed "$"). As a conscientious developer I steer clear of abusing private apis, even if this limit is not enforced.

@wmaurer

This comment has been minimized.

Show comment
Hide comment
@wmaurer

wmaurer Feb 10, 2016

I agree with @cburgdorf. I've been working with Rx for a few years, but am far from being an expert, however I have always been under the impression that the (over)use of Subject is an anti-pattern. Encouraging the use of Subject in this case will further perpetuate its misuse, especially by newcomers to Angular2 and Rx.

IMO, ideally what should be exposed is an Observable. This was what I attempted to do with the makeObservableFunction I hacked together (inspired by @mattpodwysocki's work for Angular 1). In this case, it works differently than the original suggestion. The event is still handled from the template:

<button class="search-button" (click)="click()">

and makeObservableFunction('click') dynamically creates the click method which returns an Observable.

To be clear, I find this also far from optimal.
But I also find using selectors in code to be not optimal.
Promoting the use of Subject is also not optimal.

IMO, what would be perfect (possibly being contentious here!) is an addition to Angular's template syntax which allows exposing an event as an Observable (instead of calling a method).

<button class="search-button" {click}="click">

... which would bind to a (hot) Observable<MouseEvent> on the component.

wmaurer commented Feb 10, 2016

I agree with @cburgdorf. I've been working with Rx for a few years, but am far from being an expert, however I have always been under the impression that the (over)use of Subject is an anti-pattern. Encouraging the use of Subject in this case will further perpetuate its misuse, especially by newcomers to Angular2 and Rx.

IMO, ideally what should be exposed is an Observable. This was what I attempted to do with the makeObservableFunction I hacked together (inspired by @mattpodwysocki's work for Angular 1). In this case, it works differently than the original suggestion. The event is still handled from the template:

<button class="search-button" (click)="click()">

and makeObservableFunction('click') dynamically creates the click method which returns an Observable.

To be clear, I find this also far from optimal.
But I also find using selectors in code to be not optimal.
Promoting the use of Subject is also not optimal.

IMO, what would be perfect (possibly being contentious here!) is an addition to Angular's template syntax which allows exposing an event as an Observable (instead of calling a method).

<button class="search-button" {click}="click">

... which would bind to a (hot) Observable<MouseEvent> on the component.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Feb 10, 2016

Member

The problem with a purely declarative syntax is that it doesn't allow you to bind to dynamic children of a component - think dynamic tabs or stuff in an ng-content etc. The @ObserveChild decorator follows the @ViewChild/ViewChildren pattern.

Member

robwormald commented Feb 10, 2016

The problem with a purely declarative syntax is that it doesn't allow you to bind to dynamic children of a component - think dynamic tabs or stuff in an ng-content etc. The @ObserveChild decorator follows the @ViewChild/ViewChildren pattern.

@timkindberg

This comment has been minimized.

Show comment
Hide comment
@timkindberg

timkindberg Feb 10, 2016

I agree there is a problem to solve and I'm not saying we don't do it like that, I'm just saying it's different. @ViewChild is different because you pass in a class token, so its stricter. The css selector and string based event is just very different. You could again make the same argument that we should have done regular old events the same way, e.g. @OnEvent('.some-selector', 'event'). But we didn't... why is that? Do we want to?

timkindberg commented Feb 10, 2016

I agree there is a problem to solve and I'm not saying we don't do it like that, I'm just saying it's different. @ViewChild is different because you pass in a class token, so its stricter. The css selector and string based event is just very different. You could again make the same argument that we should have done regular old events the same way, e.g. @OnEvent('.some-selector', 'event'). But we didn't... why is that? Do we want to?

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Feb 11, 2016

Member

Yeah, but we need to be able to pass in a class token for this scenario as well, that's the point.

In the same way you can do:

<div #thing></div>
<some-component></some-component>

and do either

@ViewChild('thing')

or

@ViewChild(SomeComponent)

This follows the same convention.

Member

robwormald commented Feb 11, 2016

Yeah, but we need to be able to pass in a class token for this scenario as well, that's the point.

In the same way you can do:

<div #thing></div>
<some-component></some-component>

and do either

@ViewChild('thing')

or

@ViewChild(SomeComponent)

This follows the same convention.

@timkindberg

This comment has been minimized.

Show comment
Hide comment
@timkindberg

timkindberg Feb 11, 2016

@robwormald ok, I'd not known about the @ViewChild('thing') version of the @ViewChild syntax. So that helps bridge the gap a bit. I've definitely driven my point home enough by now ;)

timkindberg commented Feb 11, 2016

@robwormald ok, I'd not known about the @ViewChild('thing') version of the @ViewChild syntax. So that helps bridge the gap a bit. I've definitely driven my point home enough by now ;)

@rickyk586

This comment has been minimized.

Show comment
Hide comment
@rickyk586

rickyk586 Feb 13, 2016

I agree with @timkindberg, @cburgdorf, and ultimately @wmaurer.

Events should be handled in either the controller or the template, not both. I think consistency is very important. Also, I do not have much experience with Rx, but it seems Subject should be used sparingly if possible.

So far the solution I like the best is makeObservableFunction(). The createObservableFunction method used for Angular 1 is clean and easy to understand (ex1, ex2). However I agree that makeObservableFunction() is not optimal as @wmaurer states, and I do not have a better solution in mind.

rickyk586 commented Feb 13, 2016

I agree with @timkindberg, @cburgdorf, and ultimately @wmaurer.

Events should be handled in either the controller or the template, not both. I think consistency is very important. Also, I do not have much experience with Rx, but it seems Subject should be used sparingly if possible.

So far the solution I like the best is makeObservableFunction(). The createObservableFunction method used for Angular 1 is clean and easy to understand (ex1, ex2). However I agree that makeObservableFunction() is not optimal as @wmaurer states, and I do not have a better solution in mind.

@TheLarkInn

This comment has been minimized.

Show comment
Hide comment
@TheLarkInn

TheLarkInn Mar 9, 2016

Member

I agree @wmaurer.

Do we want to force people to use observables? Or maybe set a configuration for the component to use one, the other, or both (and have Observable be the default option for events).

Member

TheLarkInn commented Mar 9, 2016

I agree @wmaurer.

Do we want to force people to use observables? Or maybe set a configuration for the component to use one, the other, or both (and have Observable be the default option for events).

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Mar 11, 2016

Member

Just a little update. We think this use case is important and it should be included in the core of Angular. For now the focus is on Final, and this is a feature which can be added after final release, so don't expect much progress until after final release.

Member

mhevery commented Mar 11, 2016

Just a little update. We think this use case is important and it should be included in the core of Angular. For now the focus is on Final, and this is a feature which can be added after final release, so don't expect much progress until after final release.

@fenduru

This comment has been minimized.

Show comment
Hide comment
@fenduru

fenduru Mar 15, 2016

@robwormald I'd like to voice my concern about your comment

these are merely additive proposals, which add functionality that does not currently exist, so nobody panic.

One of the most difficult things with Angular 1 is how there are many ways to accomplish the same thing. Do I use service or factory? What about directive vs component? Should I use scope: { foo: '=' } or bindToController: { foo: '=' }? In Angular 1's case it is apparent that some of these are the result of iteration over time while trying not to break existing code, but this seems like a really unfortunate starting point for Angular 2. I fear that there will not be a clear "best practice" when there are many ways of accomplishing the same thing.

Ideally I think Angular 2 would have embraced observables as a first class citizen, and everything would have just used them (instead of requiring boilerplate to hook them up). But given that this is not the case, would it be possible to detect when you are trying to use observables and behave differently?

For instance, it would be nice to be able to do something like:

<input type='text' (click)='inputClick$($event)' >
class MyComponent {
  @ObservableCallback
  inputClick$
}
{
  @ObservableCallback
  inputClick$
}

Since inputClick$ is annotated as being an observable, "invoking" it in the template would be equivalent to calling subject.next($event)

fenduru commented Mar 15, 2016

@robwormald I'd like to voice my concern about your comment

these are merely additive proposals, which add functionality that does not currently exist, so nobody panic.

One of the most difficult things with Angular 1 is how there are many ways to accomplish the same thing. Do I use service or factory? What about directive vs component? Should I use scope: { foo: '=' } or bindToController: { foo: '=' }? In Angular 1's case it is apparent that some of these are the result of iteration over time while trying not to break existing code, but this seems like a really unfortunate starting point for Angular 2. I fear that there will not be a clear "best practice" when there are many ways of accomplishing the same thing.

Ideally I think Angular 2 would have embraced observables as a first class citizen, and everything would have just used them (instead of requiring boilerplate to hook them up). But given that this is not the case, would it be possible to detect when you are trying to use observables and behave differently?

For instance, it would be nice to be able to do something like:

<input type='text' (click)='inputClick$($event)' >
class MyComponent {
  @ObservableCallback
  inputClick$
}
{
  @ObservableCallback
  inputClick$
}

Since inputClick$ is annotated as being an observable, "invoking" it in the template would be equivalent to calling subject.next($event)

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Apr 8, 2016

@mhevery any chance you'd reconsider? I think the community could help with both design and implementation. This is super important if you going fully reactive and could reducer boilerplate by a lot..

fxck commented Apr 8, 2016

@mhevery any chance you'd reconsider? I think the community could help with both design and implementation. This is super important if you going fully reactive and could reducer boilerplate by a lot..

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Apr 9, 2016

Member

@fxck we have priority of things. If we do this, then we will not get to something, what we consider more important.

Member

mhevery commented Apr 9, 2016

@fxck we have priority of things. If we do this, then we will not get to something, what we consider more important.

@TheLarkInn

This comment has been minimized.

Show comment
Hide comment
@TheLarkInn

TheLarkInn Apr 9, 2016

Member

I agree with @mhevery. We can still implement observable events with extra work, I'd rather see something that is broke be fixed and wait for this in 2.x

Member

TheLarkInn commented Apr 9, 2016

I agree with @mhevery. We can still implement observable events with extra work, I'd rather see something that is broke be fixed and wait for this in 2.x

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

EDIT: The bit below as before I realized there were TWO proposals in this thread.

So I have a very limited understanding of how things currently work, so forgive me if this is a stupid question... but if I understand how Angular handles calling methods on Components from template correctly, this proposal might not be necessary.

Shouldn't this "just work™"?

<button (click)="clicks$.next($event)">make request</button>

From what I've been told, Angular is basically converting that to ($event) => this.clicks$.next($event) under the hood. Which would work just fine.

Contributor

benlesh commented Apr 21, 2016

EDIT: The bit below as before I realized there were TWO proposals in this thread.

So I have a very limited understanding of how things currently work, so forgive me if this is a stupid question... but if I understand how Angular handles calling methods on Components from template correctly, this proposal might not be necessary.

Shouldn't this "just work™"?

<button (click)="clicks$.next($event)">make request</button>

From what I've been told, Angular is basically converting that to ($event) => this.clicks$.next($event) under the hood. Which would work just fine.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Apr 21, 2016

Member

@blesh yes, it does, and that's how lots of people do it already. its much trickier for elements that don't exist in the template already and stuff that's loaded into the view

Member

robwormald commented Apr 21, 2016

@blesh yes, it does, and that's how lots of people do it already. its much trickier for elements that don't exist in the template already and stuff that's loaded into the view

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

EDIT: Again confused by thread depth and two proposals, I'm talking about the original issue here.

its much trickier for elements that don't exist in the template already and stuff that's loaded into the view

Wat? Why?

Contributor

benlesh commented Apr 21, 2016

EDIT: Again confused by thread depth and two proposals, I'm talking about the original issue here.

its much trickier for elements that don't exist in the template already and stuff that's loaded into the view

Wat? Why?

@laurelnaiad

This comment has been minimized.

Show comment
Hide comment
@laurelnaiad

laurelnaiad Apr 21, 2016

I think @robwormald 's two bullets associated with the "idiomatic longhand syntax" sum up the goodness of the proposal pretty well. A decorator to establish cold/lazy subscriptions that are unsbuscribed for free by framework onDestroy makes a lot of sense to me.

laurelnaiad commented Apr 21, 2016

I think @robwormald 's two bullets associated with the "idiomatic longhand syntax" sum up the goodness of the proposal pretty well. A decorator to establish cold/lazy subscriptions that are unsbuscribed for free by framework onDestroy makes a lot of sense to me.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

EDIT: Yup, still thinking about the original issue/proposal, no idea we were talking about a different proposal half way through the thread...

Look, I'm clearly all for RxJS'ing the hell out of things. I'm just unsure what the added syntax here buys anyone. Is it that you no longer need to define clicks$ as a property with a Subject on the component? Because having it there provides a hook point for testing, right? Niavely, you can next into that subject any time you want and inspect the outcome. In a more advanced world, you could replace it completely with a "hot observable" from Rx's TestScheduler and run the component through its paces with virtual time and marble diagrams.

Contributor

benlesh commented Apr 21, 2016

EDIT: Yup, still thinking about the original issue/proposal, no idea we were talking about a different proposal half way through the thread...

Look, I'm clearly all for RxJS'ing the hell out of things. I'm just unsure what the added syntax here buys anyone. Is it that you no longer need to define clicks$ as a property with a Subject on the component? Because having it there provides a hook point for testing, right? Niavely, you can next into that subject any time you want and inspect the outcome. In a more advanced world, you could replace it completely with a "hot observable" from Rx's TestScheduler and run the component through its paces with virtual time and marble diagrams.

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Apr 21, 2016

Member

This case is easy, as you mentioned: https://plnkr.co/edit/IBKc0O4P0zIAjfbgiokd?p=preview - a simple button that you assign at design-time.

The issue is that doing something like document.querySelectorAll('foo') is generally verboten in angular, as its not platform safe (doesn't work in a web worker, for example).

We have a mechanism for (platform safely) querying the view to return angular-friendly refs:

@Component({
  selector: 'tabs',
  template: `
    <some-child></some-child>
    <ng-content>
      <!-- stuff is projected into here -->
    </ng-content>   
  `
})
class SomeComponent {
  //query the view for all children with a selector
  @ViewChildren('some-child') someChildren:ChildElementType;
  //query the view for a specific child by type
  @ViewChild(SomeChildComponent) someChild: ChildElementType[];
  //query elements projected into the component's ng-content element
  @ContentChildren('tab') tab: Tab;
}

imagine it used like this:

<tabs>
  <tab>Tab 1 content </tab>
  <tab>Tab 2 content </tab>
</tabs>

So in this situation, you can't simply declaratively use (click)="$clicks.next($event)" because the elements don't exist when you define the template (in NG1 terms, they're transcluded) This proposal is effectively an angular-safe equivalent of Observable.fromEvent(document.querySelector('tab'), 'click') - but compatible with custom events defined by Components

Member

robwormald commented Apr 21, 2016

This case is easy, as you mentioned: https://plnkr.co/edit/IBKc0O4P0zIAjfbgiokd?p=preview - a simple button that you assign at design-time.

The issue is that doing something like document.querySelectorAll('foo') is generally verboten in angular, as its not platform safe (doesn't work in a web worker, for example).

We have a mechanism for (platform safely) querying the view to return angular-friendly refs:

@Component({
  selector: 'tabs',
  template: `
    <some-child></some-child>
    <ng-content>
      <!-- stuff is projected into here -->
    </ng-content>   
  `
})
class SomeComponent {
  //query the view for all children with a selector
  @ViewChildren('some-child') someChildren:ChildElementType;
  //query the view for a specific child by type
  @ViewChild(SomeChildComponent) someChild: ChildElementType[];
  //query elements projected into the component's ng-content element
  @ContentChildren('tab') tab: Tab;
}

imagine it used like this:

<tabs>
  <tab>Tab 1 content </tab>
  <tab>Tab 2 content </tab>
</tabs>

So in this situation, you can't simply declaratively use (click)="$clicks.next($event)" because the elements don't exist when you define the template (in NG1 terms, they're transcluded) This proposal is effectively an angular-safe equivalent of Observable.fromEvent(document.querySelector('tab'), 'click') - but compatible with custom events defined by Components

@TheLarkInn

This comment has been minimized.

Show comment
Hide comment
@TheLarkInn

TheLarkInn Apr 21, 2016

Member

Not only that but I'd assume we'd want to make it compatible with extensions of EventManagerPlugin that allow you to create custom events like "clickOutsideOfElement" etc.

Member

TheLarkInn commented Apr 21, 2016

Not only that but I'd assume we'd want to make it compatible with extensions of EventManagerPlugin that allow you to create custom events like "clickOutsideOfElement" etc.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

[REMOVED] I lacked context

Contributor

benlesh commented Apr 21, 2016

[REMOVED] I lacked context

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

Apologies, I didn't realize there was two proposals in here. Catching up.

Contributor

benlesh commented Apr 21, 2016

Apologies, I didn't realize there was two proposals in here. Catching up.

@benlesh

This comment has been minimized.

Show comment
Hide comment
@benlesh

benlesh Apr 21, 2016

Contributor

Okay... feedback: It seems like you'd have to be sure that @ObservableChild('.foo-bar', 'click') was more than just Observable.fromEvent(document.querySelector('.foo-bar'), 'click').

You'll need to do one of two things:

  1. Event delegation to the component it lived on, filtering out the events that matched a particular CSS selector.
  2. Or you can do something during the compilation and processing of content that allows you to identify those elements and set them up in a more direct fashion. I'm sure the latter is probably possible, but I don't know enough about it, and it sounds vastly more complicated than the first option. It may or may not be more efficient than the first option, because you'd be looking at adding N handlers rather than 1 handler, but at least you could remove all handlers that weren't being used.
Contributor

benlesh commented Apr 21, 2016

Okay... feedback: It seems like you'd have to be sure that @ObservableChild('.foo-bar', 'click') was more than just Observable.fromEvent(document.querySelector('.foo-bar'), 'click').

You'll need to do one of two things:

  1. Event delegation to the component it lived on, filtering out the events that matched a particular CSS selector.
  2. Or you can do something during the compilation and processing of content that allows you to identify those elements and set them up in a more direct fashion. I'm sure the latter is probably possible, but I don't know enough about it, and it sounds vastly more complicated than the first option. It may or may not be more efficient than the first option, because you'd be looking at adding N handlers rather than 1 handler, but at least you could remove all handlers that weren't being used.
@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Jul 10, 2016

Member

I sketched out (don't worry about the implementation nor consider using it yet!) what this API might look like in a plunk: https://plnkr.co/edit/bhA0at1BTbFoUzcpJBgs?p=preview

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Total Count: {{counter | async}}</h2>
      <incrementer></incrementer>
      <button #decrement>decrement</button>
    </div>
  `,
  directives: [Incrementer]
})
export class App {
  //query and listen to component output
  @ObserveChild(Incrementer, 'increment') increments:Observable<any>;
  //query and listen to a DOM element
  @ObserveChild('decrement', 'click') decrements:Observable<any>;
  counter: Observable<number>;

  ngAfterViewInit(){
    this.counter = Observable.merge(
      this.increments,
      this.decrements.mapTo(-1)
    )
    .startWith(0)
    .scan((total, value) => total + value, 0);
  }

}
Member

robwormald commented Jul 10, 2016

I sketched out (don't worry about the implementation nor consider using it yet!) what this API might look like in a plunk: https://plnkr.co/edit/bhA0at1BTbFoUzcpJBgs?p=preview

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Total Count: {{counter | async}}</h2>
      <incrementer></incrementer>
      <button #decrement>decrement</button>
    </div>
  `,
  directives: [Incrementer]
})
export class App {
  //query and listen to component output
  @ObserveChild(Incrementer, 'increment') increments:Observable<any>;
  //query and listen to a DOM element
  @ObserveChild('decrement', 'click') decrements:Observable<any>;
  counter: Observable<number>;

  ngAfterViewInit(){
    this.counter = Observable.merge(
      this.increments,
      this.decrements.mapTo(-1)
    )
    .startWith(0)
    .scan((total, value) => total + value, 0);
  }

}
@gdi2290

This comment has been minimized.

Show comment
Hide comment
@gdi2290

gdi2290 Jul 11, 2016

Member

@robwormald when in the component lifecycle will the ObserveChild Observable be available? Right now the prototype is limited to ngOnInit etc. It could be kool if we could get the child Observable within the constructor then we can wire up some crazy stuff.

Also, can we get syntax to "bubble" the event up the Observable child to its parents? I only want to subscribe at the root level so probably something like a passthrough/expose rather than subscribe proxy at each level

github-tipe-logo

Member

gdi2290 commented Jul 11, 2016

@robwormald when in the component lifecycle will the ObserveChild Observable be available? Right now the prototype is limited to ngOnInit etc. It could be kool if we could get the child Observable within the constructor then we can wire up some crazy stuff.

Also, can we get syntax to "bubble" the event up the Observable child to its parents? I only want to subscribe at the root level so probably something like a passthrough/expose rather than subscribe proxy at each level

github-tipe-logo

@TheLarkInn

This comment has been minimized.

Show comment
Hide comment
@TheLarkInn

TheLarkInn Jul 11, 2016

Member

@gdi2292 oo like am automagic 'controlled' .share() to children?

Member

TheLarkInn commented Jul 11, 2016

@gdi2292 oo like am automagic 'controlled' .share() to children?

@gdi2290

This comment has been minimized.

Show comment
Hide comment
@gdi2290

gdi2290 Jul 11, 2016

Member

update: the plunker angular version needs to be updated to latest angular for it to work again

I want access to child outputs in the constructor so can have full access to rxjs that way we can unlock the reactive potential out of Angular 2. Here's an example using the initial plunker prototype

screen shot 2016-07-11 at 1 53 11 am https://plnkr.co/edit/8aN2hK?p=preview I was able to get it working on the prototype in a similar hacky way and using `EventEmitter (aka Subject)` as a proxy. With this, we're able to create cycles at any level in the component tree. Now I just want to pass the child Observables up the tree which I guess you could support with by combining `@output` and `@ObserveChild` as I have in the example and also Observable inputs.

All I want is for everything in constructor to be Observables so we can wire everything up. This includes Input/ViewChild/ViewChildren/ContentChild/ContentChildren (pending router Data/Resolved/Params) which would allow for Angular 2 to become powerful with advanced reactive configs. If anyone wants to deal with sync values they can use the correct lifecycle hooks

@ObserveInput
@ObserveViewChild
@ObserveViewChildren
@ObserveContentChild
@ObserveContentChildren
/
I got all of these working in a prototype via AngularClass/angular2-observe-decorators

github-tipe-logo

Member

gdi2290 commented Jul 11, 2016

update: the plunker angular version needs to be updated to latest angular for it to work again

I want access to child outputs in the constructor so can have full access to rxjs that way we can unlock the reactive potential out of Angular 2. Here's an example using the initial plunker prototype

screen shot 2016-07-11 at 1 53 11 am https://plnkr.co/edit/8aN2hK?p=preview I was able to get it working on the prototype in a similar hacky way and using `EventEmitter (aka Subject)` as a proxy. With this, we're able to create cycles at any level in the component tree. Now I just want to pass the child Observables up the tree which I guess you could support with by combining `@output` and `@ObserveChild` as I have in the example and also Observable inputs.

All I want is for everything in constructor to be Observables so we can wire everything up. This includes Input/ViewChild/ViewChildren/ContentChild/ContentChildren (pending router Data/Resolved/Params) which would allow for Angular 2 to become powerful with advanced reactive configs. If anyone wants to deal with sync values they can use the correct lifecycle hooks

@ObserveInput
@ObserveViewChild
@ObserveViewChildren
@ObserveContentChild
@ObserveContentChildren
/
I got all of these working in a prototype via AngularClass/angular2-observe-decorators

github-tipe-logo

@mgechev

This comment has been minimized.

Show comment
Hide comment
@mgechev

mgechev Jul 14, 2016

Member

I like the proposal. The declarative bindings will not only enforce a better style of reactive programming but also stimulate applications with logic-less templates, which will increase cross-platform portability.

On top of that, having access to the Subjects in the controllers' constructors will help us write even more declarative code (as the prototype of @gdi2290 shows).

Member

mgechev commented Jul 14, 2016

I like the proposal. The declarative bindings will not only enforce a better style of reactive programming but also stimulate applications with logic-less templates, which will increase cross-platform portability.

On top of that, having access to the Subjects in the controllers' constructors will help us write even more declarative code (as the prototype of @gdi2290 shows).

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Oct 6, 2016

Is this being worked on now that final is out?

fxck commented Oct 6, 2016

Is this being worked on now that final is out?

@gdi2290

This comment has been minimized.

Show comment
Hide comment
@gdi2290

gdi2290 Oct 10, 2016

Member

I have a lib published if someone wants to experiment with the decorators

github-tipe-logo

Member

gdi2290 commented Oct 10, 2016

I have a lib published if someone wants to experiment with the decorators

github-tipe-logo

@fxck

This comment has been minimized.

Show comment
Hide comment
@fxck

fxck Oct 10, 2016

Sure, where can I find it?

fxck commented Oct 10, 2016

Sure, where can I find it?

@otodockal

This comment has been minimized.

Show comment
Hide comment
@amcdnl

This comment has been minimized.

Show comment
Hide comment
@amcdnl

amcdnl Nov 8, 2016

Contributor

@robwormald @IgorMinar - Is there any status updates on this?

I spoke w/ @gdi2290 the other day and we think I've got a pretty good use case for the issue being discussed.

In angular2-data-table I have the ability for users to re-order table header columns using 2 controls: orderable and draggable. In this scenario, draggable is completely decoupled from orderable. The header container is the orderable and the column cells are draggables. Code.

img

The orderable uses ViewChildren to subscribe to 2 events on each of the draggables like:

@ContentChildren(DraggableDirective, { descendants: true })
private draggables: QueryList<DraggableDirective>;

unfortunately since the column headers that are draggables can change, I have to use Differs to constantly diff the columns, unsubscribe from old one and subscribe to new ones like so:

  @ContentChildren(DraggableDirective, { descendants: true })
  private draggables: QueryList<DraggableDirective>;

  private differ: any;

  constructor(differs: KeyValueDiffers) {
    this.differ = differs.find({}).create(null);
  }

  ngAfterContentInit() {
    this.updateSubscriptions();
    this.draggables.changes.subscribe(
        this.updateSubscriptions.bind(this));
  }

  ngOnDestroy() {
    this.draggables.forEach(d => {
      d.dragStart.unsubscribe();
      d.dragEnd.unsubscribe();
    });
  }

  updateSubscriptions() {
    const diffs = this.differ.diff(this.draggables.toArray());

    if(diffs) {
      const subscribe = ({ currentValue, previousValue }) => {
        unsubscribe({ previousValue });

        if(currentValue) {
          currentValue.dragStart.subscribe(this.onDragStart.bind(this));
          currentValue.dragEnd.subscribe(this.onDragEnd.bind(this));
        }
      };

      const unsubscribe = ({ previousValue }) => {
        if(previousValue) {
          previousValue.dragStart.unsubscribe();
          previousValue.dragEnd.unsubscribe();
        }
      };

      diffs.forEachAddedItem(subscribe.bind(this));
      diffs.forEachChangedItem(subscribe.bind(this));
      diffs.forEachRemovedItem(unsubscribe.bind(this));
    }
  }

using @gdi2290 approach this could be very clean.

Contributor

amcdnl commented Nov 8, 2016

@robwormald @IgorMinar - Is there any status updates on this?

I spoke w/ @gdi2290 the other day and we think I've got a pretty good use case for the issue being discussed.

In angular2-data-table I have the ability for users to re-order table header columns using 2 controls: orderable and draggable. In this scenario, draggable is completely decoupled from orderable. The header container is the orderable and the column cells are draggables. Code.

img

The orderable uses ViewChildren to subscribe to 2 events on each of the draggables like:

@ContentChildren(DraggableDirective, { descendants: true })
private draggables: QueryList<DraggableDirective>;

unfortunately since the column headers that are draggables can change, I have to use Differs to constantly diff the columns, unsubscribe from old one and subscribe to new ones like so:

  @ContentChildren(DraggableDirective, { descendants: true })
  private draggables: QueryList<DraggableDirective>;

  private differ: any;

  constructor(differs: KeyValueDiffers) {
    this.differ = differs.find({}).create(null);
  }

  ngAfterContentInit() {
    this.updateSubscriptions();
    this.draggables.changes.subscribe(
        this.updateSubscriptions.bind(this));
  }

  ngOnDestroy() {
    this.draggables.forEach(d => {
      d.dragStart.unsubscribe();
      d.dragEnd.unsubscribe();
    });
  }

  updateSubscriptions() {
    const diffs = this.differ.diff(this.draggables.toArray());

    if(diffs) {
      const subscribe = ({ currentValue, previousValue }) => {
        unsubscribe({ previousValue });

        if(currentValue) {
          currentValue.dragStart.subscribe(this.onDragStart.bind(this));
          currentValue.dragEnd.subscribe(this.onDragEnd.bind(this));
        }
      };

      const unsubscribe = ({ previousValue }) => {
        if(previousValue) {
          previousValue.dragStart.unsubscribe();
          previousValue.dragEnd.unsubscribe();
        }
      };

      diffs.forEachAddedItem(subscribe.bind(this));
      diffs.forEachChangedItem(subscribe.bind(this));
      diffs.forEachRemovedItem(unsubscribe.bind(this));
    }
  }

using @gdi2290 approach this could be very clean.

@amcdnl amcdnl referenced this issue Nov 30, 2016

Closed

New selection implementation #328

2 of 3 tasks complete
@gdi2290

This comment has been minimized.

Show comment
Hide comment
@gdi2290

gdi2290 Dec 17, 2016

Member

see current issue for better rxjs support
#13248
github-tipe-logo

Member

gdi2290 commented Dec 17, 2016

see current issue for better rxjs support
#13248
github-tipe-logo

@amcdnl

This comment has been minimized.

Show comment
Hide comment
@amcdnl

amcdnl Dec 19, 2016

Contributor

@gdi2290 - should this be closed now?

Contributor

amcdnl commented Dec 19, 2016

@gdi2290 - should this be closed now?

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Dec 20, 2016

Member

Closed in favor of #13248

Member

mhevery commented Dec 20, 2016

Closed in favor of #13248

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment