Angular how do I use *ngFor with the Async pipe? - html

Hi I'm having problems with using the asynchronous ngFor, I've got the simplest example of this thing, an array of objects that is obtained from a server onInit, and I want to iterate on int once it arrives,this is how I've written it on the template:
<p *ngFor="let msg of messages | async">test</p>
I mean it looks ok to me but apparently not, here's the ts part:
export class ChatComponent implements OnInit {
url = 'http://localhost:8080';
otherUser?: User;
thisUser: User = JSON.parse(sessionStorage.getItem('user')!);
channelName?: string;
socket?: WebSocket;
stompClient?: Stomp.Client;
newMessage = new FormControl('');
messages?: Observable<Array<Messaggio>>;
constructor(
private route: ActivatedRoute,
private userService: UserService,
private http:HttpClient
) {}
ngOnInit(): void {
this.userService
.getUserByNickname(this.route.snapshot.paramMap.get('user')!)
.subscribe((data) => {
this.otherUser = data;
this.otherUser.propic = "data:image/jpeg;base64,"+ this.otherUser.propic;
this.connectToChat();
});
}
connectToChat() {
const id1 = this.thisUser.id!;
const nick1 = this.thisUser.nickname;
const id2 = this.otherUser?.id!;
const nick2 = this.otherUser?.nickname!;
if (id1 > id2) {
this.channelName = nick1 + '&' + nick2;
} else {
this.channelName = nick2 + '&' + nick1;
}
this.loadChat();
console.log('connecting to chat...');
this.socket = new SockJS(this.url + '/chat');
this.stompClient = Stomp.over(this.socket);
this.stompClient.connect({}, (frame) => {
//func = what to do when connection is established
console.log('connected to: ' + frame);
this.stompClient!.subscribe(
'/topic/messages/' + this.channelName,
(response) => {
//func = what to do when client receives data (messages)
let data:Messaggio = JSON.parse(response.body);
console.log(data);
//this.messages.push(data);
//this.messages = this.messages.slice();
}
);
});
}
loadChat(){
let messages: Array<Messaggio>;
this.http.post<Array<Messaggio>>(this.url+'/getMessages' , this.channelName).subscribe(data =>{
messages = data;
console.log(messages);
})
}
the section regarding the question is the loadChat method which is called in a method called in the onInit, so basically it is called in the on init, and the declaration of the array
point is the array gets defined I even print it on the console but the html page doesn't do jack

Make sure your message object is of type Observable.
and
Add a null check before looping over it with a ngIf
once you messages observable has some data this below code will work fine
<div *ngIf="(messages | async)">
<p *ngFor="let msg of messages | async">test</p>
</div>

Thanks to those who are still answering this but I solved it from the first comment and the problem was: I'm stupid and I assigned the data from the server to an array local to the method instead of the property of the component, if I did that it would have worked from the begininng
lmao

Related

how to unit test subscription to a BehaviourSubject in angular

I have a UserManagementService which exposes an Observable of a BehaviourSubject.
this.userSignInState$ = this.signInStateSubject.asObservable();
I subscribe to userSignInState in a nav component.
constructor(public userManagementService: UserManagementService, private fb:FormBuilder, private helper:HelperService) {
this.userSignInStateSubscription = this.userManagementService.userSignInState$.subscribe(
(result:Result)=> {
console.log("In nav - result from user signin state ",result);
let subscribed:UserSigninState = result.additionalInfo;
console.log("new user signin state received:", subscribed);
this.userLoggedIn = subscribed.isSignedIn;
if(subscribed.isSignedIn && subscribed['additional-info'] !== ''){
this.profile = JSON.parse(subscribed['additional-info']) as UserProfileAPI
}
if(!subscribed.isSignedIn && subscribed['additional-info'] !== ''){
// let error:ServerResponseAPI = JSON.parse(subscribed['additional-info']) as ServerResponseAPI
//let errorMessage:string = this.helper.userFriendlyErrorMessage(error);
this.navEvent.emit(new NavContext(subscribed['additional-info']));
}
},
(error:ServerResponseAPI)=>{
console.log("got error from the Observable: ",error);
let errorMessage:string = this.helper.userFriendlyErrorMessage(error);
this.navEvent.emit(new NavContext(errorMessage));
// this.userloggedIn =false;
},
()=>{ //observable complete
console.log("observable completed")
//this.userloggedIn =false;
});
}
I want to unit test nav. The spec should test that the component subscribes to userSignInState$ and handles Result correctly. How do I do this? As this is a unit test, I don't want to use the real UserManagementService
I wrote the following spec
fit('should subscribe to user sign in state observable',()=>{
let userManagementService = TestBed.get(UserManagementService);
let navComponent:NavComponentComponent = component;
console.log('component is ',navComponent);
navComponent.userLoggedIn = false;
let dummyUserProfile = new UserProfileAPI(new User('fn','ln','test#test.com'));
userManagementService.signInStateSubject.next(new Result('success',(new UserSigninState(true,JSON.stringify(dummyUserProfile ))).toString));
expect(navComponent.userLoggedIn).toBe(true)
});
but I got error Expected undefined to be true.
I don't understand why userLoggedIn is undefined. I have declared it in the nav class
export class NavComponentComponent implements OnInit {
userLoggedIn:boolean;
...
}
I set it in ngOnInit.
ngOnInit(){
this.userLoggedIn = false;
...
}
I also moved the subscription logic to ngOnInit but that doesn't work either and gives the same result.
The issue was with the way I was creating Result. I should have not used .toString with userSignInState. From another question I posted in SO, "reference to .toString without () is just a reference to that function, so if you log that you get the code for that function." Also, "toString() will not work as userSignInState doesn't have a meanigful string representation and is defaulting to [object Object]". I removed toString and the code worked as additional-info is of type any

Why isn't my function returning the proper JSON data and how can I access it?

I'm running services to retrieve data from an API. Here is one of the services:
robotSummary(core_id, channel_name){
const params = new HttpParams()
var new_headers = {
'access-token': ' '
};
this.access_token = sessionStorage.getItem('access-token');
new_headers['access-token'] = this.access_token;
const myObject: any = {core_id : core_id, channel_name: channel_name};
const httpParams: HttpParamsOptions = { fromObject: myObject } as HttpParamsOptions;
const options = { params: new HttpParams(httpParams), headers: new_headers };
return this.http.get(this.baseURL + 'web_app/robot_summary/',options)
.subscribe(
res => console.log(res),
)
}
}
The data shows up properly on the console, but I still can't access the individual keys:
Here is how I call it:
ngOnInit(): void{
this.login.getData(this.username, this.password).subscribe((data) => {
this.robotSummaryData = this.getRobotSummary.robotSummary(this.core_id, this.channel_name);
console.log("robosummary"+ this.robotSummaryData)
});
}
When I call this function and assign it to a variable, it shows up on console as [object Object]. When I tried to use JSON.parse, it throws the error: type subscription is not assignable to parameter string. How can I access the data? I want to take the JSON object and save it as an Object with appropriate attributes. Thanks!
Do not subscribe inside your service, do subscribe in your component, change your service as follows,
robotSummary(core_id, channel_name){
const params = new HttpParams()
var new_headers = {
'access-token': ' '
};
this.access_token = sessionStorage.getItem('access-token');
new_headers['access-token'] = this.access_token; const myObject: any = { core_id: core_id, channel_name: channel_name };
const httpParams: HttpParamsOptions = { fromObject: myObject } as HttpParamsOptions;
const options = { params: new HttpParams(httpParams), headers: new_headers };
return this.http.get(this.baseURL + 'web_app/robot_summary/', options)
.map((response: Response) => response);
}
and then in your component,
ngOnInit(){
this.api..getRobotSummary.robotSummary(this.core_id, this.channel_name).subscribe((data) => {
this.data = data;
console.log(this.data);
});
}

