I'm in the process of learning Angular 2 using TypeScript. So far I've written a little API service that uses HTTP get method to feed me json data using observables. Everything is working fine, I can use the data in my view, I can also use the data in my component, but only while I'm subscribed to the getData() method.
Why is that and what other possibilities do I have to make the object array available to all methods in my component for easy iteration and management?
Example component:
export class SomeComponent implements OnInit {
public someData: DataObject[];
public constructor(private service: SomeService) {}
public ngOnInit(): void {
this.loadData();
this.useData();
}
private loadData(): void {
this.service.getData().subscribe(data=> {
this.someData = data;
this.someData.forEach(dataObject => {
// this works fine
});
});
}
private useData(): void {
this.someData.forEach(dataObject => {
// dataObject is (of type?) undefined, why?
});
}
}
It's because http calls are async. Your this.useData(); does not wait this.loadData(); to finish. This should work:
private loadData(): void {
this.service.getData().subscribe(data=> {
this.someData = data;
this.useData();
});
}
Related
I'm trying to retrieve data from an array in my services through a get function. I'm aware that I can use the .filter or .find functions, however, I'm really getting confused about the execution and after many tries I was unable to retrieve the information. I can understand that this may considered a very basic question but I'm fairly new to this. Any help would be really appreciated.
tracker-data.ts:
export class TrackerData {
entry?: number;
exit?: number;
clicks: string[] = [];
url?: string;
public get clickCount(): number
{ return this.clicks.length; }
}
tracker.service.ts
import { Injectable } from '#angular/core';
import { TrackerData } from '../modules/tracker-data';
#Injectable({
providedIn: 'root'
})
export class TrackerService {
websiteData: TrackerData[] = [];
public count = 0;
constructor() { }
addTrackerData(trackerData: TrackerData): void {
this.websiteData.push(trackerData);
}
getData() {
return this.websiteData;
}
}
summary.component.ts (where the data should be displayed)
import { Component, OnInit } from '#angular/core';
import { TrackerService } from 'src/app/services/tracker.service';
import { TrackerData } from 'src/app/modules/tracker-data';
#Component({
selector: 'app-summary',
templateUrl: './summary.component.html',
styleUrls: ['./summary.component.scss']
})
export class SummaryComponent implements OnInit {
websiteData: TrackerData[] = [];
constructor(
private trackerService: TrackerService,
) { }
ngOnInit(): void {
}
getData(){
this.websiteData = this.trackerService.getData();
console.log(this.websiteData);
}
}
You are not receiving the data in the log because the function isn't being called anywhere in the code and it's also not being used in any of the lifecycle events. To initialize data from a service it is common to call functions from services in the ngOnInit lifeCycle event. This seems to be what you are trying to do so you should call the trackerService.getData() function there.
It would look something like this:
ngOnInit(): void {
this.websiteData = this.trackerService.getData();
console.log(this.websiteData);
}
There are more ways to call the function, it is possible to call the function in the constructor(when the component is being created) but this is not standard practice. Functions can also be triggered when an event occurs on your webpage, for example when a user clicks on a button.
I suggest you look more into the Angular documentation though because it's very good and explains all the basics in better detail: https://angular.io/docs
I want to capture the emit event from one component into other component
export class modifyController implements angular.IComponentController {
static $inject = ['$scope','$state'];
constructor(public $scope:ng.IScope,public $state: ng.ui.IStateService) {
this.sendMessage();
};
public sendMessage(): void {
let tempObj = { 'title': 'name', 'id': '1234' }
this.$scope.$emit('modify',tempObj);
}
}
In Other component's controller
constructor(public $scope:ng.IScope) {
//need to write this outside constructor
this.$scope.$on('modify', (event: ng.IAngularEvent, data: any) => {
console.log('from ON event...',data);
});
}
In angularjs1.4 i can directly use $scope.$on() like any other function , but while using this in same way in angularjs1.7 with typescript, i am getting error -[ts] Unexpected token. A constructor, method, accessor, or property was expected.
How to use this.$scope.$on in typescript,AngularJS1.7?
Thanks
I am looking for a way to do some action after all controls on a page are loaded. These controls are loaded in parallel by calling http get.
I tried code similar to the one below but it doesn't seem to do the trick. If it worked correctly, the sometext should display 'done'. It doesn't. I am not sure I understand correctly how the forkJoin works. I used to do this kind of chaining in Angular 1.x using promises. Any help in understanding the problem and a solution is appreciated.
The solution I am looking for is similar to this question for Angular 1.x: Angular combining parallel and chained requests with $http.then() and $q.all()
Complete code is at http://plnkr.co/edit/xH6VJo
Source is src/dash.ts
This is Angular 5 and typescript.
export class dash implements OnInit {
sometext = 'Some text ...';
private httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(private http: HttpClient) {}
click_me(): void {
this.sometext = '';
forkJoin([
of(this.first_()),
of(this.second_())
]).subscribe(val => {
this.sometext = 'done...';
})
}
first_(): void {
this.http.get<any>('data/sampledata.json',this.httpOptions).subscribe(val=> {
this.sometext = val.items[0].value;
});
}
second_(): void {
this.http.get<item[]>('data/sampledata.json',this.httpOptions).subscribe(val=> {
this.sometext = val.items[1].value;
});
}
}
The main reason is because your first() and second() doesn't return an Observable. .forkJoin() can only take in an array of Observables, fire them in parallel, and then it has to wait for all observables to complete before it starts to emit the resultant values. The main reason your complete() handler never gets executed is because there is no Observables in the .forkJoin() in the first place, hence none of them can complete, and your .forkJoin() will never emit.
Here's what you should do. For first() and second(), have them return an observable. If you want to change the this.sometext when both of them executes, use the operator .do():
first_(): Observable<any> {
this.http.get<any>('data/sampledata.json', this.httpOptions)
.do(val => {
this.sometext = val.items[0].value;
});
}
second_(): Observable<any> {
this.http.get<item[]>('data/sampledata.json', this.httpOptions)
.do(val => {
this.sometext = val.items[1].value;
});
}
Now your click_me() should be working fine:
click_me(): void {
this.sometext = '';
forkJoin([
of(this.first_()),
of(this.second_())
]).subscribe(val => {
this.sometext = 'done...';
})
}
Note that since Observable.forkJoin() fires their requests in parallel, there is NO GUARANTEE that your first() will be executed before your second().
I started working with meteor which seems to be good for my use, a problem occurred where I get my documents only 9/10 times. I think I implemented something wrong.
I use Angular 1.5 and Typescript
My collection gets created in the lib folder at /
import { Mongo } from 'meteor/mongo';
export const Locations= new Mongo.Collection('locations');
then the collection gets imported to my service
import {app} from '../../js/lib/app';
import {Locations} from '../../../lib/collections';
export class LocationsService {
locations: any;
constructor(){
console.log('Constructor called');
this.locations = Locations
.find({})
.fetch();
console.log('documents loaded');
console.log(this.locations);
}
public createLocation(any:any){
Locations.insert(any);
}
public updateLocation(identity:any, modifier:any){
Locations.update(identity,modifier);
}
}
app.service('locationsService', LocationsService);
Here are the console.logs from 3 different page refreshes:
It looks like the amount of docs I get is totally random.
Here is some code that will help you. It uses the "resolve" feature of ui-router to hold up loading of the page until data is loaded. In this case there are two things being resolved:
User record
Elders record
The second one needs an "elderid" from users.profile in order to find an elder record.
function config($locationProvider, $urlRouterProvider, $stateProvider) {
'ngInject';
$stateProvider
.state('member.calendar', {
url: '/calendar',
template: "<membercalendar></membercalendar>",
resolve: {
currentUser: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Meteor.user());
}
}
});
return deferred.promise;
},
elder: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Elders.find({_id: Meteor.user().profile.elderid}));
}
}
});
return deferred.promise;
}
}
});
}
This works well if you want the data to be loaded fully before the page loads. If you don't mind an asynchronous update to the page, you can use getReactively to make a helper run once the data has resolved. I can give you example code for that too if you like.
My new Service simply subscribes
export class LocationsService {
locations:any;
constructor(){
console.log('Constructor called');
//Subscribe to a collection//localStorage.getItem('ID')
Meteor.subscribe('locations', 2223 );
this.locations = Locations;
console.log('documents loaded');
}
public createLocation(any:any){
Locations.insert(any);
}
public updateLocation(identity:any, modifier:any){
Locations.update(identity,modifier);
}
}
app.service('locationsService', LocationsService);
In my controller i simply add the fetching of my documents in the Tracker.
import {app} from '../../js/lib/app';
import {LocationsService} from './LocationsService';
import {Tracker} from 'meteor/tracker';
export class LocationsController {
static $inject = ['locationsService','$reactive','$scope'];
public $reactive: any;
public $scope: any;
public locations: any[];
constructor(private locationsService: LocationsService, $reactive:any, $scope:any){
this.locationsService = locationsService;
this.$reactive = $reactive;
this.$scope = $scope;
$reactive(this).attach(this.$scope);
Tracker.autorun(() => {
//console.log('autorun');
this.locations = locationsService.locations.find({}).fetch();
console.log(this.locations)
});
}
public createLocation(location:any){
console.log('Locations does what it should');
console.log(location);
this.locationsService.createLocation(location);
}
public updateLocation(location:any, modifier:any){
this.locationsService.updateLocation(location._id,modifier)
}
}
app.controller('locationsController', LocationsController);
The only problem I have now is that the modell updates like a charm but not the view when I create new locations. The autorun works and the new location gets saved in my collection but I see it only if I reload. But that one is low priority for me.
Basically what i try to do is to hit my API once and save the result inside global variable in my Service, and then share and modify this value in my parent and child component with two helpers functions.
repairs.service.ts
public myItems:any[];
public GetRepairs = ():Observable<any> => {
this.headers = new Headers();
this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));
return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
.map((res) => {return res.json();
}).map((item) => {
let result:Array<any> = [];
if (item.items) {
item.items.forEach((item) => {
result.push(item);
});
}
this.myItems = result;
return this.myItems;
});
};
public GetItems() {
return this.myItems;
};
public UpdateItems(data:any[]) {
this.myItems = data;
};
And then in my main component i do
repairs.component.ts
export class RepairsComponent implements OnInit {
public myItems:any[];
constructor(private _userService:UserService,
private _RepairsService:RepairsService,
public _GlobalService:GlobalService) {
}
ngOnInit() {
this._userService.userAuthenticate();
this.getAllItems();
}
private getAllItems():void {
this._RepairsService
.GetRepairs()
.subscribe((data) => {
this._RepairsService.UpdateItems(data);
},
error => console.log(error),
() => {
this.myItems = this._RepairsService.GetItems();
});
}
}
This work just fine but when i try to invoke GetItems() in child component i get undefinded. I try to do it inside constructor and ngOnInit with the same result.
child.component.ts
export class ChildComponent {
private items:any[] = [];
constructor(private _RepairsService:RepairsService,
private _Configuration:Configuration) {
this.items = this._RepairsService.GetItems();
// undefinded
}
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
}
From what i can see in the limited amount of code you shared, it would seem you are trying to get the items before the http get call finishes and saves the data. I think a better design pattern would be to make the GetItems() function also an observable or promise, and check if the data is there, if not call the http get call, and once that completes send the data back to the different components that need it.
As #MSwehli mentioned with async code execution you can't rely on the order of code lines. In this code:
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
the async code in GetItems(); is scheduled for later execution into the event queue and then continued with the sync code. The scheduled code will be executed eventually but it's not determined when. It depends on the response of the server in this example.
If you return a Promise you can use .then(...) the chain the execution so that your code is only executed when the async execution is completed.
There are two errors/inconsistencies in your code:
userAuthenticate() call followed with getAllItems() call. These calls are async, user is not yet authenticated by the time getAllItems() is called, getAllItems will fail.
Solution here is to chain calls using rxjs flatMap:
//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
return repairsService.GetRepairs();
}).subscribe(..process repairs..);
getAllItems() is called nearly at the same time as GetItems(). In most cases it fails also, because previous http request is not completed when GetItems() is called.
In my opinion early initialization is not necessary here, use service directly:
//ChildComponent
ngOnInit() {
this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}
You could add console.log statements in each part of the code to see the order of events in your app.