5

I am making a 'skip to content' link. The link is in app.component and the content is in login.component, along with the target.

I've tried several methods, none of which worked out. So now I'm trying emitter/listener:

app.component (emitter):

import { Component, Output, EventEmitter} from '@angular/core';

export class AppComponent {

    @Output() skipToCtrl: EventEmitter<any> = new EventEmitter();

    skipLink() {
        this.skipToCtrl.emit();
    }
}

<a href="#content-start" (click)="skipLink()">Skip to main content</a>

login.component (listener):

<input type="text" #firstControl name="username" />

Don't know how login can subscribe to the event from within login. I keep finding articles that say not to do it How to subscribe to an event on a service in Angular2? but no articles that describe how to do it.

DaveC426913
  • 2,012
  • 6
  • 35
  • 63
  • I think you're confusing events from Angular 1. – Reactgular Aug 01 '17 at 16:30
  • 1
    Well then it's Angular 2 articles that are confused. I'd be happy to be educated. I have yet to find an article that shows how a button in one component can trigger an effect in another component. – DaveC426913 Aug 01 '17 at 16:34

4 Answers4

6

Event emitters are used to add output into the templates. Using the round brackets () for binding to the event.

When you create a component:

@Component({...})
public export FooComponent {
   @Output() name: EventEmitter = new EventEmitter<string>();
}

The above component now has an attribute that can be used with the () brackets in another template.

In another template:

 <foo (name)="// code here //"></foo>

When the event is emitted (by calling this.name.emit("value")) the above "code here" expression is executed by Angular. The emit function takes a single argument which is passed to the expression as a $event variable.

To have another component receive these events is to use the expression in that other component's template.

Let's create a Bar component

@Component({...})
export class BarComponent {
    public setName(value:string) {
        console.log(value); // will output "chicken"
    }
}

The above component has this template:

<foo (name)="setName($event)"></foo>

Now in the Foo component we emit a value.

@Component({...})
public export FooComponent implements OnInit {
   @Output() name: EventEmitter = new EventEmitter<string>();

   public ngOnInit() {
       this.name.emit("chicken");
   }
}

This creates one-way communication from the Foo component to the Bar component via the template expressions.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • Foo is its own component. I can't place it inside Bar. If anything, bar (login) is actually a subset of foo (app). – DaveC426913 Aug 01 '17 at 16:46
  • Didn't you say on my answer that they weren't in a parent-child relationship? Seems they are, and you've got it backwards like I said in my answer. You'll never get a green square on this question until you grasp how this fits in. You don't need eventEmitters at all. – chairmanmow Aug 01 '17 at 18:00
  • Correct, in Angular 1 you could broadcast events up or down the scope tree. That feature does not exist in Angular 2. Events are purely a template specific feature. I don't really understand what problem you're trying to solve in your question. Why can't the link just use `[routerLink]` to go to the content? – Reactgular Aug 01 '17 at 18:07
0

in the login.component, u can write

<input type="text" #firstControl (skipToCtrl)="callsomemethod($event)" name="username" />
Janier
  • 3,982
  • 9
  • 43
  • 96
0

You're putting the event emitter in the wrong component for one. Event emitter is for child -> parent communication. So you'd probably want to put that in your login-component, if you want it to do anything.

login.component.ts

 export class LoginComponent {
     @Output() skipToCtrl: EventEmitter<any> = new EventEmitter();
     emitSkipEvent(): void {
         this.skipToCtrl.emit(true);
     }
  }

app.component.html

 <login-component (skipToCtrl)="skipLink()">

There's a problem with my solution though, that event will never get emitted because I never call loginComponent.emitSkipEvent, I don't think EventEmitter behavior is what you're looking for as you want to pass data from the parent to the loginComponent. You probably want to put an @Input parameter on your login component and implement onChanges to handle the behavior. BETTER ANSWER :

 import {OnChanges} from '@angular/core'
 export class LoginComponent implements OnChanges {
     @Input() skipToCtrl: boolean;
     ngOnChanges(){
         if(skipToCtrl){
             // handle login component behavior changes here. Looks like you want to focus on the input
             document.querySelector('input').focus();
         }
      }
}

Then in your app.component.ts you'd create a boolean variable to toggle

 linkClicked:boolean;

Then in your app.component.html you can change the click behavior and bind the linkClicked to your login component's skipToCtrl boolean :

<a href="#content-start" (click)="linkClicked = true">Skip to main content</a>
<login-component [skipToCtrl]='linkClicked'>

Now when you click the link it will get passed down to the login component, and the OnChanges logic will fire.

chairmanmow
  • 649
  • 7
  • 20
  • They're just two components. One has a link 'skip to content', the other has some content with a control I want to bring in to focus. That's all. There's no parent-child relationship. – DaveC426913 Aug 01 '17 at 17:04
  • You sure about that? Do you have two separate applications for your App Component and Login Component? I doubt it and if you do, put how you're rendering the components in your question. Most likely they have some relationship. Usually your app component is the highest parent of all... but if you're having a hard time reaching that conclusion there's not much I can do to help you. – chairmanmow Aug 01 '17 at 17:22
  • Not two separate applications, just two components. I don't actually know the relationship between them because I didn't build the structure. I do know that the login HTML content is nested within the app HTML wrapper. I don;t know if there are other layers in between. – DaveC426913 Aug 01 '17 at 19:05
  • I tried your method with 'implements OnChanges' but it complains that login incorrectly implements the interface. When I tried to fix that it just tells me the next layer down is incorrectly implementing the interface. – DaveC426913 Aug 01 '17 at 19:10
  • I just noticed I forgot a closing brace in the on changes function (fixed it in answer), although your IDE probably warned you on that and presumably you fixed it. What is complaining and what's it telling you? Do you have the implements statement? Does it complain if you change ngOnChanges() to a blank function? – chairmanmow Aug 01 '17 at 19:59
-1

100% Working!!!! It's basically a patch using Javascript:

Make a hidden input element in which component(app.component in your case) you need to call

<input type="hidden" id="getdata" (click)="Mydata();">

Here Mydata will be function in app.component

And call/Click this from other component(Login) using javascript:

document.getElementById('getdata').click();
afsarkhan10182
  • 2,172
  • 1
  • 13
  • 17