I'm trying to create a dynamic dashboard. Right now I'm using this dynamicServer to inject the component, but when I display the same component a second time, the information is shared between. I'm trying to isolate the "views" and create a new instance. Any help?
dynamicServer.ts
loadComponent(viewContainerRef: ViewContainerRef) {
const childComponent = this.factoryResolver.resolveComponentFactory(this.resolveView(this.ViewtoInjet));
const instance = viewContainerRef.createComponent(childComponent).instance;
}
ViewContainer.ts
#ViewChild('dynamic', {
read: ViewContainerRef
}) viewContainerRef: ViewContainerRef;
constructor(#Inject(DynamicServiceTestService) service, private winRef:
WindowRefService) {
this.service = service;
}
ngOnInit() {
this.service.loadComponent(this.viewContainerRef);
}
Views
As you can see, I am rendering the same component twice, but if I click to add numbers with the buttons, the value is shared between them. Any way to generate a new instance of every component to isolate them?
EDIT
I add a stackblitz https://stackblitz.com/edit/angular-tpryfl?file=src/app/view-container/view-container.component.ts
When i put my code in stackblitz i realized that my code was ok!!, but in my computer code i was using a service to refresh the counter, so it was ok so the counter in the service has to be common, but now ive got another question.
Is it possible to open 2 references of the same component, so if i add numbers in one component will i see the changes in the copy of the same component?
Related
How can I get the id of a task in my todo app without using ActivatedRoute method for updating functionality in angular ?
this.route.paramMap.subscribe(params => {this.id = params.get('id');})
this is the way which I'm using now
If you don't want to use ActivatedRoute you need to store the id in a service or in the localStorage before changing page, so in the new page you can retrieve it.
If it's because you don't want to use an observable then you could maybe use this in ngOnit if you component initilze every time there is a new product.
const id: string = route.snapshot.params.id;
If you id comes from a component and you want to share it with another component, then you can use #output to share it to a parent component or #input to pass it to a child component. You could also put in a service.In Angular services or most often singletons that can be shared across your app.
Angular 10.1.6, TypeScript 4.0.5
Hello
Context
In my webapp, I have 3 components.
Component A stores an Array<Image>
export class ComponentParentA implements OnInit {
images: Array <Image> ;
addImageToList(newImage: Image) {
this.images.push(newImage)
}
}
The Component C displays the list of images.
export class ComponentChildC implements OnInit {
#Input() images: Array <Image>;
}
The component C is called from the Component A html template like this :
<ComponentChildC [images]="images"></ComponentChildC>
Component B is in charge to contact my API, add an image which has been selected previously. The API return an Image object, which correspond to my model. The component emit the Image so the ComponentA add the returned image to its Array (Calling addImageToList)
addImage is an Observable return by this.http.post (HttpClient)
export class ComponentChildB implements OnInit, AfterViewInit {
#Output() eventCreateImage : EventEmitter<Image>;
addImage(data) {
this.service.addImage(data).subscribe((image) => {
this.eventCreateImage.emit(image)
})
}
}
Component A calls Component B like this :
<ComponentChildB (eventCreateImage)="addImageToList($event)"></ComponentChildB>
Problem
The Component B add an image when I click on a button. This click trigger the addImage function. All the component work correctly and the image is save on my server. The API returns the image and the latter is correctly stored in the Array in Component A. However, the view of the component C is not updated and the newly created image doesn't appear. If I click a second time on the button, a new image is stored. This time, the previous image, which I couldn't see previously, appears correctly. The new image don't.
I would like the image to appear directly.
What I already did
I saw similar issues on the web.
First, I tried to replace this.images.push(newImage) by this.images = this.images.concat(newImage) in addImageToList function. It doesn't work.
Next, I saw that, because images is an array, Angular doesn't detect any change when I add a new Image in my array. I made my Component C implements the OnChange interface. Effectively, the ngOnChange function is not called.
I saw here that I could try to reload my component manually So I made my Component C implement DoCheck. Something strange appeared. When I click on the button and I do console.log(this.images) in ngDoCheck in component C, I see that the list is correctly updated. But the image still doesn't appear. So I try to call ChangeDetectorRef methods like markForCheck and detectChanges to notify the component of the change, but it didn't work.
If you have any idea where my problem may be coming from, I would be grateful if you could help me
I didn't test but i think it's cause strategy detection. You can try 3 differents solutions (I can't test now but normaly it will be work).
PS: sorry for my english
1/ in your object #Component add changeDetection in your components
#Component({
selector: 'app-component',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
then in your function addImageToList() replace your code by
addImageToList(newImage) {
this.images = [...this.images, newImage]
}
2/ in your component C replace your #Input by
#Input() set images(value: Array <Image>) {
myImages = value
}
the properties binding in html will be myImages in this context
3/ You can change manualy the strategy detection with the service ChangeDetectorRef. Add this service in your constructor component A then in your function add :
constructor(
private cd: ChangeDetectorRef
){}
addImageToList(newImage: Image) {
this.images = [...this.images, newImages];
this.cd.markForCheck();
}
If this solutions don't work look the documentation of detection strategy in Angular or try to mix 1 and 2 together
I cant seem to figure out what the issue is,
I have a service that has a behavior subject like so...
popupSource = new BehaviorSubject<any>('');
popup(component) {
this.popupSource.next(component);
}
then In my header component
popupClick() {
this._service.popup('example');
}
then in my header.html
<button (click)="popupClick()"></button>
then In my app component
ngOnInit() {
this._service.popupSource.subscribe((result) => {
console.log(result);
})
}
so whats happening is the click is firing the this._service.popup('example'); but its never hitting the subscription...
I've put breakpoints on each function and It succesfully reaches this.popupSource.next(component) but then nothing?? Every Time I click the button I should be getting the console log.
Im not sure what Im doing wrong... Ive left out code for brevity sake so please let me know if you need more information
EDIT
Ive also tried doing
private popupSource = new BehaviorSubject<any>('');
getPopup = this.popupSource.asObservable();
popup(component) {
this.popupSource.next(component);
}
and the in my app component listend to the getPopup instead
ngOnInit() {
this._service.getPopup.subscribe((result) => {
console.log(result);
})
}
and that's not working either, I cant figure out what the problem is...
Thanks
Your issue here is that you provide your service in two different modules, that end up with two instances of your service. The simplest way if you are using Angular v6 is to use the providedIn flag in your service:
import { Injectable } from '#angular/core';
#Injectable({providedIn: 'root'})
class myService {}
With this way you don't need to provide your service in the providers array of any module, it will be automatically provided in the root injector.
Documentation about this can be found here : Angular providers.
If your are using Angular v5 you should only provide your service in your AppModule.
In your service, write a new method like this:
popupSource = new BehaviorSubject<any>('');
getPopupSource() {
return this.popupSource.asObservable();
}
And subscribe to that instead of subscribing directly to the subject itself. You could just add the 'asObservable()' part whenever you subscribe to it instead of creating an entirely new method, but I like the separate method so I can keep the Subject private, and I usually subscribe to something multiple times in different places throughout an app, so it cuts down on repetition.
In your component :
this._service.getPopupSource().subscribe( result => { etc...})
EDIT :
Demo recreation of your scenario - https://stackblitz.com/edit/angular-n6esd5
You may not have the same instance of service, my same problem was that I had #Injectable({ providedIn: 'root'}) for my service, but also I put this service in the component providers [] array, just remove the providers array, then it works
<side-nav [navTitle]="navTitle"></side-nav>
<router-outlet>
</router-outlet>
I have navigation bar at the root component. I created [navTitle] with #Input Decorator inside the side-nav component. side-nav component is placed in another component(root-component). However I want access [navTitle] and change from component which loaded inside the router-outlet acording to which component is loaded. How do I achieve that?
You can't pass any data to router-outlet as to regular component (at the current version of Angular it's not possible, may be it will be added in the future), so the following syntax is invalid:
<router-outlet [dataToPass]="'something'"></router-outlet>
In provided case, you can use services to share data between your components, and I think, that using observable is the best way, because you will get the updated version of data realtime:
data.service.ts
// Other service stuff
#Injectable()
export class DataService {
private navTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('Default nav title');
public setNavTitle(newNavTitle: string): void {
// Sets new value, every entity, which is subscribed to changes (`getNavTitle().subscribe(...)`) will get new value every time it changes
this.navTitle$.next(newNavTitle);
}
public getNavTitle(): Observable<string> {
// Allow to `subscribe` on changes and get the value every time it changes
return this.navTitle$.asObservable();
}
}
side-nav.component.ts
// Other component stuff
export class SideNavComponent implements OnInit, OnDestroy {
public navTitle: string = '';
private getNavTitleSubscription: Subscription;
constructor(private _dataService: DataService) { }
ngOnInit() {
// Will update the value of `this.navTitle` every time, when you will call `setNavTitle('data')` in data service
this.getNavTitleSubscription = this._dataService.getNavTitle()
.subscribe((navTitle: string) => this.navTitle = navTitle);
}
ngOnDestroy() {
// You have to `unsubscribe()` from subscription on destroy to avoid some kind of errors
this.getNavTitleSubscription.unsubscribe();
}
}
And any component, which is loaded in that router-outlet:
any.component.ts
// Other component stuff
export class SideNavComponent implements OnInit {
private navTitleToSet: string = 'Any title';
constructor(private _dataService: DataService) { }
ngOnInit() {
// Set title from current component
this._dataService.setNavTitle(this.navTitleToSet);
}
}
In such case you don't really need to pass the value from root component to side-nav, because you already have a subscription in side-nav component and you will have access to the latest value. If you need navTitle in both root and side-nav components, you can just move the logic with subscription to root.
And here is the working STACKBLITZ.
You can use a service to communicate between components. I have created a short example which would give you a glimpse of how it can be done.
The service being a singleton, has only one instance and hence the properties remain the same.
Hope it helps.
https://stackblitz.com/edit/angular-paziug?file=src%2Fapp%2Fapp.component.ts
I just started playing with angular 2 and i've ran into a small problem, that i ve searched for in various forms and also angulars documentation.
I've managed to make a service that makes a call and then i want in a component when i press a button to load another component with dynamicload component and have access to the ajax result.
The problem is that I can t figure out how to do that..
The question is how can I make the result accesible in other components using Observables or Promises method.
If I understood correctly your question, you are looking a way to insert a data from request to another nested component.
I hope this image will clarify for you the data flow for this case.
Your Root component is calling a service method which returns for you promise object.
Then you map needed data from response to the component model inside Root Component constructor.
And your Child component should be subscribed for the model which you was preparing in previous step.
ngOnInit() {
this.dataService.getSomeData()
.subscribe((data: IData) => {
this.data = data;
});
}
Just a short example above how to set model in the root component from the promise object to the local model.
New research:
There is another way to fill your components by data from api's. You can use EventEmitter to emit event from service, and then, you can subscribe for this event inside you created components, so they will get a data, each time there will be called the service. Here is nice example of this strategy in the first answer. Service Events
Hope it will help you, let me know if you will need additional info!
Just create a service, then inject the service where you want.
Here it's an example how to share a service ajax data across many components without making the request twice :
https://stackoverflow.com/a/36413003/2681823
the Service:
#Injectable()
export class DataService {
constructor(private http: Http) { }
private _dataObs = new ReplaySubject<request>(1);
getData(forceRefresh?: boolean) {
// On Error the Subject will be Stoped and Unsubscribed, if so, create another one
this._dataObs = this._dataObs.isUnsubscribed ? new ReplaySubject(1) : this._dataObs;
// If the Subject was NOT subscribed before OR if forceRefresh is requested
if (!this._dataObs.observers.length || forceRefresh) {
this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.subscribe(
requestData => {
this._dataObs.next(requestData);
},
error => this._dataObs.error(error));
}
return this._dataObs;
}
}
the Component:
#Component({
selector: 'child',
template : `<button (click)="makeRequest()" class="btn">Click me!</button>`
})
export class Child {
constructor(private _dataService: DataService) { }
makeRequest() {
this._dataService.getData().subscribe(
requestData => {
console.log('ChildComponent', requestData);
}
}
}
A full working example/plunker can be found here : http://plnkr.co/edit/TR7cAqNATuygDAfj4wno?p=preview