Angular 4/5 | Dropdown directive - javascript

I am trying to create a dropdown directive which I can use to add dropdowns to any DOM element. I wrote this directive:
import { Directive, HostListener } from '#angular/core';
#Directive({
selector: '[appDropdown]'
})
export class DropdownDirective {
constructor() { }
isOpen: boolean = false;
#HostListener('click') toggle() {
if (this.isOpen === true) {
this.hide();
} else {
this.show();
}
this.isOpen = !this.isOpen;
}
private show() {
// What to do here?
}
private hide() {
// What to do here?
}
}
This directive basically listens for a click on the DOM element it is applied to. When it registers a click I want to toggle the dropdown element that is in the same container as the element which I applied the directive to (by using the display property). My html code looks like this in a simplified form:
<a appDropdown>Dropdown</a>
<ul class="dropdown"> <--- How can I apply a display style property to this element when clicking the anchor (to hide/show the dropdown)?
<li><a>Option A</a></li>
<li><a>Option B</a></li>
</ul>
I read through the Angular docs carefully but it is not explained there.
I hope you get my problem. Thanks!

It's my understanding that this isn't the correct use for directives. I'm trying to achieve a very similar output.
I'm trying to implement something similar, and reading through the documentation (and #evandro-mendes' answer above), structural directives are designed to alter the behavior of the DOM, without defining any templates themselves (see angular docs).
Look at https://github.com/valor-software/ngx-bootstrap/blob/development/src/dropdown/bs-dropdown.directive.ts (or the docs/demo https://valor-software.com/ngx-bootstrap/#/dropdowns) and they are using a directive to show/hide/manipulate the component, but not to declare its own content.
My solution, using ngx-bootstrap is to create a component as follows:
dropdown.component.ts:
#Component({
selector: 'dropdown',
template: './dropdown.component.html'
})
export class AutosuggestComponent {
#Input() viewModel: string[];
#Input() isOpen: boolean = false;
}
dropdown.component.html
<div dropdown [isOpen]="isOpen" [autoClose]="true" placement="top">
<ng-content #input>
</ng-content>
<div *dropdownMenu class="dropdown-menu" role="menu">
<div *ngFor="let item of viewModel" class="dropdown-item">
{{item}}
</div>
</div>
usage
<dropdown [viewModel]="sourceData" [isOpen]="isDropdownOpen">
<input type="text">
</dropdown>
This way I don't define any custom behaviour, and can still use the dropdown for any input within the app.
Hope this helps :)

I think some Angular5 attribute should be included.
Please follow this link:
Building An Angular 5 Project with Bootstrap 4 and Firebase

Related

Angular - Disable Button

<div id="home-container">
<div class="sidebar">
<button mat-raised-button [disabled]="true">This is a button</button>
</div>
<div class="main-panel">
<ac-map></ac-map>
</div>
</div>
Environment:
Angular 10
Chrome/Firefox (Incognito mode)
Hi guys,
I'm experiencing some undesirable behaviour when displaying my angular project. The above example shows a simple component with a button that is disabled by default (I'm using 'true' as a placeholder for a variable). When I load the component the button should be disabled. HOWEVER. When the component is loaded the button is enabled for the first second or two and then is disabled - making it look disorganised. How can I avoid this?
Kind regards,
Scott.
According to your requirement, I suggest you read the document about the component lifecycle of Angular. https://angular.io/guide/lifecycle-hooks
Please check this out for a live sample.
import { Component, OnInit } from "#angular/core";
/**
* #title Basic buttons
*/
#Component({
selector: "button-overview-example",
templateUrl: "button-overview-example.html",
styleUrls: ["button-overview-example.css"]
})
export class ButtonOverviewExample implements OnInit {
disabled = true;
toggle() {
this.disabled = !this.disabled;
}
ngOnInit(): void {
this.disabled = false;
}
}
https://stackblitz.com/edit/toggle-angular-material-button-sample?file=src/app/button-overview-example.html
In this sample, I used the variable disabled to indicate the button state, and implemented the OnInit hook which I change false to true.

