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.
Related
I am using AngularJS for web app and in that I am trying to read data from APIs. Thus i have made few Models in accordance with the API's result set. Among many Models, Lets talk about a single Model TYPE
//This is the JSON API is returning
{
"records":[
{
"ID":"1",
"TYPE":"mythological"
}
],
"pagination":{
"count":"1",
"page":1,
"limit":10,
"totalpages":1
}
}
Now I have made the following Model for TYPE
//type.ts
export class Type {
"ID":string;
"TYPE":string;
}
After fetching the data from API i am successfully storing it and running through my code using following TYPE component ts.
//gallery.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from 'src/app/services/data.service';
import { Type } from 'src/app/models/type';
#Component({
selector: 'app-gallery',
templateUrl: './gallery.component.html',
styleUrls: ['./gallery.component.scss']
})
export class GalleryComponent implements OnInit {
types: Type;
constructor(private data: DataService) { }
ngOnInit() {
}
clickfunction(){
this.data.getData().subscribe(data=>{
this.types=data.records;
console.log(this.types);
});
}
}
ALso, i am fetching data from this service
//data.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class DataService {
dataUrl:string = 'http://localhost/api-slim/public/index.php/api/info/type';
constructor(private http: HttpClient) { }
getData() {
return this.http.get(this.dataUrl);
}
}
Although the application is running its obviously giving me the following error, which i need to radicate.
Date: 2019-09-26T19:42:06.903Z - Hash: a1b41d5889df87ba0aa3
5 unchanged chunks
Time: 780ms
ℹ 「wdm」: Compiled successfully.
ERROR in src/app/components/gallery/gallery.component.ts(23,21): error TS2339: Property 'records' does not exist on type 'Object'.
NOW
The pagination data that the API is providing is common in each of the API response, but as you can see none of my models are consuming it. What would be the best way to store and use that pagination in each of my model. I have tried to made a temporary demo class in gallery.component.ts as follows,
export class TEMP {
records: TYPE[];
pagination: [];
}
But it's ugly. Is there any efficient fix?
Your model class doesn't really reflect the API response.
A model is like a custom data structure that you can use like a data type like this:
export TEMP { //consider renaming this to something more meaningful
records: Array<Type>;
pagination: Pagination;
}
export class Type {
ID: string;
TYPE: string;
}
export Pagination{
count: string;
page: number;
limit: number;
totalpages: number;
}
I'm currently getting started with Angular 2 and got stuck on something probably pretty simple:
I have a shared service chatMessageService.ts:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ChatMessageService {
private messageList = new BehaviorSubject<string>("");
currentMessage = this.messageList.asObservable();
constructor() {
}
public addMessage(msg:string) {
this.messageList.next(msg) }
}
The service is imported by two components, one that calls it's addMessage function to add the message to the Observable and then my chatComponent.ts looks like this (shortened fpr convinience):
import { Component } from '#angular/core';
import { Message } from './message';
import { ChatMessageService } from './chatMessage.service';
#Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent {
conversation: Message[] = [];
//.....
constructor(private chatMessageService: ChatMessageService) { }
addUserMessage(message) {
this.conversation.push({
content: message
});
}
ngOnInit() {
this.chatMessageService.currentMessage.subscribe(message => {this.addUserMessage(message);} )
}
}
My crisis arises at that last subscripion part. When I replace
{this.addUserMessage(message);}
with
{console.log(message)}
the message is printed out perfectly fine. If I call the addUserMessage()-method manually it works just fine. But when I call the method right there, with the message as argument, nothing happens. The method isn't even executed?
Thankful for your insights!
It looks like you need some buffering in the service.
Instead of BehaviorSubject, try
private messageList = new ReplaySubject<string>(10);
See working example: Plunker
Recently ive made an angular 2 todo app that is working, however im not using a service for this app, and ive heard that using a service is the way to go. But i am not entirely sure how i refactor my code so that i can push data into my service instead.
My component:
import { Component } from '#angular/core';
import { Todo } from './todo';
import { TODOS } from './mock-todos';
import { TodoService } from './todo.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass'],
providers: [TodoService]
})
export class AppComponent {
title = 'Todo List';
selectedTodo: Todo;
completed = false;
constructor(private todoService: TodoService){
}
onSelect(todo: Todo): void {
this.selectedTodo = todo;
}
addTodo(value: any) {
this.todoService.addTodo(value);
console.log(value);
}
deleteTodo(todo) {
this.todos.splice(todo,1);
console.log("This todo has been deleted"+ todo);
}
completedTodo(todo){
todo.isCompleted = !todo.isCompleted;
todo.completed = !todo.completed;
}
}
My Service:
import { Injectable } from '#angular/core';
import { Todo } from './todo';
#Injectable()
export class TodoService {
todos: Todo[] = [];
lastId: number = 0;
constructor() { }
addTodo(value: any) {
this.todos.push(value);
console.log("This was pushed");
}
}
I thought i was able to use the service to push my data there , instead of having the component to handle this. So the service can be used for other components.
I would be happy to get a reply to this.
Instead of performing actions on variable in component, you can instead store your todos in the service, and when you want to make changes to your array, you just call the service functions. This is pretty well covered in the Services tutorial in the official docs, but just to throw in a short example for getting and adding todos:
In component, get the todos in OnInit and store in local variable.
ngOnInit() {
this.todos = this.todoService.getTodos()
}
The adding of a todo, call the service to do the adding.
addTodo(todo) {
this.todoService.addTodo(todo)
}
Your TodoService looks codewise totally right, so you were almost all there with your code :)
I have a PermissionService, which provide user roles. At the server-side data will not be uploaded if the user is not corresponds on role. The back-end is written with asp.net web api, which will use attributes to secure data. On upload page will be static upload user roles, the idea is just to show or hide elements on page which depending from user role.
The PermissionsService check avaiable role in its array. There are methods like isSeller(), isManager(). And all what i want is to provide accessibility from each view. For now i have this implementation.
permission.service
import { Injectable } from "#angular/core";
export enum Roles {
Admin,
Manager,
Moderator,
Seller
}
interface IPermissionDictionary {
[key: string]: boolean;
}
#Injectable()
export class PermissionService {
private permissions: IPermissionDictionary = {};
public constructor() {
this.emitPermissions();
}
private emitPermissions(): void {
let selector = document.querySelectorAll("#roles > span");
let availableRoles = Array.from(selector).map(element => element.textContent);
for (let role in Roles) {
if (!/^\d+$/.test(role)) { // for strings types in Roles
this.permissions[role] = availableRoles.indexOf(role) > -1;
}
}
}
public isInRole(role: string): boolean {
return this.permissions[role];
}
public isAdmin() {
return this.isInRole(Roles[Roles.Admin]);
}
public isSeller() {
return this.isInRole(Roles[Roles.Seller]);
}
public isManager() {
return this.isInRole(Roles[Roles.Manager]);
}
public isModerator() {
return this.isInRole(Roles[Roles.Moderator]);
}
}
app.component
import { Component } from "#angular/core";
import { ROUTER_DIRECTIVES } from "#angular/router";
import { PermissionService } from "./share/permission.service";
import { HomeComponent } from "./home/home.component";
import { OrderComponent } from "./order/order.component";
#Component({
selector: "admin-panel",
templateUrl: "../app/app.template.html",
directives: [ROUTER_DIRECTIVES],
precompile: [HomeComponent, OrderComponent]
})
export class AppComponent {
constructor(private permissionService: PermissionService) {
}
}
main.ts
import { bootstrap } from "#angular/platform-browser-dynamic";
import { AppComponent } from "./app.component";
import { APP_ROUTES_PROVIDER } from "./app.routes";
import { HTTP_PROVIDERS } from '#angular/http';
import { PermissionService } from "./share/permission.service";
bootstrap(AppComponent, [APP_ROUTES_PROVIDER, HTTP_PROVIDERS, PermissionService]);
For now to access the method of PermissionService need to inject it in component constructor. And in template is is use like
<div *ngIf="permissionService.isAdmin()">will show if you are admin</div>
But every time to inject my service in each component where i want to use it seems for me strange. And i just want to get access it from every part of my app like:
<div *ngIf="isAdmin()">will show if you are admin</div>
I think the person who asked this question has another version of Angular2 (perhaps a pre-release?), but in the latest version if you need to export a service for all the app you do it in the following way.
First, in your main.ts you must have a class that you bootstrap, like this:
platformBrowserDynamic().bootstrapModule(AppModule);
In this class "AppModule" (or whatever it is in your case), you should be able to add a global service provider in this way:
...
import {GlobalService} from './global-service.service'
#NgModule({
...
providers: [MyGlobalService],
...
})
export class AppModule{ ...}
In this way MyGlobalService is available for all other components.
Hopefully this will be useful to someone :).
Some option could be to create top level super class with the permission methods and then just subclass in view .ts. Not sure if this suits you as you still need to import super class into your components and extend it. It can also violate the "is-a".
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