I have a dynamic component loader, and I need to pass in data through a service. I can get the data to display if I fire the function on click for example, but not OnInit.
I have tried using AfterViewInit
Eventually the data will be coming from an API
Update:
Working StackBlitz
app.component.html
<app-answer-set></app-answer-set>
header.component.html
<ng-template appHeaderHost></ng-template>
header.component.ts
export class HeaderComponent implements OnInit {
#Input() component;
#ViewChild(HeaderHostDirective) headerHost: HeaderHostDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
this.loadComponent();
}
loadComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.component);
const viewContainerRef = this.headerHost.viewContainerRef;
viewContainerRef.createComponent(componentFactory);
}
}
header-host.directive.ts
export class HeaderHostDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
header-data.service.ts
export class HeaderDataService {
private headerDataSource = new Subject<any>();
headerData$ = this.headerDataSource.asObservable();
constructor() { }
setHeaderData(data: any) {
this.headerDataSource.next(data);
}
}
answer-set.component.html
<app-header [component]="component"></app-header>
<button (click)="setHeaderData()">click</button>
answer-set.component.ts
export class AnswerSetComponent implements OnInit {
component: any = AnswerSetHeaderDetailsComponent;
constructor(private headerDataService: HeaderDataService) { }
ngOnInit() {
this.setHeaderData();
}
setHeaderData() {
const data = [{name: 'Header stuff'}];
this.headerDataService.setHeaderData(data);
}
}
answer-set-header-details.html
<dt>First:</dt>
<dd>Description</dd>
<dt>Second</dt>
<dd>Description</dd>
<p>
data will show on click of button but not <code>onInit</code>:
</p>
<p>
{{headerData}}
</p>
answer-set-header-details.component.ts
export class AnswerSetHeaderDetailsComponent implements OnInit {
constructor(private headerDataService: HeaderDataService) { }
headerData: any;
ngOnInit() {
this.headerDataService.headerData$
.subscribe(data => {
this.headerData = data[0].name;
});
}
}
After running through the Angular documentation I found that I needed to pass the data to the componentRef instance, something like this:
(<IHeader>componentRef.instance).data = headerItems.data;
After a little refactoring I ended up passing both the component and the data through a service, see the updated StackBlitz for a working example.
Eventually I'll look to pass in multiple components, but for now this works.
header.component.html
<ng-template appHeaderHost></ng-template>
header.component.ts
/* ... */
export class HeaderComponent implements OnInit {
#ViewChild(HeaderHostDirective) headerHost: HeaderHostDirective;
subscriptionManager = new Subscription();
constructor(
private headerService: HeaderService,
private componentFactoryResolver: ComponentFactoryResolver
) {
const headerConfigSubscription = this.headerService.headerConfig$
.subscribe((headerItems: HeaderItem) => {
this.loadComponent(headerItems);
});
this.subscriptionManager
.add(headerConfigSubscription);
}
ngOnInit() {
}
loadComponent(headerItems: HeaderItem) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(headerItems.component);
const viewContainerRef = this.headerHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
(<IHeader>componentRef.instance).data = headerItems.data ? headerItems.data : null;
}
ngOnDestroy() {
/**
* #description Unsubscribe from all subscriptions
* to prevent memory leaks
*/
this.subscriptionManager.unsubscribe();
}
}
header.service.ts
/* ... */
export class HeaderService {
private headerConfigSource = new Subject<any>();
headerConfig$ = this.headerConfigSource.asObservable();
constructor() { }
configureHeaderItems(headerItems: HeaderItem) {
this.headerConfigSource.next(headerItems);
}
}
header.ts
/* ... */
export interface IHeader {
data: any;
}
export interface IHeaderItem {
component: Type<any>;
data?: any;
}
export class HeaderItem implements IHeaderItem {
constructor(
public component: Type<any>,
public data?: any
) {
this.component = component;
this.data = data;
}
}
main.component.html
<app-header></app-header>
main.component.ts
export class MainComponent implements OnInit {
headerItems: HeaderItem;
constructor(private headerService: HeaderService) { }
ngOnInit() {
this.configureHeaderItems();
}
configureHeaderItems() {
this.headerItems = new HeaderItem(MainHeaderDetailsComponent, {});
this.getHeaderItemData();
}
/**
* #description This is where we would make the API call and get the data
* but we can mock it for now
*/
getHeaderItemData() {
const data = {
name: 'I am loaded dynamically!'
};
this.headerItems.data = data;
this.headerService.configureHeaderItems(this.headerItems);
}
}
main.module.ts
#NgModule({
/* ... */
// Need to add to entryComponents
entryComponents: [MainHeaderDetailsComponent]
})
export class AnswerSetModule { }
main-header-details.component.html
<p>
Header content. {{data.name}}
</p>
main-header-details.component.ts
/* ... */
export class MainHeaderDetailsComponent implements OnInit {
constructor() { }
#Input() data: any;
ngOnInit() {
}
}
Related
I have a Project that uses HTTP Calls to fetch Data from API Endpoint and than display on Screen.
It's a simple ToDoList. So you can add Items to the list, see all Items in your List, delete items and so on.
The Project structure is this:
Items-Component (Holds the entire App basically)
Item-list-component
Item-detail-component
Item-edit-component
item-add-component
Item.service
The Items.component.html looks like this:
<div class="row">
<div class="col-md-5">
<app-item-list></app-item-list>
</div>
<div class="col-md-7">
<router-outlet></router-outlet>
</div>
So we can see that the item-list-component and the other 3 components (binded via router-outlet) are sibling components, that's what I think.
So my Problem is now:
I want that whenever a new Item is created the items[] in the items.list component should refresh automatically. Now I must click a "Fetch Items" button to refresh the items[].
When I add a new Item, it fires a method from my item.service, it holds a fetchItems Method that just returns an Observable of the API Endpoint, like this:
Item-add component.ts:
#Component({
selector: 'app-item-add',
templateUrl: './item-add.component.html',
styleUrls: ['./item-add.component.css']
})
export class ItemAddComponent implements OnInit {
constructor(private itemService: ItemService, private route: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
}
onCreatePost(item: Item) {
// Send Http request
this.itemService.createAndStorePost(item.description, item.isComplete);
//Here I want that the items[] in the items.list component refreshes when I add new Item
this.onCancel();
}
onCancel() {
this.router.navigate([''], {relativeTo: this.route});
}
}
And the item.service.ts:
#Injectable()
export class ItemService {
constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router) {
}
fetchItems(): Observable<Item[]> {
return this.http.get<Item[]>('https://localhost:44321/api/TodoItems');
}
fetchItem(id: number): Observable<Item> {
return this.http.get<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
createAndStorePost(description: string, isComplete: boolean) {
var item = { description: description, isComplete: isComplete };
this.http.post('https://localhost:44321/api/TodoItems', item)
.subscribe(Response => {
});
}
deleteItem(id: number): Observable<Item> {
return this.http.delete<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
updateItem(id:number, item: Item) {
this.http.put<Item>('https://localhost:44321/api/TodoItems' + '/' + id, item).subscribe();
}
}
Then the items-list component catches that Observable and subscribes to it and sets the Response from that subscription to and items[] in the component itself:
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
constructor(private route: ActivatedRoute, private router: Router, private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
});
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
}
What can I do to trigger that the items.list should fetch Items again?
I can't use #ViewChild because it is no Parent-Child relation.
Can I implement and instance of item.list anywhere in the project and just call the onFetchItems Method?
Thanks!
you can use BehaviorSubject to share data between your different components.
Here is an example:
In your ItemService.
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ItemService {
private _itemsSource = new BehaviorSubject([]);
currentItems = this._itemsSource.asObservable();
constructor() { }
updateItems(items: []): void {
this._itemsSource.next(items)
}
}
In your ItemsComponent, you update the new value in the service after you get all the items,
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
items: Item[] = [];
constructor(private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
this.updateItems(this.items)
});
}
updateItems(newItems: []): void {
this.itemService.updateItems(newItems)
}
}
And in your ItemListComponent
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
subscription: Subscription;
constructor(private route: ActivatedRoute,
private router: Router,
private itemService: ItemService) { }
ngOnInit(): void {
this.subscription = this.itemService.currentItems.subscribe(items => this.items = items)
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
i want find the params in previous route in angular typescript .
i use this code :
private previousUrl: string = undefined;
private currentUrl: string = undefined;
constructor(private router: Router) {
this.currentUrl = this.router.url;
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.previousUrl = event.url;
this.currentUrl = this.currentUrl;
}
});
}
but i can not access to the params of this url :
http://localhost:4200/claims-manager/200/edit
i want ti access 200 . how can i find params in url ????
You can do it in your component file but It is a best practice to do it in a service (using rxjs) to pass data and call it in your component file
In your service
export class myService {
constructor() { }
private param = new BehaviorSubject("");
sharedParam = this.param.asObservable();
paramToPass(param:string) {
this.param.next(param)}
}
In your component class that set param
export class ComponentSetParam {
param: string
constructor(private myService: Service)
this.myService.setParam(this.param);
}
in your appModule
#NgModule({
declarations: [YourComponents]
imports: [ AppRoutingModule, YourModules...],
providers: [ShareService],
})
export class AppModule {}
Component that you want to pass data
export class ComponentGetParam {
paramFromService: string
constructor(private myService: Service) {
this.shareService.sharedData.subscribe(data : string => {
this.paramFromService = data;
})
}
}
Try this:
readonly _destroy$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
constructor(
private activatedRoute: ActivatedRoute,
) {
this.activatedRoute.parent.paramMap
.pipe(
distinctUntilChanged(),
takeUntil(this._destroy$)
)
.subscribe((params: ParamMap) => {
const id = params.get('id');
});
}
ngOnDestroy() {
this._destroy$.next(true);
this._destroy$.complete();
}
Where 'id' is a name, that you use in the routing, e.g.
path: '/claims-manager/:id/'
Demo You can do it in service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ShareService {
constructor() { }
private paramSource = new BehaviorSubject("");
sharedData = this.paramSource.asObservable();
setParam(param:string) { this.paramSource.next(param)}
}
in constructors
constructor(private shareService: ShareService)
in component in ngOnDestroy set this like this.shareService.setParam(param);
in appmodule
providers:[ShareService ]
in new component in ngOnInit or in constructor get like
this.shareService.sharedData.subscribe(data=> { console.log(data); })
I have a loop on a component which represents a list of graph cards on my real app.
I have copied this component ( and loop it ) as the original
Hello Component
export class HelloComponent {
message:string;
printedMessage:string
#Input() elm:string;
constructor(private data: DataService, private router : Router) { }
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message)
}
updateService(){
this.data.changeMessage(this.message);
this.printedMessage=this.data.messageSource.value
}
navigateToSibling(){
this.router.navigate(['/sibling']);
}
}
app component
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
<h1>Copy </h1>
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
DataService component
export class DataService {
messageSource = new BehaviorSubject<string>("default message");
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Expected behaviour
What I would is when change the input value on the component 1 for example , only the value on the input of the copied component 1 changes.
Actual behaviour
Actually when I change a value inside an input all the other inputs are changings.
Here's a stackblitz example
Below is a solution that will solve you issue. This may not be a perfect solution but you need something similar.
hello.html
<h1>App component {{elm}}</h1>
<input type="text" [(ngModel)]="message">
<button (click)="updateService()" type="button">Save</button> {{printedMessage}}
Data Service
import {
Injectable
} from '#angular/core';
import {
BehaviorSubject
} from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
messageSource = new BehaviorSubject < any > ("default message");
constructor() {}
changeMessage(message: string, elem: any) {
this.messageSource.next({
message: message,
elem: elem
});
}
}
HelloComponent
import {
Component,
Input
} from '#angular/core';
import {
DataService
} from "./dataService";
import {
Router
} from '#angular/router';
#Component({
selector: 'hello',
templateUrl: './hello.html',
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
message: string;
printedMessage: string
#Input() elm: string;
constructor(private data: DataService, private router: Router) {}
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message.elem === this.elm ? message.message : this.message);
}
updateService() {
debugger
this.data.changeMessage(this.message, this.elm);
this.printedMessage = this.data.messageSource.value.message;
}
navigateToSibling() {
this.router.navigate(['/sibling']);
}
}
Have also updated the Stackblitz Demo. Hope this helps :)
i have service, in service, I have a "cohortTabsResult" method whitch sets the charts array. i want to use this arry in "chart.component"
export class CohortService {
public charts: Array<any>;
cohortTabsResult(obj){
this.charts = []
const subscription = this.cohortDataReq(obj).subscribe(res => {
if(res.status !== 500){
const dataObj = {
definedChart: obj.graph_type,
definedChartData: []
};
this.charts.push(dataObj);
const response = res.json()
//console.log(response)
if (response.error) {
//this.isLoaded = false;
}
else{
Array.prototype.forEach.call(response.data, dataRes => {
const newData = this.getChartDataFormat(dataRes, obj.graph_type, "userType")
dataObj.definedChartData = _.cloneDeep(newData);
});
}
}
});
}
}
and this is my chart.component here I am getting the empty array.
export class ChartCohortComponent implements OnInit{
charts: any;
constructor(private cohortService: CohortService, private route:
Router, public activatedRoute: ActivatedRoute) {
this.charts = this.cohortService.charts;
}
ngOnInit(){
console.log("ch", this.charts)
}
}
import CohortService to your component, add it to the providers in #component, now you can access the variables inside the service. :D
import { CohortService } from '../../cohort.services'; // whatever the path is..
#Component({
selector: '',
templateUrl: '',
styleUrls: [''],
providers: [CohortService]
})
export class ChartCohortComponent implements OnInit{
charts: any;
constructor(private cohortService: CohortService, private route:
Router, public activatedRoute: ActivatedRoute) {
this.charts = this.cohortService.charts;
}
ngOnInit(){
console.log("ch", this.charts)
}
}
I'm learning Angular2. In order to that, I have 2 components, on the click of one of the components, the other component should be notified and act with that.
This is my code so far:
export class JsonTextInput {
#Output() renderNewJson: EventEmitter<Object> = new EventEmitter()
json: string = '';
process () {
this.renderNewJson.next(this.json)
}
}
The process function is being called on the click on the first component.
On the second component I have this code:
export class JsonRendered {
#Input() jsonObject: Object
ngOnChanges () {
console.log(1)
console.log(this.jsonObject)
}
}
The ngOnChanges is never runned, I dont get how to pass the info from one component to other
EDIT
There is an app component which is parent of those 2 components. None of both is parent of the other
This is how my clasess look now:
export class JsonRendered {
private jsonObject: Object
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
this.jsonObject = jsonChangeService.jsonObject
jsonChangeService.stateChange.subscribe(json => { this.jsonObject = json; console.log('Change made!') })
}
}
export class JsonTextInput {
json: string = '';
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
}
process () {
this.jsonChangeService.jsonChange(this.json)
}
}
And the service
import {Injectable, EventEmitter} from '#angular/core';
#Injectable()
export default class JsonChangeService {
public jsonObject: Object;
stateChange: EventEmitter<Object> = new EventEmitter<Object>();
constructor(){
this.jsonObject = {};
}
jsonChange(obj) {
console.log('sending', obj)
this.jsonObject = obj
this.stateChange.next(this.jsonObject)
}
}
Create a service like so...
import {Injectable, EventEmitter} from 'angular2/core';
#Injectable()
export class MyService {
private searchParams: string[];
stateChange: EventEmitter<any> = new EventEmitter<any>();
constructor(){
this.searchParams = [{}];
}
change(value) {
this.searchParams = value;
this.stateChange.next(this.searchParams);
}
}
Then in your component...
import {Component} from 'angular2/core';
import {MyService} from './myService';
#Component({
selector: 'my-directive',
pipes: [keyValueFilterPipe],
templateUrl: "./src/someTemplate.html",
providers: [MyService]
})
export class MyDirective {
public searchParams: string[];
constructor(private myService: MyService) {
this.myService = myService;
myService.stateChange.subscribe(value => { this.searchParams = value; console.log('Change made!') })
}
change(){
this.myService.change(this.searchParams);
}
}
You have to subscribe to the eventemitter, then update your variable. The change event in the service would get fired of from something like...
(click)="change()"