Avoid two of the same componets alter each other Angular - javascript

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

Related

calling child method in parent component

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>

WYSIWYG Editor not registering update

I am building my own custom WYSIWYG text editor in Angular using a content editable div. I am extracting it as a component. I want to be able to type some text and format it and then when I click a button from the parent component it should either change the text in the editor or update it.
My parent component looks as follows:
#Component({
selector: 'app-parent',
templateUrl: `
<form [formGroup]="form">
<app-default-editor
formControlName="body" >
</app-default-editor>
</form>
<button (click)="changeValue()">Change Value</button>
<p *ngIf="!form.valid">Counter is invalid!</p>
<pre>{{ form.value | json }}</pre>
`
})
export class ParentComponent {
initialText = '<H1>This is the heading</H1>';
form = this.fb.group({
body: '<H1>This is the heading</H1>'
});
changeValue(){
this.form.patchValue({ body: 'This is my new text' });
}
}
My editor component looks as follows:
#Component({
selector: 'app-parent',
templateUrl: `
<button (click)="executeCommand('bold')" ><i class="fas fa-bold"></i></button>
<button (click)="executeCommand('italic')" ><i class="fas fa-italic"></i></button>
<button (click)="executeCommand('underline')" ><i class="fas fa-underline"></i></button>
<div
#htmlEditor
id="htmlEditor"
contenteditable="true"
(input)="onTextChanged()"
[innerHTML]="initialText">
</div>
`
})
export class EditorComponent implements ControlValueAccessor, OnChanges {
#Input()
initialText = "";
#Input()
_textValue = "";
#Input()
minValue = 0;
#ViewChild('htmlEditor') htmlEditor;
ngAfterViewInit() {
this.htmlEditor.nativeElement.innerHTML = this.initialText;
}
get textValue() {
return this._textValue;
}
set textValue(val) {
this._textValue = val;
this.propagateChange(this._textValue);
}
writeValue(value: any) {
if (value !== undefined) {
this.textValue = value;
}
}
propagateChange = (_: any) => { };
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() { }
validateFn: Function;
ngOnChanges(changes) {
this.validateFn = createEditorRequiredValidator();
}
onTextChanged() {
this.textValue = this.htmlEditor.nativeElement.innerHTML;
}
executeCommand(cmd) {
document.execCommand(cmd, false, this.htmlEditor.nativeElement);
}
}
export function createEditorRequiredValidator() {
return function validateEditor(c: FormControl) {
let err = {
requiredError: {
given: c.value,
required: true
}
};
return (c.value.length == 0) ? err : null;
}
}
With the current code, I can format text to be bold, italic etc and the form in the parent component can display the value in the tags. When I click the button to change the text to a predefined value the form updates but the editor doesn't show any changes.
There are two little mistakes that prevent your code from working as it should.
1. No provider for NG_VALUE_ACCESSOR
This one should have given you errors in your console. To make sure you can use formControlName directive the component needs to provide a value accessor. You achive that by adding the providers when defining the editor component. Like this:
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditorComponent), multi: true}
]
When you did this the writeValue function within your editor component is called everytime the form control for the field body changes. Lets go to little mistake number two...
2. innerHtml bound to initialText
You update the text property within writeValue but that property is never bound to the template. The innerHtml binding only looks for the initialText property - and that hasn´t changed.
Change your binding to look like this:
<div #htmlEditor
id="htmlEditor"
contenteditable="true"
(input)="onTextChanged()"
[innerHTML]="textValue">
You can utilize the initial text within the getter and return it in case of an empty text value.
I ended up changing the changeValue method in the parent component to:
changeValue(){
this.form.patchValue({ body: [WHATEVER THE TEXT MUST BE] });
this.initialText = [WHATEVER THE TEXT MUST BE]
}
This seems to work but I don't think it is the best answer.

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

Ng Bootstrap - how to call modal Component from another component another component?