Handle DOM events in Angular 4 [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I am new to Angular and trying to understand what is the best approach to handle DOM events in Angular similar to what we use to do in jQuery. For example, in the HTML code below, I would like to add a new CSS class named open to all sections with class filter-section when btnfilter button is clicked -
<aside class="filter ng-scope" id="search-filters">
<button class="btn btn-filter in" id="btnfilter">
<i class="ico ico-collapsed"></i>
<i class="ico ico-expand none"></i>
</button>
<section class="refine filter-section">
<span class="sub-title" title="Filter">
<i class="ico ico-gr-filter"></i>
<span class="title none">FILTERS</span>
</span>
</section>
<section class="refine filter-section">
<span class="sub-title" title="Filter">
<i class="ico ico-gr-filter"></i>
<span class="title none">FILTERS</span>
</span>
</section>
</aside>
In jQuery, we could have done something like below -
$('#btnfilter').click(function () {
$(this).find('.filter-section').addClass('new-class');
});
I would like to know the Angular way of achieving the same. Please advise.
You can use the usual Angular Property Binding to add your class and provide a flag variable in you Typescript file like this.
export class YourComponent {
isBtnToggled: boolean = false;
constructor() {}
toggleClass() {
this.isBtnToggled = !this.isBtnToggled;
}
}
Your Html template should look like that:
<aside class="filter ng-scope" id="search-filters">
<button class="btn btn-filter in" id="btnfilter" (click)="toggleClass()">
<i class="ico ico-collapsed"></i>
<i class="ico ico-expand none"></i>
</button>
<section class="refine filter-section" [class.css-class-here]="isBtnToggled">
<span class="sub-title" title="Filter">
<i class="ico ico-gr-filter"></i>
<span class="title none">FILTERS</span>
</span>
</section>
</aside>
So what we did here is, we created a function toggleClass() in your Typescript File that toggles the value of your Variable isBtnToggled. When you click the button your value will change thus, Angular Property Binding [class.your-css-class] will trigger and add your CSS Class on your element. If you have multiple filter-section classes, you can follow the same binding and it will work just the same for the rest of them.
You could also use plain JavaScript and just use the querySelectorAll('.filter-section') example that DeborahK provided, but i suggest that you stay away from DOM manipulation in angular. Its slow, heavy, and will not work 100% with other techniques like Angular Universal (Server Side Rendering) etc.
I hope i helped you, Cheers
Here is what I came up with:
constructor(private elRef:ElementRef) { }
ngAfterViewInit(): void {
let elementList = this.elRef.nativeElement.querySelectorAll('.filter-section');
elementList.forEach(element => {
// element.className += ' new-class';
element.classList.add('new-class');
console.log(element);
});
}
This finds all of the elements with the defined query selector and for each one, adds the requested class.
Here is my take on this using Renderer2. This should be server side rendering (SSR) safe, and avoid any coupling to the DOM.
First, I added a template varaible (#filterSection) to the section below.
<aside class="filter ng-scope" id="search-filters">
<button class="btn btn-filter in" id="btnfilter" (click)="getFunky()">
<i class="ico ico-collapsed"></i>
<i class="ico ico-expand none"></i>
</button>
<section class="refine filter-section" #filterSection>
<span class="sub-title" title="Filter">
<i class="ico ico-gr-filter"></i>
<span class="title none">FILTERS</span>
</span>
</section>
</aside>
And this is the component code that I put together:
import { Component, Renderer2, AfterViewInit,
ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-filter-section',
templateUrl: './filter-section.component.html',
styleUrls: ['./filter-section.component.css']
})
export class FilterSectionComponent implements AfterViewInit {
#ViewChild('filterSection') filterSection: ElementRef;
private filterElement: HTMLElement;
constructor(private renTwo: Renderer2) { }
ngAfterViewInit() {
if (this.filterSection && this.filterSection.nativeElement) {
this.filterElement = this.filterSection.nativeElement;
}
}
getFunky() {
this.renTwo.addClass(this.filterElement, 'new-class');
}
}
This gets Renderer2 via constructor injection. In the ngAfterViewInit lifecycle method, I check the filterSection ElementRef to make sure it is truthy, and has a nativeElement. If so, assign the nativeElement to a local private variable for use within the component. In the click function (getFunky), using Renderer2, I add a class with the addClass function, passing in the private filterElement and the class I want to add.
Renderer2 can be used to do a wide variety of DOM manipulation. See below.
Renderer2 reference
None of these answers quite get the angular way of doing things.
You're stated goal is to have all elements with a certain class on a page add class when a button is clicked.
I want to generalize this a bit and say, you want all elements of a certain type to react in a certain way to an event.
step 1 is the binding which you seem familiar with:
<button (click)="broadcastEvent()">My Event Button</button>
This is simple click event binding that everyone who's read an angular tutorial should know.
Next we need to have some way of broadcasting and receiving the event, this is a case for an angular service:
#Injectable()
export class EventBroadcastService {
private eventSource = new Subject();
event$ = this.eventSource.asObservable();
broadcastEvent() {
this.eventSource.next();
}
}
this is a simple service that has an rxjs subject and observable. The service exposes an API that allows consumers to send events and listen to them. back to our original component that contains our button, we'll inject the service and tie our button click to this function:
#Component({
selector: 'event-button',
template: `<button (click)="broadcastEvent()">My Event Button</button>`
})
export class EventButtonComponent {
constructor(private eventBroadcastService: EventBroadcastService) {}
broadcastEvent() {
this.eventBroadcastService.broadcastEvent();
}
}
now, finally we need to define a consumer of this event to react to it:
#Directive({
selector: '[eventConsumer]'
})
export class EventConsumerComponent implements OnInit, OnDestroy {
constructor(private eventBroadcastService: EventBroadcastService) {}
#HostBinding(‘class’) addClass = '';
private eventSub;
ngOnInit() {
this.eventSub = this.eventBroadcastService.event$.subscribe(e => this.addClass = 'new-class');
}
ngOnDestroy() {
this.eventSub.unsubscribe();
}
}
this directive can be applied to any arbitrary element and allow you to manipulate the class.
Finally we put it together:
#Component({
template: `
<event-button></event-button>
<section class="filter-section" eventConsumer>
CONTENT
</section>
`,
providers: [EventBroadcastService]
})
export class MainComponent {
}
This example is fairly contrived, but hopefully gets the point across of how you can use a mix of components, directives and services to achieve similar results to jquery in a more controlled manner. This might seem like a lot for something simple but puttnig in infrastrucutre like this yields dividends in large complex applications.
However, this is still pretty cumbersome for something that seems fairly simple. The reason it seems this way is because you're still in the jQuery mindset and need to migrate your thinking and approach to the angular style. You want to use the class directive like this:
<button class="btn btn-filter in" id="btnfilter" (click)="applyClass = true">
<i class="ico ico-collapsed"></i>
<i class="ico ico-expand none"></i>
</button>
<section class="refine filter-section" [class.new-class]="applyClass">

