calling child method in parent component - javascript

I have two components. One is Parent and second is child.
What I want to do is Add new element in child component(for now it is simple div).
It is working when I click button (Add pallet) in child html.
When I click the button (Add pallet) on parent HTML it's not working.
It's creating a new instance of pallets but this div doesn't show on child HTML. Result in console.log is the same.
Child component:
import { Component } from "#angular/core";
import { Pallet } from '../pallet.model';
#Component({
selector: 'app-workspace',
templateUrl: './workspace.component.html',
styleUrls: ['./workspace.component.css']
})
export class WorkspaceComponent {
pallets: Pallet[] = [];
addPallet1(){
let temp = new Pallet();
temp.palletName = 'Pallet'+(this.pallets.length+1);
temp.id=(this.pallets.length+1).toString();
temp.width = "500";
this.pallets.push(temp);
console.log(temp); /*=>>
Pallet {palletName: "Pallet1", id: "1", width: "500"}
id: "1"
palletName: "Pallet1"
width: "500"
__proto__: Object
*/
}
ngOnInit(){}
Child HTML:
<div id="workspace" class="workspace">
<div class="workspace-box" cdkDrag >
<button (click) = "addPallet1()">Add Pallet</button>
<button (click) = "check()">check</button>
<div class="pallet" *ngFor="let pallet of pallets; let index=index" cdkDrag [ngStyle]="{'width.px': pallet.width }">
<div
[(ngModel)]="pallet.palletName"
name="{{pallet.palletName}}"
id="{{pallet.id}}"
ngDefaultControl>
<label>{{pallet.palletName}}</label>
</div>
</div>
<div>
parent html:
<button (click)="appChildWorkspace.addPallet1()">Add Pallet</button>
<app-workspace #appChildWorkspace></app-workspace>

Related

How to random generate a component then display it in Angular?

not sure whether what I am trying to do is possible but I would like to click the generate button which then returns a random item from the array and once that array has been returned, I would like to connect it to a div in my parent component which then returns the specific child component only.
MY HTML:
<div class="components" *ngFor="let component of components" >
<div class="brother" *ngIf="">
<app-brother></app-brother>
</div>
<div class="sister">
<app-sister></app-sister>
</div>
<div class="baby">
<app-baby></app-baby>
</div>
</div>
MY TS
export class ParentComponent {
#Input() component: string | undefined;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
let myChild = this.components[Math.floor(Math.random() * this.components.length)];
console.log(myChild);
}
}
You can insert a component dynamicall but here it would be better to use a NgSwitch statement.
<div class="components"[ngSwitch]="component" >
<div class="brother" *ngSwitchCase="brother">
<app-brother></app-brother>
</div>
<div class="sister" *ngSwitchCase="sister">
<app-sister></app-sister>
</div>
<div class="baby" *ngSwitchCase="baby">
<app-baby></app-baby>
</div>
</div>
TS:
export class ParentComponent {
public component: string;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
this.component = this.components[Math.floor(Math.random() * this.components.length)];
}
}

How to disable other divs coming from loop except the clicked one

