I have different problem.
I retrieve data from Services into variable, but this data doesn't display on screen after loading (I use Subscription for this operation). All data appears on screen when I click button with function getMessages(){ console.log(this.messages);}
Can you explain why?
import {Component, NgZone, OnInit} from '#angular/core';
import {GmailApiService} from "../google/api/gmailApi.service";
import {Subscription} from "rxjs/Rx";
#Component({
selector: 'gmail-app',
templateUrl: '/app/gmail/gmail.component.html'
})
export class GmailComponent implements OnInit{
public messages: Array<string>;
subscription:Subscription;
constructor(private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to:(azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => this.messages = messages);
}
getMessages(){
console.log(this.messages);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Component files:
<template [ngIf]="messages">
Messages:
<h1 *ngFor="let message of messages">**{{message?.id}}**</h1>
</template>
<button id="button-get" (click)="getMessages(event)">
getMessages
</button>
---UPDATE---SOLUTIONS---
I found solutions for my problem, i add zone in subscription and now it's work correctly. Below I present part of my code after h
constructor(private zone:NgZone, private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to:(azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => {
this.zone.run(() => {
this.messages = messages;
});
});
}
I think it may be associated with lifecycle hooks. See this: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
I'm not sure but maybe you should implements doCheck and in subscribe call ngDoCheck method? Something like this:
export class GmailComponent implements OnInit, DoCheck {
public messages: Array<string>;
subscription:Subscription;
constructor(private gmailApi: GmailApiService){
}
ngOnInit() {
this.gmailApi.checkAuthAuto('from:(xyz#zyx.com) OR to: (azx#saa.com)');
this.subscription = this.gmailApi.openMessages$
.subscribe(messages => {
this.messages = messages;
ngDoCheck();
});
}
getMessages(){
console.log(this.messages);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
ngDoCheck() {
//do nothing, just call
}
}
Related
I have parent component which call a REST api to get profile (JSON) data. the parent component pass this data to child component using input (array) to create dynamic form (input fields). All fields have been created successfully. the form is saving the data to DB using API post.
Problem: If the user navigate to home page and come back to profile dynamic form the data value is showing with the data version before save (old version of the data). If I refresh the screen, then I can get the correct data. the view has not been updated when load the form again. I checked the log and the JSON data being returned updated, but the form is getting the previous version of the values.
Parent Html
<df-profile [profileData]="profileData"></df-profile>
Parent Component
import { Component, ChangeDetectorRef, Input, OnInit} from '#angular/core';
import { Http } from '#angular/http';
import { Router } from '#angular/router';
import { AuthenticationService, UserService } from '../services/index';
import { ProfileModel } from '../models/index';
#Component({
moduleId: module.id,
selector: 'user-profile',
templateUrl: 'user.profile.component.html'
})
export class UserProfileComponent implements OnInit {
profileData: ProfileModel[] = [];
public isDataAvailable: boolean = false;
constructor(
public router: Router,
private userService: UserService,
private authenticationService: AuthenticationService,
) {}
ngOnInit() {
console.info("Starting GameDay Component ngOnInit ... ...");
console.info(this.authenticationService.isLoggedIn());
if (this.authenticationService.isLoggedIn()) {
this.getData().then(() => this.isDataAvailable = true);
} else {
this.authenticationService.logout();
}
}
getData(): Promise<ProfileModel<any>[]> {
return this.userService.get.profile().then(data => {
this.profileData = data[0]['profileList'];
});
}
}
Child Dynamic Form Html
<form (ngSubmit)="form.valid && onSubmit()" [formGroup]="form">
<div class="kode-team-match" *ngFor="let item of profileData">
<ul>
<li class="home-kode-field"><df-field [field]="item.field" [form]="form"></df-field></li>
</ul>
<div class="clearfix"></div>
</div>
<div class="form-group">
<div class="col-md-12" style="text-align:center;">
<button type="submit" [disabled]="loading" class="kode-modren-btn thbg-colortwo"> Salvar</button>
</div>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
Child Dynamic Form Component
import { Component, Input, ChangeDetectorRef, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
import { AuthenticationService, UserService } from '../services/index';
#Component({
moduleId: module.id,
selector: 'df-profile ',
templateUrl: 'profile.form.component.html',
providers: [ProfileControlService]
})
export class ProfileFormComponent implements OnInit {
#Input() profileData: ProfileModel[];
form: FormGroup;
payLoad = '';
constructor(
private pcs: ProfileControlService,
) {
this.profileData = [];
}
ngOnInit() {
//console.log('Starting form component ... ...');
this.form = this.pcs.toFormGroup(this.profileData);
}
onSubmit() {
this.userService.saveUserProfile(array)
.subscribe(result => {
this.isFailed = result;
if (result === true) {
this.service.success(this.translate.instant('register login'), this.translate.instant('register success'));
} else {
this.error = this.userService.errorMessage;
}
this.loading = false;
});
}
}
I'm struggling to see what I'm doing wrong here. Can anyone help?
I don't know if I need to invoke change detection and where.
I'm calling data from a service. I have to pass in an object with properties to retrieve the data. From what I can tell, I'm passing it in correctly and not receiving any errors in console, but the object that I assign to the data is empty. I'm including my Service and Component where I'm outputting the data.
I call the exact same endpoint from an Angular1 App with the same "payload" values and get my data object returned. I'm sure I'm missing something simple. Still getting used to Angular 2.
SERVICE
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable ()
export class DataService {
private qrDataUrl = 'http://my.data.url/endpoint';
private payload = {
DeliveryTypeId: 0,
PickupLocationid: 0,
PaymentTypeId: 0,
Items: ['XXXX'],
ApplicationToken:'123MYTOKEN456',
DateOfBirth: "1961-01-01T00:00:00.000Z"
};
constructor(private http: Http){ }
getQr():Observable<any>{
return this.http.put(this.qrDataUrl, this.payload)
.map((res:Response) => res.json());
}
}
COMPONENT
import { Component, OnInit } from '#angular/core';
import { DataService } from '../shared/dataService';
#Component({
selector: 'my-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
providers: [DataService]
})
export class HomeComponent implements OnInit {
qrData = { };
constructor(private dataService: DataService) {
// Do stuff
}
getData(){
this.dataService.getQr().subscribe(data => this.qrData = data);
console.log(this.qrData); //This logs an empty object
}
ngOnInit() {
console.log('Hello Home');
this.getData();
}
}
Asynchronous operation doesn't work synchronous way. You have to wait until the ajax gets completed. You can only see a data inside subscription of your getQr function once the service call resolved.
getData(){
this.dataService.getQr().subscribe(data => {
this.qrData = data;
console.log(this.qrData); //This logs an empty object
});
}
I'm quite new to Angular 2 and I would like to transfer an array made in a parent component, via #Input(), to its child.
In the parent I create the array, add data from a service, and display it in the console (Console output 1). In the child component I then use ngOnChanges to display it in the console again (Console output 2). As you can see below, the length of the array changes from 12 to 0. I suppose this is because the array changes to an object when it's passed to the child?
How would I fix this?
Parent
import { Component, OnInit } from '#angular/core';
import { Module, MapMarkerData } from './coreclasses';
import { TimelineService } from './input.service';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
providers: [TimelineService]
})
export class AppComponent implements OnInit {
modules: Module[];
mapMarkerData: any;
constructor(private timelineService: TimelineService) {
this.mapMarkerData = new Array<MapMarkerData>();
}
getModules(): void {
this.timelineService.getModules().then(modules => {this.modules = modules; this.setMapModuleData(this.modules);});
}
setMapModuleData(modules: Array<any>): void {
for (let module of modules) {
if (module.className) {
var id = module.id;
var className = module.className;
let contents: Object = {id: id, className: className};
this.mapMarkerData.push(contents);
}
}
console.log(this.mapMarkerData); // CONSOLE OUTPUT 1
console.log(this.mapMarkerData.length);
}
}
Child
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '#angular/core';
import { MapMarkerData } from './coreclasses';
#Component({
selector: 'timeline-map',
templateUrl: 'app/timeline.map.component.html'
})
export class TimelineMapComponent implements OnChanges {
#Input()
mapMarkerData: any;
ngOnChanges(changes: any) {
console.log(this.mapMarkerData); // CONSOLE OUTPUT 2
console.log(this.mapMarkerData.length);
}
}
Parent Template
...
<div id="map" class="mapLarge">
<timeline-map [mapMarkerData] = "mapMarkerData"></timeline-map>
</div>
...
Console Output 1
Array[12]: [Object, Object, ... ]
Console Output 2
Array[0]: [Object, Object, ... ]
EDIT Important
because you're passing same reference into child component, so the ngOnChanges lifecycle only fired 1 time.
please checkout this version, open your console tabs: https://plnkr.co/edit/WUDGOx?p=preview
so, if you wanna catch every changes in ngOnChanges lifecycle, you must passing a difference array, like this: https://plnkr.co/edit/8awiqe?p=preview
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-root',
template: `
<h2>App Component</h2>
<p><strong>This app will trigger ngOnChanges with immutable array</strong></p>
<app-content [posts]="posts">
</app-content>
`
})
export class AppComponent implements OnInit {
latestPosts: any[] = [];
posts: any[] = [];
ngOnInit() {
// fake api call
setTimeout(() => {
this.latestPosts.push.apply(this.latestPosts, [
{name: 'Post no.1'},
{name: 'Post no.2'},
{name: 'Post no.3'}
]);
this.posts = [].concat(this.latestPosts);
}, 300);
}
}
=== 2nd option === you could check by yourself in DoChecklifecycle: https://plnkr.co/edit/oxsISD?p=preview
import { Component, Input, DoCheck, IterableDiffers } from '#angular/core';
#Component({
selector: 'app-content',
template: `
Status: {{ status }}
<div *ngFor="let post of pp">
{{ post.name }}
</div>
`
})
export class ContentComponent implements DoCheck {
#Input()
posts: any[];
differ: IterableDiffers;
status: string = '';
constructor(private differs: IterableDiffers) {
this.differ = this.differs.find([]).create(null);
}
ngDoCheck() {
var changes = this.differ.diff(this.posts);
if (changes) {
console.log('ngDoCheck');
this.status = 'ngDoCheck invoked!'
}
}
}
Note that you must pay a cost because the above ngDoCheck method will invoke on every change detection run.
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
https://angular.io/docs/ts/latest/api/core/index/DoCheck-class.html
https://angular.io/docs/ts/latest/api/core/index/SimpleChange-class.html
https://angular.io/docs/ts/latest/api/core/index/IterableDiffers-class.html
END
for the initial state, it's empty, then the value will assign to this property.
js log async
I am trying to have this component get string data from a service that returns an observable
import { Component, OnInit, OnDestroy } from '#angular/core';
import { TabFourService } from "./tabfour.service";
import { Subscription } from "rxjs";
#Component({
selector: 'tab-four',
template: `
{{title}}
`
})
export class TabFourComponent implements OnInit, OnDestroy{
title: string = "This is Tab four";
subscription: Subscription;
constructor(private tabfourService: TabFourService){}
ngOnInit(){
console.log("now in init");
this.getItems();
this.getItems();
}
getItems(){
console.log("now in get items");
this.subscription = this.tabfourService.getItems()
.subscribe(data => console.log("testing observable"));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Here is a simple service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from "rxjs";
#Injectable()
export class TabFourService {
items: string;
private itemSource = new Subject<string>();
constructor(){}
itemSource$ = this.itemSource.asObservable();
getItems(): Observable<string> {
this.itemSource.next("aaa");
return this.itemSource$;
}
}
I list the service as a provider in #NgModule() so that it can be shared by all component instances. There is also a router for TabFourComponent so every time I navigate to it I should see "testing observable" in the console.
But it didn't show until I called getItems() twice.
I wonder why it didn't get triggered at the first time.
tabfour.component.ts:20 now in init
tabfour.component.ts:26 now in get items
tabfour.component.ts:26 now in get items
tabfour.component.ts:28 testing observable
Edit:
While in another case where services provide data from http.get
// in service
getLibraryFromDatabase(): Observable<any[]> {
return this.http.get(<some url>)
.map(data => data.json())
.catch(this.handleError);
}
Component doesn't need to call service's method again after subscription.
// in component
ngOnInit() {
this.subscription = this.libraryService.getLibraryFromDatabase()
.subscribe(libs => this.createLibs(libs));
}
If the subscribed method is not called until the second getItems() execution, it's because the event is triggered BEFORE the subscription is taken in charge. Register the subscription in the ngOnInit method, (that's the best place to do this kind of things) and then call the method that triggers the event that you have subscribed to:
ngOnInit() {
this.subscription = this.tabfourService.getItems()
.subscribe(data => console.log("testing observable"));
// The subscription has been taken in charge, now call the service's method
this.tabFourService.getItems();
}
Doing some experimentation with Angular2 and curious about how to solve a situation where a service exposes a shared observable. Where one component is responsible for getting the data and another is responsible for displaying the data. Here is some code:
The common HttpService responsible for getting the data
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class Service {
subject = new Subject<string[]>;
observable$ = this.subject.asObservable();
constructor(private http: Http) {}
observable: Observable<string[]>;
get(link: string): Observable<string[]> {
this.observable$ = this.http.get('myapi.com')
.map((res: Response) => this.subject.next(res.json())
.catch(this.handleError);
return this.observable$;
}
/**
* Handle HTTP error
*/
private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
The GetComponent responsible for getting the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Service } from '../shared/index';
#Component({
moduleId: module.id,
selector: 'get',
templateUrl: 'get.component.html',
providers: [Service],
})
export class GetComponent {
constructor(public service: Service) {}
submit() {
this.service.get(this.url).subscribe();
}
}
The DisplayComponent responsible for displaying the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
#Component({
moduleId: module.id,
selector: 'display',
templateUrl: 'display.component.html'
})
export class DisplayComponent {
subscription: Subscription;
constructor(public service: Service) {}
ngOnInit() {
// the observable is undefined :(
this.subscription = this.service.observable$.subscribe(data => {
console.log(data);
},
error => {
// this never gets reached :(
})
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
}
This design works ok except the error handling does not work for the DisplayComponent. Also the map function in the HttpService doesn't seem quite right this.subject.next(res.json()
Also it doesn't seem quite right that the GetComponent has to "subscribe" like this this.service.get(this.url).subscribe();
What is the proper way to design this sort of thing? How can I get the DisplayComponent to observe errors thrown by the HttpComponent
Your code has several problems where Service.$observable is modified several times:
observable$ = this.subject.asObservable();
this.observable$ = this.http.get('myapi.com') .... // remove this line
The right way to do this:
Get Component calls Service to get data. After data is loaded, Get Component emits a Data Ready event with the data. Display Component, and other components that use this data, listen to DataReady event and update data when the event is emitted.
Code to explain my answer:
#Injectable()
export class GetService {
/// subject
subject = new Subject<string[]>()
/// the observable
observable = this.subject.asObservable()
constructor(private $http: Http) {
}
/// get data
getData() {
// not override observable here
this.$http.get("api.com")
.map(response => response.json()) // map the data
.subscribe((data: string[]) => this.subject.next(data), // emit data event
error => this.subject.error(error)) // emit error event
}
}