Angular 4 - validator custom function this is undefined - javascript

I'm building an app
with component FormComponent.
inside I'm using reactive forms module from angular core
and create a custom validator.
the function is calling another function by using this - as I supposed it will refer to the FormComponent,
but it refers to be 'undefined'
(?)
The code in onInit defines the FormGroup and FormControl
and outside of it defines the functions
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
formInsurance:FormGroup;
private id:FormControl;
constructor(){}
ngOnInit() {
this.id = new FormControl('',[
Validators.required,
Validators.minLength(3),
Validators.maxLength(10),
Validators.pattern('^[0-9]+(-[0-9]+)+$|^[0-9]+$'),
this.foo
]);
this.formInsurance = new FormGroup({
id:this.id
})
}
foo(control:FormControl){
this.boo();
if(control.value){
return {
objToReturn: {
returned: name
}
};
}
return null
}
boo(){
console.log('boo')
}
}

The context in the foo method when called from within the FormControl is not referencing the FormComponent.
You can do the following to fix this behavior using bind to set the context yourself:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
formInsurance:FormGroup;
private id:FormControl;
constructor(){}
ngOnInit() {
const id = new FormControl('',[
Validators.required,
Validators.minLength(3),
Validators.maxLength(10),
Validators.pattern('^[0-9]+(-[0-9]+)+$|^[0-9]+$'),
this.foo.bind(this)
]);
this.id = id;
this.formInsurance = new FormGroup({
id
})
}
foo(control:FormControl) {
this.boo();
if(control.value){
return {
objToReturn: {
returned: name
}
};
}
return null
}
boo(){
console.log('boo')
}
}

And of course, the other alternative is the arrow function, which auto-binds to the this context:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators, ValidationErrors } from '#angular/forms';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
formInsurance:FormGroup;
private id:FormControl;
constructor() {}
ngOnInit() {
this.id = new FormControl('',[
Validators.required,
Validators.minLength(3),
Validators.maxLength(10),
Validators.pattern('^[0-9]+(-[0-9]+)+$|^[0-9]+$'),
this.foo
]);
this.formInsurance = new FormGroup({
id:this.id
})
}
foo = (control: FormControl): ValidationErrors => {
this.boo();
if (control.value) {
return {
objToReturn: {
returned: name
}
};
}
return null
}
boo() {
console.log('boo')
}
}

Related

Angular: hiding a component with *ngIf doesn't work

I have just started with Angular and have already faced an issue: a button actually toggles the variable I need (show) but it doesn't affect the course.component
course.component must show app-csgo-course, boolean show is true because the component is visible, but after it toggles in navbar.component, nothing changes.
<app-csgo-course *ngIf="show"> </app-csgo-course>
import { NavbarComponent } from './../navbar/navbar.component';
import { Component, OnInit} from '#angular/core';
import { CourseService } from 'src/app/course.service';
#Component({
selector: 'app-course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
constructor() { }
ngOnInit(): void { }
service = new CourseService;
show = this.service.GetShow();
}
In navbar.component there's a button which toggles the "show" variable
<button (click)="ToggleShow()" >
<li class="nav-item active" id="csgo-logo">
<a href="#">
<img class="game-logo" src="assets\img\csgo-logo.png" title="Counter Strike: Global Offensive">
<!-- <a>CS:GO <span class="sr-only">(current)</span></a> -->
</a>
</li>
</button>
import { CourseService } from 'src/app/cheat.service';
import { Component, OnInit, Input, Output, } from '#angular/core';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
service = new CourseService;
show = this.service.GetShow();
ngOnInit(): void {
}
public ToggleShow() {
this.service.show = this.service.ToggleShow();
console.log(this.service.show);
return this.service.show;
}
}
The course.service file
#Injectable({
providedIn: 'root'
})
export class CourseService {
show: boolean = true;
GetShow() {
return this.show;
}
ToggleShow() {
return this.show = !this.show
}
constructor() { }
}
}
Would appreciate your help!
Since you are new to Angular, let me break it down for you.
You need to create a BehaviorSubject to capture the event of toggle (this is called reactive programming which is a achieved using RxJS in Angular ).
Do not use new for a service, rather inject it in constructor.
course.service
#Injectable({
providedIn: 'root'
})
export class CourseService {
private show: boolean = true;
private toggle$ = new BehaviorSubject<boolean>(true);
constructor() { }
toggleEvent() {
return this.toggle$.asObservable();
}
toggleShow() {
this.show = !this.show
this.toggle$.next(this.show);
}
}
in NavbarComponent
import { CourseService } from 'src/app/cheat.service';
import { Component, OnInit, Input, Output, } from '#angular/core';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
show = boolean;
// IMP: make sure to inject the service and not do "new CourseService;"
constructor(public service: CourseService){}
ngOnInit(): void {
this.service.toggleEvent().subscribe(showFlag => {
this.show = showFlag;
})
}
public ToggleShow(): void {
this.service.toggleShow();
}
}
in courseComponent
import { Component, OnInit} from '#angular/core';
import { CourseService } from 'src/app/course.service';
#Component({
selector: 'app-course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
show: boolean ;
// IMP: make sure to inject the service and not do "new CourseService;"
constructor(public service: CourseService){}
ngOnInit(): void {
this.service.toggleEvent().subscribe(showFlag => {
this.show = showFlag;
})
}
}
PS: I would suggest you to read about "how to unsubscribe an observable" and how it causes memory leaks. Once you get some idea, you should implement that in the above provided code as well. That's a best practice. Happy learning. Let me know if you have any more questions

