ngFor doesn't fires after update depending variable in Angular2 - javascript

I have 2 components: CommandListComponent and CommandLineComponent. Inside of a CommandListComponent template i handle a click event on a text string:
CommandListComponent template:
<li *ngFor="#command of commandList" class="b-command-list__command"><span (click)="checkCommand(command)" class="b-command-list__text">{{command}}</span></li>
commandlist.component.ts
import {CommandLineComponent} from "./commandline.component";
...
export class CommandListComponent {
commandLineComponent: any;
constructor(private _commandLine: CommandLineComponent) {
this.commandLineComponent = _commandLine;
}
checkCommand(command: string): void {
this.commandLineComponent.add(command);
}
}
When click is fired i pass choosen command to add method of a CommandLineComponent:
export class CommandLineComponent {
commands: string[] = [];
add(command: string): void {
if (command) this.commands.push(command);
console.log(this.commands);
}
}
And within a template of a CommandLineComponent i print a list of a commands with *ngFor:
<li *ngFor="#command of commands" class="b-command-textarea__command">{{command}}</li>
But *ngFor doesn't fires when i choose a command and commands array of a CommandLineComponent updated. So, data binding is not working. commands array updates successfully:
Thank you for help.

The problem is the way you reference the commandLineComponent component. If there is a relation between them you could use the ViewChild decorator
class CommandListComponent {
#ViewChild(CommandLineComponent)
commandLineComponent: any;
(...)
}
If not, you need to use a shared service to share the commands list between these two components. Something like that:
export class CommandService {
commands:string[] = [];
commandAdded:Subject<string> = new Subject();
add(command: string): void {
if (command) {
this.commands.push(command);
this.commandAdded.next(command);
}
console.log(this.commands);
}
}
You need to define the service when bootstrapping your application and both components can inject it.
class CommandListComponent {
constructor(private commandService:CommandService) {
}
}
checkCommand(command: string): void {
this.commandService.add(command);
}
The CommandLineComponent component will be notified of a new command like this and can update the view accordingly:
class CommandLineComponent {
constructor(private commandService:CommandService) {
this.commandService.commandAdded.subscribe(command => {
// Update the list displayed in the component...
});
}
}

Related

httpClient.get is undefined when using dynamic function/observable references

