I built an Angular 2 component and I have a doubt: is there a standard that defines if the events triggered inside the component should be broadcasted (vie #Output, for example) or the component can handle them itself?
Let me show some code:
the component may be called content-detail and the template is:
<ion-card (click)="navigate()">
<h3>{{ rawcontent.name }}</h3>
</ion-card>
the .ts has a navigate() method, and inside it I call some route (in my case it's an Ionic 2 app, which uses NavController, but it could be anything else).
#Component({
selector: 'content-detail',
templateUrl: 'content-detail.html'
})
export class ContentDataComponent {
navigate() {
//do seomething, like navigate to another page
}
}
Is this a good practice?
Or the best is that the component use EventEmitter to emit it to it's parent (as explained here, for example)
I can't find any documentation about it. What I guess is that it would be easy to test the component if it simply emit the event to the parent instead of navigate (or do anything else), because I wouldn't need to inject some router or navcontroller.
Could anyone help?
(someEvent)="someMethod()" is a common way to handle events in templates
#Output() xxx; is for communicating from child to parent
for all other scenarios use a shared service
for communication between siblings there is a shortcut using template variables
<my-a-comp #a></my-a-comp>
<my-b-comp (someEvent)="a.someMethod()"
See also https://angular.io/docs/ts/latest/cookbook/component-communication.html
you can use host listeners like :
#Component({
selector: 'content-detail',
templateUrl: 'content-detail.html'
})
export class ContentDataComponent {
#HostListener('click') onClick() {
// do navigation ...
}
}
Related
Background
I am using inheritance in Angular, the major issue was that I initially had to pass services from parent to child, something like
// Parent
export class ParentComponent {
protected myService: MyService;
constructor(private myService: MyService) {
}
}
// Child
export class ChildComponent extends ParentComponent {
constructor(private myService: MyService) {
super(myService);
}
}
To avoid this I found a different approach using Injector from #angular/core. In this approach we follow the below steps
Create a service
app-injector-service.ts
import { Injector } from '#angular/core';
export class AppInjectorService {
private static injector: Injector;
static setInjector(injector: Injector) {
AppInjectorService.injector = injector;
}
static getInjector(): Injector {
return AppInjectorService.injector;
}
}
in the main.ts file
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then((ref) => {
AppInjectorService.setInjector(ref.injector);
})
.catch((err) => console.error(err));
and parent class file would look like
export abstract class ParentComponent {
protected myService: MyService;
constructor() {
const injector = AppInjectorService.getInjector();
this.myService = injector.get(MyService);
}
}
With this approach, no need to pass service to the super call! Amazing
My Issue
The approach above worked very well untill I needed to extract the route param from the activated route so I followed the same structure. To my surprise It is not working. I tested with manually injecting the ActivatedRoute in the child class and the parameter exists. I dont understand why the parameter is not available in the class when we inject ActivatedRoute using the AppInjector. Ofcourse I can go back to injecting it in the child class then passing it to the super function so that it can be accessed by the Parent class but this is what I was trying to avoid...
Below is a stackblitz demo showing the issue, click on the route and you will notice that we get back null from the ActivatedRoute injected in the parent class but a value in the ActivatedRoute in the child class. Basically I am trying to find if there is anything am doing wrong or is there something am missing while using the Injector
Demo
There are two problems in your code: using inheritance for components and using a non-singleton service as a singleton service.
you are setting up injector in your service AppInjectorService.setInjector(ref.injector); after app is bootstrapped but what happens when one of your component is directly used in your root component(AppComponent) and that component inherited that parent comp class, as you are using injector inside parent to resolve all required dependencies, it won't work because you do not have access to injector by that time, setting up injector in constructor using that static method is a bad idea because constructors of components can be invoked even though they are not being rendered yet and they also may be called during some bootstrapping process so you see the problem? your parent component's constructor is called before AppInjectorService.setInjector(ref.injector);. This may have worked if all the components inheriting your parent component were initialized lazily/dynamically so that app is bootstraped and your injector is setup in your service before using it.
Inheritance is a bitch, especially with components it'll bite you in the ass sooner or later, use DI to reduce repetitive code, in your case you can create a service that can contain all the required dependencies and inject it into whatever component you want to, the lines of code to inherit a component vs injecting a service are typically the same though this comes with the limitation that you can't mix and match services of different scopes(Singletons/Module level/Component level) but that is expected to be honest.
Second problem is using a contextual service out of context, ActivatedRoute is meant to be injected into components that are part of route config, let's takte example of this config:
{ path: ":id", component: ChildComponent }
if you inject ActivatedRoute into ChildComponent, it'll work as expected but if you inject it into AppComponent, it won't work it's not gonna throw any errors but when you'll try to listen to changes in paramMap, you won't get it because it AppComponent is not connected to that specific path. If you want to globally listen to changes in route then prefer Router.
One thing to note here, when you pass that route config to RouterModule, it'll now be responsible for creating instances of components based on route path and it can then inject an injector with a newly created dependency(like ActivatedRoute) into that component so injecting dependencies like those anywhere may not work.
I want to add protection to components.
If a user doesn't have permission to see this component it wont be rendered.
I have tried putting my if in the constructor and return false but it still renders.
I also added the if to the template itself but then I didn't see the view but the component is still alive and it adds complexity to the code as I need to maintain several places of the same if.
Is there a way to tell the component to not render at all ?
constructor( private userService: UserService) {
if (this.userService.isAllowed("see_trade_groups") === false) {
return;
}
}
For that purpose you can see CanActivate. Put it on the component route and it will do the job you want.
In that you can write a logic, based on which the route will be navigated or not.
Component compilation lifecycle is handled by Angular compiler, so a component is unable to control its own lifecycle, and it should be controlled from the outside.
A common way to handle this is to use router. The lifecycle in route components differs from regular components because it's handled by router; they are attached to <router-outlet> component. It's possible to prevent compilation in route components but not in regular components.
Otherwise this should be handled with a directive. ngIf is built-in way to prevent the compilation of regular components.
So it becomes
<foo *ngIf="userService.isAllowed('see_trade_groups')"></foo>
Since this requires to inject userService to parent component every time it's needed, this will result in a lot of boilerplate code. An appropriate solution is to create a directive that behaves similarly to ngIf - or extend it to provide desired functionality:
import {Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '#angular/core';
import {NgIf, NgIfContext} from '#angular/common';
...
#Directive({
selector: '[role]'
})
class Role extends NgIf {
#Input() role: string;
constructor(
viewContainer: ViewContainerRef,
templateRef: TemplateRef<NgIfContext>
public userService: User
) {
super(viewContainer, templateRef);
}
ngOnChanges({ role }: SimpleChanges) {
this.ngIf = this.userService.isAllowed(role);
// can also subscribe to some observable to add/remove a component any time
}
}
Which is used like:
<foo *role="'see_trade_groups'"></foo>
Notice that Role is * structural directive. This allows it to control the compilation of an element it was specified on, similarly to how ngIf and ngFor do.
Currently I am trying to test a child component which is accepting an input from the host component, and used within the ngOnInit life cycle hook like the code below.
#Component({
selector: 'my-child-component',
template: '<div></div>'
})
class ChildComponent implements OnInit {
#Input() myValue: MyObject;
transformedValue: SomeOtherObject;
ngOnInit():void {
// Do some data transform requiring myValue
transformedValue = ...;
}
}
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class HostComponent {
someValue: MyObject = new MyObject(); // how it is initialized it is not important.
}
How should the ChildComponent be tested in this case where myValue needs the to be present upon creation while being able to have access to ChildComponent.transformedValue for assertion.
I tried creating the ChildComponent using the Angular TestBed class like this
componentFixture = testBed.createComponent(LoginFormComponent)
however the ngOnInit would have already been called up to the point where I call
fixture.componentInstance.myValue = someValue;
I also tried creating a fixture of the HostComponent, and while that works, I got stuck at getting access to the ChildComponent instance that was created, which i require to perform assertions on the ChildComponent.transformedValue field.
Help is greatly appreciated!
Thanks a lot!
Angular offers the ability to inject children components to their parent components using the #ViewChild() decorator. See https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
By updating the TestHostcomponent (that is written within the .spec.ts file) to the following
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class TestHostComponent {
#ViewChild(MyChildComponent)
childComponent: MyChildComponent;
}
it exposes its child component instance ( and its variables ), making the assertion of the 'transformedValue' possible, as per below.
componentFixture = testBed.createComponent(TestHostComponent)
expect(componentFixture.componentInstance.childComponent.transformedValue).toEqual(...someValue);
Consider this plunker
import {Component, OnInit, Input, OnChanges, DoCheck, ChangeDetectionStrategy, EventEmitter} from 'angular2/core'
#Component({
selector: 'child11',
template: `
<button (click)="change_obj()">Button in Child11</button>
<div>This is child11: {{my_obj11['name']}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Child11 {
#Input()
my_obj11: Object;
change_obj(){
this.my_obj11['name'] = 'some other name';
}
}
#Component({
selector: 'child1',
template: `
<child11 [my_obj11]="my_obj1"></child11>
<div>This is child1: {{my_obj1['name']}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
directives: [Child11]
})
export class Child1 {
#Input()
my_obj1: Object;
}
#Component({
selector: 'parent',
template: `
<div>
<child1 [my_obj1]="my_obj" ></child1>
This is my_obj in parent: {{my_obj['name']}}
</div>
`,
directives: [Child1]
})
export class App {
my_obj: Object = {'name': 'name1'};
}
Here is the relationship between components
Parent
|
Child1
|
Child11
We note that Child1 have changeDetection: ChangeDetectionStrategy.OnPush
The above code is quite simple, parent send an object to child1 which sends the exact same object to child11.
child11 then update the primitive of the object.
We see that both parent and child1's object is updated even though child1 have changeDetection: ChangeDetectionStrategy.OnPush
I am guessing changeDetection: ChangeDetectionStrategy.OnPush only works one way: top to bottom.
Is that true?
If so is there a reason for this?
According to Savkin's blog post (well, it is buried in a comment to #vivainio), with OnPush, Angular will only check the component for changes (i.e., check the template bindings) when
any of its input properties changes
it fires an event (e.g., a button click)
an observable fires an event [Note that this is not entirely correct. The observable needs to use | async in the view/template in order for change detection to run. See comments on this answer for more info.]
If any of those conditions are met, Angular will also "mark" all components up the tree to the root component as needing change detection. It will then run change detection. All of the ancestor components, even if they are configured for the OnPush strategy will be checked for changes, since they are currently "marked".
This explains why the views for the Parent/App and Child1 components get updated as a result of an event firing in component Child11.
This "mark up the tree" functionality is by design, and for the exact reason/scenario you show in your sample code – i.e., a component changes some application data, and ancestor components have data bindings for that same data in their views. How does Angular ensure the views will get updated? It has to "mark" all ancestors up to the root component so that when change detection runs, all of those components will be checked.
Note that this doesn't cover all scenarios though. Suppose that the changed application data is also present in a component view in some other, unrelated, "branch" of the component tree, and that branch uses OnPush. That component view will not get updated.
I know it has been a while, but i came across a similar problem. Hence i wanted to stress on the fact that OnPush does not always change the way parent and child component communication works. If the child component's #Input property is an object (reference type) then the only way OnChanges is triggered on the child is when the reference changes (new object is created) regardless of OnPush. OnPush makes more sense for template binding, requesting angular to do value equality or just compare references.
Please do correct me if i was wrong.
I have written two services in Angular 2. One of those is a basic, customised class of Http with some custom functionality in (it looks basic for now, but it will be expanding):
ServerComms.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class ServerComms {
private url = 'myservice.com/service/';
constructor (public http: Http) {
// do nothing
}
get(options) {
var req = http.get('https://' + options.name + url);
if (options.timout) {
req.timeout(options.timeout);
}
return req;
}
}
Another class, TicketService utilises this class above, and calls one of the methods in the service. This is defined below:
TicketService.ts
import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';
#Injectable()
export class TicketService {
constructor (private serverComms: ServerComms) {
// do nothing
}
getTickets() {
serverComms.get({
name: 'mycompany',
timeout: 15000
})
.subscribe(data => console.log(data));
}
}
However, I receive the following error whenever I try this:
"No provider for ServerComms! (App -> TicketService -> ServerComms)"
I do not understand why? Surely I do not need to inject every service that each other service relies upon? This can grow pretty tedious? This was achievable in Angular 1.x - how do I achieve the same in Angular 2?
Is this the right way to do it?
In short since injectors are defined at component level, the component that initiates the call services must see the corresponding providers. The first one (directly called) but also the other indirectly called (called by the previous service).
Let's take a sample. I have the following application:
Component AppComponent: the main component of my application that is provided when creating the Angular2 application in the bootstrap function
#Component({
selector: 'my-app',
template: `
<child></child>
`,
(...)
directives: [ ChildComponent ]
})
export class AppComponent {
}
Component ChildComponent: a sub component that will be used within the AppComponent component
#Component({
selector: 'child',
template: `
{{data | json}}<br/>
Get data
`,
(...)
})
export class ChildComponent {
constructor(service1:Service1) {
this.service1 = service1;
}
getData() {
this.data = this.service1.getData();
return false;
}
}
Two services, Service1 and Service2: Service1 is used by the ChildComponent and Service2 by Service1
#Injectable()
export class Service1 {
constructor(service2:Service2) {
this.service2 = service2;
}
getData() {
return this.service2.getData();
}
}
#Injectable()
export class Service2 {
getData() {
return [
{ message: 'message1' },
{ message: 'message2' }
];
}
}
Here is an overview of all these elements and there relations:
Application
|
AppComponent
|
ChildComponent
getData() --- Service1 --- Service2
In such application, we have three injectors:
The application injector that can be configured using the second parameter of the bootstrap function
The AppComponent injector that can be configured using the providers attribute of this component. It can "see" elements defined in the application injector. This means if a provider isn't found in this provider, it will be automatically look for into this parent injector. If not found in the latter, a "provider not found" error will be thrown.
The ChildComponent injector that will follow the same rules than the AppComponent one. To inject elements involved in the injection chain executed forr the component, providers will be looked for first in this injector, then in the AppComponent one and finally in the application one.
This means that when trying to inject the Service1 into the ChildComponent constructor, Angular2 will look into the ChildComponent injector, then into the AppComponent one and finally into the application one.
Since Service2 needs to be injected into Service1, the same resolution processing will be done: ChildComponent injector, AppComponent one and application one.
This means that both Service1 and Service2 can be specified at each level according to your needs using the providers attribute for components and the second parameter of the bootstrap function for the application injector.
This allows to share instances of dependencies for a set of elements:
If you define a provider at the application level, the correspoding created instance will be shared by the whole application (all components, all services, ...).
If you define a provider at a component level, the instance will be shared by the component itself, its sub components and all the "services" involved in the dependency chain.
So it's very powerful and you're free to organize as you want and for your needs.
Here is the corresponding plunkr so you can play with it: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.
This link from the Angular2 documentation could help you: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html.
Surely you do.
In Angular2, there are multiple injectors. Injectors are configured using the providers array of components. When a component has a providers array, an injector is created at that point in the tree. When components, directives, and services need to resolve their dependencies, they look up the injector tree to find them. So, we need to configure that tree with providers.
Conceptually, I like to think that there is an injector tree that overlays the component tree, but it is sparser than the component tree.
Again, conceptually, we have to configure this injector tree so that dependencies are "provided" at the appropriate places in the tree. Instead of creating a separate tree, Angular 2 reuses the component tree to do this. So even though it feels like we are configuring dependencies on the component tree, I like to think that I am configuring dependencies on the injector tree (which happens to overlay the component tree, so I have to use the components to configure it).
Clear as mud?
The reason Angular two has an injector tree is to allow for non-singleton services – i.e., the ability to create multiple instances of a particular service at different points in the injector tree. If you want Angular 1-like functionality (only singleton services), provide all of your services in your root component.
Architect your app, then go back and configure the injector tree (using components). That's how I like to think of it. If you reuse components in another project, it is very likely that the providers arrays will need to be changed, because the new project may require a different injector tree.
Well, i guess you should provide both services globally:
bootstrap(App, [service1, service2]);
or provide to component that uses them:
#Component({providers: [service1, service2]})
#Injectable decorator adds necessary metadata to track dependecies, but does not provide them.