Angular directive cannot be used? - javascript

1) Created a new directive with angularCLI.
import {Directive, ElementRef, OnInit} from '#angular/core';
#Directive({
selector: '[scrollable]'
})
export class ScrollableDirective implements OnInit{
constructor(public el:ElementRef) { }
ngOnInit(){
console.log('its working!')
}
}
2) Angular CLI automatically adds the directive to the app.module declarations
import { ScrollableDirective } from './scrollable/scrollable.directive';
#NgModule({
declarations: [
...
ScrollableDirective
],
3) Try to use the directive as an attribute
<div class="menu-container" *ngIf="menuService.showMenu" [scrollable]>
4) Resulting error
Error: Uncaught (in promise): Error: Template parse errors:
Can't bind to 'scrollable' since it isn't a known property of 'div'.
I have read the official documentation and I seem to be doing all the right things. I cannot understand what I could have missed and why the directive cannot be used.

Try adding the scrollable directive without the [] bindings:
<div class="menu-container" *ngIf="menuService.showMenu" scrollable>
[] would be if you are passing a value to the directive, but you aren't utilizing any #Input values in you directive, so it would not be needed.
The docs use the binding brackets [highlightColor]="'orange'" because it's expecting a string value from the consumer to specify a color. #Input would only be needed if you are needing a value passed to the attribute directive to use in some way.
#Kevin is right that the error is being caused by #Input not being added to the directive configuration, but in this case you don't need it, so avoid the import/export of that decorator.

Related

Databinding in Angular, understanding in detail

I am new to Angular and I just started learning it recently. I came across the concept of Databinding in Angular. I was able to understand the syntax and stuff but there were some questions that I couldn't find an answer for. These are the queries I had:
When we export a class from the component TS file, we can use the class properties in HTML file. For eg: Databinding a class property to a HTML element works. But how does this HTML element know the class or the class attribute? How does the HTML file have access to it?
Why exactly are we exporting a class for a component to be used? Is the component a class too? If yes, then wehen we use the component are we calling that class and this leads to rendering the HTML and CSS mentioned in the component?
Please let me know.
Answering your question in details requires having an in-depth knowledge about how Angular internally works, but here's a starting point:
I've generated a component using angular CLI:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
public myProperty: number = 0;
constructor() { }
ngOnInit(): void {
}
}
So:
Is the component a class too?
Yes, as you can see from the labels "export class", your component is first of all a regular JS class, which implements the OnInit interface, has an empty constructor and defines a public variable.
If yes, then when we use the component are we calling that class?
Exactly: Angular does a bit of magic (see the next point of the answer), so whenever finds a html tag named <app-example></app-example>, it creates an ExampleComponent instance and replaces that tag with the html you defined on example.component.html
and this leads to rendering the HTML and CSS mentioned in the component?
The magic happens just above the class definition: Angular heavily relies on Typescript Decorators, which are an (still) experimental feature of Typescript. Decorators allows you (or Angular in our case) to alter the behaviour of a class, for example by intercepting methods call, property changes (did you just say databinding?) and constructor parameters (and this is how Angular's dependency injection works).
In the #Component decorators, which is linked to the below ExampleComponent class, you're defining three things:
the selector, or tag name that Angular will search in the DOM and replace with your component's html
Where to find your component's html, which will be linked to each of your ExampleComponent instance
Stylesheets for your component's html
So, when a property on your component changes (for example myProperty), Angular intercepts that change thanks to the #Component decorators you've defined (to understand how, do a little search about decorators), and will re-render his html. Inserting that property value on a paragraph like <p>{{myProperty}}</p> is just a matter of string replacement.
So, now you have the answer to your first question:
But how does this HTML element know the class or the class attribute? How does the HTML file have access to it?
It's not the html that knows which component it belongs, it's the component (or Angular that handles that component) that knows which html has to render and which css needs to apply.
Why exactly are we exporting a class for a component to be used?
This is simply to let Angular know that we have defined a component. Exporting something from a .ts file makes it available on the rest of the project, and particularly on the AppModule file, where you will find your ExampleComponent among the declarations array:
#NgModule({
declarations: [
AppComponent,
ExampleComponent
],
// Something else

Angular 11 structural directive breaks when compiling via AOT

I am attempting to migrate from Angular 8 to 11.
I have gotten everything to work except for a custom directive. The directive works when running the app using ng serve, but breaks when compiled with the --aot or --prod flags. The browser console log reports the following error:
core.js:10073 NG0303: Can't bind to 'hasPermission' since it isn't a known property of 'span'.
where hasPermission is the directive, and span is any element I attempt to use the directive. This used to work fine in Angular 8.
The directive itself:
import { Directive, OnInit, Input, ElementRef, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[hasPermission]',
})
export class PermissionsDirective implements OnInit {
constructor(
private element: ElementRef,
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
) {}
ngOnInit() {
...
}
// this is the structural call we're looking for
// the directive will respond to *hasPermission="'perm'"
#Input('')
set hasPermission(value: any) {
...
}
}
it is declared and exported using a SharedModule:
#NgModule({
declarations: [
...
PermissionsDirective,
...
],
exports: [
...
PermissionsDirective,
...
]
})
export class SharedModule {}
and finally, it's used in many other modules/components like so:
some.module.ts
import { SharedModule } from 'path/shared.module';
#NgModule({
imports: [
SharedModule,
],
declarations: [
SomeComponent
]
})
somehtml.html
...
<div *hasPermission="'somepermission'">...</div>
...
some.component.ts
#Component({ templateUrl: 'somehtml.html' })...
I'm thinking this has something to do with the -aot flag not placing modules in the right place so that they're visible? Otherwise I don't understand why there are no issues using just ng serve
Minimal example shown here: https://stackblitz.com/edit/angular-ivy-rzu5jm?file=src/app/app.component.html
Browser console for the stackblitz shows the same error (NG0303)
The issue is with #Input(''). You should not set anything inside the decorator, or if you do it needs to have the same name, that if you want to name it as an alias. Removing the empty string should fix it:
#Input()
set hasPermission(value: any) {
console.log(value);
}
FORKED STACKBLITZ
You must remove the rename from the decorator #Input('') -> #Input()
#Input()
set hasPermission(value: any) {
...
}

