Building a wrapper directive (wrap some content / component) in angular2 - html

I'm pretty new building directives with Angular2. What I want is to create a popup directive that will wrap the content with some css classes.
Content
Content can be pure text and headers like:
<div class="data">
<h2>Header</h2>
Content to be placed here.
</div>
Then I want to give this a directive attribute like: popup
<div class="data" popup>
<h2>Header</h2>
Content to be placed here.
</div>
What the directive should do, is to wrap the div inside, lets say:
<div class="some class">
<div class="some other class">
<div class="data">
<h2>Header</h2>
Content to be placed here.
</div>
</div>
</div>
The case i described so far, is this a attribute or structural directives.
import { Directive, ElementRef, HostListener, Input } from '#angular/core';
#Directive({
selector: `[popup]`
})
export class PopupDirective {
}

The other answer is related but different.
For something closer, see this: How to conditionally wrap a div around ng-content - my solution is for Angular 4, but the linked question has some hints about how this might be doable for Angular 2.
I solved this problem with a component and a directive combined. My component looks something like this:
import { Component, Input, TemplateRef } from '#angular/core';
#Component({
selector: 'my-wrapper-container',
template: `
<div class="whatever">
<ng-container *ngTemplateOutlet="template"></ng-container>
</div>
`
})
export class WrapperContainerComponent {
#Input() template: TemplateRef<any>;
}
and my directive like this:
import { Directive, OnInit, Input, TemplateRef, ComponentRef, ComponentFactoryResolver, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[myWrapperDirective]'
})
export class WrapperDirective implements OnInit {
private wrapperContainer: ComponentRef<WrapperContainerComponent>;
constructor(
private templateRef: TemplateRef<any>,
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) { }
ngOnInit() {
const containerFactory = this.componentFactoryResolver.resolveComponentFactory(WrapperContainerComponent);
this.wrapperContainer = this.viewContainerRef.createComponent(containerFactory);
this.wrapperContainer.instance.template = this.templateRef;
}
}
To be able to load your component dynamically, you need to list your component as an entryComponent inside your module :
#NgModule({
imports: [CommonModule],
declarations: [WrapperContainerComponent, WrapperDirective],
exports: [WrapperContainerComponent, WrapperDirective],
entryComponents: [WrapperContainerComponent]
})
export class MyModule{}
so the HTML in the end is:
<some_tag *myWrapperDirective />
Which renders as:
<my-wrapper-container>
<div class="whatever">
<some_tag />
</div>
</my-wrapper-container>

You can achieve this with a component attribute selector and Angular 2 Content Projection <ng-content>
#Component({
selector: 'my-app',
template: `
<div class="app">
<div class="data" myWrapper>
<h2>Header</h2>
Content to be placed here.
</div>
</div>
`
})
export class AppComponent {}
#Component({
selector: '[myWrapper]',
template: `
<div class="my-class">
<div class="my-sub-class">
<ng-content></ng-content>
</div>
</div>
`
})
export class MyComponent {
}

Related

What is the purpose of <app-control-message> in angular 8 and how it is used?

I need to create a button for uploading a file in my form. I am using tag in my html but it is throwing an error : Can't bind to 'control' since it isn't a known property of 'app-contorl-message'.
Here is my html code -
<div class="col-lg-6">
<div class="form-group">
<div class="custom-file">
<input *ngIf="mode == 'CREATE'" type="file" (change)="onFileSelect($event)"
accept=".jpg,.jpeg,.png"/>
<app-contorl-message [control]="form.get('file')"></app-contorl-message>
</div>
</div>
</div>
here is def of onSelect($event) :
onFileSelect(event) {
this.form.get('file').setValue(event.srcElement.files);
this.fileData = event.srcElement.files;
console.log(event);
}
Thanks in advnace!
In your AppControlMessageComponent you need to create an #Input() named control. To learn more about inputs and output, visit: https://angular.io/guide/inputs-outputs
app-control-message.component.ts
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-app-control-message",
templateUrl: "./app-control-message.component.html",
styleUrls: ["./app-control-message.component.css"]
})
export class AppControlMessageComponent implements OnInit {
#Input() control; // Input whose value parent component will provide
constructor() {}
ngOnInit() {}
}