ANGULAR. Send array data get it of Itunes API; from component-search to component-main, via service

In angular 7. I need to send an Array information get it from Itunes Api, which is included in a component called "search", to another component called "catalog". I've understand that in this case I've to use a service which allows to share the info between them. Here's some code. What's wrong?
I've tried with viewchild, input, output, but there's no result; because both components aren't "relatives".
"search"
"search"
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
import { RequestService } from '../../services/request/request.service';
import {DataShareService} from '../../services/dataShare/data-share.service';
import { Music } from '../../models/music';
#Component({
selector: 'search',
styleUrls: ['./ion-searchbar.component.sass'],
templateUrl: './ion-searchbar.component.html',
providers: [RequestService, DataShareService],
})
export class IonSearchBarComponent implements OnInit {
public searchResults: Music[];
public searchValue: string;
constructor(public _requestService: RequestService, private _dataShareService: DataShareService) {}
ngOnInit() {
this._dataShareService.$sendDataObservable.subscribe(
data => {
this.searchResults = data
})
}
sendData(searchResults: Music[]){
console.log("executat");
this._dataShareService.sendData(searchResults);
}
search(){
this._requestService.getMusic(this.searchValue).subscribe(
result => {
this.searchResults = result.results;
console.log(result.results);
this.sendData(this.searchResults);
},
error =>{
console.log(<any>error);
}
);
}
}
"service"
import { Injectable } from '#angular/core';
import { Music } from '../../models/music';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class DataShareService {
private _sendDataSubject = new Subject<Music[]>();
$sendDataObservable = this._sendDataSubject.asObservable();
constructor() { }
sendData(data: Music[]){
this._sendDataSubject.next(data);
console.log(data);
}
}
"catalog"
import { Component, OnInit, Input } from '#angular/core';
import {RequestService} from '../../services/request/request.service';
import {DataShareService} from '../../services/dataShare/data-share.service';
import { Music } from '../../models/music';
#Component({
selector: 'catalog',
templateUrl: './catalog.component.html',
styleUrls: ['./catalog.component.sass'],
providers: [RequestService, DataShareService]
})
export class CatalogComponent implements OnInit {
public title: any;
public InfoLlegada: any;
constructor(private _dataShareService: DataShareService) {}
ngOnInit() {
console.log(this.InfoLlegada)
this._dataShareService.$sendDataObservable.subscribe(
data => {
this.InfoLlegada = data
console.log(data);
});
}
}
Not sure if this is the actual cause, but there an issue with your this binding in getMusic subscription in search component. Try this.sendData.call(this, result.results);

