Forcing change detection when an angular service value changes - javascript

I've got a function that checks the value of an observable and based on that value performs some logic to change the value of a variable that I've defined in a service. Everything is working as it should except that the changed value is not rendering (updating) in the web component through string interpolation when it gets changed. It is being changed correctly in the service (when I console.log it is coming back correctly) but just not getting it to update the component for some reason. I've read a lot about ngZone, ChangeDetectorRef etc. and have implemented those strategies on other areas where I've had update issues in the past, but for some reason they are not working here. Code below, any guidance would be appreciated as I've banged my head against this one for a while.
//From the component where I'm performing the checks on the observable. The component where I'm doing the string interpolation on the data service value is a different component
ngOnInit(){
this.myObservable$ = this.scanitservice.decodedString$.subscribe(data => {
if (
data ==
this.adventuredata.exhibit[this.adventuredata.adventure.currentAmount]
.target
) {
this.adventuredata.sharedVariables.guideText =
'You found the right clue! Great job! Answer the question correctly to claim your prize.';
console.log(this.adventuredata.sharedVariables.guideText);
this.showQuiz = true;
this.changeDetector.detectChanges();
console.log('Evaluated as matched');
}
});
}
//From the data service
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AdventuredataService {
constructor() {}
sharedVariables = {
guideText: '',
quizText: ''
};
}
<div class="card-body float-right m-0 p-0">
<img
src="{{ this.adventuredata.adventure.guideImage }}"
alt=""
class="card-img-top w-25 float-left"
/>
<span class="card-text p-0">
{{ this.adventuredata.sharedVariables.guideText }}
</span>
</div>

Related

How to show Different data in UI then Backend Data

I am new to angular. From Backend i am getting space data as attached in console image.
<div class="row">
<div class="col-12 mt-3 pr-0 overflow-auto d-flex align-items-center">
<div *ngFor="let data of spaces; let i=index;" class="spaceTabs cursorPointer"
[ngClass]="{ active: selectedSpace === data.space }">
<p class="rangeTag">Rs: {{data.range}}</p>
<span (click)="spaceTabHandler(data)">{{data.space | titlecase}}</span>
</div>
</div>
</div>
This is how i am showing space data as data.space in UI.
My requirement is livingroom should display as Living, diningroom as Dining Room .
How can i manipulate data in UI according to requirement.
Any lead would be helpful.
You can use a pipe to transform your output
#Pipe({
name: 'space'
})
export class SpacePipe implements PipeTransform {
transform(value:string, args?:string): any {
switch(value || null) {
case 'livingroom':
return 'Living';
case 'diningroom':
return 'Dining Room';
default:
return value;
}
}
}
<span (click)="spaceTabHandler(data)">{{data.space | titlecase | space}}</span>
you can create a pipe, then use that pipe to show what you want,
like below
import {Pipe} from ‘angular2/core’;
#Pipe({
name: ‘unCamelCase’
})
export class UnCamelCasePipe {
transform(value: string) {
return value
// insert a space between lower & upper
.replace(/([a-z])([A-Z])/g, '$1 $2')
// space before last upper in a sequence followed by lower
.replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3')
// uppercase the first character
.replace(/^./,(str) => str.toUpperCase())
}
}
then use it like
<span (click)="spaceTabHandler(data)">{{data.space | unCamelCase}}</span>
make sure you add UnCamelCasePipe to your module providers
Create a viewModel with the fields u need in the front end. Then after the backend call returns populate your data where u set Living Room instead of livingroom. That is usually the correct approach
SO in typescript:
export class DataViewModel{
public SpaceName:string;
public .... other properties you need
}
Then in your angular component in the subscribe to the back end call:
this.mybackendmethod.get().subscribe((backenddata)=>{
const viewModel = new Array<DataViewModel>();
backenddata.foreach((item)=>{
const viewModelItem = new DataViewModel();
switch(item.space){
case 'livingroom':
viewModelItem.SpaceName= 'Living Room';
break;
}
viewModel.push(viewModelItem);
});
});
Something like this.

Vue getter returns undefined when page reload