Angular extends parent component HTML

I know this is an old question but I cant find a simple answer and it seems so strange.
I have a father component with its own html and several component that extends this one.
I need something like this:
Father's HTML template
<p class="father"> somethings </p>
<child-component></child-component> <-- some kind of angular tag
class ChildComponent extends FatherComponent
<p> Im a Child </p>
Result rendering Child
<p class="father"> somethings </p>
<p> Im a Child </p>
Is this so hard to get?
UPDATE AS ASKED
#Component({
selector: 'BaseComponent',
templateUrl: 'BaseComponent.html',
})
export class BaseComponent implements OnInit
{
...
}
#Component({
selector : 'ChildBaseComponent ',
templateUrl : 'ChildBaseComponent .html',
})
export class ChildBaseComponent extends BaseComponent
{
....
}
I think what you're looking for here is content projection. You can find plenty of articles about this topic. I modified you examples slightly
#Component({
selector: 'BaseComponent',
template: `
<p class="father"> somethings </p>
<ng-content></ng-content>
`,
})
export class BaseComponent implements OnInit
{
...
}
#Component({
selector : 'ChildBaseComponent',
templateUrl : `
<BaseComponent><p> Im a Child </p></BaseComponent>`,
})
export class ChildComponent
{
#ViewChild(BaseComponent) public baseComponent: BaseComponent
}
The thing is Angular doesn't inherit metadata of the parent class, it overrides it.
There plenty of tools angular provides to deal with code sharing. You can always access your parent component from the child thanks to ViewChild decorator.
And as the answer to your question - no, there is no such tag.

How can we create modal pop-up in angular from scratch

Need to create a modal popup on button click,
is there any way to make it from scratch without adding any extra dependency for it,
with the below method, I am getting the fully functional modal, but have no confidence whether this is a good way in angular or not,
please suggest
HTML
<button class="hl-sort" (click)="openSortingModal()">
Sort
</button>
<div class="modal-container" *ngIf="modalContent">
<h1>i am modal content</h1>
<button (click)="closeModal()">Close</button>
</div>
components.ts
modalContent = false;
openSortingModal(){
this.modalContent = true;
console.log('clicked')
}
closeModal(){
this.modalContent = false;
}
If you don't want to add any extra dependency, I suggest you create a re-usable component using HTML + CSS as follows:
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-modal',
template: `
<div #myModal class="container">
<div class="content">
<p>Some content here...</p>
<button (click)="close()">Close</button>
</div>
</div>
`,
styleUrls: ['./modal.component.css']
})
export class ModalComponent {
#ViewChild('myModal', {static: false}) modal: ElementRef;
open() {
this.modal.nativeElement.style.display = 'block';
}
close() {
this.modal.nativeElement.style.display = 'none';
}
}
Then use it in your container:
import { Component, ViewChild } from '#angular/core';
import { ModalComponent } from './modal/modal.component';
#Component({
selector: 'my-app',
template: `
<app-modal #modal></app-modal>
<p>
Open a Pure HTML + CSS with Angular
</p>
<button (click)="openModal()">Open Modal</button>
`,
styleUrls: []
})
export class AppComponent {
#ViewChild('modal', {static: false}) modal: ModalComponent
openModal() {
this.modal.open();
}
}
See a working example here: https://stackblitz.com/edit/angular-modal-html-css
I hope it helps!
If you do not want to reinvent the wheel, you can use this
https://ng-bootstrap.github.io/#/components/modal/examples
well, if you want to "re-invent the wheel", don't forget close the modal when you click outside
improving the Luixaviles's answer, the component modal can be like
<div #myModal class="container" (click)="tryClose()">
<div #content class="content">
<ng-content></ng-content>
</div>
</div>
Well, you see that I make a function "tryClose" if you click on the div "myModal", this function check if we click but we don't click inside "content"
tryClose() {
const clickTarget = event.target as HTMLElement;
if (!(this.content.nativeElement as HTMLElement).contains(clickTarget))
this.close();
}
Using <ng-content> allow us write in app.component some like
<app-modal #modal>
<p>Some content here...</p>
<button (click)="modal.close()">Close</button>
</app-modal>
<p>
Open a Pure HTML + CSS with Angular
</p>
<button (click)="modal.open()">Open Modal</button>
The rest of code in modal component is simple:
export class ModalComponent {
#ViewChild("myModal", { static: false }) modal: ElementRef;
#ViewChild("content", { static: false }) content: ElementRef;
open() {
this.modal.nativeElement.style.display = "block";
}
close() {
this.modal.nativeElement.style.display = "none";
}
}
See the Luixaviles's forked stackblitz
Update a simple stopPropagation makes the thinks easer
<div #myModal class="container" (click)="close()">
<div (click)="$event.stopPropagation()" class="content">
<ng-content></ng-content>
</div>
</div>
Obviously it is a good idea to create your own component rather using a third party. Just make sure, your popup must be a reusable or dynamic popup.

