Reference to service from dynamically added component in Angular 6 CLI - javascript

I created a directive in Angular 6 named 'DeleteDirective' and reference to a service 'DeleteService' to make sure I can delete an item from my application. After the item is marked as deleted (in PHP back-end), I'll show an Undo element via the 'UndoComponent' that I dynamically added in the DeleteService. No problems so far.
#Directive({
selector: '[appDelete]'
})
export class DeleteDirective {
constructor(
#Inject(ViewContainerRef) viewContainerRef,
renderer: Renderer2
) {
service.renderer = renderer;
service.setRootViewContainerRef(viewContainerRef);
service.addUndoElement();
}
#HostListener('click') onClick() {
// (Some code to execute deletion)
this.deleteService.showUndoElement();
}
#Injectable({
providedIn: 'root'
})
export class DeleteService {
constructor(
rendererFactory: RendererFactory2,
private factoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
) {
this.renderer = rendererFactory.createRenderer(null, null);
this.factoryResolver = factoryResolver;
}
setRootViewContainerRef(viewContainerRef) {
this.rootViewContainer = viewContainerRef;
}
addUndoElement() {
const factory = this.factoryResolver.resolveComponentFactory(UndoComponent);
const component = factory.create(this.rootViewContainer);
// this.rootViewContainer.insert(component.hostView);
this.appRef.attachView(component.hostView);
const domElem = (component.hostView as EmbeddedViewRef<any>)
.rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
}
Now, in the UndoComponent HTML I created a link to undo the action, named restoreItem. I would like to use another service named ListService to get some data again.
#Injectable()
export class UndoComponent implements OnInit {
constructor(private listService: ListService) {
}
restoreItem() {
this.currentList = this.listService.getSelectedList();
console.log(this.currentList); // null
}
}
It seems I cannot reference to the ListService (or any other service) from this dynamically added component to the DOM. It returns null. Any ideas how I can access a service from a dynamically added Component? Thanks so much for any directions!
Edit: added Listservice stub code for clarification
#Injectable({
providedIn: 'root'
})
export class ListService {
lists: List[];
list: List[];
currentList: List;
constructor(private http: HttpClient) { }
setSelectedList(list: List): void {
this.currentList = list;
}
getSelectedList(): List {
return this.currentList;
}
private handleError(error: HttpErrorResponse) {
console.log(error);
return throwError('Error! something went wrong.');
}
}

Are you setting the value of currentList in ListService in anyway.
setSelectedList in ListService is never called which is being used to set value of currentList. So currentList remains null.

Related

Confusing while Passing data between the components

I am new in angular 6, I am creating the project using angular 6. I am coming to the problem while sharing the data.
Here is my code:
1) Component Sidebar:
selectedCategory(type:any) {
this.loginService.categoryType = type; // need to pass this data
}
2) List Comp:
export class ListPostsComponent implements OnInit {
ngOnInit() {
// here I need the data
}
}
3) Service:
export class LoginService {
categoryType:any;
}
In your service make categoryType a Subject and call the next() when you need to pass data to another component:
#Injectable({
providedIn: 'root',
})
export class LoginService {
private categoryType: Subject<any> = new Subject<any>();
public categoryType$ = this.categoryType.asObservable();
public sendData(data: any){
this.categoryType.next(data);
}
}
Now in your Component Sidebar, you need to inject the service LoginService and call the sendData method:
constructor(private loginService: LoginService ){ }
selectedCategory(type:any) {
this.loginService.sendData(type);
}
Since a Subject is both an Observer and an Observable you can subscribe to the Subject and listen for changes in the component you wish to receive the data:
export class ListPostsComponent implements OnInit {
constructor(private loginService: LoginService ){ }
ngOnInit() {
this.loginService.categoryType$.subscribe((data) => {
//use your data here
});
}
}
Here is a working example of the above solution in Stackblitz: https://stackblitz.com/edit/angular-2sld4k?file=src%2Fapp%2Floginservice.service.ts

Angular4 - let multiple unrelated components notify each other of the problem of updating data, and whether there is a cleaner coding method?

I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!

angular 4+ assign #Input for ngComponentOutlet dynamically created component

