Angular 2: Template not updating after http service update - javascript

I'm still trying to learn ng2 so I'm sure this is a common noob problem but none of the solutions I found online helped. anyone know what my problem is?
I have a service with the getEventList() method as an observable
getEventList(): Observable<any>{
return this.http.get('./assets/data.json')
.map(response => response.json());
}
I subscribe to it in my component
ngOnInit(){
this.eventService.getEventList()
.subscribe(events => {
this.zone.run(()=>{
this.events = events;
console.log(this.events)
});
});
}
My template just tries to output the events variable in json format
<p>
{{
events | json
}}
</p>
As you can see I'm assigning the value of events to a local variable in a zone.run() callback. Still not seeing my template update. I've also tried ApplicationRef.tick and ChangeDetectorRef.detectChanges() but neither seem to detect my changes.
Console.log call does confirm that the data was updated. Any click handler fired on this component also seems to update the view.
Anyone know what I'm doing wrong here? Did I post enough of the code to get some advice? Let me know what else you would like to see.

This should do it, assuming you haven't assigned a value to this.events before calling getEventList() method:
<div *ngIf="events">
{{ events | json }}
</div>

The seed was setting the ChangeDetectionStrategy to OnPush. Setting this to default solved my problem.

Related

*ngFor simply not working with array of Objects

