Angular 1 directive inside angular 2 application - javascript

We created an Angular 2 application using this awesome Angular2 Seed which works very well. So the question that I have is, how can I upgrade this Angular 1 directive:
import template from './sbgOutlineButton.html!text';
var app = angular.module('sbgOutlineButton', []);
app.directive('sbgOutlineButton', function() {
let link = function(scope, element, attributes) {
if (attributes.icon === undefined) {
let materialIcon = element.find('i.material-icons');
materialIcon.addClass('hidden');
}
};
return {
link: link,
restrict: 'E',
template: template,
replace: true,
transclude: true,
scope: { icon: '#' }
};
});
export default app;
So that I can use it in the following Angular 2 component:
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { UpgradeAdapter } from '#angular/upgrade';
#Component({
moduleId: module.id,
selector: 'test-page',
templateUrl: 'testpage.page.html',
styleUrls: ['testpage.page.css']
})
export class TestPage implements OnInit {
constructor() { }
ngOnInit() { }
}
Do you guys maybe have any idea on how I will be able to accomplish this? Is it even possible? Because a lot of the other articles that I have found during my research suggests that your "base" application should be Angular 1...
Thanks in advance.
Francois

How about converting your angular1 directive to angular2 directive?
NOTE: I don't know whether it will be useful or not but just have a look.
Look at the demo here : https://plnkr.co/edit/4Fhtm76iJl0aQmgjO7n0?p=preview
customeDirective.ts
import {Directive, Attribute,ElementRef,Renderer} from '#angular/core';
#Directive({
selector: '[myAttr]'
})
export class myDir {
constructor(#Attribute('icon') private icon,private el:ElementRef,private rd: Renderer){
console.log(this.icon);
if(this.icon===null){ //<--- you can play here as per your need.
console.log('icon is undefined');
}
else{
rd.setElementClass(el.nativeElement, 'myClass',true);
}
console.log(el.nativeElement);
}
}
AppComponent.ts
//our root app component
import {Component} from '#angular/core';
import {myDir} from 'src/customDirective';
#Component({
selector: 'my-app',
directives:[myDir],
template:
`
<style>
.myClass{
color:red;
background:yellow;
}
</style>
<div myAttr icon="myIcon">Angular2</div> <!-- icon attribute is present so it will add the class -->
<!-- OR USE BELOW HTML INSTEAD OF ABOVE -->
<div myAttr>Angular2</div> <!-- icon attribute is not present so it gives null -->
`
})
export class App {}

You need to upgrade to angular2 by using "#angular/upgrade": "2.0.0-rc.4",
Guide
Because a lot of the other articles that I have found during my
research suggests that your "base" application should be Angular 1...
It's if you have a already angular 1 project and you want to upgrade to one. Angular2 don't need angular1 as base
writing directive in angular2
import { Directive, ElementRef, Input } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
My First Attribute Directive
Highlight me!
import { Component } from '#angular/core';
import { HighlightDirective } from './highlight.directive';
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [HighlightDirective]
})
export class AppComponent { }
you Don't need to mix up angular1 into two..

Related

Angular - trying to use child component function in parent view but I'm gettting an error