function share in header and sidebar component angular 6

I need to sidebar component function in sidebar.
my header component
import { Component, OnInit, Input,ViewChild } from '#angular/core';
import { SidebarComponent } from '../sidebar/sidebar.component';
#ViewChild(SidebarComponent) SidebarComponent;
ngOnInit() {
this.SidebarComponent.testFunction();
}
sidebar component
testFunction() {
console.log('value');
}
I added an essential code block for the understanding purpose. when I use the above code error said,
ERROR TypeError: Cannot read property 'testFunction' of undefined
at HeaderComponent.push../src/app/layout/components/header/header.component.ts.HeaderComponent.ngOnInit (header.component.ts:57)
can u help me to fix this issue?
call it after child view is initialized.
#ViewChild(SidebarComponent) sidebarComponent: SidebarComponent;
ngAfterViewInit() {
this.sidebarComponent.testFunction();
}
For sharing of functions in Angular, it is better to use a service and call it's functions in both of the components.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class SharedService {
constructor() { }
sharedFunction(){
console.log('here');
}
}
And in both of components, component1:
import { SharedService } from '../shared.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: '.....',
templateUrl: '.......',
styleUrls: ['........']
})
export class Component1 implements OnInit{
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.sharedFunction();
}
}
component2:
import { SharedService } from '../shared.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: '.....',
templateUrl: '.......',
styleUrls: ['........']
})
export class Component2 implements OnInit{
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.sharedFunction();
}
}
Could be done using a service. For illustration purpose let's call it HeaderAndSidebarService:
Filename: header-and-sidebar.service.ts
#import { Injectable } from "#angular/core";
#Injectable()
export class HeaderAndSideBarService {
public testFunction() {
console.log('value');
}
}
To use the service, provide it within both header and sidebar component:
import { Component, OnInit, Input,ViewChild } from '#angular/core';
import { HeaderAndSideBarService } from "./header-and-sidebar.service";
#Component({
...,
providers: [HeaderAndSideBarService]
})
export class HeaderComponent {
constructor(private service: HeaderAndSideBarService ) { }
ngOnInit() {
this.service.testFunction();
}
}

Call modal from one sibling component to other angular