Type Undefined is not assignable to type [Film]

I am learning how to make http request in angular using service and here is my code:
export class ApiCallService {
// DECLARATIONS:
tmdb_api_key = '*******'; // personal api key to access the TMDB API
posterBaseAddress = 'https://image.tmdb.org/t/p/w300'; // base
address of the TMDB poster link, to add film specific path
// SEARCH PARAMETERS
requestPages: number; // get the number of pages for the request so is possible to load all the films (request is 1 page at time)
pageToLoad = 1; // number of page to load, start with 1
baseLanguage = 'en-US'; // return film in english, search can be done in local language but return english titles
foundFilms: [Film] = [];
constructor(private http: Http) {}
// TODO: GET method to search for film
getFilmsByTitle(filmTitle: string, page: number) {
// if the search has only 1 page will be 1 otherwise will load the films on the respective page
this.pageToLoad = page;
return this.http.get('https://api.themoviedb.org/3/search/movie?' +
'api_key=*******$' +
'&language=' + this.baseLanguage +
'&query=' + filmTitle +
'&page=' + this.pageToLoad +
'&include_adult=false')
.map(
(response: Response) => {
const films = response.json();
for (const film of films['results']) {
const singleFilm = new Film(film.title, film.overview, film.release_date, film.poster_path, film.genre_ids);
this.foundFilms.push(singleFilm);
}
this.requestPages = films.total_pages;
return this.foundFilms;
}
);
}
}
export class AppComponent implements OnInit {
constructor(private apiCallService: ApiCallService) {}
ngOnInit() {
this.apiCallService.getFilmsByTitle('Harry', 1).subscribe(
(films: [any]) => {
console.log(films);
}
);
}
}
When I launch the app I get this error, ERROR in src/app/services/apicall.service.ts(15,5): error TS2322: Type 'undefined[]' is not assignable to type '[Film]'.
Property '0' is missing in type 'undefined[]'.
And in the console I have this error: Cannot read property 'push' of undefined
I don't understand why, i tried several was to declare the foundFilms array but I cannot make it work.
I hope someone can help
Thanks
Alessandro
You need to initialize your array when you declare it.
foundFilms: Film[] = [];

