1

I'm struggling around with the observe service approach between two components. I have a profile component that includes a settings component. Both uses the user.service where the provider is defined within app.module.ts. The provider isn't defined any where else. So when I change as example the firstName over the settings.component and save it. The change won't get reflected within the profile.component sidebar until I reload the page.

After hours: I found a solution to do that over an eventemitter that will be trigger as soon as the update occurs. This would solve my problem above but I think this isn't the right way to go since I read the article below:

What is the proper use of an EventEmitter?

Shouldn't it work without an event emmiter?

So my question: What approach would you use for that situation or what is the best practise? Of course there can be an issue within my code. I'm really new in angular.

app.module.ts

Within the app.module.ts i defined:

 providers: [UserService]

profile.component.ts

@Component({
    selector: 'app-profile',
    templateUrl: './profile.component.html',
    styleUrls: ['./profile.component.css']
})

export class ProfileComponent implements OnInit {
    user: any;
    Role = Role;
    constructor(private _userService: UserService, ) { }


    ngOnInit() {
        const token = localStorage.getItem('token');
        const role = jwt.decode(token).user.role;
        this._userService.getUser().subscribe(v => { this.user = v; });
        // Event Emmiter Subscription
        this._userService.change.subscribe(v => { this.user = v; });
    }
}

profile.component.html

<div *ngIf="user" class="container mt50">
    <div class="row profile">
        <div class="col-md-3">
            <div class="profile-sidebar">
                <!-- SIDEBAR USERPIC -->
                <div class="profile-userpic">
                    <img gravatar [email]="user.email" [size]="256" class="img-responsive animated bounceIn" alt="">
                </div>
                <!-- END SIDEBAR USERPIC -->
                <!-- SIDEBAR USER TITLE -->
                <div class="profile-usertitle">
                    <div class="profile-usertitle-name">
                        {{ user.firstName }} {{ user.lastName }}
                    </div>
                    <div class="profile-usertitle-job">
                        {{ user.email }}
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-9">
                <app-profile-settings-admin *ngIf="user.role === Role.ADMINISTRATOR"></app-profile-settings-admin>
        </div>
    </div>
</div>

user.service.ts

@Injectable()
export class UserService {
    public user: any;

    constructor(private http: Http) { }
    // EVENT EMIITER
    public change: EventEmitter<any> = new EventEmitter();
    getUser() {
        const token = localStorage.getItem('token')
            ? '?token=' + localStorage.getItem('token')
            : '';
        return this.http.get('api/user' + token)
            .map((response: Response) => {
                this.user = response.json().obj;
                return this.user;
            })
            .catch((error: Response) => Observable.throw(error.json()));
    }

    updateUser(user) {
        const body = JSON.stringify(user);
        const headers = new Headers({'Content-Type': 'application/json'});
        const token = localStorage.getItem('token')
            ? '?token=' + localStorage.getItem('token')
            : '';
        return this.http.patch('api/user/' + token, body, {headers: headers})
            .map((response: Response) => {
                const result = response.json();
                this.user = result.obj;
                // EMIT EVENT
                this.change.emit(this.user);
                return this.user;
            })
            .catch((error: Response) => Observable.throw(error.json()));
    }
}

profile.settings.admin.component.ts

Within that component there is nothing more than a form that will fire the following code on submit:

onSubmit() {
    this._userService.updateUser(this.userForm.value)
        .subscribe(
            data => {
                this._toastyService.default('Settings saved!');
                console.log(data);
            },
            error =>{
                this._toastyService.default('Error occured!');
                console.log(error);
            }
        );
}
Aravind
  • 40,391
  • 16
  • 91
  • 110
vitalragaz
  • 308
  • 1
  • 10
  • 1
    https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service – JB Nizet Apr 02 '18 at 10:03
  • 1
    A best practice is to not use EventEmitter for that, this was explained in linked question https://stackoverflow.com/questions/36076700/what-is-the-proper-use-of-an-eventemitter . Use RxJS Subject instead. – Estus Flask Apr 02 '18 at 10:12

1 Answers1

1

Live working example You do not need to fire an Event to share (and sync) data between components. Let Angular services (and Angular forms) to control that for you:

Profile.component

ngOnInit() {
    const token = localStorage.getItem('token');
    const role = jwt.decodeToken(token).user.role;
    this._userService.getUser().subscribe(v => {
        this.user = v; 
    });
}

user.service

getUser() {
    const token = localStorage.getItem('token')
        ? '?token=' + localStorage.getItem('token')
        : '';
    return this.http.get('api/user' + token)
        .pipe(map((user: HttpResponse<any>) => {
            this.user = user;
                return this.user;
            }),
        catchError((error: HttpErrorResponse) => Observable.throw(error)))
}

updateUser(user) {
    const body = JSON.stringify(user);
    const headers: HttpHeaders =  new HttpHeaders({'Content-Type': 'application/json'});
    const token = localStorage.getItem('token')
        ? '?token=' + localStorage.getItem('token')
        : '';
    return this.http.patch('api/user/' + token, body, {headers})
        .pipe(map((user: HttpResponse<any>) => {
            this.user = user;
            return this.user;
        }),
        catchError((error: HttpErrorResponse) => Observable.throw(error)));
}

profile.settings.admin.component.ts

ngOnInit() {
    this._userService.getUser().subscribe((user: User) => {
        this.user = user;         
    });
}

onSubmit() {
    this._userService.updateUser(this.user)
        .subscribe(
            data => {
                this._toastyService.default('Settings saved!');
                console.log(data);
            },
            error =>{
                this._toastyService.default('Error occured!');
                console.log(error);
            }
        );
}

profile.settings.admin.component.html

<form #userForm="ngForm">
    <div class="form-group">
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name" aria-describedby="nameHelp" 
            placeholder="enter name" [(ngModel)]="user.name" name="name" required #name="ngModel">
        <div [hidden]="name.valid || name.pristine"
            class="alert alert-danger">
            Name is required
        </div>
    </div>
     <div class="form-group">
        <label for="lastName">Last Name</label>
        <input type="text" class="form-control" id="lastName" aria-describedby="lastNameHelp" 
            placeholder="enter last name" [(ngModel)]="user.lastName" name="lastName" required #lastName="ngModel">
        <div [hidden]="lastName.valid || lastName.pristine"
            class="alert alert-danger">
            last Name is required
        </div>
    </div>
    <div class="form-group">
        <label for="email">Email</label>
        <input type="email" class="form-control" id="email" aria-describedby="emailHelp" 
            placeholder="enter email" [(ngModel)]="user.email" name="email" required #email="ngModel">
        <div [hidden]="email.valid || email.pristine"
            class="alert alert-danger">
            email is required
        </div>
    </div>

    <button type="submit" class="btn btn-success" (click)="onSubmit()" >Submit</button>
</form>
Luillyfe
  • 6,183
  • 8
  • 36
  • 46