In Angular 4 to dynamically create a component you can use ngComponentOutlet directive: https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html
something like this:
Dynamic component
#Component({
selector: 'dynamic-component',
template: `
Dynamic component
`
})
export class DynamicComponent {
#Input() info: any;
}
App
#Component({
selector: 'my-app',
template: `
App<br>
<ng-container *ngComponentOutlet="component"></ng-container>
`
})
export class AppComponent {
this.component=DynamicComponent;
}
How do I pass #Input() info: any; information in this template <ng-container *ngComponentOutlet="component"></ng-container> ?
Such a feature was discussed in the pull request for ngComponentOutlet but was dropped for now.
Even the componentRef shown currently in https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html is not public and therefore not available https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L78
I'd suggest you create your own directive derived from https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L72
and assign values to inputs like shown in Angular 2 dynamic tabs with user-click chosen components
this.compRef.instance.someProperty = 'someValue';
With the help of the post of #Günter Zöchbauer I solved a similar problem this way - I hope you can adapt it somehow.
First I defined some interfaces:
// all dynamically loaded components should implement this guy
export interface IDynamicComponent { Context: object; }
// data from parent to dynLoadedComponent
export interface IDynamicComponentData {
component: any;
context?: object;
caller?: any;
}
then I implemented them inside of the dynamically loaded component
dynamicLoadedComponentA.ts
// ...
export class DynamicLoadedComponentA implements IDynamicComponent {
// ...
// data from parent
public Context: object;
// ...
After that I built a new component which is responsible for the magic. Important here is that I had to register all dyn. loaded components as entryComponents.
dynamic.component.ts
#Component({
selector: 'ngc-dynamic-component',
template: ´<ng-template #dynamicContainer></ng-template>´,
entryComponents: [ DynamicLoadedComponentA ]
})
export class DynamicComponent implements OnInit, OnDestroy, OnChanges {
#ViewChild('dynamicContainer', { read: ViewContainerRef }) public dynamicContainer: ViewContainerRef;
#Input() public componentData: IDynamicComponentData;
private componentRef: ComponentRef<any>;
private componentInstance: IDynamicComponent;
constructor(private resolver: ComponentFactoryResolver) { }
public ngOnInit() {
this.createComponent();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['componentData']) {
this.createComponent();
}
}
public ngOnDestroy() {
if (this.componentInstance) {
this.componentInstance = null;
}
if (this.componentRef) {
this.componentRef.destroy();
}
}
private createComponent() {
this.dynamicContainer.clear();
if (this.componentData && this.componentData.component) {
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(this.componentData.component);
this.componentRef = this.dynamicContainer.createComponent(factory);
this.componentInstance = this.componentRef.instance as IDynamicComponent;
// fill context data
Object.assign(this.componentInstance.Context, this.componentData.context || {});
// register output events
// this.componentRef.instance.outputTrigger.subscribe(event => console.log(event));
}
}
}
here the usage of this shiny new stuff:
app.html
<!-- [...] -->
<div>
<ngc-dynamic-component [componentData]="_settingsData"></ngc-dynamic-component>
</div>
<!-- [...] -->
app.ts
// ...
private _settingsData: IDynamicComponent = {
component: DynamicLoadedComponentA,
context: { SomeValue: 42 },
caller: this
};
// ...
I think for now you can use
https://www.npmjs.com/package/ng-dynamic-component
It is made specifically for this issue

typescript in angular2, how to access class variables through this

Given this code, how can I access to the object "sessions"? it fails due to "this" being null:
/// <reference path="chrome/chrome-app.d.ts" />
import { Component, Input, OnInit } from '#angular/core';
import { AppService } from './app.service';
#Component({
selector: 'tabs',
templateUrl: './templates/app.html',
providers: [ AppService ]
})
export class AppComponent implements OnInit {
public sessions : Object;
constructor( private appService : AppService ) {}
getBookmarkLists() {
console.log(this.sessions) // it gives undefined
this.sessions['test'] = 'yea'; // it fails
this.appService.getBookmarks().then(function(bookmarks : any) {
console.log(this.sessions) // it fails
});
}
ngOnInit() {
this.getBookmarkLists();
}
}
What I would expect is to be able to access to the variable and populate it.
You didn't initialized this Sessions object anywhere, should be as far I know:
export class AppComponent implements OnInit {
public sessions: Session[] = []; // You forgot here to initialize it
constructor( private appService : AppService ) {}
getBookmarkLists() {
console.log(this.sessions) // no it shouldn't give undefined
this.sessions['test'] = 'yea'; // and this shouldn't fail
this.appService.getBookmarks().then((bookmarks : any) => {
// this should be an arrow function or function bound to use it
// otherwise this will point to the function itself.
console.log(this.sessions) // it shouldn't fail
});
}
ngOnInit() {
this.getBookmarkLists();
}
}
with the sessions = []; being the crucial part.
So it's not only an issue of this which references the class instance in methods as it should.
The callback passed to the then should be an arrow function not a classic function, to keep the this reference to the class instance.

Getting dependency from Injector manually inside a directive

I am trying to create a generic directive which will take a class type for rule validation and according to the rule in the class the directive will either show or hide an element.
This is my attempt so far.
PLUNKER Demo
myIf-Directive.ts
#Directive({
selector: '[myIf]'
})
export class MyIfDirective {
constructor(private _viewContainer: ViewContainerRef,
private _template: TemplateRef<Object>)
{ }
#Input() set myIf(rule: string) {
//rule class type will come as string
//how can I use string token to get dependency from injector?
//currently harcoded
//will the injector create new instance or pass on instance from parent?
let injector = ReflectiveInjector.resolveAndCreate([AdminOnly]);
let adminOnly : IRule = injector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
show ? this.showItem() : this.hideItem();
}
private showItem() {
this._viewContainer.createEmbeddedView(this._template);
}
private hideItem() {
this._viewContainer.clear();
}
}
app-component.ts
#Component({
selector: 'my-app',
template: `
<div *myIf="'AdminOnly'">
<h2>Hello {{name}}</h2>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
But I am stuck in 2 places:
I keep getting the error No Provider for AuthService
I do not know how I can get the dependency from Injector using class name as string rather than the type
Any suggestion whether this is the right way to do it or where I am going wrong is highly appreciated.
You need to pass the parent injector like
export class MyIfDirective {
constructor(private injector:Injector, private _viewContainer: ViewContainerRef,
private _template: TemplateRef<Object>)
{ }
#Input() set myIf(rule: string) {
let resolvedProviders = ReflectiveInjector.resolve([AdminOnly]);
let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);
let adminOnly : IRule = childInjector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
show ? this.showItem() : this.hideItem();
}
private showItem() {
this._viewContainer.createEmbeddedView(this._template);
}
private hideItem() {
this._viewContainer.clear();
}
}
See also Inject service with ReflectiveInjector without specifying all classes in the dependency tree
Just update for Angular version 10+:
From your service:
#Injectable({
providedIn: 'any'
})
export class AdminOnly { ... }
In your directive or a pure function, ...:
import { Injector } from '#angular/core';
...
const injector: Injector = Injector.create({
providers: [{provide: AdminOnly, deps: []}]
});
const adminOnly: AdminOnly = injector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
...
See more

Categories

Resources