Angular's `#Host` decorator not reaching the top?

In my main app.ts I've declared a global provider :
providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
(Where createDependency is just a function that returns a class which has a getName() method.)
I also have a components :
<my-app-component-3>Hello from 3</my-app-component-3>
Code :
#Component({
selector: 'my-app-component-3',
template: `
<div>Component3:
<ng-content></ng-content>
: <span [innerHTML]="dependency?.getName()"></span>
</div>
`,
})
export class Component3 {
constructor(#Host() #Optional() public dependency: Dependency) {}
}
The result is:
Component3: Hello from 3 :
But I expect the result to be :
Component3: Hello from 3 :AppModule provider
Because basically the app structure is :
<my-app>
<my-app-component-3>
</my-app-component-3>
</my-app>
Question:
Why doesn't #Host() match the parent provider ?
(which is : providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}])
To my knowledge - the injector should seek for a Dependency in this manner :
So why doesn't it find it ?
PLUNKER
Notice
I already know that if I remove #host - it does reach the top. My question is why adding #host - is not reaching the top - despite the fact thatmy-component3 is under my-app !!
Check out A curios case of the #Host decorator and Element Injectors in Angular for in-depth explanation of how #Host decorator works and where Element Injectors come into this picture.
In order for it to work you should define dependencies in the in the parent component and using viewProviders:
#Component({
selector: 'my-app',
viewProviders: [{provide: Dependency, useValue: createDependency('AppModule provider')}],
...
export class MyApp {}
Here is what the comments inside metadata.ts say:
Specifies that an injector should retrieve a dependency from any
injector until reaching the host element of the current component.
So basically it says that a host element injector and all injectors above are not used when resolving a dependency. So if your MyApp component has the following template:
<my-app-component-3></my-app-component-3>
and the resulting components tree look like this:
<my-app>
<my-app-component-3></my-app-component-3>
</my-app>
neither MyApp component's injector nor App module injectors are used to resolve dependency for the my-app-component-3.
However, there's the following interesting code in the ProviderElementContext._getDependency that performs one additional check:
// check #Host restriction
if (!result) {
if (!dep.isHost || this.viewContext.component.isHost ||
this.viewContext.component.type.reference === tokenReference(dep.token !) ||
// this line
this.viewContext.viewProviders.get(tokenReference(dep.token !)) != null) { <------
result = dep;
} else {
result = dep.isOptional ? result = {isValue: true, value: null} : null;
}
}
which basically checks if the provider is defined in the viewProviders and resolves it if found. That's why viewProviders work.
So, here is the lookup tree:
Usage
This decorator is mostly used for directives to resolve providers from the parent injector within the current component view. Even the unit test is written only to test directives. Here is a real example from the forms module how it's decorator is used.
Consider this template for the A component:
<form name="b">
<input NgModel>
</form>
NgModel directive wants to resolve a provider supplied by the form directive. But if the provider is not available, there's no need to go outside of a current component A.
So NgModel is defined like this:
export class NgModel {
constructor(#Optional() #Host() parent: ControlContainer...)
While form directive is defined like this:
#Directive({
selector: '[formGroup]',
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
...
})
export class NgForm
Also, a directive can inject dependencies defined by its hosting component if they are defined with viewProviders. For example, if MyApp component is defined like this:
#Component({
selector: 'my-app',
viewProviders: [Dependency],
template: `<div provider-dir></div>`
})
export class AppComponent {}
the Dependency will be resolved.
I wonder if the #Optional() is injecting null. I believe that one might be the culprit.
Edit
So from your plunker I can’t seem to find an actual host for the component 3. Something like
<parent-component>
<component-3><component-3/>
<parent-component/>
On my understanding here it seems what it’s looking for.
just remove #Host() decorator from your Component 3 constructor:
Component({
selector: 'my-app-component-3',
template: `
<div>Component3:
<ng-content></ng-content>
: <span [innerHTML]="dependency?.getName()"></span></div>
`,
})
export class Component3 {
constructor(#Optional() public dependency: Dependency) {}
}
Angular will take the provider from the AppModule.
straight from Angular's docs on dependency injection and the Host decorator: https://angular.io/guide/dependency-injection-in-action#qualify-dependency-lookup-with-optional-and-host
The #Host decorator stops the upward search at the host component.
The host component is typically the component requesting the dependency.
with the #Host decorator, you're telling it to only check the host component for a provider, and you're making it optional, so it's just seeing there's no provider and quitting.
In practice, the use case for the Host decorator is extremely narrow, and really only ever makes sense if you're projecting content.

Is Angular2 Injectables and creating instance or same?

I am new to es6, typescript and Angular2 stuff, I have tried directive example. It looks like following..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(el: ElementRef, renderer: Renderer) {
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
and i have tried following variants, but didn't work as i expected..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor() {
console.log(new ElementRef())
//renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
also tried this..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(ElementRef, Renderer) {
console.log(new ElementRef())
//renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
I didn't get the difference between el: ElementRef syntax and normal object instance creation with new ElementrRef .. Please Explain The Difference And Logic Behind Them and el: ElementRef relation and equivalent in normal or es6 js.. Thanks in advance :)
This is the basic building stone of angular 2 Dependency Injection (DI) mechanism. In short: if you need in your component reference to some service etc., you can must ask Angular via constructor. With line constructor(el: ElementRef, renderer: Renderer) you are basically asking the framework: "When you will construct the highlight directive for me, give me these two objects, ElementRef and Renderer. Without them, I as a highlight directive cannot live."
And how are these two objects obtained? Through DI framework during lifecycle of directive. I strongly suggest to read this awesome article for better understanding (it is a must): http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
In second and third examples (where your constructor is empty so nothing is injected to your directive on creation), you cannot simply create ElementRef through new(), because it requires more dependencies, for example nativeElement, as stated here: https://angular.io/docs/js/latest/api/core/index/ElementRef-class.html
But you are not providing these and simply cannot.

Angular2 Recursive Templates in javascript

I have been trying to follow this tutorial to create a nested tree view. The tutorial is in typescript while I am trying to do a similar thing in javascript with Angular2.
In the typescript code, the tree-view component looks like so:
import {Component, Input} from 'angular2/core';
import {Directory} from './directory';
#Component({
selector: 'tree-view',
templateUrl: './components/tree-view/tree-view.html',
directives: [TreeView]
})
export class TreeView {
#Input() directories: Array<Directory>;
}
In javascript that should convert to:
TreeView = ng.core
.Component({
selector: 'tree-view',
templateUrl: './components/tree-view/tree-view.html',
directives: [TreeView],
inputs: ['directory'],
})
.Class({
constructor: function() {
},
});
However, javascript throws the following error:
EXCEPTION: Unexpected directive value 'undefined' on the View of
component 'function () {'
I believe it's because I'm calling directives: [TreeView] before TreeView has been fully defined. If I remove that directive line, the error goes away. However, I don't know why it would work in typescript and not javascript if typescript simply compiles to javascript. This is the compiled javascript from the typescript code. I'm not sure what I'm missing. Any help would be super appreciated.
This question has been answered a few times
First of all classes are not hoisted. Quoting from MDN
An important difference between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it [...]
The documentation for forwardRef says
For instance, forwardRef is used when the token which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when the token which we use when creating a query is not yet defined.
So it's as easy as adding forwardRef to your code
directives : [ng.core.forwardRef(function() { return TreeView; })]
You can read more about this subject
Forward references in Angular 2
Others questions from StackOverflow

Categories

Resources