1

I am working on an Angular2 application and one of the @Components has a button that when clicked will send a post request to my server which will either respond with an Ok(string) or a BadRequest(string).

I am having trouble updating an @Input field of one of my @Components after getting the answer from the server.

Below are simplified version of some of my classes.

My Component class

@Component({
    moduleId: module.id,
    selector: 'model-comp',
    templateUrl: './model.component.html',
    styleUrls: ['./model.component.css']
})
export class MyComponent{
    @Input() model: Model;
    @Output() emitter: EventEmitter<Model> = new EventEmitter<Model>();

    public constructor(private service: MyService){}

    public toggle(): void {
        this.service.send(model.id, model.name){
            .subscribe(
                result  => this.onSuccess(result)),
                error   => this.onError(error),
                ()      => this.onComplete());
    }

    public onSuccess(result: string): void {
        if(result.inculdes("Some Text")) this.model.flag = true;
        else this.model.flag = false;
        this.emitter.emit(this.model);
    }

    public onError(error: any): void {
        //notification using bootstrap-notify
    }

    public onComplete(): void {
        //currently empty
    }
}

My Service class

export class MyService{

    public send(id: string, name: string){
        return <Observable<string>>this.http
            .post('url', new Dto(id, name))
            .map(result => this.getData<string>(result))
            .catch(this.catchBadResponse);
    }

    private getData<E>(result: Response): E {
        //checking if result.status is ok
        var body = result.json ? res.json(): null;
        return  <E>(body || {});
    }

    private catchBadRespomse: (error: any) => Observable<any> = (error: any) => {
        var response = <Response>error;
        var json = response.json();
        var msg = json.Message;
        var errormsg = json?
            (json.error ? json.error: JSON.stringify(msg?msg:json)) :
            (response.statusText || 'Error?');
        return Obserable.of(errormsg);
    }

}

Template of MyComponent

<button (click)="toggle()"
[ngClass]="{'class1': true, 'class2': model.flag}">Text</button>

Template of Parent Component

<div *ngFor="let model of getList()">
    <model-comp [model]="model" (emitter)="onEmit($event)"></model-comp>
</div>

The onEmit Function

onEmit(evt: any): void{
    if(evt instanceof Model){
        var evtModel = evt as Model;
        this.list.find(search => search.id == evtModel.id)
            .isFav = evtModel.isFav;
    }
}

The problem is that even though I post my data and receive the response, The property flag of my model does not change.

I think that the click event reloads the component thus removing the observers of the EventEmitter.

So is there any way to cancel the reload, not lose the observers of the EventEmitter or any other way to update the root object or the element class?

15
  • If you have an input and output that belong together you should name it like @Input() model:Model; and @Output() modelChange:EventEmitter<Model> = ... then you can use "two-way" binding like [(model)]="someProp" Commented Dec 19, 2016 at 10:17
  • @GünterZöchbauer does it matter how I name the output field? Commented Dec 19, 2016 at 10:18
  • Names always matter ;-) If it's about the same property then they should be named according to the schema I mentioned. Just a hint - not related to your actual issue. Commented Dec 19, 2016 at 10:20
  • Why would the subscription reload the component? I don't think this is the case. Why do you think the the model.flag is not changed - because the class2 is not added? Commented Dec 19, 2016 at 10:32
  • Can you try to add a button like <button (click)="model.flag = !model.flag">click me</button> and check if this results in the expected behavior? Commented Dec 19, 2016 at 10:33

3 Answers 3

1
+50

update (see comments below the question)

If getList() (what *ngFor binds to) returns a new list every time it is called, *ngFor will be permanently busy rerendering the items because change detection will cause getList() being called again and again.

Binding to a function that returns a new object or array every time it's called directly will cause serious issues like exceptions and dramatic performance degredation.

Using method/function calls in the view is strongly discouraged in general. Rather assign the list to a field and bind to that field instead of the method.

ngOnInit() is fine for initializing the list but also any event handler for initializing or updating the list.

original

If you modify the model value that you got passed in from the parent, then the parent also sees the change. Emitting the value as an event is probably redundant.

I guess you are modifying list (from <div *ngFor="let model of list">) in onEmit() which then causes *ngFor to rerender the list.

Sign up to request clarification or add additional context in comments.

1 Comment

I can't imagine this to work. The syntax in your version doesn't make any sense. I'm not entirely sure whether class1 and class2 need the '. I assumed they are only required in Dart but I might have mixed something up. But I'm sure the = is wrong.
0

I don't think you should change @input property from within the component. it suppose to listen and act to changes from the parent component.

5 Comments

So is there any way to change the model of the parent component? Or a way to add/remove the class of the button?
absolutely, you should read this: blog.thoughtram.io/angular/2016/10/13/…
Not quite sure where I should use the 2 way data binding? Could you add it to your answer? Should I add it like <my-comp [(model)] = "model.subModel"></my-comp> ?
exactly. the parent push data to the child component and listen to changes coming from the child component.
I tried using an EventEmitter but the observers get removed when I call emit in the onSuccess() method (works when called from the toggle but it's kinda useless there).
0

MyComponent.ts

export class MyComponent{
    @Input() model: Model;
    //@Output() emitter: EventEmitter<Model> = new EventEmitter<Model>();

    public constructor(private service: MyService){}

    public toggle(): void {
        this.service.send(model.id, model.name){
            .subscribe(
                result  => this.onSuccess(result)),
                error   => this.onError(error),
                ()      => this.onComplete());
    }

    public onSuccess(result: string): void {
        if(result.inculdes("Some Text")) this.model.flag = true;
        else this.model.flag = false;
        //this.emitter.emit(this.model);
        this.service.emitter.next(false);
    }

    public onError(error: any): void {
        //notification using bootstrap-notify
    }

    public onComplete(): void {
        //currently empty
    }
}

Service

@Injectable // important
export class MyService{
    public emitter: Subject<any> = new Subject();
    public send(id: string, name: string){
        return <Observable<string>>this.http
            .post('url', new Dto(id, name))
            .map(result => this.getData<string>(result))
            .catch(this.catchBadResponse);
    }

    private getData<E>(result: Response): E {
        //checking if result.status is ok
        var body = result.json ? res.json(): null;
        return  <E>(body || {});
    }

    private catchBadRespomse: (error: any) => Observable<any> = (error: any) => {
        var response = <Response>error;
        var json = response.json();
        var msg = json.Message;
        var errormsg = json?
            (json.error ? json.error: JSON.stringify(msg?msg:json)) :
            (response.statusText || 'Error?');
        return Obserable.of(errormsg);
    }

}

Now you can listen to Service.emitter anywhere in app

2 Comments

Pretty sure it should be @Injectable()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.