So say i have page one:
This page contains multiple variables and a constructor. it could look something like this:
export class TestPage implements OnInit {
testInt: number;
testString: string;
constructor(private someService: SomeService) {
}
ngOnInit() {
this.testInt = this.someService.getInt();
this.testString = this.someService.getLongText();
}
}
Now when this page loads it correctly sets the values.
Now say that I change page and on this page, I change some of the values in the service.
When I then come pack to this TestPage it hasn't updated the values.
Does this have something to do with caching? or with push state?
How can I make sure that the page is "reloaded" ?
Try using RxJS.
#Injectable({...})
class SomeService {
private _testInt: BehaviorSubject<number> = new BehaviorSubject<number>(0); // initial value 0
setTestInt(value: number) {
this._testInt.next(value);
}
getTestInt(): Observable<number> {
return this._testInt.asObservable();
}
}
#Component({...})
class TestPage implements OnInit {
public testInt: number;
public testInt$: Observable<number>;
private subscription: Subscription;
constructor(private someService: SomeService) {}
ngOnInit() {
// one way
this.testInt$ = this.someService.getTestInt();
// or another
this.subscription = this.someService.getTestInt()
.subscribe((value: number) => {
this.testInt = value;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
in the HTML:
<p>{{ testInt }}</p>
<p>{{ testInt$ | async }}</p>
If you are subscribing to a Observable, make sure you unsubscribe after the usage (usually On Destroy lifecycle hook).
Async Pipe does that out of the box.
Or try the ionViewWillEnter lifecycle hook.
As you can see in the official documentation:
ngOnInit will only fire each time the page is freshly created, but not when navigated back to the page.
For instance, navigating between each page in a tabs interface will only call each page's ngOnInit method once, but not on subsequent visits.
ngOnDestroy will only fire when a page "popped". link That means that Page is cached, yes. Assigning value On Init will set the value only the first time page is visited and therefore not updated.
Related
In an Angular 11 app, I have a simle service that mekes a get request and reads a JSON.
The service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Promo } from '../models/promo';
#Injectable({
providedIn: 'root'
})
export class PromoService {
public apiURL: string;
constructor(private http: HttpClient) {
this.apiURL = `https://api.url.com/`;
}
public getPromoData(){
return this.http.get<Promo>(`${this.apiURL}/promo`);
}
}
In the the component, I need to compare the array of products with the array of campaign products (included in the JSON mantioned above) and higlight the promoted products:
export class ProductCardComponent extends DestroyableComponent implements OnInit, OnChanges
{
public promoData: any;
public promoProducts: any;
public isPromoProduct: boolean = false;
public ngOnInit() {
this.getCampaignData();
}
public ngOnChanges(changes: SimpleChanges): void {
this.getCampaignData();
}
public getPromoData() {
this.promoService.getPromoData().pipe(takeUntil(this.destroyed$)).subscribe(data => {
this.promoData = data;
this.promoProducts = this.promoData.products;
let promoProduct = this.promoProducts.find((product:any) => {
return this.product.unique_identifier == product.unique_identifier;
});
if (promoProduct) {
// Update boolean
this.isPromoProduct = true;
}
});
}
}
In the component's html file (template), I have:
<span *ngIf="isPromoProduct" class="promo">Promo</span>
There are no compilation errors.
The problem
For a reason I have been unable to understand, the template does not react to the change of the variable isPromoProduct and the template is not updated, despite the fact that I call the function inside ngOnInit and ngOnChanges.
Questions:
Where is my mistake?
What is a reliable way to update the template?
subscribing to Observable inside .ts file it's mostly not a best practice.
try to avoid it by using async pipe of Angular.
you need to store the observable in the variable and not the data returned from the observable, for example:
// this variable holds the `observable` itself.
this.promoData$ = this.promoService.getPromoData()
and then in the template you can do it like this:
<div *ngIf="promoData$ | async as promoData">
here you can access the promoData
</div>
you can still use pipe() to map the data etc but avoid the subscribe()
The isPromoProduct boolean is not an input. The ngOnChanges gets triggered for changes on your properties that are decorated with the #Input decorator. For your particular case, you can inject the ChangeDetectorRef and trigger change detection manually:
constructor(private cdr: ChangeDetectorRef) {}
// ...
public getPromoData() {
this.promoService.getPromoData().subscribe(data => {
// ...
if (promoProduct) {
// Update boolean
this.isPromoProduct = true;
this.cdr.detectChanges();
}
});
}
You also don't need to manage httpClient subscriptions. The observables generated by a simple get or post request will complete after they emit the response of the request. You only need to explicitly manage the unsubscribe for hot observables (that you create from subjects that you instantiate yourself).
Lets say I have 2 components, aComponent and bComponent. I have them redered inside the AppComponent
<app-a>
<app-b>
And I have service myService that has method .trigger().
What I want is to show only aComponent, but whenever I call myService.trigger() from another part of code, it would switch and show bComponent. That's perfect implementation that I can't reach.
Question is: Is it possible to do so? And if not what is the best closest solution.
The only working solution I got:
I added .trigger() inside AppComponent
export class AppComponent {
title = 'spa';
show: boolean = false;
trigger() {
this.show = true;
}
}
And rendered components like so:
<div *ngIf="!show; else show">
<app-a></app-a>
</div>
<ng-template #show>
<app-b></app-b>
</ng-template>
Then whenever I want to trigger switching, I add instance of the app to the constructor and call it's method:
export class AnotherComponent implements OnInit {
constructor(
private app: AppComponent
) {}
ngOnInit(): void {
this.app.trigger();
}
}
Even though it's working pretty good, I myself see that it's a dirty solution. Components are not intended to be used inside another components, but Services are.
You can use Subject from rxjs library for that.
In your service file:
// a-service.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({ providedIn: 'root' })
export class AService {
private subject = new Subject<any>();
trigger(state: boolean) {
this.subject.next(state);
}
getTrigger(): Subject<any> {
return this.subject;
}
}
and in your AppComponent:
// app.component.ts
...
private show = false;
constructor (private aService: AService) { }
ngOnInit() {
this.aService.getTrigger().subscribe(state => {
this.show = state;
});
}
the template can be as you provided - it's fine:
<div *ngIf="!show; else show">
<app-a></app-a>
</div>
<ng-template #show>
<app-b></app-b>
</ng-template>
And if you want to trigger from another component, you do it like this:
// another.component.ts
...
constructor (private aService: AService) { }
ngOnInit() {
this.aService.trigger(true);
}
One way to communicate between different components and services which aren't directly related, is via 'Subjects'.
You can try to create a subject and pass in values to it from myService.trigger(). And you can subscribe to that subject from whichever component you want to access that trigger data.
Within my anguular app , i ve this service :
#Injectable()
export class myService{
myBehaviouSubject= new BehaviorSubject("");
setData(){
this.myBehaviouSubject.next("123");
}
}
Inside my app.component , i m able to get the value , but i want to keep it readonly or editable only inside the service itself , i want to prevent to push any data from component (.next('DATA'))
#Component({
})
export class AppComponent implements OnInit {
constructor(public myService : MyService) { }
getData(){
// GET VALUE
this.myService.myBehaviouSubject.value
}
unwantedMethodToSetValue(){
// SET VALUE -> i want to prevent this
this.myService.myBehaviouSubject.next("unwanted value")
}
}
Suggestions ?
You can keep the observable inside service only by declaring it as private field of a class.
#Injectable()
export class myService {
private myBehaviouSubject = new BehaviorSubject("");
// Use this observable inside the app component class.
myBehaviouSubjectObservable = myBehaviouSubject.asObservable();
setData() {
this.myBehaviouSubject.next("123");
}
}
#Component({
})
export class AppComponent implements OnInit {
constructor(public myService: MyService) {}
getData() {
// You can subscribe to observable and can get value here
this.myService.myBehaviouSubjectObservable.subscribe((value) => {
console.log(value);
})
}
unwantedMethodToSetValue() {
// SET VALUE -> you cannot do this here now.
this.myService.myBehaviouSubject.next("unwanted value")
}
}
Use property access modifiers:
#Injectable()
export class MyService{
private myValueSubject: BehaviorSubject<string> = new BehaviorSubject<string>("");
public readonly myValueObservable: Observable<string> = this.myValueSubject.asObservable();
public setData() {
this.myValueSubject.next("123");
}
public getData(): string {
return this.myValueSubject.value;
}
}
Instances of MyService will not have a publicly accessible subject.
I usually try to avoid a method like getData, favoring subscriptions to the related observable. If I ever find myself writing those kinds of methods, it's a warning flag to re-evaluate my architecture. If you just want to store a value and get/set it with methods, use a plain old private property. The entire purpose of the subject is defeated if you are only ever getting the value through a method like getData()
Check out the documentation for typescript classes, which discusses access modifiers: https://www.typescriptlang.org/docs/handbook/classes.html
The traditional answer : If you return the Subject as an observable, you disallow .next() calls.
But in your case, you also want direct access to the current value without subscribing, so you could add a getter for that too.
#Injectable()
export class myService{
private readonly myBehaviouSubject = new BehaviorSubject("");
setData(){
this.myBehaviouSubject.next("123");
}
public get myObservable$(): Observable<string>{
return this.myBehaviourSubject;
}
public get currentValue(): string{
return this.myBehaviourSubject.value;
}
}
https://stackblitz.com/edit/angular-protected-rxjs-subject
in this solution which I hope meet you needs:
be aware that there is no subscription
fetching updates handled manually
Property 'myBehaviourSubject' is private and only accessible
within class 'TestService'.
This question already has an answer here:
How to share data between components using a service properly?
(1 answer)
Closed 2 years ago.
hello i am using angular 8 and i would like to know how can i access the set value in any page ?
my code
class.ts
export class testClass {
get test():string{
return this.sexe;
}
set test(val:string){
this.sexe = val;
}
}
in clild.ts
import { testClass } from '../class';
export class Child{
constructor (private test:testClass){}
test (){
this.test.test = "hello";
}
in parent.js
import { testClass } from '../class';
export class Parent{
constructor (private test:testClass){}
test (){
console.log(test.test);
}
}
in app.module.ts
import { testClass } from '../class';
providers: [testClass],
what am i doing wrang to get "test undifined" in parent.js
Not to sure what you mean by setting and getting the value in any page? I'm assuming you mean component?
If so I'd use a service like so
#Injectable({
providedIn: 'root'
})
export class ExampleService{
private _value: any;
private _valueObs$ = new BehaviorSubject(null);
set setValue(newValue: any): void{
this._value = newValue;
}
get getNewValue(): any{
return this._value;
}
set setObservableValue(newValue: any): void{
this._valueObs$.next(newValue)
}
get getNewObservableValue(): any{
return this._valueObs$;
}
}
There are two approaches in the above method, the first is a pretty standard set and get, the second is utilising something known as a Subject, I'll touch on the difference in the next section.
To then use this service in any component
#Component({
selector: 'example',
})
export class ExampleComponent implements OnInit {
newValue: any;
constructor(private readonly exampleService: ExampleService
) { }
ngOnInit(): void {
this.getObservableExampleValue();
}
getExampleServiceValue(): any {
this.exampleService.getNewValue;
}
setExampleServiceNewValue(value: any): void {
this.exampleService.setNewValue = value;
}
getObservableExampleValue() {
this.exampleService.getNewObservableValue.subscribe((newObsValue) => {
this.newValue = newObsValue
})
}
setObservableExampleValue(value: any): void{
this.exampleService.setObservableValue(value);
}
ngOnDestroy(){
this.exampleService.getNewObservableValue.unsubscribe();
}
}
So I wont go into detail on the standard setValue & getNewValue, you can invoke them how you see fit.
Now the second approach is great if you want several components to be aware of a particular value at one time, so lets say we set the _valueObs$ with the setObservableValue method, and we have used this service in 5 different components, all 5 of those components will receive that value, very handy right?
Now you'll notice it's important that we actually invoke the getNewObservableValue so we can open the stream, normally you'd do this on the ngOnInit so the components template/code can have access to the value, assuming your looking to use the value straight away, otherwise you can invoke it at a later date, the way subscribing/observable's work is a bit like a tap.
Imagine you have a tap, and you turn it on - Known as subscribing
this.exampleService.getNewObservableValue.subscribe((newObsValue) => {
this.newValue = newObsValue
})
Well the tap is turned on and now emits a stream of water or again in this case a stream of data, so every time you set a new value, the new piece of data will come through that stream and will automatically update the this.newValue within your component.
But it's also important to turn the tap off! We don't want to be wasting water when we are done using it, this is when we unsubscribe when the component is no longer being used so
ngOnDestroy(){
this.exampleService.getNewObservableValue.unsubscribe();
}
This is to prevent what is known as a memory leak, which is beyond the scope of this answer, know to learn more about Rxjs I'd read some documentation - https://www.learnrxjs.io/ or watch some youtube videos there are plenty of tutorials out there!
Hopefully I've explained comprehensively enough if not feel free to comment.
You have to use a service.
The services are initialized when the app starts, and remain so until it stops. Passing a value through a service allows you to access it anywhere you call the service.
So if you had the following:
#Injectable()
export class ExampleService {
public varIWant: string = 'I wan't to use this anywhere.'
}
You can access it in your components, by doing:
import { ExampleService } from '../my/path/to/service'
export class Parent {
constructor(private exampleService: ExampleService) { }
public setVarAsLocal: string = this.exampleService.varIWant;
public changeServiceVariable() {
this.setVarAsLocal = 'New Value For String';
this.exampleService.varIWant = this.setVarAsLocal;
}
}
And that's it. As long as the instance is running the value will hold;
Is it possible to get (event) notification about every change of URL (without reloading page, like we can do with use location.replaceState())?
More precisely: I don't change component or page. I just change URL for future.
UPADATE Not elegant solution: manually triggering
var popStateEvent = new PopStateEvent('popstate', { state: state });
dispatchEvent(popStateEvent);
You may need to create a service injectable to root and add a subject for it that get triggered in every onDestroy for each component.
in a service:
//imports here
export class TestService {
pageChange$ = new Subject();
}
in all components where you want to trigger the change:
//imports
export class TestComponent implements OnDestroy {
//component properties
constructor(private testSrv: TestService){}
ngOnDestroy(){
let notification = 'This page is getting closed';
testSrv.pageChange$.next(notification);
}
}
in a component where you want to receive the change:
//imports
export class HeaderComponent implements OnInit {
//component properties
constructor(private testSrv: TestService){}
ngOnInit(){
let notification = 'This page is getting closed';
testSrv.pageChange$.subscribe(notification => {
console.log(notification);
});
}
}
This is an overall idea of what you might do to solve your issue.
Update
If you want to just track url changes, you need to use Router:
constructor(private router: Router) {}
ngOnInit(){
this.router.events.subscribe((val) => {
if(this.router.navigated){
//do something here
}
});
}