I have the TS code
getItems().subscribe(val=>{
this.items=val;
})
which works fine. When I console.log(this.items) I get
"array:0{size:XL,quantity:1}"
Buuut.
Amazingly, something I've never dealt with before in angular,
when I run the code in the html template of
<div *ngFor="let item of items">
{{item.size}}
</div
items =[{size:'XL',quantity:4}]
I get ABSOLUTELY NOTHING. This is really bizzarre because I've written hundreds of these statements and have never had this issue before. Please help me.
Solved:
What I've figured out is that calling getItems() with a subject, where the getItems method returns a subject "asobservable" can ONLY be done in the same component, directly after the subject is populated. Populating a subject prior to page change seems to still allow the data to be transferred enough to log in console, but not to be usable by the greater application.
So therefore doing
updateItems(items);
getItemsUpdated().subscribe(val=>{
this.items=val
})
where there is a subject and
updateItems(items:items){
this.subject.next(items)
}
getItemsUpdated(){
return this.subject.asObservable()
}
would Work, but only when called directly next to each other in the same component.
Since in this case I was updating the subject, switching pages, and then calling the subject, I was experiencing a weird sort of subject limbo where the console was still able to record the value passed to the subject, but it was not being resolved fully by the receiving component, and therefore was not functioning with the *ngFor.
Correct Flow
updateItems(items);
---------->Navigate to next page-------->
getItemsUpdated().subscribe(val=>{
this.items=val
})
where there is a behaviorSubject and
updateItems(items:items){
this.behaviorSubject.next(items)
}
getItemsUpdated(){
return this.behaviorSubject.asObservable()
}
For this case, using a BehaviorSubject instead of Subject allowed the data to be process "on time" correctly and work with the app.
Thanks everybody for your input!
You should be using a BehaviorSubject or a ReplaySubject; plain Subject will only give you the values emitted after the subscription.
export class CartService {
public cartUpdated: Subject<Item[]> = new BehaviorSubject([]);
More info in: Subject vs BehaviorSubject vs ReplaySubject in Angular
Fixed Stackblitz
Your stackblitz doesnt allow ngFor so the setup must be wrong. Here is what I think is the problem:
Your app has a weird data flow, when you subscribe to your observable, you wont receive any data until something calls next on your subject. What you seems to be doing from what I've seen in your stackblitz is:
Call to update which is going to to give a value to your Subject
You subscribe and its already too late
You can't get the last value from a subject and since you subsribed too late, your console.log from the subscribe is never actually doing anything. You are seeing the console.log related to your update(the one from hello component)

panelChange binding a value on the panel Angular 6

Overview
Im learning Angular and JHipster trying to get the id of an objet in a collection.
but I can't get the event click to work on the panel also a I can't use the panelChange event on the panel itself.
Is a DAFO analysis so i need the id to get the elements
My thoughts
think im donning wrong the binding or im using a different event
this is in the TypeScript side of the component
ngOnInit() {
this.activatedRoute.data.subscribe(({ planEstrategico }) => {
this.planEstrategico = planEstrategico;
this.idPlan = planEstrategico.id;
this.cargarAnaliziFoda(this.idPlan);
});
}
cargarElementosFoda(id) {
console.log(id);
}
first I try this
<ngb-panel
(click)="cargarElementosFoda(diagnosticoFoda.id)"
*ngFor="let diagnosticoFoda of diagnosticoFodas"
>
dint work so I try this
<ngb-panel
(panelChange)="cargarElementosFoda(diagnosticoFoda.id)"
*ngFor="let diagnosticoFoda of diagnosticoFodas"
>
also don't work
I read only works on the ngb-accordion
but the problem is not all the panels are for DOFA just one or two.
Questions
Best way to get the id of my collection with click event?
Alternative ways to get the id? maybe on the typescript side of the
component
Notes
im really new on Angular, TypeScript and Jhipster please if I miss something important let me know it on the comment I will added to the question.

delay before binding template angular

this is kind of relate to ExpressionChangedAfterItHasBeenCheckedError again.
in my situation, I have a parameter 'data' which I recover from ajax call.
this data is used to create child via #input directive. I wish the binding only occurs when data is defined. but I have no idea how to do that with angular
export class UserComponent{
data: any
constructor(
private userService: UserService){
this.userService.getUser()
.subscribe(result => {
this.data = result;
}
}
);
}
because the way it works, angular will display an ExpressionChangedAfterItHasBeenCheckedError, which I understand why, but how to ask angular to wait for the callback to be done and data to be != from undefined before start binding and stuff, the goal is to have some child initialized with the real value coming from the database.
If I should wait for the end of the cycle before binding "real" data, it's fine, but how to do it without having this error, (and please without using setTimeout as it looks a lot of rubbish !).
Thanks
<!--the template if it matter --><somechildtag [data]="data" ></somechildtag>
Load your component with ng-container.
For example:
<div *ngIf="data?.length > 0">
<ng-container *ngComponentOutlet="myChildComponent;
ngModuleFactory: childComponentsModule;"></ng-container>
</div>
So, you will render your child only if your data object is populated.
In your component:
private childComponentsModule: NgModuleFactory<any>;
constructor(private compiler: Compiler){
this.childComponentsModule = compiler.compileModuleSync(ChildComponentsModule);
}
You can see a more detailed example of how to load dynamically a component here:
Angular2: Use Pipe to render templates dynamically
Angular's documentation:
https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html

Using data from Observable Angular 2

First, I'd like to thank the community for all the support learners like me get while working with new technologies.
I've been using Angular for a while now, and there's something I still don't quite understand nor have I seen asked elsewhere.
Supposing I have a service that returns an Observable with the data I need, how should I use this data properly, in terms of performance?
I know I can use the async pipe and avoid having to sub/unsub, but this only happens in the template. What if I needed to use the same data in the component as well? Wouldn't subscribing again (from the template with the async pipe and from the component with .subscribe())?
How do I keep the observable up to date? For example, I have a table displaying data from an API. If I click on the 2nd page (pagination), I'd like to make another call and have the observable updated.
I'm sorry if this has been asked before, I personally couldn't find if on Stackoverflow. Thanks for your attention!
If you need the data in the component as well, you can just subscribe to it. BUT maybe you should not (see below)...
it's there that you use the operators, you can combine observables to define a custom data flow:
foo$: Observable < Foo[] > ;
randomClickEvent = new Subject < clickEvent > ();
ngOnInit() {
let initialFetch = this.fooService.getData().share()
this.foo$ = Observable.merge(
initialFetch, // need the initial data
initialFetch.flatMap(foos => {
this.randomClickEvent.switchMap(() => { //listen to click events
return this.fooService.getMore().map((moreFoos: Foo[]) => { //load more foos
return foos.concat(...moreFoos) //initial foos values + new ones
})
})
})
);
}
<span *ngFor="let foo of (foo$|async)">{{foo.name}}</span>
<button (click)="randomClickEvent.next($event)">Load More foos !</button>
Most of people just use simple operators like map(),do(), etc and manage their subscription imperatively, but it is usually better to not subscribe, so you avoid many side effects and some "Ooops I forgot to unsubscribe here". usually you can do everything you need without subscribing.
Observables exist to describe a data flow, nothing more, nothing less. It's a paradigm of functional programming: you don't define how things are done, but what they are. Here, this.foo$ is a combination of the initial fooService.getData() and every fooService.fetchMore() that may occur.

How to use Session.setDefault() with external data inside helper

I use Meteor 1.2.1. I am currently developing a Course Catalog Web App.
I'm trying to create a filtering feature just like Angular Filters. It should let the user specify his needs and then the course list refreshes automatically as he changes the filters.
This is my code:
app.html
<!-- ********** -->
<!-- courseList -->
<!-- ********** -->
<template name="courseList">
<div class="list-header">
<div>Course Name</div>
<div>Description</div>
<div>Category</div>
<div>Modality</div>
<div>Unit</div>
</div>
<div class="list-body">
{{ #each course }}
{{ > courseRow}}
<hr />
{{ /each }}
</div>
</template>
app.js
Template.courseList.helpers({
'course': function(){
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
}
});
So when I run this, I get an empty set for the course helper above, even though there's some dummy data present on the database.
It's seems to me that the issue is with Session.setDefault(). When I console.log the Session.course variable right after the find, I get an empty array [], maybe because there was no time to get the data from the server (or maybe not, because I'm developing with autopublish for now, I don't really know).
After I apply some of the filters (code not shown here), everything goes back to normal. This initialization only is the problem.
I've tried to call Session.set() inside Template.courses.rendered(), Template.courses.created(), Template.courses.onRendered(), Template.courses.onCreated() (yes, I was kinda desperate) but none of them worked.
Can someone please advise on that issue? Maybe I'm not trying the correct Meteor aproach, as I am a Meteor beginner.
Thanks!
I think you try to use template subsciption. so you can send some terms for publication. example;
Template.foo.onCreated(function() {
this.name = new reactiveVar("");
this.autorun( () => {
const terms = {
name: this.name.get()
};
this.subscribe("Posts_list", terms);
});
});
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
There are two things here. First thing is that your helper will run whenever either the result of Courses.find().fetch() or Session.get('course') changes, since both of them are reactive data sources. To avoid this, you can do
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
If you want to set the session variable so that you can use it somewhere else, then you can use Session.set instead of Session.setDefault, because Session.setDefault will only assign value only is the session variable is not already available. But I don't know why you want to do that, since you can use Posts.find().fetch() where ever required instead of session variable. Both are reactive.
Template.foo.helpers({
lists: function() {
Session.set('course', Courses.find().fetch());
return Session.get('course');
}
});
If you want to assign the course session variable only for the first time when you get non-empty data then you might want to do something like this.
Template.foo.helpers({
lists: function() {
if (Courses.find().count() > 0) {
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
} else {
return [];
}
});
If you can tell why you need the session variable, we might think of a different approach to solve the problem.
Hope it helps.

Categories