Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2, change another component's view from unrelated component

I'm able to change the value of a component's variable from another component as console.log() shows in this example.

My problem is that second component's view does not refresh despite the variable change.

Example:

first.component.ts

import {Component} from '@angular/core';
import {SecondComponent} from './second.component';

@Component({
    selector: 'first',
    template: `
        <h2>First component</h2>
        <button (click)="changeOutside()">Change</button>
    `,
    providers: [SecondComponent]
})
export class FirstComponent {
    constructor(private otherComponent: SecondComponent) {}
    changeOutside() {
        this.otherComponent.change();
    }
}

second.component.ts

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

@Component({
    selector: 'second',
    template: `
        <h2>Second component</h2>
        <div>Pet: {{animal}}</div>
        <button (click)="change()">Change</button>
    `
})
export class SecondComponent {
    animal: string = 'Dog';
    change() {
        this.animal = 'Cat';
        console.log(this.animal);
    }
}

Both components are totally unrelated from different branches of the DOM tree.

I've also tried with the first component emitting an event and the second component subscribing to it with the same result, the variable changes but view does not update.


After Günter's suggestion (thanks), I've tried with the next solution with no success. Even the console.log is not working.

first.component.ts

import {Component} from '@angular/core';
import {UpdateService} from './update.service';

@Component({
    selector: 'first',
    template: `
        <h2>First component</h2>
        <button (click)="changeOutside()">Change</button>
    `
})
export class FirstComponent {
    constructor(private updateService: UpdateService) {}
    changeOutside() {
        this.updateService.changeAnimal('Cat');
    }
}

update.service.ts

import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';

@Injectable()
export class UpdateService {
    // Observable string sources
    private newAnimalSource = new Subject<string>();
    // Observable string streams
    newAnimal$ = this.newAnimalSource.asObservable();

    changeAnimal(animal: string) {
        this.newAnimalSource.next(animal);
    }
}

second.component.ts

import {Component} from '@angular/core';
import {UpdateService} from './update.service';
import {Subscription} from 'rxjs/Subscription';
import {Subject} from 'rxjs/Subject';

@Component({
    selector: 'second',
    template: `
        <h2>Second component</h2>
        <div>Pet: {{animal}}</div>
        <button (click)="change()">Change</button>
    `
})
export class SecondComponent {
    animal: string = 'Dog';
    subscription: Subscription;
    constructor(private updateService: UpdateService) {
        this.subscription = updateService.newAnimal$.subscribe(
            animal => {
                console.log(animal);
                this.animal = animal;
        });
    }
    change() {
        this.animal = 'Cat';
    }
}

Solved

Finally, the solution proposed by Günter worked after adding UpdateService as a provider in the @NgModule of app.module.ts

like image 354
Manel Avatar asked Sep 27 '16 10:09

Manel


People also ask

How do you call a method from a component from another component?

We can click the “Call Function” button. It will call the function in the first component. Now we can click the second component link and click the “Call First Component Function” button.

How do you call Ngoninit of another component?

The short answer is to use ViewChild to call the function on component B.

How do you pass data between components in angular using Behaviorsubject?

First, create a service product-service with property a product$ field behavior subject to keep the product's value and declare a variable selectedProduct as observable from the product behavior subject. Next, create a new method, setProduct , to set the selected product and update the behavior.


2 Answers

update

I'm pretty sure the cause of your problem is that you expect

constructor(private otherComponent: SecondComponent) {}

to do something that it actually doesn't do at all.

With this constructor you get a SecondComponent instance injected that is not related to the component shown in your page. This is why your view isn't updated. You didn't update the visible component but an entirely unrelated class.

You need to use a different strategy to get a reference to the other component.

The best way is to not get a reference to the component at all but instead inject a shared service to both and communicate using observables.

For more details refer this official angular 2 cookbook

original

If both components are "totally unrelated", I assume this means they are from individually bootstrapped module and therefore inside different Angular2 applications on the same page.

In this case these components run in different zones and the click event will only cause change detection in the calling component but not in the callee component.

You need to invoke change detection explicitly in the callee to get the view updated. For example like

import {Component, ChangeDetectorRef} from '@angular/core';

@Component({
    selector: 'second',
    template: `
        <h2>Second component</h2>
        <div>Pet: {{animal}}</div>
        <button (click)="change()">Change</button>
    `
})
export class SecondComponent {
    constructor(private cdRef:ChangeDetectorRef){}

    animal: string = 'Dog';
    change() {
        this.animal = 'Cat';
        console.log(this.animal);
        this.cdRef.detectChanges();
    }
}

or

import {Component, NgZone} from '@angular/core';

@Component({
    selector: 'second',
    template: `
        <h2>Second component</h2>
        <div>Pet: {{animal}}</div>
        <button (click)="change()">Change</button>
    `
})
export class SecondComponent {
    constructor(private zone:NgZone){}

    animal: string = 'Dog';
    change() {
        this.zone.run(() => {
          this.animal = 'Cat';
          console.log(this.animal);
        });
    }
}
like image 71
Günter Zöchbauer Avatar answered Sep 20 '22 11:09

Günter Zöchbauer


Solved

As I edited in the question, the solution proposed by Günter, which is the second series of files, worked after adding UpdateService as a provider in the @NgModule of app.module.ts

like image 32
Manel Avatar answered Sep 23 '22 11:09

Manel