1
votes

I am using the Angular CLI v1.0.0-beta.19-3 to scaffold/build an Angular 2 website. I generate my component as follows ng g component file-upload.

I noticed that the generated component file-upload.component.ts looks like the following.

@Component({
  selector: 'file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.css']
})
export class FileUploadComponent implements OnInit {
 ...
}

I need to use this component, ng2-file-upload, inside my own component. So I try to add the directives property on the @Component decorator, however, I get an error in VS Code.

@Component({
  selector: 'file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.css'],
  directives: [ FILE_UPLOAD_DIRECTIVES ]
})
export class FileUploadComponent implements OnInit {
 ...
}
[ts] Argument of type '{ selector: string; templateUrl: string; styleUrls: string[]; directives: undefined[]; }' is not assignable to parameter of type 'Component'.
       Object literal may only specify known properties, and 'directives' does not exist in type 'Component'.

And my imports look like the following.

import { Component, OnInit, Input } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
import { FileSelectDirective, FileDropDirective, FileUploadModule, FileUploader, FileUploaderOptions } from 'ng2-file-upload';

When I run the app ng serve, I see the following error reported in the console.

zone.js:388 Unhandled Promise rejection: Template parse errors:
Can't bind to 'uploader' since it isn't a known property of 'div'. ("
  [ngClass]="{'nv-file-over': hasBaseDropZoneOver}"
  (fileOver)="fileOverBase($event)"
  [ERROR ->][uploader]="uploader"
  class="well my-drop-zone">

Here's my dependencies in package.json.

{
  "name": "app-www",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "ng serve",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~2.1.1",
    "@angular/compiler": "~2.1.1",
    "@angular/core": "~2.1.1",
    "@angular/forms": "~2.1.1",
    "@angular/http": "~2.1.1",
    "@angular/platform-browser": "~2.1.1",
    "@angular/platform-browser-dynamic": "~2.1.1",
    "@angular/router": "~3.1.1",
    "angular2-cookie": "^1.2.5",
    "bootstrap": "^3.3.7",
    "core-js": "^2.4.1",
    "jquery": "^3.1.1",
    "lodash": "^4.16.6",
    "ng2-bootstrap": "^1.1.16",
    "ng2-file-upload": "^1.1.2",
    "rxjs": "5.0.0-beta.12",
    "ts-helpers": "^1.1.1",
    "zone.js": "^0.6.25"
  },
  "devDependencies": {
    "@types/jasmine": "^2.2.30",
    "@types/lodash": "^4.14.38",
    "@types/node": "^6.0.45",
    "angular-cli": "1.0.0-beta.19-3",
    "codelyzer": "1.0.0-beta.1",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "1.2.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-cli": "^1.0.1",
    "karma-jasmine": "^1.0.2",
    "karma-remap-istanbul": "^0.2.1",
    "protractor": "4.0.9",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "~2.0.3",
    "webdriver-manager": "10.2.5"
  }
}

The doc says to use this import statement.

import { FILE_UPLOAD_DIRECTIVES } from 'ng2-file-upload';

But in VS Code, it says the following.

[ts] Module '"/Users/jwayne/git/app-www/node_modules/ng2-file-upload/ng2-file-upload"' has no exported member 'FILE_UPLOAD_DIRECTIVES'.

What's even more interesting, is that I created my own login component like the following.

@Component({
  selector: 'login-component',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
 ...
}

In order to use this in another component, I don't even have to reference it as a directive or anything like that anymore. For example, I have a default component that uses this login component. The HTML looks like the following.

<login-component></login-component>

And the code looks like the following (note no directive property for the component decorator).

@Component({
  selector: 'app-default',
  templateUrl: './default.component.html',
  styleUrls: ['./default.component.css']
})
export class DefaultComponent implements OnInit, OnDestroy {
 ...
}

Although the app.module.ts file is modified now.

import { LoginComponent } from './login/login.component';
import { DefaultComponent } from './default/default.component';
@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    DefaultComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      { path: 'default', component: DefaultComponent },
      { path: 'main', component: MainComponent, canActivate: [ UserAuthGuard ] },
      { path: '', redirectTo: '/default', pathMatch: 'full' },
      { path: '**', redirectTo: '/default' }
    ])
  ],
  providers: [ 
    UserAuthGuard
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

So, there's a lot of stuff going on, but to start off, what happened to the directives property for the component decorator?

2

2 Answers

2
votes

It seems the docs were using an older RC version of Angular. In the meantime the angular team introduced the concept of NgModules to better structure your app and to avoid repeating your used directives. So you no longer declare the directives used in the Component decorator. For details on this, please have a look at the angular docs on NgModule.

The library you are using also adapted to this but probably the docs didn't change yet. Check their demo code and this commit in the repository to see how you need to import the module now and also have a look at the API changes.

2
votes

The key to solving this problem was importing the directives in app.module.ts and then declaring them with @NgModule. Here's a snippet of app.module.ts.

import { FileSelectDirective, FileDropDirective} from 'ng2-file-upload';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
    FileSelectDirective, FileDropDirective
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      { path: 'default', component: DefaultComponent },
      { path: 'main', component: MainComponent, canActivate: [ UserAuthGuard ] },
      { path: '', redirectTo: '/default', pathMatch: 'full' },
      { path: '**', redirectTo: '/default' }
    ])
  ],
  providers: [ ... ],
  bootstrap: [AppComponent]
})
export class AppModule { }

In my component, I still had to import the directives. Here's another snippet.

import { Component, OnInit, Input } from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
import { 
  FileSelectDirective,
  FileDropDirective, 
  FileUploader, 
  FileUploaderOptions 
} from 'ng2-file-upload';
import * as _ from 'lodash';
import { LoginService } from '../service/login.service';

@Component({
  selector: 'file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.css']
})
export class FileUploadComponent implements OnInit {
  //body omitted
}