I am a newbie and learning angular. I have a background of angularjs. I have structure of my app like this.
// src /
// app /
// app-footer
// app-footer.component.css , app-footer-component.html,app-footer.component.ts
// app-header
// app-header.component.css,app-header.component.html,app-header.ts
// home
// home.component.css,home.component.html,home.component.ts
// shared
// modals
// todo
// update-todo.css,update-todo.html,update-todo.ts
I am using ng-bootstrap and with the help of it i am showing a modal on click. My home.component.html looks like this.
<div class="container home-page">
<div class="jumbotron text-center">
<h1>I'm a Todo-aholic
</h1>
</div>
<div id="todo-form" class="row">
<div class="mx-auto col-sm-8 text-center">
<form action="">
<div class="form-group">
<input type="text" name="something" [(ngModel)]="todo" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever">
<div class="add-button-todo-app">
<button class="btn btn-primary" (click)="pushInTodoArr(todo) " [disabled]="todo =='' ? true : false">ADD</button>
</div>
</div>
</form>
</div>
</div>
<div class="list-of-todos">
<ul>
<li *ngFor="let item of todoArray;let i=index">
<span class="tick">
<i class="fa fa-check" aria-hidden="true"></i>
</span>
{{item}}
<span class="trash" (click)="removeItemInTodoArr(item);">
<i class="fa fa-trash" aria-hidden="true"> </i>
</span>
<span class="trash" (click)="content.openLg(content)">
<i class="fa fa-pencil" aria-hidden="true"> </i>
</span>
</li>
</ul>
</div>
<update-todo></update-todo>
and app.component.ts looks like this.
import { Component, OnInit } from '#angular/core';
import { NgbdModalOptions } from '../shared/modals/todo/update-todo';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
/* todo array list */
todoArray: String[] = [];
todo: string = "";
constructor() {
}
ngOnInit() {
}
/* Push data in todo array */
pushInTodoArr(value) {
this.todoArray.push(value);
this.todo = '';
}
/* remove item in array */
removeItemInTodoArr(key) {
for (var i = this.todoArray.length - 1; i >= 0; i--) {
if (this.todoArray[i] === key) {
this.todoArray.splice(i, 1);
}
}
}
/* update item in array */
updateItemInTodoArr(key,updatedValue) {
for (var i = this.todoArray.length - 1; i >= 0; i--) {
if (this.todoArray[i] === key) {
this.todoArray[i] = updatedValue;
}
}
}
}
Its basically a todo app. Which adds delete and updates. I want to update the field in modal.
This is my update-todo.ts.
import {Component, ViewEncapsulation} from '#angular/core';
import {NgbModal, ModalDismissReasons} from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'update-todo',
templateUrl: './update-todo.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./update-todo.css']
})
export class NgbdModalOptions {
closeResult: string;
constructor(private modalService: NgbModal) {}
openLg(content) {
this.modalService.open(content, { size: 'lg' });
}
}
Now I want to open the pop up and pass the value on click update icon. i am calling this function (click)="content.openLg(content)" but i get Cannot read property 'openLg' of undefined.
Will some one please point me to right direction. I am stumbling between ng-bootstrap,ngx-bootstrap and ng2-bootstrap. But i want to do it with ng-bootstrap.
This is how I did it in my app:
Import:
import { NgbModal, NgbModalOptions } from '#ng-bootstrap/ng-bootstrap';
constructor:
constructor(
private _modalService: NgbModal
) { }
Call:
onEdit(car: ICars) {
const modalRef = this._modalService.open(RentACarDetailComponent, { size: 'lg' });
modalRef.componentInstance.car = car;
modalRef.componentInstance.type = 'edit';
modalRef.componentInstance.pageTitle = 'Edit vehicle';
modalRef.result.then((result) => {
if (result) {
let answer: number;
this._rentACarService.editVehicle(result, 2)
.takeUntil(this.ngUnsubscribe)
.subscribe(
result => { answer = result; },
error => { this.errorMessage = <any>error; },
() => {answer > 0) {
this._helperService.showInfo('You have succesfully editet vehicle.', 5);
this.ngOnInit();
}
});
}
},
(reason) => { });
}
car, type and PageTitle are set as Inputs in modal component

Angular2 how to remove 1 specific element from list with mouseclick

I am having a small issue in my code. Ive created an weather application using angular 2 and it works fine. Though do i have a small problem when clicking "delete this city button" where it delets only the last city in and not the one I wanted, how can i solve this? Here is my code:
clearWeatherItems() {
for(WEATHER_ITEMS, function(i)){
var city = WEATHER_ITEMS[i];
if(city == WEATHER_ITEMS[i]) {
city.splice(i, 1);
return false;
}
}
} }
Ive also tried doing it this way but still the same problem occurs:
clearWeatherItems() {
WEATHER_ITEMS.splice(-1);
}
here is my weather-item.component.ts:
import { Component, Input } from 'angular2/core';
import { WeatherItem } from "./weather-Item";
#Component({
selector: 'weather-item',
template: `
<div id="clear">
</div>
<article class="weather-element">
<div class="col-1">
<h3>{{ weatherItem.cityName }}</h3>
<p class="info">{{ weatherItem.description }}</p>
</div>
<div class="col-2">
<span class="temperature">{{ weatherItem.temprature }}°C</span>
</div>
<button class="delete" (click)="clearWeatherItems($event, weatherItem)">X</button>
</article>
`,
styleUrls: ['src/css/weather-item.css'],
// inputs: ['weatherItem: item']
})
export class WeatherItemComponent {
#Input('item') weatherItem: WeatherItem;
clearWeatherItems() {
// event.stopPropagation();
this.weatherItem.clearWeatherItems();
}
}
my weather.data.ts:
import { WeatherItem } from "./weather-Item";
export const WEATHER_ITEMS: WeatherItem[] = [];
and here is my weather-item.ts:
import { WEATHER_ITEMS } from "./weather.data";
export class WeatherItem {
constructor(public cityName, public description: string, public temprature: number) {
}
clearWeatherItems(item) {
WEATHER_ITEMS.splice(-1);
}
}
Somebody knows what to do?
Best regards from a programming noob :P
Just declare a method on ts file
clearWeatherItem(item:any){
let index: number = this.WEATHER_ITEMS.indexOf(item);
if (index !== -1) {
this.data.splice(index, 1);
}
}
call this method by passing the item to be removed from the HTML template

Categories

Resources