How to bind a context to a passed argument - a callback function? - javascript

When passing an argument, the context is lost to the function. How can I bind it in my typescript file?
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
getName(): string {
return this.name;
}
onAction(cb: Function) {
return cb();
}
}
<p>
{{onAction(getName)}}
</p>
Error: Cannot read property 'name' of undefined
If you make the binding in the template, then this will work.
<p>
{{onAction(getName).bind(this)}}
</p>
But I would like to make the linking in the controller.
I would be grateful for any help!

If I understand you correctly, you can do the binding in the constructor in AppComponent:
export class AppComponent {
name = "Angular";
constructor() { // ***
this.getName = this.getName.bind(this); // ***
} // ***
getName(): string {
return this.name;
}
onAction(cb: Function) {
return cb();
}
}

Related

Error returning an array of objects from an Angular function

Good afternoon, I have a problem when I want to return an array of objects from an external function.
I have declared an Object class with 2 properties, its constructor and one that works where I return an array with more than 50 objects for this example I only put 4 objects
export class Object {
formcontrolName: String;
formcontrolTraducido: String;
constructor(formcontrolName: String, formcontrolTraducido: String) {
this.formcontrolName = formcontrolName;
this.formcontrolTraducido = formcontrolTraducido;
}
getData() {
return [
{ formcontrolName: 'callId', formcontrolTraducido: 'CId' },
{ formcontrolName: 'collegeCareerId', formcontrolTraducido: 'Carrera' },
{
formcontrolName: 'collegeDepartmentId',
formcontrolTraducido: 'Nombre del Departamento/Centro',
},
{
formcontrolName: 'detailedAreaKnowledgeId',
formcontrolTraducido: 'Campo Detallado',
},
];
}
}
The problem is that I want to call from another component the getData function of the Object class, but when returning I get the following error:
Type '() => { formcontrolName: string; formcontrolTraducido: string; }[]' is missing the following properties from type 'Object[]': pop, push, concat, join
import { Component, OnInit } from '#angular/core';
import { Object } from './object';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
data: Object;
arrayData: Object[];
constructor() {}
ngOnInit() {
this.arrayData = this.data.getData;
console.log('arrayData: ' + this.arrayData);
}
}
stackblitz example code
I am new to angular and to working with arrays, I would like to know what I can do to solve my problem. Thank you very much
First of all the type for the property arrayData is not of type Object which is the class that you're using, so a more proper type would be the same as the one that is returned by the method getData.
You can get its return type by using the typescript helper type ReturnType, so a good way to type this would be like:
arrayData: ReturnType<Object['getData']>;
One recommendation that I have is not to use the name Object for a class, since it has the same name as Object a javascript builtin.
After that the problem that you have here:
ngOnInit() {
this.arrayData = this.data.getData();
console.log('arrayData: ' + this.arrayData);
}
Is that you are assigning a method to a variable that expects an array of something, that's why you get the error:
Type '() => { formcontrolName: string; formcontrolTraducido: string; }[]' is missing the following properties from type 'Object[]': pop, push, concat, join
You just need to call the method like:
this.arrayData = this.data.getData();
With that all in mind the final code would look like:
import { Component, OnInit } from '#angular/core';
import { Object } from './object';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
name = 'Angular';
data: Object;
arrayData: ReturnType<Object['getData']>;
constructor() {}
ngOnInit() {
this.arrayData = this.data.getData();
console.log('arrayData: ' + this.arrayData);
}
}

Javascript/Angular- How to do string interpolation inside a variable or a class method?

