I have two components that should be connected via Observable().
In the first i am declaring
#Injectable()
export class One{
private changeConfirmed = new Subject<boolean>();
changeConfirmed$ = this.changeConfirmed.asObservable();
public init$: EventEmitter<boolean>;
registerChangeConfirmed(category:boolean){
alert('sending')
this.changeConfirmed.next(category);
}
onMsg(){
this.registerChangeConfirmed(true);
}
}
onMsg is event bound to template
and in the second one
Import { One } from './pat/to/it'
#Component({
providers:[One]
})
export class two{
constructor( private childClass : One ){
this.childClass.changeConfirmed$.subscribe( x => {alert(x)})
}
}
However the event does not get emitted.
But when i emit event in class two instead of class one = i include
this.childClass.registerChangeConfirmed(true)
in class two the event gets triggered. Why isn't it working when i invoke it from class one?
Don't provide the service One on the component. This way each component instance will get its own One instance. If you provide it at a common parent (AppComponent) then they will get a shared instance.
Angular2 DI maintains an instance (singleton) per provider.
Related
I am writing a Javascript npm package. In my code I have a single class which I want to act like an angular service. Only one instance of that class should be created and needs to be shared wherever wanted in the project.
//this class object would be shared across project and only one object can be created. also, its implementation could change in future
export class SharedClass {
constructor(somethingImp) {
}
//more methods
}
export class ProjectClass1ThatNeedsSharedClassObj {
//it should get the required object
}
export class ProjectClass2ThatNeedsSharedClassObj {
//it should get the required object
}
How can I write a simple DI to achieve this functionality?
If you want enforce a single object through all methods of DI, you can use a static variable. This is the pattern I normally use
export class SearchProvider {
private static _default: SearchProvider
constructor() {
}
static get Default() {
return this._default || (this._default = new SearchProvider())
}
}
export class Consumer() {
private SearchProvider _provider = SearchProvider.Default;
}
1)Create that class as a service with #Injectable decorator
2)Import that class into class where you want to use it
3)create a instance of service class into that class
4)now you can access methods of service class using instance created in constructor
using this keyword & . operator
5)You have to write service class once & use it many times as you want
Happy coding
I'm passing a function as parameter from parent to child component. When click event is occurred, function of parent component trigger, but all the property of parent component is undefined. For example,
Parent Component
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(): Observable<any> {
// here this.notificationService is undefined
console.log( this.notificationService );
}
}
Parent html
<notification-menu [unread]= "unreadNotification"></notification-menu>
child Component
export class NotificationMenuComponent implements OnInit {
#Input() updateUnread: Function;
}
child html
<button type="button" class="icon-button" (click)="updateUnread()">
</button>
Now when I click on notification button, unreadNotification is triggered, but value of this.notificationService in console.log is undefined.
How can I solve this?
You should use #Input() to pass values from parent to child and #Output() to pass values from child to parent.
Child HTML:
<button type="button" class="icon-button" (click)="update()">
</button>
Child Component:
export class NotificationMenuComponent implements OnInit {
#Output() updateUnread = new EventEmitter<string>();
update() {
this.updateUnread.emit("I am working man!");
}
}
Parent HTML:
<notification-menu (updateUnread)= "unreadNotification($event)"></notification-menu>
Parent Component:
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(dataFromChild: string) {
console.log(dataFromChild);
}
}
The answer from #nimeresam is good advice - using an #Output is an idomatic way to achieve this.
It's worth noting though, that the reason that your original solution doesn't work is due to the way that javascript handles the this context.
Writing (click)="updateUnread()" is equivalent to saying this.updateUnread() with this being NotificationMenuComponent - as notificationService does not exist on NotificationMenuComponent you get the undefined error.
To have the context of the parent component used, you would need to bind the context to the updateUnread function before passing it into the child component.
This can be achieved either by converting the function to be an arrow functionn, or using Function.bind
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
It's normally a good idea to enable the typescript option for --noImplicitThis to help catch these errors (though unsure if it will detect it in this case)
You can use arrow function so that you can use parent component's information. You can try as like as given below.
updateUnreadNotification = () => {
// by using arrow function you can get notificationService information
console.log( this.notificationService );
}
Hope your problem will be solve by this.
<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
What am trying to achieve is I would like to emit a custom event in angular2 globally and have multiple components listen to it so not just the parent-child pattern
In my event source component, I have
export class EventSourceComponent{
#Output() testev = new EventEmitter();
onbtnclick(){
this.testev.emit("i have emitted an event")
}
}
Now I would like other components to get this event
export class CmpTwoComponent{
//here get the emitted event with data
}
How do I achieve the above?
You could use a shared service for that.
export class EventSourceComponent{
constructor(private sharedService : SharedService){}
onbtnclick(){
this.sharedService.testev.next("i have emitted an event")
}
}
export class CmpTwoComponent{
//here get the emitted event with data
constructor(sharedService : SharedService){
sharedService.testev.subscribe((event)=>{console.log(event)})
}
}
and then the sharedService would be
#Injectable()
export class SharedService{
public testev = new Subject()
}
Obviously, if you still need the Output so the parent component could be able to subscribe normally, you could add that too :
export class EventSourceComponent{
#Output() testev = new EventEmitter();
constructor(private sharedService : SharedService){}
onbtnclick(){
this.testev.emit("i have emitted an event")
this.sharedService.testev.next("i have emitted an event")
}
}
There is no pattern in Angular that allows to achieve what you ask for.
The best option that I can think of for you would be to create a custom service. Make some service that you inject into AppComponent (therefore having a single instance of the service). In the Service you can have whatever logic you want.
Consider the following snippet of Parent's template:
<div *ngFor= "let event of events" >
<event-thumbnail [theEvent] = 'event'></event-thumbnail>
</div>
Also event-thumbnail component definition is:
export class EventThumbnailComponent{
intoroduceYourself(){
console.log('I am X');
}
}
In Parent component class, I want to iterate over all generated event-thumbnail elements, access the component beneath each, and call introduceYourself function on single one of them.
You want to use the #ViewChildren() decorator to get a list of all instances of a specific component type within the view:
class ParentComponent implements AfterViewInit {
#ViewChildren(EventThumbnailComponent)
eventThumbnails: QueryList<EventThumbnailComponent>;
ngAfterViewInit(): void {
// Loop over your components and call the method on each one
this.eventThumbnails.forEach(component => component.introduceYourself());
// You can also subscribe to changes...
this.eventThumbnails.changes.subscribe(r => {
// Do something when the QueryList changes
});
}
}
The eventThumbnails property will be updated whenever an instance of this component is added to or removed from the view. Notice the eventThumbnails is not set until ngAfterViewInit.
See the docs here for more information:
https://angular.io/docs/ts/latest/api/core/index/ViewChildren-decorator.html
Your child component should have #Input() theEvent to get access to the event you are passing. Then you can use the following lifecycle hook:
ngOnInit(){
introduceYourself(){
console.log('I am X');
}
}