Passing value to sibling component using shared service - javascript

I want to pass the value from select list - ListComponentComponent to sibling component - DisplayComponentComponent and display the value in the template of DisplayComponentComponent. I want to use shared service for that. I created service and I am passing the value on change. However when I want to console.log this value in my display component I can't see anything. Here is my code.
Display component
export class DisplayComponentComponent implements OnInit {
val: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(result => {
this.val = result
});
}
}
List
export class ListComponentComponent implements OnInit {
list: any;
selected: string;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.list = [
{
text: 'test1',
value: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3',
value: 'test3'
}
]
this.selected = this.list[0].value;
this.myService.update(this.selected);
}
getSelected(val) {
this.selected = val;
this.myService.update(this.selected);
}
}
Service
#Injectable()
export class MyServiceService {
public source = new Subject<any>();
val = this.source.asObservable();
update(input: any) {
this.source.next(input);
}
constructor() { }
}
The value should be displayed here:
<p>
{{result}}
</p>
https://stackblitz.com/edit/angular-7lhn9j?file=src%2Fapp%2Fmy-service.service.ts

If you wand to show the values on application load you need to change the subject to BehaviorSubject
private _onChanged: BehaviorSubject<any> = new BehaviorSubject({});
public val= this._onChanged.asObservable();
update(input: any) {
this._onChanged.next(input);
}
constructor() { }
Demo

You have to bind to the right value in your display-component.component.html part:
<p>
{{val}} <!--not {{result}}-->
</p>

I found a small thing in your code. instead of bellow
<p>
{{result}}
</p>
you should use
<p>
{{val}}
</p>

The value is getting updated everything is right.
val: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(result => {
console.log(result);
this.val = result
});
}
in HTML you are using {{result}} there is no such variable use {{val}} instead, or change variable name
result: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(res => {
console.log(result);
this.result = res
});
}

Related

populate an array from the #input array in angular

I have an array coming from #input like this:
export interface XXModel {
id: number;
category: string;
serialNumber: string;
description: string;
}
#Input() assets: XXModel[];
I created another array to get the Id and description from the previous array to use this array to provide data to a component in the html
public _assets:{key:number, value:string}[];
How can I fill the _assets array with the id and description from the assets array to populate a component in HTML and receive data from the _assets array?
I tried this approach, but I get undefined and it's not working:
#Input() assets: XXModel[];
public _assets:{key:number, value:string}[];
ngOnInit() {
this.assets.map(item => {
if(item){
const {id, description} = item;
this._assets.push({key:id, value:description});
}
});
console.log(this._assets)
}
also I tried this way :
#Input()
get assets(): XXModel [] {
return this._assets as any
}
set assets(value: XXModel []) {
value.map(asset=>{
this._assets.push({key:asset.id,value:asset.description})
})
}
public _assets: {key:number, value:string}[];
In angular #Input properties don't have a value if accessed inside ngOnInit hook. They would have a value first in ngOnChanges() hook.
You can just udpdate your code to:
class Component implements OnChanges {
#Input() assets: XXModel[];
public _assets:{key:number, value:string}[];
public ngOnChanges(changes: SimpleChanges) {
const { assets } = changes;
if(assets.firstChange) return
this._assets = this.assets.map(({ id, description }) => ({ key: id, value: description }))
}
}

Filter array by array with true or false value Angular