How to load dynamic HTML into DIV with component? Angular5

I have been trying to find the solution of this problem from two days. Unfortunately, I can not get what I want. I am using Angular5.
<div class="form-group col-md-12" [innerHTML]="GetItemsOfHolder(item.children[1],1,
'UserConfigGroupsLabelHolder') | keepHtml"></div>
This is what my function looks like:
GetItemsOfHolder(item: any,divName:string, recursive: boolean = false,typeName:string="")
{
return html;
}
Everything works fine, unless The html which I am returning contains one package named Select2
This is what I use to add the html into this div it works very fine. Until I wanted to add the dynamic package.
What I mean is return html contains the package component like this:
itemhtml +="<select2 data-type='"+holderItem.itemType+"'
[data]='this.dropdownData."+holderItem.name+"'></select2>"
This just returns the plain text to the browser and doesn't work as expected.
What I want is the string to be turned into component or any other way which works and generates the select2 dropdown.
I have been trying to search so many things.But it doesn't works
This is good but I can not understand this And dynamiccomponentloading is deprecated.
Can anyone please give me an idea How can I resolve this problem? Any example would be a great.
As commented by #Devcon
Angular will sanitize pretty much everything so that is why you are
getting plain text. What you want to look into is ReflectiveInjector
and mainly ComponentFactoryResolver. The main idea is that components
need some other info(services, other components, etc) to be rendered,
so you use the Injector to get Dependency Injection refs then the
Component factory builds your component. You then insert this to a
ViewChild reference. There is a more complicated way of dynamically
making components that uses the compiler and requires a
ModuleWithComponentFactories, this is what angular actually uses.
And searching on the angular, I accept that angular should not be done this way.
As I have to create the fully dynamic page which must be rendered in html. I changed my json little bit and using the
ng-container and ng-template and using ngswitch
I made recursive call in the template it self and found its working very fine.
I get many advantages using this:
The HTML (I render dynamically) itself is in HTML, Code is clean and readable, easily maitainable.
The example given here is pretty much the same I have done.
https://stackoverflow.com/a/40530244/2630817
A small example is here:
<ng-template #itemsList let-itemsList>
<div *ngFor="let item of itemsList;let i = index">
<div [ngSwitch]="item.itemType">
<div class="form-group" *ngSwitchCase="'TEXT'">
<label>
{{item.label}}
</label>
<input id="{{item.name}}" value="{{item.value}}" type='text' class='form-control txtbox ui-autocomplete-input'/>
</div>
<div class="form-group" *ngSwitchCase="'PASSWORD'">
<label>
{{item.label}}
</label>
<input id="{{item.name}}" value="{{item.value}}" type='password' class='form-control txtbox ui-autocomplete-input'/>
</div>
<div class="form-group" *ngSwitchCase="'BOOLEAN'">
<label style='width:40%'>{{item.label}}</label>
<div class="form-group"><input id="{{item.name}}" type='checkbox' /></div>
</div>
<div class="form-group" *ngSwitchCase="'LABEL'">
<label class="form-control">{{item.label}}</label>
</div>
<div class="form-group" *ngSwitchDefault>
<label>
{{item.label}}
</label>
<select2 class="form-control" [data]="GetDropDowndata(item.holderId)" [cssImport]="false" [width]="300" [options]="GetOptions(item.type)"></select2>
</div>
</div>
</div>
You can load every you want in one div, you have to play with ng-template and ng-content.
First you have to create one directive:
import {Directive, ViewContainerRef} from '#angular/core';
#Directive({
selector: '[dynamic]',
exportAs: 'dynamicdirective'
})
export class DynamicDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
After you have to put it in some ng-template like:
<p>
page works!
</p>
<ng-template #sider=dynamicdirective dynamic></ng-template>
and use it like
import {Component, ComponentFactoryResolver, OnInit, ViewChild} from '#angular/core';
#Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.css']
})
export class PageComponent implements OnInit {
#ViewChild('sider')
sider;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
ngOnInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SomeComponent);
this.sider.viewContainerRef.createComponent(componentFactory);
});
}
}
and normally will see you component loaded at the place of you ng-template (you can call https://angular.io/api/core/ViewContainerRef#clear if you want to reset your view)
I already play with this, you can find some code here https://github.com/nicearma/dynamic
I thought to leave this here for anyone who encounters the same issue in the future.
If you don't want to bother manually instantiating components with ComponentFactory as the other answers suggest, you can also use a library I wrote for the explicit purpose of loading components into dynamic content: ngx-dynamic-hooks.
You can give it any string that contains the selector of a desired component and it will automatically be loaded in its place. You can even load components by other text patterns other than just their selectors! See it in action in this Stackblitz.
There's a lot more bells and whistles, if you need them. In the link above, you'll find a fairly detailed documentation that should set you up easily.

Ngrx Store - Should all UI changes be in store?

I'm using Angular 2 with Ngrx and Angular Material.
Are all UI changes like dropdown or dialog supposed to be in store?
For example:
<button md-button [mdMenuTriggerFor]="menu">Menu</button>
<md-menu #menu="mdMenu">
<button md-menu-item>Item 1</button>
<button md-menu-item>Item 2</button>
</md-menu>
Should I support dropdown in the store?
Next example:
<md-sidenav-container class="example-container">
<md-sidenav #sidenav class="example-sidenav">
Jolly good!
</md-sidenav>
<div class="example-sidenav-content">
<button type="button" md-button (click)="sidenav.open()">
Open sidenav
</button>
</div>
</md-sidenav-container>
Can I use open method? Maybe I should create component that wrap sidenav in component that operate only on input Input() open;?
For me, there are generally 3 questions I ask.
Are the UI changes isolated to the component and do not affect other components?
Do the UI changes need to be persisted when the view returned to after have been destroyed?
For debugging purposes do you want to track this state (possibly in order to recreate the view of the end user?)
If you answered NO to these 3 questions then you probably do NOT need to put the state of that component in the store.
By not coupling something the store, you make it more modular, easier to test and reusable.
You should use the store to keep state and the UI should be bound to that state. You can then keep Observable to that state hierarchy or subscribe to those state changes and update the UI accordingly and dynamically.
This is what I had in mind:
import { Component } from "#angular/core";
import { Store } from "#ngrx/store";
// Other relevant imports ....
#Component({
selector: 'some-comp',
templateUrl: 'some-comp.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SomeComponent implements OnDestroy {
employeeObs$:Observable<EmployeeInfo>;
constructor( private store: Store<IAppState>) {
this.employeeObs$ = this.store.select('employee')
.map((next: IEmployeeState) => next.info);
}
// ...
}
And then on html template something like:
<!-- Some other regular DOM
...
...
-->
<!-- Conditional DOM based on state -->
<div *ngIf="employeeObs$ | async as employeeInfo">
<div type="text">{{employeeInfo.name}}</div>
</div>
<!-- Some other regular DOM
...
...
-->
The conditional tag will only show if Observable has data...

