Angular subject not updating form - javascript

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.

Related

Parsing data from service to component in angular

In my service I make a call:
this.potentialOrganizations(currentNode.id)
.subscribe(data => {
console.log('consoling the org data!!!!!!! ' + JSON.stringify(data))
this.potentialOrgData = [];
this.potentialOrgData = data;
this._potentialOrgs.onNext(true);
})
The data consoles fine, as you can see, I tried using an observable method but it's not working for some reason!
I may need a new way to be able to call the data, as I said in my components html I have this: although it doesn't work:
<ul *ngIf="this.engagementService.potentialOrgData.length > 0">
<li *ngFor="let org of this.engagementService.potentialOrgData">
<p class="listedOrgs">{{ org.name }}</p>
</li>
</ul>
In my component I had this:
ngOnInit(): void {
this.engagementService.potentialOrgs$.subscribe(
(data) => {
if (data) {
console.log('are we hitting here inside the potentialORG DATA!??!?!?!!?!?')
this.potentialOrganizations = this.engagementService.potentialOrgData;
}
}
)
this.potentialOrganizations = this.engagementService.potentialOrgData;
}
it doesnt console, even though in my service i have the observable thing set:
private _potentialOrgs = new BehaviorSubject<boolean>(false);
public potentialOrgs$ = this._potentialOrgs.asObservable();
I was thinking maybe I need to use #input instead? but how to do that properly?
You could make this a little more simple here by trying the following. If potentialOrgData is set from a subscription in the service which it is, it will stay fresh as subscriptions stay open. You will be able to use the variable in the service directly.
public requestedData = [];
public ngOnInit(): void {
this.requestedData = this.engagementService.potentialOrgData;
}
In your template.
<ul *ngIf="requestedData.length">
<li *ngFor="let org of requestedData">
<p class="listedOrgs">{{ org.name }}</p>
</li>
</ul>
Behaviour Subjects and Observable's are very powerful but not always necessary.

Forcing change detection when an angular service value changes

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>

routerlink = "functionName()" invoked immediately upon page load

My component's html is this:
<div id="summary">
<div *ngFor="let question of thisSurvey">
<div>
<span class="badge">#{{question.questionNumber}}</span>
<span>{{question.questionText}}</span>
</div>
<p>Your answer: {{question.questionAnswer}}</p>
</div>
</div>
<br/>
<button class="btn btn-danger yes-no-btn" routerLink="/survey">Go Back</button>
<button class="btn btn-primary" [routerLink]="submitSurvey()" routerLinkActive="active">Finish</button> <!-- Issue here -->
When the page loads, submitSurvey is invoked immediately, and is then constantly invoked. This is submitSurvey:
// Send the answers back to the api for processing
submitSurvey() {
// Make sure everything is answered
const allOKClientSide: boolean = this.surveyService.checkEntireForm(this.thisSurvey);
if (allOKClientSide) {
if (this.surveyService.checkFormOnline(this.thisSurvey).subscribe()) {
return '/placeOne';
}
}
return '/placeTwo';
}
The method begins to hit the service immediately and continues until I kill the server. How do I keep the function from being invoked until the button is clicked? I'm new to Angular and am probably just making a rookie mistake, if so you may point that out as well. Thanks in advance.
[routerLink] is an Input, note the []. So Angular will resolve that immediately and every change detection cycle, to satisfy the template. You want to use (click) which is an Output, note the () and will only call when the button is clicked. Then instead of returning the url on the submitSurvey() function call router.navigate() (inject the router first.)
html
<button class="btn btn-primary" (click)="submitSurvey()" routerLinkActive="active">Finish</button>
ts
constructor(private router: Router) { }
public submitSurvey(): void {
// Make sure everything is answered
const allOKClientSide: boolean = this.surveyService.checkEntireForm(this.thisSurvey);
if (allOKClientSide) {
if (this.surveyService.checkFormOnline(this.thisSurvey).subscribe()) {
this.router.navigateByUrl('/placeOne');
return;
}
}
this.router.navigateByUrl('/placeTwo');
}
You want your method to be called when the button is clicked. You can do this by using (clicK):
Instead of
[routerLink]="submitSurvey()"
do
(click)="submitSurvey()"
Then you use the router in your class to do the navigation:
constructor(private router: Router) {}
submitSurvey() {
// ...
this.router.navigate(['/placeOne']);
}

How to push data into array inside a pop up (pop up opens for particular id) using angularjs