I have a blog with some posts. When you click on the preview you will redirect on the page post.
On the page of the post, I use a getter to load the correct post (I use the find function to return object.name which corresponds to the correct object in the array of objects).
const state = {
ricettario: [], // data that contains all recipes (array of objects)
}
const actions = {
// Bind State and Firestore collection
init: firestoreAction(({ bindFirestoreRef }) => {
bindFirestoreRef('ricettario', db.collection('____').orderBy('data'))
})
const getters = {
caricaRicetta(state) {
console.log('Vuex Getter FIRED => ', state.ricettario)
return nameParamByComponent => state.ricettario.find(ricetta => {
return ricetta.name === nameParamByComponent
})
}
}
In the component, I call the getter in the computed property
computed: {
...mapGetters('ricettaStore', ['caricaRicetta']),
ricetta() {
return this.caricaRicetta(this.slug) // this.slug is the prop of the URL (by Router)
}
}
Anything goes in the right way but when I reload the page in the POST PAGE, the getter will fire 2 times:
1. return an error because the state is null
2. return the correct object
// screen below
So everything works fine from the front but not at all in the console and in the App.
I think the correct way is to call the getters in the created hook. What I've to change? It is a problem with the computed prop, getters or state?
POST PAGE:
<template>
<div v-if="ricetta.validate === true" id="sezione-ricetta">
<div class="container">
<div class="row">
<div class="col s12 m10 offset-m1 l8 offset-l2">
<img
class="img-fluid"
:src="ricetta.img"
:alt="'Ricetta ' + ricetta.titolo"
:title="ricetta.titolo"
/>
</div>
</div>
</div>
</div>
<div v-else>
...
</div>
</template>
You are trying to validate undifined property. So you need to check ricetta first.
Try like this:
<div v-if="ricetta && ricetta.validate === true" id="sezione-ricetta">
Database synchronization is asynchronous, ricettario is initially an empty array. Computed value is recomputed once synchronization is finished and ricettario array is filled, the component is updated.
Even if ricettario weren't empty, find may return undefined if it finds nothing. This needs to be handled where ricetta is used:
<div v-if="ricetta && ricetta.validate" id="sezione-ricetta">
The error log is quite explicit, there is a xxx.validate somewhere in your Ricetta component template, but that xxx is undefined.
Because of this, your app crashes and stops working. I doubt it has anything to do with Vuex

VueJS/Laravel - Sharing props between Laravel and Vue

I have defined a component called EditorNavigation.vue like so:
<template>
<div>
<ul>
<li v-bind:class="{'is-active':(active === field.id)}" v-for="field in fields">
<a :href="'/streams/' + stream_token + '/fields/' + field.id">{{field.name}}</a>
</li>
</ul>
<div>
</template>
<script>
export default {
props: ["fields", "active", "stream_token"],
created() {
this.fields = JSON.parse(this.fields);
this.active = JSON.parse(this.active);
this.stream_token = JSON.parse(this.stream_token);
}
};
</script>
As you can see in my component, I need three variables:
Fields (array of all fields)
An unique token for a specific resource
The current active field id (so I can set the is-active class).
In my Laravel view file, I use the component like this:
show.blade.php
<editor-navigation fields="{{ json_encode($stream->fields) }}" active="{{ json_encode($field->id) }}" stream_token="{{ json_encode($field->stream->token) }}"></editor-navigation>
So above code works fine, however it feels a bit "messy" - since I need to use the editor-navigation component in a lot of pages, and I am wondering what will happen as soon as I need another variable sent to it - I have to update it in all places.

Angular subject not updating form

I've been following a course on LinkedIn Learning but clicking on a list and having the values populate a form are not working for me. I'm new to Angular (and development) so apologies if this is silly, or I don't describe it correctly.
I have 2 components and an API service file pulling the data from an ASP.Net Core API:
List-codes
Add-code
Api
list-codes.component.html
<div class="card-body">
<p class="card-text">select a code from the list below.</p>
<ul class="list-group" *ngFor="let code of codes">
{{code.codeName}}
</ul>
</div>
list-codes.component.ts
ngOnInit() {
this.api.getCodes().subscribe(res => {
this.codes = res
})
}
add-code.component.html
<form>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="">:)</span>
</div>
<input type="text" class="form-control" [(ngModel)]="code.codename" name="codename" placeholder="code name">
<input type="text" class="form-control" [(ngModel)]="code.description" name="description" placeholder="description">
</div>
<button (click)="post(code)" class="btn btn-primary">Submit</button>
</form>
add-code.component.ts
export class AddCodeComponent {
code = {}
constructor(private api: ApiService) {}
ngOnit() {
this.api.codeSelected.subscribe(code => this.code = code)
}
post(code) {
this.api.postCode(code)
}
}
api.service.ts
export class ApiService {
private selectedCode = new Subject<any>(); // holds reference to clicked item in list
codeSelected= this.selectedCode.asObservable(); // subscribe
constructor(private http: HttpClient) {}
getCodes() {
return this.http.get('http://localhost:58561/api/codes');
}
postCode(code) {
this.http.post('http://localhost:58561/api/codes', code).subscribe(res => {
console.log(res)
})
}
selectCode(code) {
this.selectedCode.next(code)
}
}
Listing the codes works fine.
The issue just seems to be clicking and having the code in the list populate the values in the add-code form (it works in the video tutorial) but it doesn't work for me. I'm assuming I've missed something obvious?
I did a bit of reading and everyone seems to handle Subject Observables slightly different and I"m obviously just missing the point!
For brevity, I've provided the snippets I think are relevant. If I've overlooked something important to include please let me know.
Any help welcomed!
Cheers,
Adam
In your list-codes.component.ts you only subscribe to the observable returned by your api.service.getCodes() once because an Observable is Cold by default and thus when it completes you automatically unsubscribe.
In order for your form to keep updating you need to implement something that will keep calling your service.getCodes().subscribe(blah) to fetch new data.