Hide elements using Typescript

I'm new to Typescript and Angular Material. I want to hide elements inside elements like this.
<div id="abc">
<div id="123">
<p>Hello!</p>
</div>
<p>World</p>
</div>
<div id="def">
<p>Hello World</p>
</div>
I want to hide div block(id:123).I tried in this way.
var formElement = <HTMLFormElement>document.getElementById('123');
formElement.style.display='block';
It gets an error saying Cannot read property 'style' of null.... How can I solve this problem.
This is not the way to hide elements in Angular. Bind your element's style attribute to a boolean, like this:
<form [style.display]="isVisible ? 'block' : 'none'">... contents</form>
And in your component class:
this.isVisible = false; // whenever you need to hide an element
Or you can use *ngIf:
<form *ngIf="isVisible">... contents</form>
Please, note that *ngIf removes the node and its children completely from the DOM tree completely if the conditions turns to false, and fully recreates them from scratch when the condition turns true.
You can access the dom element using ViewChild with #localvariable as shown here,
import {Component, NgModule,ViewChild,ElementRef} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
#Component({
selector: 'my-app',
template: `
<div id="abc">
<div #id id="123">
<p>Hide!</p>
</div>
<p>World</p>
</div>
<div id="def">
<p>Hello World</p>
</div>
`,
})
export class App {
name:string;
#ViewChild('id') el:ElementRef;
constructor() {
this.name = `Angular! v${VERSION.full}`
}
ngAfterViewInit()
{
this.el.nativeElement.style.display='none';
console.log(this.el.nativeElement);
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
#Component({
selector: 'my-app',
template: `
<div #id>
<p>Hide This!</p>
</div>
<div>
<p>Hello World</p>
</div>
`,
})
export class AppComponent {
#ViewChild('id') id:ElementRef;
constructor() {}
ngOnInit()
{
this.id.nativeElement.hidden=true;
}
}
The simplest way to hide elements using DOM is
document.getElementById('123').hidden = true;
in typescript.

Angular 2 Component property shadowing

I have declared one property called foobar in the Angular 2 Foobar component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-foobar',
templateUrl: './foobar.component.html',
styleUrls: ['./foobar.component.css']
})
export class FoobarComponent implements OnInit {
foobar= 'test';
ngOnInit() {
}
}
then in foobar.component.html file I output that property like this:
<div>
<br>
{{foobar}}
</div>
and it outputs word test, however, once I have added element with id foobar:
<div>
<br>
{{foobar}}
<br>
<input type="text" #foobar>
</div>
I'm no longer able to get value from the component and it outputs [object HTMLInputElement], because it was shadowed by the input field id.
How can I get value from the component without changing component property name and input field id?
You can try this
<div>
<br>
{{ this['foobar'] }}
<br>
<input type="text" #foobar>
</div>
but it is not documented and may break in future releases
Try with two-way databinding:
<div>
<br />
{{foobar}}
<br />
<input type="text" [(ngModel)]="foobar">
</div>`
To accomplish that you need the module: #angular/forms in your package.json and donĀ“t forget to import it to your app-module:
import {FormsModule} from "#angular/forms";
#NgModule({
declarations: [ ... ],
imports: [
FormsModule,
...
]
...
})
export class AppModule{ ... }