I have some use cases for inheritance and decoration (as in Decorator pattern) of components and directives in Angular 2.
The example is component with basic template that doesn't suit the case, to the point when it easier to define a new template instead of modifying the DOM of existing one programmatically. The rest of component metadata should be inherited.
Basically it is
export const BASE_SOME_COMPONENT_METADATA = { ... };
#Component(BASE_SOME_COMPONENT_METADATA);
export class BaseSomeComponent { ... }
...
import { BaseSomeComponent, BASE_SOME_COMPONENT_METADATA } from '...';
#Component(Object.assign({}, BASE_SOME_COMPONENT_METADATA, { template: '...' });
export class SomeComponent extends BaseSomeComponent {}
And more complicated case is
#Component({ ... });
export class ThirdPartyComponent {
#Input() ...;
#Input() ...;
#Input() ...;
...
}
...
import { ThirdPartyComponent as BaseThirdPartyComponent } from '...';
#Component({
// may modify or replace any of the original properties
template: ...,
styles: ...
...
});
export class ThirdPartyComponent extends BaseThirdPartyComponent {}
Notice that ThirdPartyComponent has numerous inputs. There may be cases when it is beneficial to modify a component internally instead of wrapping it and modifying its behaviour from the outside. A wrapper component that enumerates them all in template and transfers them to ThirdPartyComponent may be WET and dirty:
<third-party inputs="inputs" that="that" should="should" be="be" enumerated="enumerated">
Extra layout elements that are introduced by wrapper components may be prohibited in some cases.
ThirdPartyComponent may be core component (a button) that is used by other third-party components. Then they should be affected too, so it may be necessary to 'decorate the decorator' all over the injector, not just extend it.
In Angular 1.x thirdPartyDirective is a service that gives full access to component DDOs, so they could be decorated, extended, deep fried, etc. What are direct counterparts to this approach in Angular 2? If this breaks some rules and voids the warranty, it's ok.
How can a component/directive that doesn't export its metadata be extended?
How can the metadata of existing component be modified?
Your inputs will be inherited from the parent class automatically. Regarding properties of the Component decorator itself, there is no native way to do that in Angular2. The Angular2 team won't provide support for this: https://github.com/angular/angular/issues/7968#issuecomment-219865739.
If you really want something like that, you need to implement with a custom decorator that updates annotations...
This article could interest you:
https://medium.com/#ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7#.8r5fuys6l
Related
I have a typical Glimmer "base" component:
import Component from '#glimmer/component';
export default class BaseComponent extends Component { ... }
It has a template like normally, but the actual implementations of that component are child componenents, that override some of the template getters and parameters so that it works with various different data types.
export default class TypeAComponent extends BaseComponent { ... }
export default class TypeBComponent extends BaseComponent { ... }
etc.
My question is: How do I specify that all the child components should use the parent class template, so I don't have to duplicate the same fairly complex HTML for all child components? Visually the components are supposed to look identical so any changes would have to be replicated across all child component types. Therefore multiple duplicated templates isn't ideal.
In Ember Classic components there was layout and layoutName properties so I could just do:
layoutName: 'components/component-name'
in the base component and all child components did automatically use the defined template.
Now that I'm migrating to Glimmer components I can't seem to figure out how to do this. I have tried:
layout property
layoutName property
template property
Using the child components without a template in hope that they would automatically fall back to the parent class template.
Only thing that seems to work is creating Application Initializer like this:
app.register('template:components/child1-component', app.lookup('template:components/base-component'));
app.register('template:components/child2-component', app.lookup('template:components/base-component'));
But that feels so hacky that I decided to ask here first if there is a proper way to do this that I have missed?
How to specify template for Glimmer Component?
tl;dr: you should avoid this.
There are two answers to two, more specific, questions:
What is the recommended way to manage complex components with shared behaviors?
Typically, you'll want to re-work your code to use either composition or a service.
Composition
<BaseBehaviors as |myAPI|>
<TypeAComponent #foo={{myAPI.foo}} #bar={{myAPI.bar}} />
<BaseBehaviors>
Where BaseBehaviors' template is:
{{yield (hash
foo=whateverThisDoes
bar=whateverThisBarDoes
)}}
Service
export default class TypeAComponent extends Component {
#service base;
}
and the service can be created with
ember g service base
then, instead of accessing everything on this, you'd access everything on this.base
Ignoring all advice, how do I technically do the thing?
Co-located components (js + hbs as separate files), are combined into one file at build time, which works like this:
// app/components/my-component.js
import Component from '#glimmer/component';
export default class MyComponent extends Cmoponent {
// ..
}
{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
The above js and hbs file becomes the following single file:
// app/components/my-component.js
import Component from '#glimmer/component';
import { hbs } from 'ember-cli-htmlbars';
import { setComponentTemplate } from '#ember/component';
export default class MyComponent extends Cmoponent {
// ..
}
setComponentTemplate(hbs`{{! app/components/my-component.hbs }}
<div>{{yield}}</div>
`, MyComponent);
So this means you can use setComponentTemplate anywhere at the module level, to assign a template to a backing class.
Why is this not recommended over the other approaches?
All of this is a main reason the layout and related properties did not make it in to Octane.
Formally supported Component inheritance results in people getting "clever"
this in of itself, isn't so much of a problem, as it is what people can do with the tool. Bad inheritance is the main reason folks don't like classes at all -- and why functional programming has been on the rise -- which is warranted! Definitely a bit of an over-correction, as the best code uses both FP and OP, when appropriate, and doesn't get dogmatic about this stuff.
Component Inheritance is harder to debug
Things that are a "Foo" but are a subclass of "Foo" may not actually work like "Foo", because in JS, there aren't strict rules around inheritance, so you can override getters, methods, etc, and have them provide entirely different behavior.
This confuses someone who is looking to debug your code.
Additionally, as someone is trying to do that debugging, they'll need to have more files open to try to under stand the bigger picture, which increases cognitive load.
Component inheritance allows folks to ignore boundaries
This makes unit testing harder -- components are only tested as "black boxes" / something you can't see in to -- you test the inputs and outputs, and nothing in between.
If you do want to test the in-between, you need to extract either regular functions or a service (or more rendering tests on the specific things).
I would say this is the classic case for a composition, where TypeAComponent and TypeBComponent use the BaseComponent.
So you have your BaseComponent with all the HTML, that basically is your template. I think its important here to think a bit more of Components also as possible Templates, not only full Components. So lets call this the TemplateComponent.
So you have your TemplateComponent which could also be a template-only component. Then you have as template for TypeAComponent and TypeBComponent:
<TemplateComponent
#type={{#title}}
#title={{#title}}
#onchange={{#onchange}}
#propertyThatIsChanged={{this.propertyThatIsChanged}}
...
/>
this allows you to have a getter propertyThatIsChanged to overwrite pieces. Common behaviour can also be placed on the TemplateComponent, or, if its common code, maybe on a BaseCodeComponent, that only contains shared code, while I would rather not do this.
For areas you want to replace this also opens the possibility to use Blocks.
The TemplateComponent, for example, could use has-block to check if a :title exists, use this block then ({{yield to="default"}}), and if not just use {{#title}}.
So, to the only obvious downside of this: you have to proxy all params. This seems ugly at first, but generally I think its better for components not to have too many arguments. At some point an options or data argument could be better, also because it can be built with js when necessary. It should also be mentioned that there is an open RFC that would address this issue. With the upcoming SFCs, I think this is the much more future-proof solution overall.
I created a new React Native project using react-native init and in the generated template, the main component class looks like this:
export default class App extends Component<{}> {
...
}
I don't really understand what the <{}> part means. I've never seen this before and all the examples seem to omit it. Just curious as to what its purpose is and if it's necessary.
When you are using typescript, you have to specify the type of values to be expected. This allows detecting mismatching properties during compile time and reduces the amount of errors.
So when you do Component<{}>, {} is the type for Props, your component will receive.
This is how React's Component class looks like:
If you notice, the type is <P, S>, which stands for <Props, State>.
There is another interface called ComponentClass that has a signature <P>,
which initializes a new component internally with state as any. This interface is used in ReactElement's type:
So all in all, you are defining a Component which accepts no props and but can have state of any type. This is usually done when you are not sure about you component's interactions.
Ideally a component should look like this:
interface IComponentState {
...
}
interface IComponentProps {
...
}
export class MyComponent<IComponentProps, IComponentState> extends React.Component {
...
}
This enforces consumer to pass any necessary properties and enforces you to have proper value of state.
This is either Typescript of Flow. You usually don't describe props as propTypes, but rather as interface or type. Then the type is passed to React.Component as a generic.
Type of props would be the passed type plus { children?: ReactNode }
Actually there are two generic arguments, second for State
Very useful and convenient stuff.
https://www.typescriptlang.org/docs/handbook/generics.html
https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
These are flow type annotations. See https://flow.org/
You'll notice that there's a #flow comment at the top of the file, and a .flowconfig file in the root of the project.
Using flow here is optional, but it can help you catch bugs.
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.
I have added a class like this.
export class Settings{
public data: string = "blopp";
}
When I try to access the data, it seems that the field I'm trying to assign that value to sees the class Settings itself but it doesn't recognize the data thingy.
How do I redesign the class to provide settings for other components?
I've read about #Output decorator but since I won't be binding to the values, it seems not the correct approach. I've made sure that the class is imported and recognized withing the component that's supposed to consume it. I've also tried the corresponding exposure but using a function in the class with settings - the same, failed result.
If you're using angular-cli and going to store in this class environment specific settings - you already have built in support for this.
Put the setting into environment.ts. For example:
export const environment = {
production: false,
someSetting: 'foo',
};
Then it can be consumed from anywhere within the app:
import { environment } from "../environments/environment"; //fix according to your project structure
#Injectable()
export class SampleService {
private foo = environment.someSetting;
}
Here you can find more info on how to add more environments and build you project with specific environment settings.
Your best bet for storing global settings is to use a service. The code for that looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class DataService {
serviceData: string;
}
I have a blog post about this here: https://blogs.msmvps.com/deborahk/build-a-simple-angular-service-to-share-data/
And a plunker here: https://plnkr.co/edit/KT4JLmpcwGBM2xdZQeI9?p=preview
Also, the #Output decorator is only for communication between a child component and a parent component where the child is nested within the parent.
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.