I am having issues using a promise to return a Degree object in Angular 2. The first return statement (uncommented) in degree.service works just fine in combination with the uncommented implementation of getDegree() in build.component. However, when I try to switch to either of the commented implementations using a promise, the object always comes back as "undefined"
degree.service.ts
import { Injectable } from '#angular/core';
import { Degree } from '../components/degree';
import { Category } from '../components/category';
import { Course } from '../components/course';
import { SAMPLE } from '../components/mock-degree';
#Injectable()
export class DegreeService{
getDegree(){
return SAMPLE;
// return Promise.resolve(SAMPLE);
// return new Promise<Degree>(function (resolve, reject) {
// resolve(SAMPLE);
// })
}
}
build.component.ts
import { Component, Input, OnInit } from '#angular/core';
import { SEMANTIC_COMPONENTS, SEMANTIC_DIRECTIVES } from "ng-semantic";
import { Course } from '../course';
import { Category } from '../category';
import { PaneComponent } from './pane/pane.component';
import { Degree } from '../degree';
import { DegreeService } from '../../services/degree.service';
const blank: Category = {
name: '',
rank: 1,
rulestat: 'no',
categories: [],
courses: []
}
#Component({
selector: 'my-build',
directives: [SEMANTIC_COMPONENTS, SEMANTIC_DIRECTIVES, PaneComponent],
templateUrl: `app/components/build/build.component.html`,
providers: [DegreeService]
})
export class BuildComponent implements OnInit{
constructor(private degreeService: DegreeService){}
level: number = 1;
currDeg: Degree;
parents = [blank, blank, blank, blank];
setLast(lst: Category){ //pass category objects, do all UI changing here
this.level = lst.rank + 1;
this.parents[lst.rank - 1] = lst;
}
getDegree(){
//this.degreeService.getDegree().then(deg => this.currDeg = deg)
this.currDeg = this.degreeService.getDegree();
}
ngOnInit(){
this.getDegree();
}
}
I don't know how you use the currDeg in your template but with promises, things are asynchronous. So the corresponding object will be undefined at the beginning since it will be set later (when the promise is resolved). And this, even if the promise is directly resolved with Promise.resolve.
export class DegreeService{
getDegree(){
return Promise.resolve(SAMPLE);
}
}
#Component({
selector: 'my-app',
providers: [DegreeService],
templateUrl: 'src/app.html'
})
export class App {
constructor(private degreeService:DegreeService) {
}
getDegree(){
this.degreeService.getDegree().then(deg => {
this.currDeg = deg;
console.log('this.currDeg = ' + this.currDeg); // <------
});
}
ngOnInit(){
this.getDegree();
}
}
See this plunkr: https://plnkr.co/edit/1fxE0okyMNj2JktURY4w?p=preview.
Related
EDIT: I made changes in the push method but it still did not work
I am making get request to an api and pushing each of the responses to an array. The array is visible when logged to console. On printing the length of the array in the template length comes out to be 5. But when I try to iterate through it using ngFor no output is being displayed
Service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import {Quote} from 'src/app/models/quote';
#Injectable({
providedIn: 'root'
})
export class StockpriceService {
url1='https://financialmodelingprep.com/api/v3/quote-short/';
url2='?apikey=efa24b272821b542c459557332c02a1e';
constructor(private http:HttpClient) {
}
//alpha apikey="VETRGM94G70WQGX9";
getQuote(symbol:string) //load data from api
{
return this.http.get<Quote>(this.url1 + symbol + this.url2);
}
}
ts file
import { Component, OnInit } from '#angular/core';
import{Quote} from 'src/app/models/quote';
import{StockpriceService} from 'src/app/services/stockprice.service';
import { timer } from 'rxjs';
#Component({
selector: 'app-stocks',
templateUrl: './stocks.component.html',
styleUrls: ['./stocks.component.css']
})
export class StocksComponent implements OnInit {
stocks: Array<Quote>=[];
symbols=['AAPL', 'GOOG', 'FB', 'AMZN', 'TWTR'];
constructor(private serv:StockpriceService) { }
ngOnInit(): void {
this.symbols.forEach(symbol => {
this.serv.getQuote(symbol).subscribe(
(data:Quote)=>{
console.log(data);
this.stocks.push(
{
symbol:data.symbol,
price:data.price,
volume:data.volume
}
);
}
)
});
console.log('stocks array is')
console.log(this.stocks);
}
}
Template
<div *ngFor="let stock of stocks">
{{stock.symbol}}
{{stock.price}}
</div>
sample api response
[ {
"symbol" : "AAPL",
"price" : 126.81380000,
"volume" : 36245456
} ]
Accordingly I have an interface defined for it as
export interface Quote{
symbol:string;
price:number;
volume:number;
}
This will work fine.
this.serv.getQuote(symbol).subscribe((data: Quote[]) => {
console.log(data);
this.stocks.push(...data);
});
I got problem with angular component.
When I make my component with selector, it works as expected: execute httpget, and render photo with title.
But in console I got two errors:
ERROR TypeError: "_co.photo is undefined"
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
and
ERROR CONTEXT
...
PhotoHolderComponent.html:2:8
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
I got html:
<div class="photo-holder">
<h2>{{photo.title}}</h2>
<img src="{{photo.url}}">
</div>
and ts:
import { Component, OnInit } from '#angular/core';
import { Photo } from './photo'
import { PhotoDeliveryService } from '../photo-delivery-service.service'
#Component({
selector: 'app-photo-holder',
templateUrl: './photo-holder.component.html',
styleUrls: ['./photo-holder.component.css']
})
export class PhotoHolderComponent implements OnInit {
photo:Photo
constructor( private photoService : PhotoDeliveryService) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => this.photo = {...data})
}
}
and service :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Photo } from './photo-holder/photo'
#Injectable({
providedIn: 'root'
})
export class PhotoDeliveryService {
value : Number
url : string
constructor(private http: HttpClient) {
this.url = "https://jsonplaceholder.typicode.com/photos/";
this.value = Math.floor(Math.random() * 10) + 1;
}
getRandomPhoto() {
return this.http.get<Photo>(this.getUrl())
}
getUrl(){
return this.url + this.value;
}
}
I suspect that could be made by binding property before query results was returned.
How can I rid off this problem, can I wait for this query, or this is different kind of problem ?
You are getting the error because before your service could resolve, the template bindings are resolved and at that time photo object is undefined.
first thing, you can initialize the photo object but then you might have to detect the changes using ChangeDetectorRef to reflect the value returned by the service.
photo:Photo = {
title:'',
url:''
};
constructor( private photoService : PhotoserviceService, private cdr:ChangeDetectorRef) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => {
this.photo = data;
this.cdr.detectChanges();
});
}
I have two components parent and child
parent is resolving a promise in constructor, child is reveiving the result of the promise as #Input() parameter
child is not receiving the result of the promise in life cicle hook other than afterViewCheck and afterContentCheck, I want to avoid those.
I also want to avoid a shared service containing shared data in behaviorSubject or something like that
so the question is, can I await the promise before construct the template with te result of the promise as an input parameter?
Parent:
// html: <app-chil [brands]="brands"></>
brands: Brand[] = []
constructor() {
this.getBrands()
}
async getBrands() {
this.brands = await new Promise<Brand[]>(resolve =>
this.equipmentService.getBrands().subscribe(brands => resolve(brands)))
}
child:
#Input() brands: Brand[] = []
constructor() { }
ngAfterViewInit() {
console.log(this.brands) // receive brands here
}
With a setter, you dont need to use any life cycle hook and its cleaner.
brands: Brand[] = [];
constructor() {
this.getBrands()
}
async getBrands() {
this.brands = await new Promise<Brand[]>(resolve =>
this.equipmentService.getBrands().subscribe(brands => resolve(brands)))
}
child:
private _brands: Brand[] = [];
#Input() set brands(data: Brand[]) {
this._brands = data;
console.log(data);
}
constructor() { }
Make observable from promise and pass that observable to the child and then use simple interpolation with async pipe.
Here goes an example. Read it and adapt it to your app (it's tested as is).
child:
import { Component, Input } from '#angular/core';
import {Observable} from 'rxjs';
#Component({
selector: 'hello',
template: `<h1>Hello {{name | async}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
#Input() name: Observable<string[]>;
}
parent:
import { Component } from '#angular/core';
import { of } from 'rxjs';
/* import { from } from 'rxjs'; //import 'from' to convert your promise to observable*/
#Component({
selector: 'my-app',
template: `<hello [name]="name"></hello>`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = of("angular"); //name = from(yourPromise)
}
So I have two not related components and I'm trying to communicate between them using a service and a BehaviorSubject. Everything is cool, data is exchanged, but when i call the service from one of the components, it doesn't trigger change detection on the other component.
So to show what I'm talking about in code:
The service:
import {Injectable, Optional, EventEmitter} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { forEach } from '#angular/router/src/utils/collection';
#Injectable()
export class SbkService {
items: any = [];
private _itemsSource = new BehaviorSubject<any>(0);
items$ = this._itemsSource.asObservable();
constructor (
private _localStorageService: LocalStorageService
) {}
storeSelection(item) {
this.items.push(item);
this.setLocalStorage();
}
removeSelection(selectionId) {
for (var i = this.items.length-1; i >= 0; i--) {
if (this.items[i].selectionId == selectionId)
this.items.splice(i, 1);
}
this.setLocalStorage();
return true;
}
getLocalStorage() {
this.items = this._localStorageService.get('items');
this._itemsSource.next(this.items);
return this.items;
}
setLocalStorage() {
this._localStorageService.set('items', this.items);
this._itemsSource.next(this.items);
return true;
}
}
Component 1:
import { Component, OnInit } from '#angular/core';
import { SbkService } from '../../services/sbk.service'
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-right-sidebar',
template: `<ul>
<li *ngFor="let selection of selections">
{{selection.name}}
<span class="cutom-btn" (click)="remove(selection.selectionId)">
delete
</span>
</li>
</ul>`,
styles: []
})
export class RightSidebarComponent implements OnInit {
selections: any = [];
subscription:Subscription;
constructor (
private _sbkService: SbkService
) {
}
ngOnInit() {
this.subscription = this._sbkService.items$
.subscribe(selections => {
this.selections = selections })
this._sbkService.getLocalStorage();
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
remove(selectionId) {
this._sbkService.removeSelection(selectionId);
}
}
Component 2:
import { Component, ViewChild, ElementRef } from '#angular/core';
import 'rxjs/add/operator/map';
import {forEach} from '#angular/router/src/utils/collection';
import {SbkService} from '../services/sbk.service'
#Component({
selector: 'app-match-table',
template: `
<div (click)="addItem('mumble', 1)">Add mumble</div>
<div (click)="addItem('ts', 2)">Add ts</div>
<div (click)="addItem('discord', 3)">Add discord</div>
`,
styles: []
})
export class MatchTableComponent {
constructor(
private _sbkService: SbkService
) {}
//Place a bet in the betslip
public addItem = (name, selectionId) => {
item: Object = {};
item.selectionId = selectionId;
item.name = name;
this._sbkService.storeSelection(item);
}
}
So, when I click on a div from component 2 (MatchTableComponent) it updates the selections array in component 1 (RightSideBarComponent) but doesn't trigger a change detection, so the sorted list doesn't get updated until i refresh the page. When i click on delete from RightSideBarComponent template, it updates the selections array and triggers the change detection.
How can I make this work? I tried subscribing to an event from SbkService in the AppComponent and from there triggering the setLocalStorage from SbkService, but no luck...
If I'm not wrong, you should set the next "sequence" on your Observable "items" through your BehaviourSubject.
Could you modify and try this?:
storeSelection(item){
const itemsAux = this._itemsSource.getValues();
itemsAux.push(item);
this._itemsSource.next(itemsAux);
}
setLocalStorage(){
this._localStorageService('items', this._itemsSource.getValues();
return true;
}
Is there a way to write a global selfmade mylogger function that I could use in Angular2 typescript project for my services or components instead of console.log function ?
My desired result would be something like this:
mylogger.ts
function mylogger(msg){
console.log(msg);
};
user.service.ts
import 'commons/mylogger';
export class UserService{
loadUserData(){
mylogger('About to get something');
return 'something';
};
};
You could write this as a service and then use dependency injection to make the class available to your components.
import {Injectable, provide} from 'angular2/core';
// do whatever you want for logging here, add methods for log levels etc.
#Injectable()
export class MyLogger {
public log(logMsg:string) {
console.log(logMsg);
}
}
export var LOGGING_PROVIDERS:Provider[] = [
provide(MyLogger, {useClass: MyLogger}),
];
You'll want to place this in the top level injector of your application by adding it to the providers array of bootstrap.
import {LOGGING_PROVIDERS} from './mylogger';
bootstrap(App, [LOGGING_PROVIDERS])
.catch(err => console.error(err));
A super simple example here: http://plnkr.co/edit/7qnBU2HFAGgGxkULuZCz?p=preview
The example given by the accepted answer will print logs from the logger class, MyLogger, instead of from the class that is actually logging.
I have modified the provided example to get logs to be printed from the exact line that calls MyLogger.log(), for example:
get debug() {
return console.debug.bind(console);
}
get log() {
return console.log.bind(console);
}
I found how to do it here: https://github.com/angular/angular/issues/5458
Plunker: http://plnkr.co/edit/0ldN08?p=preview
As per the docs in developers.mozilla,
The bind() method creates a new function that, when called, has its
this keyword set to the provided value, with a given sequence of
arguments preceding any provided when the new function is called.
More information about bind here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
If you want to use 'console.log' function just in your component you can do this:
import { Component, OnInit } from '#angular/core';
var output = console.log;
#Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() { }
printFunction(term: string): void {
output('foo');
}
}
How about using console on your main service, So we can customize and apply console.log conditionally:
myComponent.ts
export class myComponent implements OnInit {
constructor(
private config: GlobalService
) {}
ngOnInit() {
this.config.log('func name',{a:'aval'},'three');
}
}
global.service.ts
#Injectable()
export class GlobalService {
constructor() { }
this.prod = true;
public log(one: any, two?: any, three?: any, four?: any) {
if (!this.prod) {
console.log('%c'+one, 'background:red;color:#fff', two, three, four);
}
}
}
(Note: first parameter should be string in this example);
For toggling console.log ON\OFF:
logger.service.ts:
import { Injectable } from '#angular/core';
#Injectable()
export class LoggerService {
private oldConsoleLog = null;
enableLogger(){
if (this.oldConsoleLog == null) { return; }
window['console']['log'] = this.oldConsoleLog;
}
disableLogger() {
this.oldConsoleLog = console.log;
window['console']['log'] = function () { };
};
}
app.component.ts:
#Component({
selector: 'my-app',
template: `your templ;ate`
})
export class AppComponent {
constructor(private loggerService: LoggerService) {
var IS_PRODUCTION = true;
if ( IS_PRODUCTION ) {
console.log("LOGGER IS DISABBLED!!!");
loggerService.disableLogger();
}
}
}
I created a logger based on the provided information here
Its very basic (hacky :-) ) at the moment, but it keeps the line number
#Injectable()
export class LoggerProvider {
constructor() {
//inject what ever you want here
}
public getLogger(name: string) {
return {
get log() {
//Transform the arguments
//Color output as an example
let msg = '%c[' + name + ']';
for (let i = 0; i < arguments.length; i++) {
msg += arguments[i]
}
return console.log.bind(console, msg, 'color:blue');
}
}
}
}
Hope this helps
type safer(ish) version with angular 4, typescript 2.3
logger.service.ts
import { InjectionToken } from '#angular/core';
export type LoggerService = Pick<typeof console,
'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn'>;
export const LOGGER_SERVICE = new InjectionToken('LOGGER_SERVICE');
export const ConsoleLoggerServiceProvider = { provide: LOGGER_SERVICE, useValue: console };
my.module.ts
// ...
#NgModule({
providers: [
ConsoleLoggerServiceProvider,
//...
],
// ...
my.service.ts
// ...
#Injectable()
export class MyService {
constructor(#Inject(LOGGER_SERVICE) log: LoggerService) {
//...
There is now an angular2 logger component on NPM which supports log levels.
https://www.npmjs.com/package/angular2-logger