Angular 2 - Getting object id from array and displaying data

I currently have a service that gets an array of json objects from a json file which displays a list of leads. Each lead has an id and when a lead within this list is clicked it takes the user to a view that has this id in the url ie ( /lead/156af71250a941ccbdd65f73e5af2e67 )
I've been trying to get this object by id through my leads service but just cant get it working. Where am I going wrong?
Also, i'm using two way binding in my html.
SERVICE
leads;
constructor(private http: HttpClient) { }
getAllLeads() {
return this.http.get('../../assets/leads.json').map((response) => response);
}
getLead(id: any) {
const leads = this.getAllLeads();
const lead = this.leads.find(order => order.id === id);
return lead;
}
COMPONENT
lead = {};
constructor(
private leadService: LeadService,
private route: ActivatedRoute) {
const id = this.route.snapshot.paramMap.get('id');
if (id) { this.leadService.getLead(id).take(1).subscribe(lead => this.lead = lead); }
}
JSON
[
{
"LeadId": "156af71250a941ccbdd65f73e5af2e66",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Fred Dibnah",
"LeadNumber": "1603041053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e67",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Harry Dibnah",
"LeadNumber": "1603021053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e68",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "John Doe",
"LeadNumber": "1603021053",
}
]
You didn't used the newly created leads array (const leads is not this.leads), so do this:
getLead(id: any) {
return this.getAllLeads().find(order => order.LeadId === id);
}
And change your map to flatMap, because from the server you get an array, but you have to transform it to a stream of its items:
getAllLeads() {
return this.http.get('../../assets/leads.json').flatMap(data => data);
}
Don't forget to import it if you have to: import 'rxjs/add/operator/flatMap';
You can have getLead in your component level itself since you are not making any api to get the information. In your component,
this.lead = this.leads.find(order => order.id === id);
or to make the above service work, just do leads instead of this.leads
const lead = leads.find(order => order.id === id);

Angular Firebase Storage, Assigning User Input Properties to Real Time Database

I want to upload a file especially an image to my firebase storage. I found a tutorial from this link. I added the more properties like url and file to my existing class and i followed the function template on that link. But apparently i did something wrong. The file uploaded to my storage and the console log didn't return any error. I need help with assigning another properties like prdName, prdCategory, and prdSup with user input correctly. Can someone help me with this please?
//product.ts
export class Product {
$prdKey: string;
prdName: string;
prdCategory: string; //Category
prdSup: string; //supplier
prdDescription: string;
prdImage: string; //name
prdUrl: string; //url
file: File;
constructor(file: File) {
this.file = file;
}
}
//service.ts
variable: any;
selectedProduct: Product = new Product(this.variable); //-->there was an error here that said expected 1 argument but got 0 so i add variable:any;
private basePath = '/product';
pushFileToStorage(Product: Product, progress: {
percentage: number
}) {
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child(`${this.basePath}/${Product.file.name}`).put(Product.file);
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
// in progress
const snap = snapshot as firebase.storage.UploadTaskSnapshot
progress.percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100)
},
(error) => {
// fail
console.log(error)
},
() => {
// success
/*--What should i assign here?--*/
Product.prdName = Product.file.name,
Product.prdCategory = Product.file.name,
Product.prdSup = Product.file.name,
Product.prdDescription = Product.file.name,
/*------------------------------------------*/
Product.prdUrl = uploadTask.snapshot.downloadURL,
Product.prdImage = Product.file.name,
this.saveFileData(Product)
}
);
}
private saveFileData(Product: Product) {
this.firebase.list(`${this.basePath}/`).push(Product);
}
//component.ts
upload() {
const file = this.selectedFiles.item(0);
this.currentFileUpload = new Product(file);
this.ProductService.pushFileToStorage(this.currentFileUpload, this.progress);
}
<!--component.html-->
<!--form snippet-->
<form #productForm="ngForm" (ngSubmit)="upload()">
<div class="form-group">
<label>Product Name</label>
<input class="form-control" name="prdName" #prdName="ngModel" [(ngModel)]="ProductService.selectedProduct.prdName">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Please let me know if more snippets are needed. Thank you in advance.
(Update)
I put the push function inside //success condition, however i'm not sure what to assign for each class properties. Product.prdName = Product.file.name, will give me prdName equal to the file name. I tried Product.prdName = selectedProduct.prdName, but looks like it is not correct.
I figured it out, it should looks like this, works for me :D
() => {
// success
this.productList.push({
prdName: this.selectedProduct.prdName,
prdCategory: this.selectedProduct.prdCategory,
prdSup: this.selectedProduct.prdSup,
prdDescription: this.selectedProduct.prdDescription,
prdUrl: this.selectedProduct.prdUrl = uploadTask.snapshot.downloadURL,
prdImage: this.selectedProduct.prdImage = Product.file.name,
})
this.saveFileData(Product)
}