Wrapping content children in a dynamic parent tag with Angular 2

What I want to do
I want to create a reusable Angular 2 button component that may render as an <a /> tag or an <input /> tag depending on an input to the component called type. I want the button component to accept content children which will be rendered as the button label.
To illustrate: an Angular template that invokes my button component like so:<button>Hello</button> should render as <a>Hello</a> in the DOM. However, if a property type="submit" is set (e.g. <button type="submit>Hello</button>) then the output in the DOM should be <input type="submit">Hello</input>.
To further clarify, if I were using React I could create [an overly simplified version of] this component with:
const Button = ({ type, children }) =>
type === "submit"
? <input type="submit">{children}</input>
: <a>{children}</a>
Where I'm stuck
Creating an Angular component that displays content children using <ng-content /> was relatively straightforward. However, I'm yet unable to render those children inside a dynamically chosen tag - either <a /> or <input /> depending on the value of the type property.
What I've tried
I initially tried to use <ng-content /> inside an ngIf or ngSwitch directive, only to find out that <ng-content /> can appear at most once in any given template (unless it’s qualified with a selector). However, I want to output all content children so selectors are not helpful.
I keep finding references to DynamicComponentLoader, but that appears to be deprecated.
I've seen the ContentChildren decorator which would allow my button component to access the content children being passed to it, but I'm not sure how to then take those children and inject them into my component template.
I came across NgTemplateOutlet, which seems like it might help me switch between two entirely different templates (one w/ the <a /> tag and one with the <input /> tag). However that's marked as “Experimental” and I’m having trouble understanding the usage.
Any help would be greatly appreciated!!
#Component({
selector: 'adapting-button',
template: `
<a *ngIf="type !== "submit">{{value}}</a>
<input *ngIf="type === "submit" type="submit" [value]="value">
`,
})
export class AdaptingButtonComponent {
#Input() type: any;
#Input() value: any;
}
#Component({
selector: 'app-root',
templateUrl: `
<adapting-button [type]="'submit'" [value]="Hello"></adapting-button>
`,
})
export class AppComponent {
title = 'app works!';
}

Categories

Resources