I dont know if thats possible or not, but here is my ideia:
I have a class with methods and i want to present the method in a h1 Html component that matches the selected string.
Basically, i have a class of Pokemons, that each method is a pokemon, and i want to switch methods when my user select one type of pokemon.
Here is the code:
The service class (data) :
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class GenIService {
swampert = {
hp: 138,
atk: 121,
def: 110.63,
type: ' Water Ground',
};
}
The main page :
import { Component, OnInit } from '#angular/core';
import {GenIService} from "../Pokemons/gen-i.service";
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
genOne = [];
button1clicked= false;
button2clicked= false;
pokemon1Selected = '';
pokemon2Selected = '';
constructor(private gen1: GenIService ) {}
ngOnInit(): void {
console.log(this.gen1.swampert.hp);
}
buttonOneSelected() {
this.button1clicked= true;
this.button2clicked=false;
}
buttonTwoSelected() {
this.button1clicked= false;
this.button2clicked=true;
}
pokemon1SeletectedSwampert() {
this.pokemon1Selected = "Swampert";
}
pokemon2SeletectedVenusaur(){
this.pokemon2Selected = 'Venusaur';
}
The Html code:
<ion-item>
<ion-label>Name: {{}}</ion-label>
<ion-label>HP: {{this.gen1.{pokemon1Selected}.hp}}</ion-label>
<ion-label>ATK: {{}}</ion-label>
<ion-label>DEF: {{}}</ion-label>
<ion-label>Type: {{}}</ion-label>
</ion-item>
So, in the Html code i am tring to have this variable = this.gen1.swampert.hp, but switch the pokemon name with the variable of pokemon1Selected, that in this case is equal to "Swampert".
How can i do that?
Based on your declaration genOne is an array so I'm not sure what you are trying to get when you call
console.log(this.gen1.swampert.hp);
If I understand correctly what you need is something like this (provided you add name property to your pokemon object)
.ts
public getPokemonOneSelected() {
return this.genOne.find(x => x.name === this.pokemon1Selected);
}
HTML
<ion-label>HP: {{this.getPokemonOneSelected()?.hp}}</ion-label>
That '?.' is to prevent error if .find() returns undefined
As Phix pointed out it would be better to track selected pokemon with just 1 variable so instead of
pokemon1Selected = '';
pokemon2Selected = '';
just have
selectedPokemon = '';
...
public getSelectedPokemon() {
return this.genOne.find(x => x.name === this.selectedPokemon);
}
...
<ion-label>HP: {{this.getSelectedPokemon()?.hp}}</ion-label>

_co.photo is undefined console error and error context, but code works as expected

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();
});
}

Angular - How to access annotation class method

I have the following declaration of a annotation in Angular:
class ModalContainer {
public destroy: Function;
public closeModal() {
this.destroy();
}
}
export function Modal() {
return function (target: any) {
Object.assign(target.prototype, ModalContainer.prototype);
};
}
I want to use the annotation inside a component:
#Component({
selector: 'my-component',
templateUrl: 'my-component.template.html',
styleUrls: ['my-component.style.scss']
})
#Modal()
export class MyComponent implements OnInit {
private EVENT_SAVE = 'EVENT_SAVE';
private EVENT_CANCEL = 'EVENT_CANCEL';
private buttonClick(event: any, eventType: string){
if (eventType === this.EVENT_CANCEL){
this.closeModal();
} else if(eventType === this.EVENT_SAVE){
this.closeModal();
}
}
}
The problem is, TypeScript is not able to compile, since this method is not known during compile time. However, when I'm using the same method call inside the template, then it works. That means, that the the prototype was assigned.
The compiler shows this error message:
ERROR in [at-loader] ./src/main/webapp/ui/src/my-component.component.ts:128:18
TS2339: Property 'closeModal' does not exist on type 'MyComponent'.
Does anyone know, how I cann solve this?
By adding this line the compiler will not complain anymore and it works.
private closeModal: Function;
The class then looks like this:
#Component({
selector: 'my-component',
templateUrl: 'my-component.template.html',
styleUrls: ['my-component.style.scss']
})
#Modal()
export class MyComponent implements OnInit {
private EVENT_SAVE = 'EVENT_SAVE';
private EVENT_CANCEL = 'EVENT_CANCEL';
private closeModal: Function;
private buttonClick(event: any, eventType: string){
if (eventType === this.EVENT_CANCEL){
this.closeModal();
} else if(eventType === this.EVENT_SAVE){
this.closeModal();
}
}
}

How to write console.log wrapper for Angular2 in Typescript?

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

Categories