A have a client class I have a column with a sting column isSimple 'TAK' or 'NIE' (YES or NO in English) in my Class. In this class a have an argument a Canal and this class I have an argument 'Procudent' or 'Handel'. In my Reactive Form a have a form where a may a filter a Producent, Handel and Simple and I send this information to my HomeController. Now I want to filter this clients where I clicked true in my input near Handel, Producent or Simple by this columns.
My code is :
I recive this data in my HomeController array :
[
{ name: "Producent", checked: true },
{ name: "Handel", checked: true },
{ name: "Simple", checked: true }
];
My class client :
export class Client {
clientId: number;
name: string ;
district: string;
province: string;
zip: string;
city: string;
full_Address: string;
latitude: number;
longitude: number;
segment: string;
ph: string;
bh: number;
canal: string;
isSimple: string;
}
I send through this class :
import { Component, EventEmitter, OnInit, Output } from '#angular/core';
import { FormArray, FormBuilder, FormGroup } from '#angular/forms';
import { IBox } from 'src/app/Models/IBox';
#Component({
selector: 'app-type-of-client',
templateUrl: './type-of-client.component.html',
styleUrls: ['./type-of-client.component.css']
})
export class TypeOfClientComponent implements OnInit {
box_data: IBox[] = [
{ name: "Producent", checked: true },
{ name: "Handel", checked: true },
{ name: "Simple", checked: true }
];
#Output() typeOfClient = new EventEmitter<{}>();
form_model: FormGroup = this.fb.group({
search_text: this.fb.control(""),
boxes: this.fb.array(
this.box_data.map((box: IBox) => {
return this.fb.group({ checked: [box.checked],name:[box.name] });
})
)
})
constructor(private fb: FormBuilder) { }
get boxes() {
return this.form_model.get("boxes") as FormArray;
}
ngOnInit(): void {
this.boxes.valueChanges.subscribe(
(response) =>{
let clients : IBox[] =[];
for (let index = 0; index < response.length; index++) {
const element = response[index];
clients.push(element);
}
// let clients : IBox[] = this.box_data;
this.typeOfClient.emit(clients);
}
);
}
}
And now a want to try use method method include but it doesn't work. I filter like this my client throung another argument.
filterClients() {
console.log(this.checkedPH);
console.log(this.checkedSegment);
const typeClient = this.checkedTypeOfClient
this.currentClientList = this.baseClientList;
this.currentClientList = this.currentClientList.filter(client => {
return this.checkedSegment.includes(client.segment) && this.checkedPH.includes(client.ph);
});
}
In general you has a parent component that received the value
<!--see that you received the output using "$event"-->
<app-type-of-client (typeOfClient)="filter($event)"></app-type-of-client>
if your parent component I mush suppose you has an array with all the clients, use an auxiliar variable filterClients
allClients:Clients[];
filterClients:Clients[]=[]
The problem is that you received an strange object. I'm going to transform this response before make the filter
So, create a function
filter(response:any[]){
const filter={
Producent:response.find(x=>x.name="Producent").checked
Handel:response.find(x=>x.name="Handel").checked
Simple:response.find(x=>x.name="Simple").checked
}
this.filterClients=this.allClients.filter(x=>(x.canal=="Producent" && filter.Producent) &&
(x.canal=="Handel" && filter.Handel) &&
((x.Simple=="TAK" && filter.Simple) || (x.Simple=="NIE" && !filter.Simple)))
}
And iterate over filterClients.
NOTE: if you always received the three values and the values are in order. Some like
this.boxes.valueChanges.subscribe(res=>{
this.typeOfClient.emit(res.map(x=>x.checked)) //<--this emit, e.g. [true,false,true]
})
You can filter like
filter(response:any[]){
this.filterClients=this.allClients.filter(x=>(x.canal=="Producent" && response[0]) &&
(x.canal=="Handel" && response[1]) &&
((x.Simple=="TAK" && response[2]) || (x.Simple=="NIE" && !response[2])))
}

Is there a way to dynamically turn class variable values into string literal types (in Typescript)?

For example, if I have a class
export class SlideOutComponent implements OnInit {
#Input() footerActions: {
buttonType?: "link" | "wire";
name: string;
}[];
#Output() footerAction: EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
emitAction(name: SlideOutComponent["footerActions"][number]["name"]) {
}
}
is there a way to dynamically create a string literal type based off what the user passes in for the name fields? (The above example only return the type string)
Here is how this user would pass in the fields:
Template
<slide-out
[footerActions]="footerActions"
>
</slide-out>
TS
export class ListPageComponent implements OnInit {
footerActions = [
{
buttonType: "link",
name: "Close"
},
{
name: "Add"
}
];
constructor() { }
ngOnInit(): void {
}
}
So, I would want to dynamically produce type Names = "Close" | "Add"

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked while getting the value from ckEditor