When I use #ViewChild I get the error that the component is not defined.
When I use #ViewChildren I get the error that the function from that component is not a function.
I am new to using child components in Angular so I'm not sure why it's doing this when I do have the child component defined in the parent component and when it's clearly a function in the child component.
I don't want to have to define every function from the child in the parent or else what's even the point of using a separate component.
Child Component
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-mood',
templateUrl: './mood.component.html',
styleUrls: ['./mood.component.css']
})
export class MoodComponent implements OnInit {
moodColors = ['red', 'orange', 'grey', 'yellow', 'green'];
constructor() { }
ngOnInit(): void {
}
chooseMood() {
alert(this.moodColors);
}
}
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood is undefined")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChild('mood') mood: MoodComponent = new MoodComponent;
Parent Component (Relavant Part of Version with "ERROR TypeError: ctx_r3.mood.chooseMood is not a function")
import { Component, OnInit, ViewChild, ViewChildren } from '#angular/core';
import { ViewEncapsulation } from '#angular/core';
import { MoodComponent } from '../mood/mood.component';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class CalendarComponent implements OnInit {
#ViewChildren('mood') mood: MoodComponent = new MoodComponent;
Parent View
<h2 (click)="mood.chooseMood()"></h2>
You don't explicitly initialize view children via new.
Just use:
#ViewChild('mood') mood : MoodComponent;
If that doesn't work post a Stackblitz example which I can edit to resolve the issue.
Also, using ViewChild is more of an exception in Angular, and your use of it points to a probable design issue. More likely you child component should emit via an Output to the parent.
Regarding outputs, you can do something like this - though it is hard to give a precise answer without deeper knowledge of what you are trying to achieve:
export class MoodComponent implements OnInit {
#Input() moodId: string;
#Output() chooseMood = new EventEmitter<string>();
moodClicked(){
this.chooseMood.emit(moodId);
}
}
export class CalendarComponent implements OnInit {
moodChosen(string: moodId){
console.log(moodId);
}
}
// Calendar template:
<app-mood
moodId="happy"
(chooseMood)="moodChosen($event)"
></app-mood>
1 - you have to use this code
#ViewChild('mood') mood : MoodComponent;
when you are using #ViewChildren it will return list of items with the 'mood' name then you have to use this code
mood.first.chooseMood() ;
its better use ViewChildren when there is ngIf in your element
2- no need new keyword for initialize mood variable
it would be fill after ngOnInit life cycle fires

Problem calling one Angular component from another component

At work, I have run into a problem using Angular. I have this kind of Angular component:
#Component({
selector: 'foo',
templateUrl: 'foo.html'
})
export class FooComponent {
#Input() data: string;
content: string;
ngOnInit() {
this.content = this.data;
}
setValue(data) {
this.content = data;
}
}
This is initialized from my main Angular component in a code block such as this:
this.components = [FooComponent, BarComponent, BazComponent, QuuxComponent];
Now this works so far. But if I try to call the setValue() function with this.components[0].setValue("Hello world!"); I get an error "this.components[0].setValue is not a function."
What is the reason for this and how can I fix it?
This seems like a very very weird way to work with components in angular.
You really don't want to break encapsulation by calling methods inside one component from another component.
I personally haven't seen this kind of component referencing anywhere (and have doubts it is a correct approach).
There is no reason to duplicate the data property in the content.
You can pass values in the template. Or use a service if you don't have direct access to the template.
Here is a very basic example on how to modify data from the parent using a template and #Input.
app.component.ts
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
message = "I am a message from the parent";
}
app.component.html
<app-child [content]='message'></app-child>
child.component.ts
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
#Input("content") public content: string;
constructor() {}
ngOnInit() {}
}
child.component.html
<p>{{content}}</p>

How can I access an already transcluded ContentChild?

I have a angular component app-b that is used within a component app-a that is used in the app-component. The app-component has some content in app-a, app-a transcludes this with ng-content into app-b, app-b shows it with another ng-content - but how can I access this content within the component (and not it's template)?
I would think that ContentChild is the correct approach but appears to be wrong.
Example:
https://stackblitz.com/edit/angular-ddldwi
EDIT: Updated example
You cannot query by tag name with #ContentChild decorator. You can query either by template variable, component or directive selector.
app-a.component.html
<app-b>
<ng-content></ng-content>
<p #myContent>This is a content child for app-b.</p>
</app-b>
app-b.component.ts
import { Component, AfterContentInit, ContentChild } from '#angular/core';
#Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.css']
})
export class BComponent implements AfterContentInit {
#ContentChild('myContent') contentchild;
ngAfterContentInit() {
console.log(this.contentchild);
}
}
Live demo
I recommend sharing the data between components. For example, move your data (E.G. dummyName) into a service. Then add the service to each component (where you need the shared data).
Service:
import { Injectable } from '#angular/core';
#Injectable()
export class DataShareService {
public dummyName: String = 'one';
constructor() { }
}
Add the new service to app.module.ts:
providers: [DataShareService],
Child Component:
import { DataShareService } from './data-share.service';
import { Component } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
constructor(public ds: DataShareService) { }
toggle() {
this.ds.dummyName = this.ds.dummyName === 'one' ? 'two' : 'one';
}
}
Child Component template (html):
<p> {{ds.dummyName}}</p>
<button (click)="toggle()">Click Me to Toggle Value</button>
Parent Component:
import { Component, OnInit } from '#angular/core';
import { DataShareService } from './data-share.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(public ds: DataShareService) {}
displayNumber() {
return this.ds.dummyName === 'one' ? 1 : 2;
}
}
Parent Component template (html):
<p> this is the parent component:</p>
<div> value as string: {{ ds.dummyName }} </div>
<div> value as number: <span [textContent]="displayNumber()"></span></div>
<hr>
<p> this is the child component:</p>
<app-child></app-child>
Note! The child component toggle() function demonstrates how you can change the data. The parent component displayNumber() function demonstrates how to use the data, independent of it's display (I.E. as just pure data).
This appears to be impossible due to a bug in Angular:
https://github.com/angular/angular/issues/20810
Further reference:
https://www.reddit.com/r/Angular2/comments/8fb3ku/need_help_how_can_i_access_an_already_transcluded/

