How to detect change from one component into other
Asked Answered
H

2

7

Angular 4. Github source

I have a menu which is filled by a web service. The web service is in taskService, but is not necessary now.

ngOnInit() {
    this.getTasks();
    }
    getTasks(): void {
        this.taskService.getTasks()
            .subscribe(Tasks => this.tasks = Tasks);
    } 

When you click on a task it loads a page, a different component, with a form ready to update the data. It is also made by a web service and works fine. The problem is that after update the task, it is not reflected in the task menu

I am importing this:

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

and adding this to the constructor:

private cdRef: ChangeDetectorRef

And this is my best approach to the detectChanges() function, after update the data with the save() function

  this.taskService.updateTask(task, id)
          .subscribe(
              this.Ref.detach();
              setInterval(() => {
                this.Ref.detectChanges();
              }, 5000);
      );

This is the html from the menu to print the tasks:

   <li *ngFor="let task of tasks" class="d-inline-block col-md-12">
        <a routerLink="/task/{{task.id}}" > {{task.title}}</a>
        <!-- <span class="close big"></span> -->
        <button class="close big" title="delete task"
        (click)="delete(task)">x</button>
    </li>

And this is the form that updates the task

<form (ngSubmit)="save(taskName.value, taskBody.value)" #taskForm="ngForm" class="example-form">
  <mat-form-field class="example-full-width">
    <label>Task Name</label>
    <input matInput [(ngModel)]="task.name" #taskName name="name">
  </mat-form-field>

  <mat-form-field class="example-full-width">
    <textarea matInput [(ngModel)]="task.body" #taskBody name="body"></textarea>

  </mat-form-field>
  <button type="submit" class="btn btn-success" >Save</button>
</form>

Both are in different components.

I have tried to follow this tutorial, but I'm stuck, I don't know how to use the ChangeDetectorRef.

Habitue answered 20/12, 2017 at 19:31 Comment(12)
Can you post your html file too?Hetaera
done @BunyaminCoskunerHabitue
I'm trying to understand why you would need to trigger changeDetection manually. Angular should be able to pick it up when you update tasks. Do you set changeDetectionStrategy in your Component declaration?Hetaera
Have you confirmed that this.tasks in your subscribe callback is being redefined with the updated data from the form submission?Haddix
@BunyaminCoskuner no, nothing of that, i have to it manually, cuz they are in different components. Look here is the full project:Habitue
Oh, okay now I see the problem. I'll try to come up with a solution.Hetaera
After running the updateTask() in the view.task component, I did a console.log there, in the menu component, to see if something change, but no, my console log show me old data. @HaddixHabitue
Ok so after updateTask() finishes, it's returned to save() in your ViewTaskComponent, but there is no link to this.task in the subscription callback to this.taskService.updateTask(). Since updateTask uses a PATCH request, I'm assuming you're not getting the whole Task object back. So you won't be able to just say this.task = valuePassedToSubscribeCallback. Instead you can invoke this.viewTask() in that subscribe callback and fetch the entire updated Task object.Haddix
@Habitue I posted a possible solution. Check it out.Hetaera
thanks @BunyaminCoskuner i will test it now!Habitue
where to I put the invoke this.viewTask() ? in the save() function, inside the updatetask? @HaddixHabitue
Yup. Just think of it as the following flow: (1) submit form (2) make PATCH request (3) fetch updated data once PATCH finishes. Only place to know PATCH has finished is inside the callback for the subscription to updateTask.Haddix
H
6

I've looked at your code. The problem is that the view-task.component updates your tasks, but the navigation.component is not notified about this transaction. I think BehaviorSubject might be just the thing for you.

You can read more about it here

I assume you would have single array of tasks throughout your application and you would display them on your navigation component.

Task.service.ts

export class TaskService {
     // behaviorSubject needs an initial value.
     private tasks: BehaviorSubject = new BehaviorSubject([]);
     private taskList: Task[];

