I have a simple script that would add a class to a dom selector.
showParent() {
console.log('show parent');
$("seasonsGrid").toggleClass("grid-layout");
}
How does angular allow you to target these dom nodes whether they are tags or named elements?
in this case $("seasonsGrid") is a reference to
#Component({ selector: 'seasonsGrid', ...
but would be useful to know how to dom traverse using tags and named el's.
what would the ng2 equivalent be for this
var seasonsGrid = $(seasonsGrid);
You could take use of ElementRef/Renderer to get component element
import {Component, View, ElementRef} from 'angular2/angular2';
import {NgClass} from 'angular2/common'; //import when you wanted to use ngClass
import {Renderer} from 'angular2/core';
#Component({ selector: 'seasonsGrid', ...})
export class MyComponent {
let el: any;
let componentElement:any;
constructor(private elementRef: ElementRef, private renderer: Renderer){
//el = elementRef.nativeElement;
componentElement = renderer.selectRootElement(); //will have component root element
}
ngAfterViewInit() {
console.log(el); //nativeElement to process
console.log(componentElement); //nativeElement to process
}
}
But in your case you can think of to have ngClass directive to have it in a place like [ngClass]="{className: expression}"
I must need to do some more homework but from the looks of it angular 2 is by far more convoluted if all that is required just to initialize the states of a toggle class on an element. jquery is 1 line
Angular 2 is a data-driven reactive framework. Instead of thinking about selectors and DOM manipulation code, I encourage you to embrace Angular-think, which is quite different from jQuery-think.
If you want to change a class in a data-driven framework, you first declaratively bind some data to that element's class property in a component template. Then, to change the class, you simply change the bound data. As others have already mentioned, NgClass is the way to do that.
Similarly, component logic should not manipulate the DOM (i.e., it shouldn't call things like toggleClass()), rather it should change data or emit an event (up to its parent).
When Angular change detection runs, it will notice the data changes and update the DOM (or native UI) or propagate the data change to a parent component (for an emitted event) or to a child component (for an input data property change).
Angular-think is all about data-driven changes. And Angular 2 pushes us further down that road than Angular 1 did. (That's a good thing.)
This probably won't answer the question as I'm very new to NG2 (like three days), but here's a few things I've found in the documentation:
<div [class.isStopped] = "isStopped"></div>
<div [style.color] = "isStopped ? 'red' : 'blue'"></div>
<button [attr.aria-label] = "ok"></button>
<div [ngClass] = "{selected:isSelected}"></div>
ngClass is a Directive Property, just like ngStyle and ngModel. For the most part, they just took out the dash.
While we're on Property Bindings:
<img [src] = "vehicle.imageUrl" />
<vehicle-detail [vehicle] = "currentVehicle"></vehicle-detail>
You tie the component (code) to the template so in this case it wouldn't be necessary to get the element in code and change it, just change the Component (view model).
Related
I store a dynamically generated HTMLElement in a variable in my component class. I would like to display it, but I have no idea how is that possible. I tried it like this:
Inside the component class:
elem: HTMLElement;
In the template:
<ng-container *ngTemplateOutlet="elem"></ng-container>
I get this error:
TypeError: templateRef.createEmbeddedView is not a function
If you have a naked HTMLElement, there's nothing Angular-y you can do. NgTemplateOutlet expected a TemplateRef, not a vanila HTMLElement object.
To insert an HTML Element into the DOM, simply use the appropriate DOM method for that. Which one you'll use entirely depends on the place where you want to use it.
For example, you could do
const wrapper = document.querySelector('div.wrapper')
wrapper.append(el) // pass in your element
You can also grab the reference to the wrapper in a more Angular way, by adding an Angular reference in the template on the element you want to select,
<div class="wrapper" #wrapper></div>
and using ViewChild decorator on a property where you want Angular to assign the ElementRef to.
#ViewChild('wrapper', { static: true, read: ElementRef })
public wrapperElRef: ElementRef<HTMLDivElement>
Once the view is initialized (you'll be notified when this happens via the ngAfterViewInit hook), you can grab the HTML element within by accessing `.nativeElement:
this.wrapperElRef.nativeElement //: HTMLDivElement
You can now do
this.wrapperElRef.nativeElement.append(elem)
That said, unless you know what you're doing (usually wrapping an existing library or integrating with an existing large system that you cannot yet refactor completely)
Most of the times it depends how you have created the html.
I suggest specifying in comments where are you receiving the variable which holds the HTMLElement, so I could give you the most effective way to do it.
But generally -
try using innerHTML binding in the parent element.
Typescript:
public elem: HTMLElement;
public getElement(): string {
return this.elem.innerHTML;
}
HTML:
<div innerHTML="getElement()"></div>
I'm working on writing a component intended to simplify/homogenize the way our forms look and interact. The code looks something like this:
Example Usage
...
<my-form-input labelKey = "email" controlName="emailAddress" [form]="userForm">
<input myInput class="form-control" type="email" formControlName="emailAddress" />
</my-form-input>
...
You can see that "emailAddress" is passed to MyFormInputComponent as the controlName and is passed a second time to the FormControlName directive on the <input> element. I'd like to only pass this once so that my end user doesn't have to do this.
Is there a good way I can go about this, or is this just a constraint I should accept (if yes, an explanation of why this constraint exists would be welcome)? Code is shown below.
I've tried two approaches:
Setting a #HostBinding("attr.formControlName") annotation in the MyInput component. I can manipulate an attribute called formcontrolname on the element this way, but it doesn't trigger the directive that Angular Forms needs to properly register the control with the group.
Ask the user to supply formControlName to the <input> element and read the value off of this for the rest of the component. This might work, but I'd have to access the DOM directly through an ElementRef, which is not recommended. The recommended route for interacting with DOM -- Renderer -- doesn't seem to expose any ability to read attributes either.
my-form-input.component.ts
#Component({
selector: 'my-form-input',
templateUrl: './my-form-input.component.html',
styleUrls: ['./my-form-input.component.scss']
})
export class MyFormInputComponent implements OnInit, AfterContentInit {
#Input()
labelKey: string;
#Input()
controlName: string;
#Input()
form: FormGroup;
#ContentChild(MyInputDirective)
input: MyInputDirective;
ngAfterContentInit(): void {
this.initInput();
}
/**
* Updates the input we project into the template
*/
private initInput() {
this.input.updatePlaceholder(this.labelKey);
// I'd like to somehow edit the FormControlName directive applied to the input here
}
}
my-form-input.component.html
<label>{{ labelKey | translate }}</label>
<ng-content></ng-content>
<my-input-error [control]="form.controls[controlName]" [name]="labelKey | translate" />
my-input.directive.ts
#Directive({
selector: '[myInput]'
})
export class myInputDirective implements OnInit {
private placeholderKey = "";
#HostBinding("placeholder")
private placeholder: string;
updatePlaceholder(placeholderKey: string) {
this.placeholderKey = placeholderKey;
this.placeholder = this.translateService.instant(this.placeholderKey);
}
constructor(private translateService: TranslateService) {
}
}
my-form-error.component.ts
// Not shown since not relevant.
I'm still not sure the exact explanation, but some reasoning alludes to where I might have strayed.
I assumed that my component owned the elements that it was projecting, but I actually think this isn't true. Your opportunity to set attributes/directives is in the template. This means you are better off including any elements that you want to own/control into the template rather then just projecting them.
In this case, that leads you to making separate components for specific form controls (like <input>, <textarea>, etc). This is what I've ended up doing. This is better from a design endpoint anyway -- one component to wrap all possible form-controls was never going to happen. Either project a form control that duplicates some properties, like I do in the question, or create specific components. Or both (just make your most common form controls into components, wrap the one-off problems in your projecting component).
Here are some blogs that helped me find my way:
https://medium.com/#vadimkorr/implementing-nested-custom-controls-in-angular-5-c115c68e6b88
https://blog.angularindepth.com/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms-93b9eee9ee83
I'm seeking some advice how I should handle elements when working with Angular2.
I have stored some elements id's in the localstorage, and want to set a selected class name on some specific elements.
For now I'm using this way:
ngAfterViewChecked() {
// Check if seats has been selected before
var selectedSeats: elementInterface = JSON.parse(localStorage.getItem('SelectedSeats'));
if (selectedSeats != null) {
var outboundElement = document.getElementById(selectedSeats.outboundSelectedElement);
var returnElement = document.getElementById(selectedSeats.returnSelectedElement);
this.autoSelectSeats(outboundElement);
this.autoSelectSeats(returnElement);
}
}
Method:
private autoSelectSeats(element: Element) {
// Set selected class on element
element.classList.add('selected');
}
I see two issues here. The first is the ngAfterViewChecked hook that fires more than once after the view is created. Is there something I can do so it only fires once?
Second: Is there a better way to get the element when you know the id and set a class attribute on it after the content has loaded?
I can't seem to find the Angular2 way of doing it, besides using this hook.
Any idea? Also, please don't post Jquery posts, as I don't want to implement that when using Angular :)
How about adding a custom directive to each of your "seat" elements and let that directive add the CSS class?
In your template, the directive would be used as follows (I'm guessing, since you didn't show your template):
<div *ngFor="let seat of seats" [highlight]="seat.id">
...
</div>
You need to pass some information to the directive to identify which seat it is working on. It seems better to pass an id directly (e.g. seat.id) rather than to rely on HTML ids (although in your case they might be one and the same).
Now the code for the directive:
#Directive({
selector: '[highlight]'
})
export class HighlightDirective {
#Input() highlight: string; // This will contain a seat.id
constructor(el: ElementRef, ss: SeatService) {
const selectedSeats = ss.getSelectedSeats();
// If current seat found in selectedSeats, mark it as selected.
if (selectedSeats.indexOf(this.highlight) !== -1) {
this.el.nativeElement.classList.add('selected');
}
}
}
The reason I'm using an external service SeatService to get the data from localStorage is that Angular will create an instance of HighlightDirective for every match it finds in your template. You don't want to refetch the selected seats in every instance (the service lets you cache the seats and return the same data).
Angular way has pretty good documentation, classes are toggled using the following syntax: [class.selected]="selected"
I am trying to find a way to dynamically construct a template in Angular2. I was thinking templateRef might provide a way to do this. But I could be wrong.
I found an example of templateRef being used here.
I was looking at templateRef in this example. I noticed the syntax is [ng-for-template] I also tried [ngForTemplate] cause I know this has changed recently.
So at the moment I have this:
import {Component, TemplateRef} from 'angular2/core';
#Component({
selector : 'body',
template : `
<template [ngForTemplate]="container">
<div class="container"></div>
</template>
`
})
export class App
{
#ContentChild(TemplateRef) container;
constructor() {}
ngAfterContentInit()
{
console.log(this);
}
}
This example throws an error:
Can't bind to 'ngForTemplate' since it isn't a known native property
So firstly I am wondering. What is the right way to do this? The docs don't provide any examples.
Secondly, is there a good way I can add new template logic to my template or dynamically construct a template? The structure of the application can be a very large amount of different structural combinations. So if possible I would like to see if there is a way I can do this without having a huge template with a bunch of different ngIf and ngSwitch statements..
My question is really the first part about templateRef. But any help or suggestions on the second part is appreciated.
Creating your own template directive it's not difficult, you have to understand two main things
TemplateRef contains what's inside your <template> tag
ViewContainerRef as commented by Gunter, holds the template's view and will let you to embed what's inside the template into the view itself.
I will use an example I have when I tried to solve this issue, my approach is not the best for that, but it will work for explaining how it works.
I want to clarify too that you can use any attribute for your templates, even if they're already used by builtin directives (obviously this is not a good idea, but you can do it).
Consider my approach for ngIfIn (my poor approach)
<template [ngIfValue]="'make'" [ngIfIn]="obj">
This will print
</template>
<template [ngIfValue]="'notExistingValue'" [ngIfIn]="obj">
This won't print
</template>
We have here two templates using two inputs each ngIfIn and ngIfValue, so I need my directive to grab the template by these two inputs and get their values too, so it would look like this
#Directive({
selector : '[ngIfIn][ngIfValue]',
inputs : ['ngIfIn', 'ngIfValue']
})
First I need to inject the two classes I mentioned above
constructor(private _vr: ViewContainerRef, private _tr: TemplateRef) {}
I also need to cache the values I'm passing through the inputs
_value: any;
_obj: any;
// Value passed through <template [ngIfValue]="'...'">
set ngIfValue(value: any) {
this._value = value;
}
// Value passed through <template [ngIfIn]="...">
set ngIfIn(obj: any) {
this._obj = obj;
}
In my case I depend on these two values, I could have my logic in ngOnInit but that would run once and wouldn't listen for changes in any of the inputs, so I put the logic in ngOnChanges. Remember that ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed (copy and paste from the docs).
Now I basically copy & paste NgIf logic (not so complex, but similar)
// ngOnChanges so this gets re-evaluated when one of the inputs change its value
ngOnChanges(changes) {
if(this._value in this._obj) {
// If the condition is true, we embed our template content (TemplateRef) into the view
this._vr.createEmbeddedView(this._tr);
} else {
// If the condition is false we remove the content of the view
this._vr.clear();
}
}
As you see it's not that complicated : Grab a TemplateRef, grab a ViewContainerRef, do some logic and embed the TemplateRef in the view using ViewContainerRef.
Hopefully I made myself clear and I made how to use them clear enough also. Here's a plnkr with the example I explained.
ngForTemplate is only supported with ngFor
<template [ngFor] [ngForOf]="..." [ngForTemplate]="container"
or
<div *ngFor="..." [ngForTemplate]="container"
not on a plain template. It is an #Input() on the NgFor directive
Another way to use TemplateRef
If you have a reference to ViewContainerRef you can use it to "stamp" the template
constructor(private _viewContainer: ViewContainerRef) { }
ngOnInit() {
this.childView = this._viewContainer.createEmbeddedView(this.templ);
this.childView.setLocal('data', this.data);
}
I know it should be easy but angular 2.0 has no many examples yet..
In one of my components in some case I need to add class on my body tag. But my application is bootstrapped deeper than body, so I need something like
angular.element('body').addClass('fixed');
but in Angular 2.0..
BTW, I know I can somehow bootstrap my application to include body tag, but I think in some other cases I would need to select some elements anyway, so I need a solution how to do this simple task - "select element and add class to it"
Update
I'm not sure if DOM is actually still supported in RC. The related statements aren't very clear. Something like
DOM is only for internal use. Either access the DOM directly or use a custom renderer.
I haven't see how a custom renderer might be implemented or how to provide an implementation depending on the current platform (webworker, server, DOM thread).
Update
This seems to be the Angular2 way
import { DOM } from 'angular2/src/platform/dom/dom_adapter';
DOM.addClass(DOM.query("body"), 'fixed');
Import from .../src/... at your own risk. .../src/... is considered private implementation and you can't expect any guarantees that the API won't change without notice.
I tried it in Dart and it works fine (not sure if the TS import above is correct though). In Dart DOM is exported by package:angular2/angular2.dart
Original
If you want to access a DOM element that's outside of your Angular application root, just use document.querySelector(), no need to involve Angular.
As of Angular2 Version 2.0.0-beta.17.
Attribute Directives in Angular2 will do this for you nicely.
Please see this plunk written in TypeScript. This does what you want nicely.
The directive file my-highlight.ts has the line:
this.renderer.setElementClass(this.element, "red", true);
This sets the CSS class on the element.
While template.html has the actual element which is decorated with the directive [myHighlight]:
<p [myHighlight]>Mouseover to highlight me!</p>
This, to me, provides the least hack-ish answer to the question without any dependency on jqLite.
As of angular 2.4 you should inject the DOCUMENT and don't interact with any adapter:
import { Component, Inject } from '#angular/core';
import { DOCUMENT } from '#angular/platform-browser';
#Component({})
export class MyClass {
constructor (#Inject(DOCUMENT) private document) { }
doSomething() {
this.document.someMethodOfDocument();
}
}
Further reading: https://github.com/angular/angular/issues/8509
DOM Manipulation in Angular 2 / 4 app
To manipulate the DOM in Angular 2/4 apps, we need to implement the method ngAfterViewInit() of AfterViewInit. The method ngAfterViewInit() is called when the bindings of the children directives have been checked for the first time. In other words, when the view is initially rendered.
The #ViewChild provides access to nativeElement. It is recommended to not access nativeElement inside the ngAfterViewInit() because it is not browser safe. Also, it's not supported by web workers. Web workers will never know when the DOM updates.
The right way is to use renderer. The renderer needs to be injected to the component constructor. We need to provide an id reference to the HTML element on the view something like this:
<p #p1></p>
It shall be accessed by the corresponding coponent .ts file, something like this:
export class SampleComponent implements AfterViewInit {
#ViewChild("p1") p1;
constructor(private renderer: Renderer2) //Renderer set to be depreciated soon
{ }
ngAfterViewInit() {
//recommended DOM manipulation approach
this.renderer.setStyle(this.p1.nativeElement, //setElementStyle for soon to be depreciate Renderer
'color',
'red');
//not recommended DOM manipulation approach
//this.p1.nativeElement.style = "color:blue;";
}
}
I don't recommend direct DOM access from Angular, but you have a DOM hook via the ElementRef of your component. Once you have access to it you can use it directly or via jquery or any other DOM manipulation technique. I have included an example of how to use jquery to run general queries. If you are always going for the body tag you don't really need ElementRef, but it's useful for something that is relative to the root of the component.
import {Component, ElementRef, OnInit} from '#angular/core';
declare var jQuery:any;
#Component({
selector: 'jquery-integration',
templateUrl: './components/jquery-integration/jquery-integration.html'
})
export class JqueryIntegration implements OnInit {
elementRef: ElementRef;
constructor(private elementRef: ElementRef) {
}
ngOnInit() {
jQuery(this.elementRef.nativeElement).find('.moving-box').draggable({containment:'#draggable-parent'});
}
}
More info here: http://www.syntaxsuccess.com/viewarticle/using-jquery-with-angular-2.0
Demo: http://www.syntaxsuccess.com/angular-2-samples/#/demo/jquery