Unexpected behavior of [disabled] [duplicate]

This question already has answers here:
*ngFor running an infinite loop in angular2
(1 answer)
Angular2 *ngIf="afunctioncall()" results in the function being called 9 times
(1 answer)
Closed 5 years ago.
I am experiencing a very weird behavior of [disabled]. I am fetching a list of firebase docs and showing them using *ngFor.
app.component.ts
export class AppComponent implements OnInit {
postRef;
posts = [];
user;
counter = 1;
constructor( private afs: AngularFirestore ) { }
ngOnInit() {
this.postRef = this.afs.collection('post');
this.posts = this.postRef.valueChanges();
}
editPost(post) {
console.log('Edit-Post : ', post.title);
}
canEdit(post) {
console.log('CanEdit-Post : ', post.title);
console.log('Counter :', this.counter++);
return false;
}
deletePost(post) {
console.log('Delete-Post : ', post.title);
}
}
app.component.html
<div *ngFor="let post of posts | async" class="card" style="width:80%;margin: 50px 40px;">
<h5 class="card-header">{{ post.title }}</h5>
<div class="card-body">
<p>{{ post.content }}</p>
<button class="btn btn-warning is-danger" (click)="deletePost(post)"> Delete Post </button>
<button class="btn btn-primary is-warning" [disabled]="canEdit(post)" (click)="editPost(post)"> Edit Post </button>
</div>
</div>
canEdit() on [disabled] called so many times on page load (around 12 times, I have checked by console 'counter' in canEdit().
canEdit() also called on click of 'Edit Post' and 'Delete Post' button that too 6 times each. And sometimes canEdit() called automatically without any method calling or page load/refresh.
This is really weird behavior for me, anyone please explain the behavior of [disabled] here in detail.
NOTE : This behavior has nothing to do with list of post getting from firebase database collection as I have already checked this with static list of posts. I using angular v^5.0.0
that's because what you write in ngOnInit you are loading posts at first /
and render them using *ngFor
here is the scenario when it starts rendering
he checks for each button if it is disabled at first or not, that why he calls canEdit the same number of posts
and if you try to change the posts array canEdit will called again with the same scenario

Categories