I am trying to get the changed value i.e text from ckEditor and emit the resulted output to parent.
Below is the corresponding code:
editor.component.html:
<ckeditor tagName="textarea" [config]="config"
[data]="text" (change)="onValueChange($event)" formControlName="text"></ckeditor>
editor.component.ts
export class TextEditorWithLimitedWidgetsComponent implements OnInit, AfterViewChecked, OnChanges {
constructor(
private fb: FormBuilder,
private fileValidations: FileValidations,
private cdref: ChangeDetectorRef
) { }
#Input() text: string;
#Output() textValue = new EventEmitter();
form: FormGroup;
ngOnInit() {
this.form = this.fb.group({
text: ['', [
Validators.required,
CustomValidator.textEditor(30)
]]
});
this.form.setValue({
text: this.text
});
}
get f() {
return this.form.controls;
}
ngAfterViewChecked() {
// this.textValue.emit(this.form.controls);
// this.cdref.detectChanges();
//
// not working...
}
onValueChange(e) {
this.cdref.detectChanges();
}
ngOnChanges(changes: SimpleChanges): void {
this.textValue.emit(this.form.controls);
}
}
parent.component.html
<app-editor [descriptionLimit]="50" [text]="inputData.title" (input)="(inputData.title = $event.target.value);" (textValue)="getTextValue($event)"></app-editor>
parent.compoent.ts
getTextValue(event) {
const dataWithHTMLTags = event.text.value.toString();
this.inputData.title = this.fileValidations.stringsWithoutHTMLTags(dataWithHTMLTags);
console.log(this.inputData.title); // error..
}
I have tried ngAfterContentChecked as well but ending up with same error.
Output emit inner a lifecycle method cause your issue.
Should textValue emit the controls object or only value of ckeditor control?
You can simplify your form init by
this.form = this.fb.group({
text: [this.text, [
Validators.required,
CustomValidator.textEditor(30)
]]
});
}
onValueChange(e) {
this.cdref.detectChanges();
}
isn't necessary, angular events trigger change detection itself
(input)="(inputData.title = $event.target.value);"
won't work, there is no #Output named input defined in your component.
check out this documentation for component interaction
if I guess right, you would do this
ckeditor-change
onValueChange({ editor }: ChangeEvent): void {
this.textValue.emit(editor.getData());
}

Angular 2: get data from http in parent-component and subscribe on it nested

I plan to do such architecture:
component store
-- nested-component book
in store - i have an service call, which get data from service, and i do a subscription on result. Like it was described in angular2 docs (http).
And i want to use this data in nested components: in forms (formBuilder), in material-design elements etc.
Which way is the best, to do this? I'm new to angular2.
Store:
book: IBook;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getBook('1')
.subscribe((book) => {
this.book = book;
});
}
BookService:
...
getBook (id): Observable<IBook> {
return this.http.get(this.url + '/' + id)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
...
Book:
#Input() book:IBook;
constructor() {}
ngOnInit() {
/*How here can i subscribe on book http data get?, so that i can use async value in forms etc?*/
});
Because, if i use async book everywhere (not formBuilder) - all is ok, but formBuilder is in need to update values, after data is loaded in parent component. How can i do this?
What about passing the bookID to the BookComponent and letting the BookComponent handle the async http get in ngInit?
export class Book implements OnInit {
#Input() bookID: number;
private book: IBook;
constructor(private bookService: BookService) {}
ngOnInit() {
this.bookService.getBook(this.bookID)
.subscribe((book) => {
this.book = book;
});
}
}
Otherwise you have a few options which are explained in https://angular.io/docs/ts/latest/cookbook/component-communication.html
I'll briefly highlight two ways which I think you could use.
Intercept input property changes with ngOnChanges
export class Book implements OnChanges {
#Input() book: IBook;
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
for (let propName in changes) {
// handle updates to book
}
}
}
more info https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Parent and children communicate via a service
#Injectable()
export class BookService {
books = new Subject<IBook>();
getBook(id): Observable<IBook> {
return this.http.get(this.url + '/' + id)
.map(d => {
let book = this.extractData(d);
this.books.next(book);
return book;
})
.catch(this.handleError);
}
...
}
#Component({
selector: 'book',
providers: []
})
export class Book implements OnDestroy {
book: IBook
subscription: Subscription;
constructor(private bookService: BookService) {
this.subscription = bookService.books.subscribe(
book => {
this.book = book;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
#Component({
selector: 'store',
providers: [BookService]
})
export class Store {
book: IBook;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getBook('1')
.subscribe((book) => {
this.book = book;
});
}
}

Categories