     getTasks() {
         if (!this.taskList || this.taskList.length === 0) {
             this.initializeTasks();
         }

         return this.tasks.asObservable();
     }

     initializeTasks() {
          this.http.get('api/tasks')
              .subscribe(tasks => {
                   // next method will notify all of the subscribers
                   this.tasks.next(tasks);
              }, error => {
                   // proper error handling here
              });
     }

     updateTasks(task: Task) {
          this.http.post('api/updateTask')
              .subscribe(resp => {
                   // update your tasks array
                   this.tasks = ...
                   // and call next method of your behaviorSubject with updated list
                   this.tasks.next(this.tasks);
              }, error => {
                   // proper error handling here    
              });
     }
}

Navigation.component.ts

 export class NavigationComponent implements OnInit{
      tasks: Task[];
      constructor(private taskService: TaskService) {}

      ngOnInit() {
          // this method will be called every time behaviorSubject
          // emits a next value.
          this.taskService.getTasks()
              .subscribe(tasks => this.tasks = tasks);
      }
 }

View-task.component.ts

 export class ViewTaskComponent {
     constructor(private taskService: TaskService) {}

     updateTask(task: Task) {
         this.taskService.updateTask(task);
     }
 }

I haven't tried this code myself. However, I have implemented something similar on my application before. So when you try it and have a problem, let me know.

Hetaera answered 20/12, 2017 at 20:15 Comment(5)
I have a diferente get http request, it give a problem: initializeTasks(): Observable<Task[]> { const url = ${this.mainUrl}/tasks; const returnGets = this.http.get<Task[]>(url) .subscribe (tasks => { this.tasks.next(tasks); }); return returnGets } Said that the type 'subscription' is not assignable to type 'observable<Task>'. Property '_isScalar' is missing in the subcriptionHabitue
You are defining your return type as Observable<Task[]>, but you are subscribing to the http request. If you want to return an Observable, you cannot subscribe to it. If you just want to get the data and assign it to some attribute, you should subscribe to it but can not define your return type as Observable. You should do one thing or the other.Hetaera
Okay, thanks it works, but with a couple of things different: service.ts , view-task.ts and the navitation.ts.Habitue
This is my updatetask() now: updateTask (task: Task, id){ const url = ${this.mainUrl}/node/${id}; return this.http.patch(url, task, httpHaljson) .subscribe(resp => { this.initializeTasks() }); }Habitue
I'm glad it works, just one final note. You don't need to return Subscription, probably no one is gonna do anything with itHetaera
H
3

Angular isn't running changeDetection because you aren't asking it to. None of the instance variables in your ViewTaskComponent are being updated inside your save method.

In your code, after updateTask() finishes, it returns to save() in your ViewTaskComponent. But there is no link to this.task in the subscription callback to this.taskService.updateTask().

Since updateTask uses a PATCH request, I'm assuming you're not getting the whole Task object back. So you won't be able to just say this.task = valuePassedToSubscribeCallback.

Instead you can invoke this.viewTask() in that subscribe callback and fetch the entire updated Task object.

For example:

this.taskService.updateTask(task, id)
    .subscribe(
        // new line here to fetch the updated Task object
        this.viewTask()
    );

Note: I'd second @Bunyamin Coskuner's suggestion to utilize BehaviorSubject's (or even a standard Subject). If you're up for a refactor, they are a clean way to manage state within your services.

Haddix answered 20/12, 2017 at 20:22 Comment(4)
I dont know how to log this, but i got an error StaticInjectorError[NavigationComponent]: NullInjectorError: No provider for NavigationComponent!Habitue
That's thrown because on line 32 of view-task.component.ts you've initialized your NavigationComponent private variable in your class. You shouldn't need to though. Is there a reason you have that in there? Try commenting it out at least and seeing if it runs.Haddix
done, but it doesn't work. I don't get anything after save() function. :(Habitue
Are you referring to the NavigationComponent not updating? In order for that to work I'd recommend implementing a Subject or BehaviorSubject.Haddix

© 2022 - 2024 — McMap. All rights reserved.