I successfully added data to array using push method normally but failed to do so inside a pop up which opens up for a particular Id .
Here is my code:
<div class="form-group">
<label class="col-sm-3" for="pwd">Speciality:</label>
<div class="col-sm-4">
<input type="text" class="form-control" ng-model="spec" id="usr">
<button type="submit" ng-click="addSpeciality()">Add </button>
</div>
<div class="col-sm-5">
<ul>
<li ng-repeat="spec in speciality">
{{ spec }}
<button ng-click="removeSpeciality($index)">Remove</button>
</li>
</ul>
</div>
</div>
Controller code:
$scope.speciality=[];
$scope.addSpeciality = function(){
$scope.speciality.push($scope.spec);
$scope.spec = '';
};
$scope.removeSpeciality = function(index) {
$scope.speciality.splice(index, 1);
};
This is point when you need a Factory. Forget about storing any data in controllers. Really - FORGET! The only proper way to share data across application is to define Factory you like and inject it separately in every place you gonna work with that data.
You should not store data in $scope. $scope itself is an instance bind to DOM element. So only way you access any data from any level of deepness is using $parent what is a great mistake.
Your controller should $inject that Factory and call related methods when you need to change anything in data. Never pass business logic in controllers. They are like Strategy design pattern - place where you only tell what business logic should take place when you got trigger (click for example):
SpecialityFactory.addSpeciality = function () {
SpecialityFactory.specialities.push({});
}
SpecialityFactory.removeSpeciality = function (idx) {
SpecialityFactory.specialities.splice(idx, 1);
}
So it will be much easier to share business logic across your controllers:
// PageController
PageController.prototype.addSpeciality = function () {
SpecialityFactory.addSpeciality();
};
PageController.$inject = ['SpecialityFactory'];
// ModalController
ModalController.prototype.removeSpeciality = function (idx) {
SpecialityFactory.removeSpeciality(idx);
};
ModalController.$inject = ['SpecialityFactory'];
Well, first of all #Apperion is right, read his answer
but to get your code working try to add the controller to your wrapper HTML element
<div class="form-group" ng-controller="ExampleController">
and
with this array your ng-repeat won't work this way, you need to use this way:
<li ng-repeat="spec in speciality track by $index">

Update list of divs after ajax post

I have this code that loads some div
<input class="input-large" placeholder="Notat" id="txtNewNote" type="text">
<button class="btn" id="btnAddNote" type="button">Lagre</button>
#foreach (var item in Model.UserNotes)
{
<div class="alert" id="divNote-#item.UserNoteID">
<button type="button" class="close" onclick="NoteDeleteClicked(#item.UserNoteID)" id="btnCloseNote">×</button>
<strong>#item.User.Name - #item.Added</strong><br />
#item.Text
</div>
}
Then I run this javascript when the user enter more text:
var Text = $("#txtNewNote").val();
var Added = Date.now();
vvar UserID = $("#hiddenUserID").text();
$.post("/api/apiUserNotes",{ Text : Text, Added: Added, UserID: UserID }, function() { //need functianality for updating the list of divs above });
Anyone can point me in the right direction here. I cannot just create the div after the post are done because I need to fetch data from the database so that the information in the div are correct.
There are mainly two approaches:
apiUserNotes returns HTML - This approach is a bit easier to maintain since you have only one template. But it is also more restricting in the sense that HTML is good for showing, but not so much for manipulating it.
apiUserNotes returns JSON - This is more flexible since a JSON API can be consumed by pretty much anything, from HTML to native iOS or Android apps, as it's much easier to manipulate. It's also more work though, as you then have templates both on the server-side (your Razor view) as well as on the client-side (your HTML/JavaScript).
This is more or less what #1 would look like:
You first make a partial view to display user notes:
_UserNotes.cshtml:
<div id="user-notes">
#foreach (var item in Model.UserNotes)
{
<div class="alert" id="divNote-#item.UserNoteID">
<button type="button" class="close" onclick="NoteDeleteClicked(#item.UserNoteID)" id="btnCloseNote">×</button>
<strong>#item.User.Name - #item.Added</strong><br />
#item.Text
</div>
}
</div>
Once you have this partial view, you can consume it from your view:
<input class="input-large" placeholder="Notat" id="txtNewNote" type="text">
<button class="btn" id="btnAddNote" type="button">Lagre</button>
#Html.Partial("_UserNotes")
And from your apiUserNotes action:
public PartialViewResult apiUserNotes(string text, DateTime added, int userID)
{
// TODO: save new note
var notes = yourRepo.GetUserNotes(userID);
return PartialView("_UserNotes", notes);
}
Finally, your client-side scripts simply overrites the div containing all user notes:
var data = {
text = $("#txtNewNote").val(),
added = Date.now(),
userID = $("#hiddenUserID").text()
};
$.post("/apiUserNotes", data, function (result) {
$('#user-notes').html(result);
});
There are of course much more efficient ways of doing this. For example, you don't need to reload all user notes; only the one your just added. But hopefully this gets you started.
You can access above Divs in following way, and update as per your requirement.
$.post("/api/apiUserNotes",{ Text : Text, Added: Added, UserID: UserID },
function()
{
//need functianality for updating the list of divs above
$("div.alert").each(....)
});

Categories