So I asked a question a few days ago and got some headway on a solution, however now I'm stuck at another wall I'm unsure how to get over.
I have two parent components, a shared view/component with an extended base component, and a service all hooked together. The objective is to use the two parent components to drive what data is shown within the shared component. The two parent components use references to service methods passed into the shared component to get the data.
I've reached an issue where my http.get is always undefined no matter what I try. I've instantiated it like I do in my other services but I've had no luck. I suspect this is caused by how i pass in my service references. Code below:
Parent Component Code:
// PARENT COMPONENT
myData$: Observable<myType>;
searchMethod: Function;
constructor(private myService){
this.myData$ = this.myService.myData$;
this.searchMethod = this.myService.searchData;
}
// PARENT COMPONENT HTML
<app-shared-component
[myData$] = "myData$"
[searchMethod]="searchMethod">
</app-shared-component>
Shared Component Code:
export class MySharedComponent extends BaseComponent<MyType> implements OnInit {
#Input() myData$: Observable<myType>;
#Input() searchMethod: Function;
constructor() { super(); }
ngOnInit(): void {
this.data$ = this.myData$;
}
search(): void {
this.searchMethod().subscribe(//do something);
}
Base Component Code:
#Input data$: Observable<T>;
ngOnInit(): void {
this.data$.subscribe((response: T) => //do something);
super.ngOnInit();
}
Service Code:
private myDataSubject = new BehaviorSubject<MyType>(new MyType());
get myData$(): Observable<MyType> {
return this.myDataSubject.asObservable();
}
constructor(private http: HttpClient) { }
searchData(): Observable<void> {
return new Observable<void>(observer => {
this.http.get<MyType>(
'http://myuri'
).subscribe(
response => {
// do something
},
() => observer.error(),
() => observer.complete()
);
});
}
It looks like you're losing the context of your service when you set this.searchMethod = this.myService.searchData in your parent component. It should work if you change searchData() { to an arrow function: searchData = (): Observable<void> => {.

Javascript Angular 4 Change ngClass from another Component

I currently have this code in my app.component.ts
app.component.html
<div [ngClass]="myclass">
...rest of the content here
</div>
This I have the this:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
app.component.ts
export class AppComponent {
myclass: string;
changeClass(myclass) {
this.myclass = myclass;
}
}
Now, all this works fine BUT I now want to put the triggering button on another component.
If I put this on another component:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
How can I get it to change the class?
There are two ways you can do this you can use output with an EventEmit
Or you can set up a service that monitors the changes to a variable and use that as the control point for the change.
Personally, I use services for this instance as its easier to manage the code and its flow.
This answer has all the code in you need to look at.
Changing a value in two different components at the same time Angular 2
Hope that helps
There are at least two options. Subject and Observable or if this another component is a parent you can use #Input.
Subject and Observable method:
angular guide Highly recommended to read whole page.
Some component
export class SomeComponent {
constructor(private ClassService: ClassService) { }
private changeClass(class) {
this.ClassService.changeClass(class);
}
}
Another Component
export class AnotherComponent implements OnInit, OnDestroy {
constructor(private ClassService: ClassService) { }
private class: string = "";
private subscribtion: Subscribtion;
ngOnInit(): void {
this.Subscribtion = this.ClassService.someClass$.subscribe(
(class) => { this.class = class; }
)
}
ngOnDestroy(): void {
this.Subscribtion.unsubscribe();
}
}
Service
#Injectable();
export class ClassService{
constructor() { }
private someClassSource= new Subject<string>();
someClass$= this.someClassSource.asObservable();
changeClass(class) {
this.someClassSource.next(class);
}
}
taken from my answer
#Input method:
angular guide
This is very simple, when you click button changeClass method will change elClass which will be passed to another component by #Input decorator, every change of #Input will cause a detect changes which will detect that value has changed so class will change to myClass.
Parent component
parent.component.html
<another-component [elementClass]="elClass"></another-component>
<button (click)="changeClass('myClass')">change class<button>
parent.component.ts
export class ParentComponnet {
private elClass: string = "";
changeClass(class: string) {
elClass = class;
}
}
Another component (must be child component)
another.component.html
<div [ngClass]="elementClass">
another.component.ts
export class AnotherComponent {
#Input() elementClass: string;
}
There is also Child to Parent interaction via #Output (emitting event) angular guide

Angular, Get Index Of Dynamically Created Component Inside ViewContainerRef

I am trying to get the index of a dynamically created component inside ViewContainerRef
I need to get the index so I can destroy the component if I wanted too.
Code Below
#ViewChild('dynamicInsert', { read: ViewContainerRef }) dynamicInsert: ViewContainerRef
componentFactory
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef,
) { }
ngAfterViewInit() {
this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(AssetOptionComponent);
}
addAssetOption() {
const dynamicComponent = <AssetOptionComponent>this.dynamicInsert.createComponent(this.componentFactory).instance
// how to get index of this dynamically generated component ^^^^
}
Trying to use
this.dynamicInsert.remove(index: number) to destroy component
but I first need the index of the dynamically created component
this.dynamicInsert.indexOf(viewRef: viewRef)
To get the index you can use indexOf method and hostView property:
const index = this.dynamicInsert.indexOf(dynamicComponent.hostView)
Also note that if you don't specify the index view container will destroy the last component:
remove(index?: number): void {
const viewData = detachEmbeddedView(this._data, index);
if (viewData) {
Services.destroyView(viewData);
}
}
export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null {
const embeddedViews = elementData.viewContainer !._embeddedViews;
if (viewIndex == null || viewIndex >= embeddedViews.length) {
viewIndex = embeddedViews.length - 1;
}
So if you have only one component you don't need to pass index.
To remove all components you can use clear method.
If you are looking to destroy the created component you may consider a shortcut by just subscribing to it's observable destroy:
addAssetOption() {
const dynamicComponent: ComponentRef<any> = this.dynamicInsert.createComponent(this.componentFactory);
dynamicComponent.instance.destroy.subscribe(() => dynamicComponent.destroy())
}
and then upon removing event, in AssetOptionComponent, call it:
export class AssetOptionComponent {
destroy = new Destroyable();
delete(){
this.destroy.delete();
}
}
export class Destroyable extends Subject<any>{
delete() {
this.next();
}
}
Working demo

Accessing base member from derived class in function

I have created Angular2 + Typescript project. I have there alot of tables so I want to have something like base component for that.
There is my base component:
export abstract class ManagementComponent<T> implements BaseComponent {
protected selectedItem: T;
protected items: Array<T>;
}
Now there is my child component. I would like to get all items from http and then assign it into base class
export class CompetencesComponent extends ManagementComponent<Competence> implements OnInit {
thisField: string;
constructor(private service: CompetencesService) {
super();
}
ngOnInit(): void {
this.getCompetences();
}
private getCompetences() {
this.service.getCompetences().subscribe(function (competences: Array<Competence>) {
this.thisField // ok
this.items // not ok
})
}
}
Any idea how I can access base fields from subscribe methods?
Currently I'd expect that you wouldn't be able to reference either thisField or items, because you should be losing the this context inside your subscription function.
You can switch to an arrow function to retain context:
this.service.getCompetences().subscribe((competences: Array<Competence>) => { ... }
You can set list of competencies to parent class as follow:
private getCompetences() {
var self = this;
this.service.getCompetences().subscribe(function (competences: Array<Competence>) {
this.thisField // ok
self.items = competences; // not ok
})
}
The reason you are unable to access items property through this binding is the scope. Inside callback this binding is bound to something else and you loose the context.

DevExpress' DevExtreme for Angular2 and pre-selection

I'm using DevExpress' DevExtreme with Angular2. I have a data grid (below) that lists states and asks the user to select some states. It is possible that some states have already been stored in the database. How do I set the previously selected states? I can see in the documentation that I should use dataGrid.instance.selectRows(arrayOfPreviouslySelectedStates) but dataGrid is instantiated sometime after I try to set it which is in the ngOnInit().
My HTML grid:
<dx-data-grid #statesGrid id="statesContainer" [dataSource]="states" [selectedRowKeys]="[]" [hoverStateEnabled]="true" [showBorders]="true" [showColumnLines]="true" [showRowLines]="true" [rowAlternationEnabled]="true">
<dxo-sorting mode="multiple"></dxo-sorting>
<dxo-selection mode="multiple" [deferred]="true"></dxo-selection>
<dxo-paging [pageSize]="10"></dxo-paging>
<dxo-pager [showPageSizeSelector]="true" [allowedPageSizes]="[5, 10, 20]" [showInfo]="true"></dxo-pager>
<dxo-filter-row [visible]="true"></dxo-filter-row>
<dxi-column dataField="abbreviation" [width]="100"></dxi-column>
<dxi-column dataField="name"></dxi-column>
</dx-data-grid>
My componenet:
import 'rxjs/add/operator/switchMap';
import { Component, OnInit, ViewContainerRef, ViewChild } from '#angular/core';
import { CompanyService } from './../../../shared/services/company.service';
import { StateService } from './../../../shared/services/state.service';
import notify from 'devextreme/ui/notify';
import { DxDataGridModule, DxDataGridComponent } from 'devextreme-angular';
#Component({
selector: 'app-company-detail',
templateUrl: './company-detail.component.html'
})
export class CompanyDetailComponent implements OnInit {
#ViewChild(DxDataGridComponent) dataGrid: DxDataGridComponent;
companyStates: Array<ICompanyState>;
states: Array<IState>;
constructor(private CompanyService: CompanyService, private StateService: StateService) { }
ngOnInit() {
this.StateService.getStates().subscribe((states) => {
this.getSelectedStates();
this.states = states
});
}
public getSelectedStates = (): void => {
this.CompanyService.getStates(id).subscribe((states) => {
let preselectedStates: Array<IState> = this.companyStates.map((state) => {
return { abbreviation: state.Abbreviation, name: state.Name }
});
// I get an error here that says that dataGrid is undefined.
this.dataGrid.instance.selectRows(preselectedStates, false);
}
}
}
Thanks to #yurzui 's comment I was able to figure out my problems in the following way. [selectedRowKeys] deals with all preselection. It's "problem" is that it doesn't update itself when additional selections are made. So, I listened for onSelectionChanged and passed the event, which contains data about many things regarding selection, into my custom function which updates the selectedStates which I then use to save the data to the database when the save button is clicked.
Gets the preselected states from the database
public getCompanyStates = (): void => {
this.CompanyService.getStates().subscribe((states) => {
this.selectedStates = states;
});
}
Event handler
public onSelectionChanged = (e): void => {
this.selectedStates = e.selectedRowKeys;
}
The dx-data-grid portion of the HTML
<dx-data-grid #statesGrid id="statesContainer"
(onSelectionChanged)="onSelectionChanged($event)"
[selectedRowKeys]="selectedStates"
[dataSource]="states">
...
</dx-data-grid>

Categories

Resources