I have some li tags whose data is coming from a loop. When I click any li tag it will become active by changing its image and will store into localstorage so that on refresh the clicked one is still active. Here when we click, an active object is adding to json the clicked li tag and stores it in localstorage. Now the problem is when I click again on the clicked li tag or outside it, it's toggling the earlier image, which should not be happening. Again, other li tags should be disabled except the clicked one. Here is the code below https://stackblitz.com/edit/angular-skzgno?file=src%2Fapp%2Fapp.component.ts
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div>
<pre>
</pre>
<ul>
<li *ngFor="let item of statusdata" (click)="toggleActive(item, !item.active)">
<span>{{item.id}}</span>
<span>{{item.name}}</span>
<span>
<img *ngIf="!item?.active || item?.active === false" src ="https://dummyimage.com/qvga" />
<img *ngIf="item?.active === true" src ="https://dummyimage.com/300.png/09f/fff" />
</span>
</li>
</ul>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
statusdata: any;
ngOnInit() {
this.statusdata = [
{ id: 1, name: "Angular 2" },
{ id: 2, name: "Angular 4" },
{ id: 3, name: "Angular 5" },
{ id: 4, name: "Angular 6" },
{ id: 5, name: "Angular 7" }
];
this.statusdata.forEach(item => {
this.getCacheItemStatus(item);
});
}
toggleActive(item, activeStatus = true) {
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
getCacheItemStatus(item) {
const cachedItem = localStorage.getItem(`item:${item.id}`);
if (cachedItem) {
const parse = JSON.parse(cachedItem); // Parse cached version
item.active = parse.active; // If the cached storage item is active
}
}
}
It looks to me like you just need to add some logic to your toggleActive method. You could check if any items are active before deciding whether or not to do anything about the click. Does something like this solve your problem?
toggleActive(item, activeStatus = true) {
if(item.active || !this.statusdata.some(d => d.active)){
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
}

Angular - how to pass callback in menu item on parent component menu

I want to have a menu in my app bar that can get its menu items from child components. I am working with the Angular Material "mat-menu" and I'm able to display the menu item but I can't seem to fire off the associated function on the child component.
The app.component.html (parent):
<div>
<mat-toolbar style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 12px">
<div>
<button type="button" mat-icon-button id="btnMore" [matMenuTriggerFor]="appMenu" [matMenuTriggerData]="menuData">
<mat-icon>more_horiz</mat-icon>
</button>
<mat-menu #appMenu="matMenu" xPosition="before">
<ng-template matMenuContent let-aliasMenuItems="menuItems">
<button mat-menu-item *ngFor="let item of aliasMenuItems" (click)="handleMenuAction(item.action)">
{{item.text}}
</button>
</ng-template>
</mat-menu>
</div>
</mat-toolbar>
</div>
<div>
<router-outlet></router-outlet>
</div>
Here is app.component.ts (parent). It retrieves the menu data from the appService component. It also (should) execute the callback.
ngOnInit() {
this.appService.getMenuData().subscribe(menuData => this.menuData = menuData);
}
handleMenuAction(action: Function) {
action();
}
Here is the child component "company.component.ts" which passes its menu items to app.service so they can be retrieved by app.component. Notice the menuData is an object that contains an array of objects of types string and callback function.
ngOnInit(): void {
this._appService.setMenuData({
menuItems: [
{text: "Add Company", action: this.addCompany}
]});
}
addCompany(): void {
this._router.navigate(['/company', 0])
}
For some reason the click event handler is not showing up in my Chrome dev tools. I would like the menu clicks to call functions, not just perform navigation.
There may be a better way to solve this problem. If so, please provide a link to an example. TIA.
Edit: Stackblitz is at https://stackblitz.com/edit/angular-nbzoe6
Updated Answer
According to your stackblitz:
You just only need to bind the this of child component to new menu actions to run in child scope when you decide to call menu action
something like this:
company-list.component.ts
ngOnInit(): void {
this._appService.setMenuData({
menuItems: [
{text: "Add Company", action: this.addCompany.bind(this)}
]});
}
Old Answer
You can create the menuItems$ observable in your appService and subscribe on it in app.component.ts and from child component you just add new menuItems to this observable, The menuItems in your app.component will have new value
Something like this
appService.ts
class AppService {
// ...
menuItems$: BehaviourSubject<any[]> = new BehaviourSubject([]);
constrcutor() {}
// ...
}
app.component.ts
class AppComponenet {
// ...
menuItems: any[] = [];
constrcutor(private appService: AppService) {}
ngOnInit() {
// ...
this.appService.menuItems$.subscribe(newMenu => {
this.menuItems = newMenu;
});
}
}
child.compnenet.ts
class ChildComponenet {
// ...
constrcutor(private appService: AppService) {}
ngOnInit() {
// ...
this.appService.menuItems$.new(['home', 'about']);
}
}

Avoid two of the same componets alter each other Angular

I have a component for Tabs, it has its own variables and it works really good, but the thing is that if i place again other tab in the same page, when i change the value of the selected tab for one, it changes the other tab component also.
This is my tab component:
#Component({
selector: 'sys-tab',
styleUrls: ['./shared/sys.css'],
template: `
<div class="tabs">
<div *ngFor="let tab of tabs; let i = index" (click)="selectTab(tab)">
<input id="tab-{{i+1}}" type="radio" name="radio-set" class="tab-selector-{{i+1}}" [checked]="i===0"/>
<label for="tab-{{i+1}}" class="tab-label-{{i+1}}">{{tab.title}}</label>
</div>
<div class="content">
<ng-content></ng-content>
</div>
</div>
`,
})
export class TabView {
tabs: TabViewContent[] = [];
addTab(tab: TabViewContent) {
if (this.tabs.length === 0)
tab.active = true;
this.tabs.push(tab);
}
selectTab(tab) {
this.tabs.forEach((tab) => {
tab.active = false;
});
tab.active = true;
}
}
#Component({
selector: 'sys-tab-content',
styleUrls: ['./shared/sys.css'],
template: `
<div class="content-2" [hidden]="!active">
<ng-content></ng-content>
</div>
`
})
export class TabViewContent {
active: boolean;
#Input() title: string;
constructor(tabs: TabView) {
tabs.addTab(this);
}
}
It works really fine if i use it this way:
<sys-tab>
<sys-tab-content title="Principal">
Content 1
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 2
</sys-tab-content>
</sys-tab>
but if i do something like this:
<sys-tab>
<sys-tab-content title="Principal">
Content 1
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 2
</sys-tab-content>
</sys-tab>
<sys-tab>
<sys-tab-content title="Principal">
Content 3
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 4
</sys-tab-content>
</sys-tab>
When i change the value of the first component, it also change the value of the second and viceversa.
You should specify different name for each of input[radio] group:
name="{{id}}-radio-set"
and unique id and for attribute for all controls.
So here is how it could be done:
let nextId = 0;
#Component({
selector: 'sys-tab',
template: `
...
<input id="{{id}}-tab-{{i+1}}" ... name="{{id}}-radio-set" .../>
<label for="{{id}}-tab-{{i+1}}" ...></label>
...
`,
})
export class TabView {
id = `tabview-${nextId++}`;
Plunker Example

Unraveling Angular 2 book, Chapter 1, Example 5

The page shows a list of dives, it has an "add new dive", "clear dives" and a search box, which filters the displayed list as you type into it.
This is the template:
<div class="container-fluid">
<h1>My Latest Dives (Angular/TypeScript)</h1>
<div class="row">
<div class="col-sm-5">
<button class="btn btn-primary btn-lg"
[disabled]="!enableAdd()"
(click)="addDive()">
Add new dive
</button>
<button class="btn btn-danger btn-lg"
(click)="clearDives()">
Clear dives
</button>
</div>
<div class="col-sm-4 col-sm-offset-3">
<input #searchBox class="form-control input-lg"
placeholder="Search"
(keyup)="0" />
</div>
</div>
<div class="row">
<div class="col-sm-4"
*ngFor="let dive of dives | contentFilter:searchBox.value">
<h3>{{dive.site}}</h3>
<h4>{{dive.location}}</h4>
<h2>{{dive.depth}} feet | {{dive.time}} min</h2>
</div>
</div>
</div>
This is the component code:
import {Component} from '#angular/core';
#Component({
selector: 'divelog',
templateUrl: 'app/dive-log.template.html'
})
export class DiveLogComponent {
public dives = [];
private _index = 0;
private _stockDives = [
{
site: 'Abu Gotta Ramada',
location: 'Hurghada, Egypt',
depth: 72,
time: 54
},
{
site: 'Ponte Mahoon',
location: 'Maehbourg, Mauritius',
depth: 54,
time: 38
},
{
site: 'Molnar Cave',
location: 'Budapest, Hungary',
depth: 98,
time: 62
}];
public enableAdd() {
return this._index < this._stockDives.length;
}
public addDive() {
if (this.enableAdd()) {
this.dives.push(this._stockDives[this._index++]);
}
}
public clearDives() {
this.dives = [];
this._index = 0;
}
}
This is the filter code:
import {Pipe, PipeTransform} from '#angular/core';
#Pipe({name: 'contentFilter'})
export class ContentFilterPipe implements PipeTransform {
transform(value: any[], searchFor: string) : any[] {
if (!searchFor) return value;
searchFor = searchFor.toLowerCase();
return value.filter(dive =>
dive.site.toLowerCase().indexOf(searchFor) >= 0 ||
dive.location.toLowerCase().indexOf(searchFor) >= 0 ||
dive.depth.toString().indexOf(searchFor) >= 0 ||
dive.time.toString().indexOf(searchFor) >= 0);
}
}
The filter is getting invoked and the list is getting rerendered whenever I type into the search box, but not when I click "add" button. If I have something in the search box, "add" button does not result in the change of the dive list even though the content of the search box allows the new items to be displayed. How do I change the code so that clicking the "add" button would cause rerendering of the displayed list of dives?
You have a pure pipe so
its method transform will be executed only when it detects a pure
change to the input value.
For your case
*ngFor="let dive of dives | contentFilter:searchBox.value"
the input value will be dives and searchBox.value
According to the angular2 guide on pipes:
A pure change is either a change to a primitive input value
(String, Number, Boolean, Symbol) or a changed object reference (Date,
Array, Function, Object).
When a dive is added, the array reference (dives) isn't changed – hence transform method is not executed.
When something is typed into the filter input, searchBox.value does change - hence transform is executed.
So one of possibles solutions is to have always a new reference array each time a div is added:
Just replace:
this.dives.push(this._stockDives[this._index++]);
with:
this.dives = this.dives.concat(this._stockDives[this._index++]);
or:
this.dives = [...this.dives, this._stockDives[this._index++]];
Second way to do it working is use impure pipe:
#Pipe({name: 'contentFilter', pure: false })

Categories

Resources