Angular Drag and Drop component

I try use this component in angular 4
https://github.com/jellyjs/angular2-file-drop
I have something like this
import { Component, OnInit } from '#angular/core';
import { FileDropDirective } from 'angular2-file-drop';
#Component({
selector: 'app-drag-and-drop',
template: `
<div fileDrop
[ngClass]="{'file-is-over': fileIsOver}"
[options]="options"
(fileOver)="fileOver($event)"
(onFileDrop)="onFileDrop($event)">
Drop file here
</div>
`,
directives: [ FileDropDirective ],
styleUrls: ['./drag-and-drop.component.scss']
})
export class DragAndDropComponent implements OnInit {
}
I have error that import FileDropDirective from path has no exported member 'FileDropDirective' and also error in line directives: [ FileDropDirective ],
that Object literal may only specify known properties, and 'directives' does not exist in type 'Component'.
I change
import { FileDropDirective } from 'angular2-file-drop;
to
import { FileDropModule } from 'angular2-file-drop;
and also
directives to providers and working fine

Semantic UI with Angular2 - How to set Sidebar settings from jQuery in a component?

I have an Angular2 application and I want to use Semantic UI. However, there are some jQuery configurations like below that I have to run after a component loaded:
$('#app .ui.sidebar')
.sidebar({context:$('#app')})
.sidebar('setting', 'transition', 'overlay')
It is not working by importing the js file in the head of index.html or writing it in a <script> tag inside of a component template. Is there a "typescript way" to do that or how can I use a js file inside of a component?
I found this link about using jQuery in directives, then I created a sidebar directive:
import {Directive, ElementRef, OnDestroy, OnInit, Input} from '#angular/core';
import {HostListener} from "#angular/core/src/metadata/directives";
declare var $: any
#Directive({
selector: '.ui.sidebar'
})
export class SidebarDirective implements OnInit, OnDestroy {
#Input() context: string;
constructor(private el: ElementRef) {
}
public ngOnInit() {
$(this.el.nativeElement)
.sidebar({context: this.context})
.sidebar('setting', 'transition', 'overlay');
}
public ngOnDestroy() {
}
}
Then, I used it in the template:
<div id="app">
<div context="#app" class="ui left vertical menu sidebar"></div>
<div class="pusher"></div>
</div>
I have spent quite some time to get this working although it is rather simple in the end. Hope to save you some time ...
There is no need to create a directive, you can use the jQuery command as you would use with JavaScript (described at https://semantic-ui.com/modules/sidebar.html#/usage). However, "$" has to be declared and the command has to be located in a TypeScript function ("toggle()"):
import {Component} from '#angular/core';
declare var $: any;
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
toggle() {
$('.ui.sidebar').sidebar('toggle');
}
}
The corresponding section of the template may look like this:
<div class="ui fixed inverted main menu">
<a (click)="toggle()" class="launch icon item">
<i class="content icon"></i>
<p style="padding-left:1em">Menu</p>
</a>
</div>
Don't forget to add jQuery to the scripts section of .angular-cli.json:
"scripts": [
"../node_modules/jquery/dist/jquery.js",
"../node_modules/semantic-ui-css/semantic.min.js"
],
I'm using Semantic UI 2.2.12 which already depends on jQuery 3.2.1. Angular version is 4.4.4 running on node.js 6.11.2.
import { Component, OnInit } from '#angular/core';
declare var $:any;
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app works!';
ngOnInit(){
$('#app .ui.sidebar')
.sidebar({context:$('#app')})
.sidebar('setting', 'transition', 'overlay') ;
}
}

Categories

Resources