Databinding in Angular, understanding in detail - javascript

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

Related

In Angular, how can I run a small script on each component that gets loaded? (diagnostic purposes)

I want to run certain diagnostic lines of Javascript as I develop that will console log the alt attribute on all <img> tags in the HTML, for instance. This is purely for testing and will not go to production of course. On a single component I would normally run this within the ngAfterContentInit() and/or ngOnChanges() lifecycle hooks, so it would run this script whenever the images in the HTML have been loaded and rendered.
The JS would just be something simple like getElementsByTagName('img')[0].getAttribute('alt'), but how can I run this each time a component is displayed, so I can check these kinds of things without adding an import to each individual component? Is there something I can do in the AppModule for instance that controls the components that are loaded? I would like to run these kinds of scripts on dynamically/lazily loaded components too, if that is possible.
You can use a custom directive for this purpose, Define global img selector in custom directive. Then use ElementRef class to access image alt attributes value.
import { Directive, ElementRef, isDevMode, OnInit } from '#angular/core';
#Directive({
selector: 'img',
})
export class ImgDirective implements OnInit {
constructor(private element: ElementRef) {}
ngOnInit() {
if (isDevMode) {
console.log(this.element.nativeElement.getAttribute('alt'));
}
}
}
Working Example

Angular 6 Services and Class Inheritance

Angular 6 now has injectable providers which is the new recommended way of injecting services, and it works really well except I'm having a problem when using a service which extends another service. So as an example, suppose I have
#Injectable({
providedIn: 'root'
})
export class ParentAppService { ... }
#Injectable({
providedIn: 'root'
})
export class ChildAppService extends ParentAppService { ... }
The problem is that no matter what I ask for in a component, the parent class is always injected.
So if you ask for
constructor(private childAppService: ChildAppService) { ... }
you will still be provided an instance of ParentAppService, which isn't expected.
A very simple workaround would be to just register the providers in your module the old fashioned way, and this works:
#NgModule({
providers: [AppService, ChildAppService]
})
But that's basically the old way of doing things, and doesn't have the benefits of better tree-shaking and cleaner testing like the new providedIn registration process.
So my question is, what's the right way to do this? Is there a better way to register a provider so I can get the desired behavior (maybe specifying a provider token somehow?).
I've setup a super simple stackblitz example to show what's going on.
https://stackblitz.com/edit/angular-lacyab?file=src%2Fapp%2Fapp.component.ts
You'll notice it says "Hello I AM APP SERVICE!" even though the component asked to be provided the child service. If in the app module we register the providers the old way (see the commented out code), all of a sudden the proper provider gets injected.
Thanks for your help!
Update:
There is already a pull request for that https://github.com/angular/angular/pull/25033
Original version
The problem: seems new angular treeshakable services don't respect inheritance:
First angular defines(1) ngInjectableDef property on AppService function. Then you inherit ChilAppService from AppService so that child class contains all properties from parent class. And finally when angular tries to define(2) ngInjectableDef property on ChildAppService it can't because it already exists(3) thanks to javascript prototypical inheritance.
In order to fix it you can either
1) workaround it through defining undefined ngInjectableDef property on child service so that it won't read already filled from parent class property and angular will be able to define ngInjectableDef on child class:
#Injectable({
providedIn: 'root'
})
export class ChildAppService extends AppService {
static ngInjectableDef = undefined;
constructor() {
super();
this.name = 'CHILD SERVICE';
}
}
Forked stackblitz
2) or report issue in github
3) or use composition instead of inheritance as was suggested in comments.

how to access DOM elements in angular 4 service?

I am able to access DOM elements components like below
declare var jQuery: any;
declare var $: any;
//component stuff
$('.my_class').innerHeight();
I am trying to implement the same inside the service class, but dom elements and template is not accessible in the service class.
p.s: this is not duplicate of how to access them in components.
You can access DOM from an Angular service using the plain javascript document object, with some little additions to your service:
// We import not only "Injectable", but "Inject" too, from #angular/core
import { Injectable, Inject } from '#angular/core';
// We import DOCUMENT from #angular/common. Be careful, because the old import from '#angular/platform-browser' is deprecated.
import { DOCUMENT } from '#angular/common';
// Our standard service class in the usual way
#Injectable()
export class LoadingSpinnerService {
// In the constructor we inject a dependency to DOCUMENT, of type HTMLDocument
constructor(#Inject(DOCUMENT) private document: HTMLDocument) {
// We create a new div in the DOM, child of the body tag, <div id="LoadingSpinner"></div>
var NewDomElement = this.document.createElement("div");
NewDomElement.setAttribute("id", "LoadingSpinner");
document.body.appendChild(NewDomElement);
}
}
As you will probably know, you crete the service from the command line with something like:
ng g s loading-spinner
Don't forget to edit the app.module.ts to add the "import" to the service, and the item to the "providers" array in its "#NgModule" decorator:
import { LoadingSpinnerService } from './WHATEVER-DIRECTORY-YOU-CREATE-THE-SERVICE/loading-spinner.service';
(...)
providers: [LoadingSpinnerService,
(...)
About the topic of using angular services only for data, i don't agree. As you can read in the official architecture guide for services:
https://angular.io/guide/architecture-services
Service is a broad category encompassing any value, function, or feature that an app needs.
A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console.
The provided example just in this documentation is for a log data service.
Hope this helps someone.
You can't in services. You can do it javascript way like document.getElementById.
In components and directives You can use ViewChild from #angular/core
HTML:
<div class="my_class" #myElement></div>
TS:
import { ElementRef, ViewChild } from '#angular/core';
#ViewChild('myElement') myElement:ElementRef;
console.log(this.myElement.nativeElement.offsetHeight); // inside any function.
I guess you cannot directly access with CSS selectors by Angular way. Alternatively, you can just use plain old JavaScript
document.getElementsByClassName("my-class");
Note: You can only do this in components and directives not inside services
Why Services
Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data. They should focus on presenting data and delegate data access to a service.
Source: https://angular.io/tutorial/toh-pt4
In Simpler terms:
component, directive for presenting, manipulating and interacting with DOM
services are for data handling between your component and backend

Extend Angular2 Directive

I am trying to extend the angular2-clipboard npm package. I need to get access to its ngOnInit() function and override it to suit a specific use case for copying.
I am new to angular2 and unsure how to do this, the way I've been trying it so far is linked in the plunker below. I am having difficulty since the package is exported as a module called ClipboardModule and I need the directive.
Here is the plugin github and plunker for reference:
Github: https://github.com/maxisam/angular2-clipboard
The file that shows the export is src/clipboard.module.ts
Their github also has a link to a plunker example of it working
My plunker: https://embed.plnkr.co/sIxmFo/
This is how one defines a service, not a directive.
#Injectable()
export default class CopyDirective extends ClipboardModule {
public cm: any; // better way than any?
constructor(cm: ClipboardModule) {
super();
this.cm = cm;
}
}
A directive requires a #Directive() decorator instead of the #Injectable()
Inheritance with components, directives, and pipes isn't supported currently.
I also don't understand why you extend a module when you want to extend a directive. You need to extend the directive class directly.
See also https://github.com/angular/angular/issues/11606
Some scenarios can work though.
Usually you need to repeat all decorators in the subclass.
#Component(), #Directive(), #Input(), #Output(), #ViewChild(ren)(), #ContentChild(ren)(). ...

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