Home > other >  Angular updating values across views
Angular updating values across views

Time:05-11

I have an Angular app that has the following: One component has a text input and a button. The user fills in the text input and clicks the button. This updates a the URL for a router link.

The router link loads a component called view and it in turn reads the URL parameter from the router link and places that value in a service and displays it on the component so I know it worked.

So if the user type 'abc' in the text input then the router link URL would be /view/abc. So 'abc' will be displayed in the view component. Sometimes users will paste a router link like /view/def. This works to update the view component.

The part I can't get to work is to update the text input box in the other component to reflect the current value of the pasted link.

I tried using 'AfterViewChecked' to read the value from the service but that executes before the service value is updated so it is always incorrect.

These cannot bind to the same variable because this will eventually turn into a web service call and I don't want the service to be updated while the user is typing into the text input box, only when they click the button.

I'm not sure where else to look. Any searching I do just brings up data binding, but that is not my problem.

The relevant files are below but the full test sample code is on StackBlitz at https://stackblitz.com/edit/github-jwr6wj.

If you change the URL in the text input and click the button the URL display below will update. But if you paste in the pseudo URL https://github-jwr6wj.stackblitz.io/view/http%3A%2F%2Fwww.ebay.com%2F the URL displayed below will update correctly but I can't figure out how to update the text input to reflect what came in with the URL.

update.service.ts contains the URL that is the current one. This service will also load the data from a web service.

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

@Injectable({
  providedIn: 'root'
})
export class UpdateService {
  url: string = "http://www.google.com/";

  constructor() { }
}

view.component.ts is where the data selected by the user will be displayed. It parses the URL parameter for the data and updates the service with it.

import { ActivatedRoute, ParamMap } from '@angular/router';

import { UpdateService } from '../update.service';

@Component({
  selector: 'app-view',
  templateUrl: './view.component.html',
  styleUrls: ['./view.component.css']
})
export class ViewComponent implements OnInit {

  constructor(public activatedRoute:ActivatedRoute, public updateService: UpdateService) { }

  ngOnInit(): void {
    this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) =>{
      this.getUrl(paramMap);
    });
  }

  getUrl(paramMap: ParamMap): void {
    const incomingUrl = paramMap.get("url");

    if (incomingUrl == null) {
      this.updateService.url = "http://www.google.com/";
    } else {
      this.updateService.url = decodeURIComponent(incomingUrl);
    }
  }
}

view.component.html

<p>URL: {{updateService.url}}</p>

toolbar.component.ts is where the user will enter they request. sourceUrl is the variable that will be updated when the user types. However I also want it to update when the page is visited via the browser URL with the correct data as part of that URL. I can send data to the view component via the router but I can't find out how to send data back to the toolbar component.


import { UpdateService } from '../update.service';

@Component({
  selector: 'app-toolbar',
  templateUrl: './toolbar.component.html',
  styleUrls: ['./toolbar.component.css'],
})
export class ToolbarComponent implements OnInit {
  sourceUrl: string = '';

  constructor(private updateService: UpdateService) {}

  ngOnInit(): void {
    this.sourceUrl = this.updateService.url;
  }

  getViewUrl(): string {
    return '/view/'   encodeURIComponent(this.sourceUrl);
  }
}

toolbar.component.html

<div >
  <input type="text" [(ngModel)]="sourceUrl" />
  <a  [routerLink]="getViewUrl()">
    <span ></span>
  </a>
</div>

CodePudding user response:

One way to share data between components is using a Service and Observables. Change your url in the Service to be BehaviorSubject with an initial value.

The way BehaviorSubject works is that you emit values from components to update the Observable in the Service. The BehaviorSubject behaves both as an Observer and Observable.

Essentially, an Observer is an object that listens to events, in this case, updating the URL. An Observable is an object that components listen to for updates or changes. In this case, the View Component listens to the BehaviorSubject for this update to the URL.

Service

export class UpdateService {
  private url$ = new BehaviorSubject<string>('www.google.com');
  public readonly url: Observable<string> = this.url$.asObservable(); 
  constructor() {}
}

Toolbar Component

export class ToolbarComponent implements OnInit {
  sourceUrl: string = '';

  constructor(private updateService: UpdateService) {}

  ngOnInit(): void {
    this.updateService.url.subscribe((str) => {
      this.sourceUrl = str;
    });
  }

  getViewUrl(): string {
    return '/view/'   encodeURIComponent(this.sourceUrl);
  }
}

View Component

export class ViewComponent implements OnInit {
  constructor(
    public activatedRoute: ActivatedRoute,
    public updateService: UpdateService
  ) {}

  ngOnInit(): void {
    this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) => {
      this.getUrl(paramMap);
    });
  }

  getUrl(paramMap: ParamMap): void {
    const incomingUrl = paramMap.get('url');

    if (incomingUrl == null) {
      this.updateService.url.next('http://www.google.com/');
    } else {
      this.updateService.url.next(decodeURIComponent(incomingUrl));
    }
  }
}

View Component HTML

<p>URL: {{ updateService.url | async }}</p>

CodePudding user response:

You are right to try with AfterViewChecked because it's just a timing issue. What you could do is have url inside updateService defined as a BehaviourSubject, so that at the moment it's updated in your view component, you see the change in the toolbar component.

Inside the service :

public url$: BehaviorSubject<string> = new BehaviorSubject("http://www.google.com/");

Inside the view component ts :

getUrl(paramMap: ParamMap): void {
 const incomingUrl = paramMap.get("url");
 if (incomingUrl == null) {
  this.updateService.url$.next("http://www.google.com/");
 } else {         
  this.updateService.url$.next(decodeURIComponent(incomingUrl));
 }
}

And inside the view component HTML : (you can also subscribe to the Behaviour Subject directly inside the ts)

<p>URL: {{updateService.url$ | async}}</p>

And you will also have to deal with the fact that the url is a Subject inside the toolbar component ts!

Good luck, let me know if this is not clear!

  • Related