I have this Angular6 component arquitecture in my app
Main component
<app-navbar></app-navbar>
<app-dashboard></app-dashboard>
Dashboard component
<app-meseros>
</app-meseros>
<app-ultimospedidos></app-ultimospedidos>
<app-modal></app-modal>
I want to call modal from navbar.component, my modal is on dashboard on component modal.component
This is what i have tried
<!--navbar.component.html -->
<a class="nav-link btn btn-primary" (click)="openModal()">Crear pedido</a>
<!--navbar.component.ts -->
import { Component, OnInit } from '#angular/core';
import { BootstrapService } from '../../services/bootstrap.service';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
constructor(public bootstrapService: BootstrapService) {}
ngOnInit() {}
openModal() {
this.bootstrapService.toggle();
}
}
I created a service so i can communicate between my navbar.component and modal.component, this is my service
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class BootstrapService {
isOpen: any = false;
constructor() {}
toggle() {
console.log(!this.isOpen);
return (this.isOpen = !this.isOpen);
}
}
Then in modal.component.ts i want to subscribe to these changes so i can launch modal on boolean value change.
import { Component, OnInit } from '#angular/core';
import { BootstrapService } from '../../services/bootstrap.service';
import { NgbModal, ModalDismissReasons } from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
isOpen;
closeResult: string;
modalname: string;
constructor(
public modalService: NgbModal,
public bootstrapService: BootstrapService
) {}
open(content) {
// console.log(this.bootstrapService.popup);
this.modalService
.open(content, { ariaLabelledBy: 'modal-basic-title' })
.result.then(
result => {
this.closeResult = `Closed with: ${result}`;
},
reason => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
}
);
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
ngOnInit() {
this.bootstrapService.toggle().subscribe(isOpen => {
this.isOpen = isOpen;
console.log(isOpen);
});
}
}
Im not even able to subscribe to the change from bootstrapService, i get following error,
RROR in src/app/components/modal/modal.component.ts(41,36): error TS2339: Property 'subscribe' does not exist on type 'boolean'.
if i try to subscribe to value on service like this
this.bootstrapService.isOpen.subscribe(isOpen => {
this.isOpen = isOpen;
console.log(isOpen);
});
i get error on console from browser which says
DashboardComponent.html:1 ERROR TypeError: this.bootstrapService.isOpen.subscribe is not a function
i hope someone can shade some light on this approach, and if this is the best approach to take on this kind of implementations, thanks in advance!
Solved it, i was calling wrong EventEmitter from wrong library, this is updated working code
first i call service from component where i want to call my modal
import { Component, OnInit } from '#angular/core';
import { BootstrapService } from '../../services/bootstrap.service';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
constructor(public bootstrapService: BootstrapService) {}
ngOnInit() {}
openModal() {
this.bootstrapService.toggle();
}
}
Then i emit my changes so i can subscribe from modal component
import { Injectable, Output, EventEmitter } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class BootstrapService {
isOpen: any = 'isOpen';
#Output() change: any = new EventEmitter();
constructor() {}
toggle() {
this.change.emit(this.isOpen);
console.log(this.isOpen);
}
}
i reference my template with `#ViewChild('crearpedido') modalInstance;`
and finally subscribe to changes, call modal on subscribe changes.
import { Component, OnInit, ViewChild } from '#angular/core';
import { BootstrapService } from '../../services/bootstrap.service';
import { NgbModal } from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
isOpen;
closeResult: string;
#ViewChild('crearpedido') modalInstance;
constructor(
public modalService: NgbModal,
public bootstrapService: BootstrapService
) {}
ngOnInit() {
this.bootstrapService.change.subscribe(isOpen => {
this.isOpen = isOpen;
console.log(this.isOpen);
this.modalService.open(this.modalInstance);
});
}
}
Here is working repo!!
https://github.com/soyisraelortiz/componentscommunication

How can I get a directive/component instance inside another component?

