I am having a hard time using a async object in a html composition.
Here is my model:
export class Version {
isGood: boolean;
constructor(isGood: boolean) {
this.isGood= isGood;
}
}
This model is called by a component as follows:
#Injectable()
export class MyComponent {
public version: Version;
constructor(private _myService: VersionService) {}
getVersion(): void {
// async service that gets the versions
this._myService.getVersion().subscribe(
data => this.version= data,
error=> console.log(error),
() => console.log("getting all items complete")
);
}
}
My template references to the version variable as follows:
<button (click)="getVersion()">Get Version</button>
<hr>
<p style="color:red">{{error}}</p>
<h1>Version</h1>
<p>{{version.isGood}}</p>
However, I get an exception:
Cannot read property 'isGood' of undefined
From scavenging the internet, I see that my problem is because the version object is null. If I do something like:
<p>{{version | json}}</p>
I can see the correct version
If I do something like
<p>{{version.isGood | async}}</p>
I see nothing
If I edit MyComponent, and set
public version: Version = new Version();
I can execute the .isGood property fetch, but it is always empty.
Is there a different way I am supposed to load a property if I am using it in an asynchronous manner?
Use the ? operator or use an *ngIf.
<p>{{version?.isGood}}</p>
<p *ngIf="version">{{version.isGood}}</p>
Try this:
<p>{{version?.isGood}}</p>
This tells Angular to protect against version.isGood being undefined or null until you click and fetch the data for version through your service.
First me correct you. #Injectable() makes a normal typescript class as injectable service where you can share data.
To make a component you need to use #Component decoratore.
The process of data sharing between component and within the application is to create a service and add that as provides in module. And then its singleton object will available everyshere.
//module
import {NgModule} from '#angular/core';
import {YourService} from "./services/your-service";
#NgModule({
imports: [
BrowserModule
],
declarations: [
AppComponent
],
providers: [
YouService
],
bootstrap: [AppComponent]
})
export class AppModule {
}
//this is your component
import {Component} from '#angular/core';
import {YourService} from "../../services/your-service";
#Component({
selector: 'component-app',
templateUrl: '../../views/app.component.html',
})
export class HeaderComponent {
constructor(public yourService: YourService) {
}
}
//your service
import {Injectable} from "#angular/core";
#Injectable()
export class YourService {
private _message: string = 'initial message';
private _style: string = 'success';
get message(): string {
return this._message;
}
set message(value: string) {
this._message += value;
}
get style(): string {
return this._style;
}
set style(value: string) {
this._style = value;
}
}
//finally your view
<div class="row">
<div [class]=""><h1>{{swapService.message}}</h1></div>
</div>
Observable Data services.
#Injectable()
export class MyComponent {
public version = new ReplaySubject<Version>();
constructor(private _myService: VersionService) {}
init(): void {
// async service that gets the versions
this._myService.getVersion().subscribe(
data => this.version.next(data),
error=> console.log(error),
() => console.log("getting all items complete")
);
}
getVersion(): void {
this.version.asObservable();
}
}
In the template
<button (click)="init()">Get Version</button>
<hr>
<p style="color:red">{{error}}</p>
<h1>Version</h1>
<p>{{(version |async)?.isGood}}</p>
Related
I'm new to typescript and angular and I was trying to fetch some data from firebase using angularfire2 and assign it to variables to use in some other functions later. I'm only familiar with javascript dot notation where I access members of the object using dot notation seems like it doesn't work with angular can somebody please help me with extracting data from the model to variables, please
I'm still having a hard time understanding Observable and subscribes too.
code
model
export class Reacts {
sad?: number;
happy?: number;
neutral?: number;
}
service
import { Injectable } from "#angular/core";
import {
AngularFirestore,
AngularFirestoreCollection,
AngularFirestoreDocument
} from "angularfire2/firestore";
import { Reacts } from "../models/reacts";
import { Observable } from "rxjs";
#Injectable({
providedIn: "root"
})
export class ReactService {
mapCollection: AngularFirestoreCollection<Reacts>;
reacts: Observable<Reacts[]>;
constructor(public afs: AngularFirestoreDocument) {
this.reacts = this.afs.collection("reacts").valueChanges();
}
getItems() {
return this.reacts;
}
}
component
import { Component, OnInit } from "#angular/core";
import { Reacts } from 'src/app/models/reacts';
import { ReactService } from 'src/app/services/react.service';
#Component({
selector: "app-reacts",
templateUrl: "./reacts.component.html",
styleUrls: ["./reacts.component.css"]
})
export class ReactsComponent implements OnInit {
react: Reacts[];
happy: number;
sad: number;
neutral:number;
constructor(private reactsService: ReactService ) {}
ngOnInit(): void {
this.reactsService.getItems().subscribe(reacts => {
this.react = reacts;
console.log(reacts); //this works print an array object of data from database
this.happy= reacts.happy// what i'm trying to achieve
});
}
}
Ok, I'll break it down for you. You are trying to access .happy but it is actually an array of React[]
ngOnInit(): void {
this.reactsService.getItems().subscribe((reacts:Reacts[]) => { // Note I have defined its model type
this.react = reacts;
console.log(reacts); //this works print an array object of data from database
//this.happy= reacts.happy // Now VS code will show you error itself
this.happy = reacts[0].happy;
});
}
The power of typscript comes as it is strongly typed language. If you'll make changes as below in service, the VS Code will itself explain you the error:
export class ReactService {
mapCollection: AngularFirestoreCollection<Reacts>;
reacts: Observable<Reacts[]>;
constructor(public afs: AngularFirestoreDocument) {
this.reacts = this.afs.collection("reacts").valueChanges();
}
getItems(): Observable<Reacts[]> { // added return type
return this.reacts;
}
}
Once I provide return type of getItems() , you dont even have to define type in .subscribe((reacts:Reacts[]) as I have done in your component.
I'm trying to create a service to share the data between two components. I injected the service into root module to make it accessible throughout the application by doing DI into the root module provider. My code looks roughly like this.
Service
#Injectable(){
export class ForumService{
forum: any;
setForum(object){
this.forum = object;
}
getForum(){
return this.forum;
}
}
Root Module
.......
import { ForumService } from 'forumservice';
.......
#NgModule({
declarations: [.....],
imports: [.....],
providers: [....., ForumService],
bootstrap: [AppComponent]
})
export class AppModule{}
Component One
//A bunch of import statements
import { ForumService } from 'forumservice'; //Without this Angular throws a compilation error
#Component({
selector: 'app-general-discussion',
templateUrl: './general-discussion.component.html',
styleUrls: ['./general-discussion.component.css'],
providers: [GeneralDiscussionService] //Not injecting ForumService again
})
export class GeneralDiscussionComponent implements OnInit{
constructor(private forumService: ForumService){}
ngOnInit(){
helperFunction();
}
helperFunction(){
//Get data from backend and set it to the ForumService
this.forumService.forum = data;
console.log(this.forumService.forum); //prints the data, not undefined
}
}
Component Two
//A bunch of import statements
import { ForumService } from 'forumservice'; //Without this Angular throws a compilation error
#Component({
selector: 'app-forum',
templateUrl: './forum.component.html',
styleUrls: ['./forum.component.css'],
providers: []
})
export class ForumComponent implements OnInit {
forumData: any;
constructor(private forumService: ForumService){}
ngOnInit(){
this.forumData = this.forumService.forum; // returns undefined
}
}
Once I navigate from Component One to Component Two I'm expecting "This is a string". However I get undefined. Is it because of the import statements in the component? If I remove that I see a compilation error saying that ForumService is not found.
Instead of using getter and setter, use the object (not primitibe such as string) directly In your components.
Your service
#Injectable(){
export class ForumService{
forum:any = {name:string};
}
Component one
export class GeneralDiscussionComponent implements OnInit{
constructor(private forumService: ForumService){}
ngOnInit(){
this.forumService.forum.name="This is a string";
}
}
Component two
export class ForumComponent implements OnInit {
// forumTitle: string; // do not need this anymore
forum:any; // use the forum.name property in your html
constructor(private forumService: ForumService){}
ngOnInit(){
this.forum = this.forumService.forum; // use the
}
}
I know encapsulating is preferable, and with your current code you are probably encountering some timing problems. But when working with shared data in a service you can two-way bind the variable like above, and your components will be in sync.
EDIT:
Also an important notice, the variable you want to sync between components needs to be an object. instead of forumTitle:string, make it forumTitle:any = {subject:string} or something similar.
Otherwise you need to make your components as listeners for data when data changes in your service.
I'd use BehaviorSubject in this case, should be something like that:
#Injectable(){
export class ForumService{
private _forum: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public forum: Observable<any> = this._forum.asObservable();
setForum(object){
this._forum.next(object);
}
}
Then just bind it in template with async pipe: {{forumService.forum|async}} or subscribe to it.
I would like to have custom errors in my Angular2 app. Thus I have extended ErrorHandler in my component:
import { Component, ErrorHandler, OnInit } from '#angular/core';
import { GenericError } from './generic-error.component';
#Component({
selector: 'custom-error-handler',
templateUrl: 'app/error-handler/custom-error-handler.component.html?' + +new Date()
})
export class CustomErrorHandler extends ErrorHandler {
errorText: string;
constructor() {
super(false);
}
ngOnInit() {
this.errorText = 'Initial text!';
}
public handleError(error: any): void {
if (error.originalError instanceof GenericError) {
console.info('This is printed to console!');
this.errorText = "I want it to print this in the template!";
}
else {
super.handleError(error);
}
}
}
My template simply contains:
<span style="color:red">{{errorText}}</span>
First I see "Initial text!" in the template as set in ngOnInit. That's as expected.
I can then throw a new exception like this from a different component:
throw new GenericError();
and it hits the code with handleError and prints to console but it doesn't update my template errorText with:
"I want it to print this in the template!"
It's like it ignores my template, when inside the handleError function.
What could be the problem here?
* ADDED MORE INFORMATION *
I thought I should add some more information. So here is the module I made for CustomErrorHandler (maybe the problem is with the providers?):
import { NgModule, ErrorHandler } from '#angular/core';
import { CommonModule } from '#angular/common';
import { CustomErrorHandler } from './custom-error-handler.component';
#NgModule({
declarations: [
CustomErrorHandler
],
imports: [
CommonModule
],
exports: [
CustomErrorHandler
],
providers: [
{ provide: ErrorHandler, useClass: CustomErrorHandler }
]
})
export class CustomErrorModule { }
There is indeed only one instance of the CustomErrorHandler (I checked with the Augury Chrome plugin).
For completeness, here is is the GenericError component:
export class GenericError {
toString() {
return "Here is a generic error message";
}
}
The solution was to add a service as suggested in the question's comment track. This way I can set the property in the component and eventually show it in the template.
I created the service, so that it has a function which takes one parameter. Injected the service, call the service's function from the handleError in the component function, and send the text I want in the template as the parameter. Then I use an observable, to get the text back to the component.
In the constructor of the component, I added this observer.
let whatever = this.cs.nameChange.subscribe((value) => {
setTimeout(() => this.errorText = value);
});
I needed to add the setTimeout, or else it would not update the template before the second time the observable was changed.
Phew! The Angular team should make this global exception handling easier in future releases.
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
I'm trying to play with Angular 2-beta and I want to work with Http component. But there is a serious problem here:
I read this and
I know in Angular 2(Unlike Angular 1), Http component is not a service that returns a Promise. It returns something called Observable. We know that a Component is better not to use Http directly. Efficient way is to make a service that is responsible to consume Http. But how?! Should this after completing a request, return a promise? (look at here)
Does it make sense at all?!
It's possible with Angular 2 to implement services. They simply correspond to injectable classes as described below. In this case this class can be injected into other elements like components.
import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
import 'rxjs/add/operator/map';
#Injectable()
export class CompanyService {
constructor(http:Http) {
this.http = http;
}
}
You can inject an Http object in it (using its constructor) at the condition you specified HTTP_PROVIDERS when bootstraping the main component of your application:
import {bootstrap} from 'angular2/platform/browser'
import {HTTP_PROVIDERS} from 'angular2/http';
import {AppComponent} from './app.component'
bootstrap(AppComponent, [
HTTP_PROVIDERS
]);
This service can be then injected into a component, as described below. Don't forget to specify it within the providers list of the component.
import { Component, View, Inject } from 'angular2/core';
import { CompanyService } from './company-service';
#Component({
selector: 'company-list',
providers: [ CompanyService ],
template: `
(...) `
})
export class CompanyList {
constructor(private service: CompanyService) {
this.service = service;
}
}
You can then implement a method leveraging the Http object in your service and return the Observable object corresponding to your request:
#Injectable()
export class CompanyService {
constructor(http:Http) {
this.http = http;
}
getCompanies() {
return this.http.get('https://angular2.apispark.net/v1/companies/')
.map(res => res.json());
}
}
The component can then call this getCompanies method and subscribe a callback on the Observable object to be notify when the response is there to update the state of the component (in the same way you did with promises in Angular1):
export class CompanyList implements OnInit {
public companies: Company[];
constructor(private service: CompanyService) {
this.service = service;
}
ngOnInit() {
this.service.getCompanies().subscribe(
data => this.companies = data);
}
}
Edit
As foxx suggested in his comment, the async pipe could be also used to implicitly subscribe on the observable object. Here is the way to use it. First update your component to put the observable object in the attribute you want to display:
export class CompanyList implements OnInit {
public companies: Company[];
constructor(private service: CompanyService) {
this.service = service;
}
ngOnInit() {
this.companies = this.service.getCompanies();
}
}
Use then the async pipe in your template:
#Component({
selector: 'company-list',
providers: [ CompanyService ],
template: `
<ul>
<li *ngFor="#company of companies | async">{{company.name}}</li>
</ul>
`
})
export class CompanyList implements OnInit {
(...)
}
This article in two parts could give more details as well:
http://restlet.com/blog/2015/12/30/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-1/
http://restlet.com/blog/2016/01/06/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-2/
Hope it helps you,
Thierry
There is no need to convert the observable returned by Http's get() method into a promise. In most cases, the service can simply return the observable.
If we are fetching an array or a primitive type (i.e., string, number, boolean) from the server, we can simplify our controller logic by using the returned observable directly in our template, with the asyncPipe. This pipe will automatically subscribe to the observable (it also works with a promise) and it will return the most recent value that the observable has emitted. When a new value is emitted, the pipe marks the component to be checked for changes, hence the view will automatically update with the new value.
If we are fetching an object from the server, I'm not aware of any way to use asyncPipe, we could use the async pipe, in conjunction with the safe navigation operator as follows:
{{(objectData$ | async)?.name}}
But that looks complicated, and we'd have to repeat that for each object property we wanted to display.
Instead, I suggest we subscribe() to the observable in the component and store the contained object into a component property. We then use the safe navigation operator (?.) or (as #Evan Plaice mentioned in a comment) NgIf in the template. If we don't use the safe navigation operator or NgIf, an error will be thrown when the template first tries to render, because the object is not yet populated with a value.
Note how the service below always returns an observable for each of the get methods.
service.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map'; // we need to import this now
#Injectable()
export class MyService {
constructor(private _http:Http) {}
getArrayDataObservable() {
return this._http.get('./data/array.json')
.map(data => data.json());
}
getPrimitiveDataObservable() {
return this._http.get('./data/primitive.txt')
.map(data => data.text()); // note .text() here
}
getObjectDataObservable() {
return this._http.get('./data/object.json')
.map(data => data.json());
}
}
app.ts
import {Component} from 'angular2/core';
import {MyService} from './my-service.service';
import {HTTP_PROVIDERS} from 'angular2/http';
#Component({
selector: 'my-app',
providers: [HTTP_PROVIDERS, MyService],
template: `
<div>array data using '| async':
<div *ngFor="#item of arrayData$ | async">{{item}}</div>
</div>
<div>primitive data using '| async': {{primitiveData$ | async}}</div>
<div>object data using ?.: {{objectData?.name}}</div>
<div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
})
export class AppComponent {
constructor(private _myService:MyService) { console.clear(); }
ngOnInit() {
this.arrayData$ = this._myService.getArrayDataObservable();
this.primitiveData$ = this._myService.getPrimitiveDataObservable();
this._myService.getObjectDataObservable()
.subscribe(data => this.objectData = data);
}
}
Note: I put "Observable" in the service method names – e.g., getArrayDataObervable() – just to highlight that the method returns an Observable. Normally you won't put "Observable" in the name.
data/array.json
[ 1,2,3 ]
data/primitive.json
Greetings SO friends!
data/object.json
{ "name": "Mark" }
Output:
array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark
Plunker
One drawback with using the async pipe is that there is no mechanism to handle server errors in the component. I answered another question that explains how to catch such errors in the component, but we always need to use subscribe() in this case.