I have an AlertComponent that I would like to use as a directive in my AppComponent and expose it so that it's available (as a sort of singleton) to all the routes/children components from AppComponent. But I can't seem to find a way to get the instance of the AlertComponent object used as a directive in order to call it's methods and see the changes made on the directive (i.e. add/remove alerts to/from the page).
Here is AlertComponent:
import { Component } from 'angular2/core';
import { Alert } from './model';
#Component({
selector: 'alerts',
templateUrl: './alert/index.html'
})
export class AlertComponent {
alerts: Array<Alert>;
constructor() {}
add(alert: Alert) {
this.alerts.push(alert);
}
remove(index: number) {
this.alerts.splice(index, 1);
}
clear() {
this.alerts = [];
}
}
export { Alert };
And AppComponent:
import { Component, OnInit, provide } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { HTTP_PROVIDERS, RequestOptions } from 'angular2/http';
import { CookieService } from 'angular2-cookie/core';
import { UserComponent } from '../user/component';
import { AlertComponent, Alert } from '../alert/component';
import { ExtendedRequestOptions } from '../extended/RequestOptions';
import { UtilObservable } from '../util/observable';
#Component({
selector: 'app',
template: `
<alerts></alerts>
<router-outlet></router-outlet>
`,
//styleUrls: [ 'app/style.css' ],
directives: [
ROUTER_DIRECTIVES,
AlertComponent
],
providers: [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(RequestOptions, { useClass: ExtendedRequestOptions }),
CookieService,
UtilObservable,
AlertComponent
]
})
#RouteConfig([{
path: '/user/:action',
name: 'User',
component: UserComponent,
useAsDefault: true
}
])
export class AppComponent implements OnInit {
constructor(public _alert: AlertComponent) {}
ngOnInit() {
this._alert.add(new Alert('success', 'Success!'));
}
}
I'd like to have the same instance of AlertComponent available to all descendant routes/children of AppComponent (e.g. UserComponent), so as to add alerts to the same directive.
Is this possible? Or is there another, more proper way to do this?
[Update]
The chosen solution answers the title question, but I also wanted to have a simple solution to share alerts among my components. Here's how to do that:
AlertComponent:
import { Component } from 'angular2/core';
import { Alert } from './model';
export class Alerts extends Array<Alert> {}
#Component({
selector: 'alerts',
templateUrl: './alert/index.html'
})
export class AlertComponent {
constructor(public alerts: Alerts) {}
add(alert: Alert) {
this.alerts.push(alert);
}
remove(index: number) {
this.alerts.splice(index, 1);
}
clear() {
this.alerts.length = 0;
}
}
export { Alert };
AppComponent:
import { Component, provide } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { HTTP_PROVIDERS, RequestOptions } from 'angular2/http';
import { AlertComponent, Alerts } from '../alert/component'
import { UserComponent } from '../user/component';
import { ExtendedRequestOptions } from '../helpers/extensions';
#Component({
selector: 'app',
template: `<router-outlet></router-outlet>`,
directives: [
ROUTER_DIRECTIVES
],
viewProviders: [
provide(Alerts, { useValue: [] })
],
providers: [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(RequestOptions, { useClass: ExtendedRequestOptions })
]
})
#RouteConfig([{
path: '/user/:action',
name: 'User',
component: UserComponent,
useAsDefault: true
}
])
export class AppComponent {}
Basically, I'm providing a singleton array of alerts that's used by every AlertComponent.
You can move the provide() to providers (instead of viewProviders) if you want to use it outside of directives, but if not, keep it simple and restrict it this way.
Hope this helps someone :)
You need to use ViewChild decorator to reference it:
#Component({
})
export class AppComponent implements OnInit {
#ViewChild(AlertComponent)
_alert: AlertComponent;
ngAfterViewInit() {
// Use _alert
this._alert.add(new Alert('success', 'Success!'));
}
}
#ViewChild is set before the ngAfterViewInit hook method is called.
expose it so that it's available (as a sort of singleton) to all the
routes/children components from AppComponent.
Or is there another, more proper way to do this?
Create and bootstrap a service for AlertComponent, like this
AlertService
import {Injectable} from '#angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';
#Injectable()
export class AlertService {
private _alerts: Array<Alert> = [];
public alertsChange: Subject<Array<Alert>> = new Subject();
public get alerts(): Array<Alert> {
return this._alerts;
}
add(alert: Alert) {
this._alerts.push(alert);
this.alertsChange.next(this._alerts);
}
remove(index: number) {
this._alerts.splice(index, 1);
this.alertsChange.next(this._alerts);
}
clear() {
this._alerts = [];
this.alertsChange.next(this._alerts);
}
}
Bootstrap AlertService
import {bootstrap} from '#angular/platform-browser-dynamic';
import {YourApp} from 'path/to/YourApp-Component';
import { AlertService} from 'path/to/alert-service';
bootstrap(YourApp, [AlertService]);
AlertComponent
import { Component } from 'angular2/core';
import { Alert } from './model';
import { AlertService} from 'path/to/alert-service';
#Component({
selector: 'alerts',
templateUrl: './alert/index.html'
})
export class AlertComponent {
alerts: Array<Alert>;
constructor(alertService: AlertService) {
alertService.alertsChange.subscribe((moreAlerts: Array<Alert>) => {
this.alerts = moreAlerts;
})
}
}
All the routes/children components
(sample):
import { Component} from '#angular/core';
import { AlertService} from 'path/to/alert-service';
#Component({
template: `.....`
})
export class SampleComponent {
constructor(public alerts: AlertService){}
ngOnInit(){
this.alerts.add(new Alert('success', 'Success!'));
}
ngOnDestroy(){
this.alerts.clear();
}
}
To see other alike examples see this question

Categories

Resources