Hiển thị các bài đăng có nhãn angular. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn angular. Hiển thị tất cả bài đăng

How to download a file in Angular2

There are 2 ways I use to download a file with Angular2 or greater. For this example, we will use a Java REST service.

The first approach would be taking advantage of the HTTP download, where the Angular side will just call a URL that will initiate the download.

Here's the web service's code

// The download resource
@GET
@Path("/exportCSV")
Response exportCSV();

@Override
public Response exportCSV() throws IOException {
ResponseBuilder builder = Response.ok();
builder.entity(tallySheetApi.exportCSV(httpServletResponse));

return builder.build();
}

public void exportCSV(HttpServletResponse response) throws IOException {
// write result to csv file
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, HEAD");
response.setContentType("text/csv");
response.addHeader("Content-disposition", "attachment;filename=\"animes.csv\"");

writeToCSV(response.getOutputStream(), listValuesHere);

response.flushBuffer();
}

private void writeToCSV(ServletOutputStream servletOutputStream, List<Anime> animes) throws IOException {
Writer writer = new BufferedWriter(new OutputStreamWriter(servletOutputStream));

for (Anime anime : animes) {
writer.write(anime.getTitle());
writer.write(CSV_DELIMITER);
writer.write(anime.getReleaseDate());
writer.write(CSV_DELIMITER);
writer.write(anime.getRating());

writer.write(System.getProperty("line.separator"));
}

writer.close();
}

In the Angular part, we need to call the method below that will redirect to the url we defined above.

exportCSV() {
window.location.href = this.apiUrl + this.RESOURCE_URL + '/exportCSV';
}

The problem with this approach is that we cannot send security header in the request. To solve that issue we need to update both our api and the way we handle the response in Angular.

public class ByteDto {

private String fileContent;

public String getFileContent() {
return fileContent;
}

public void setFileContent(String fileContent) {
this.fileContent = fileContent;
}

}

// The download resource
@GET
@Path("/exportCSV")
Response exportCSV();

@Override
public Response exportCSV() throws IOException {
ResponseBuilder builder = Response.ok();
builder.entity(tallySheetApi.exportCSV());

return builder.build();
}

public ByteDto exportCSV() throws IOException {
ByteArrayOutputStream baos = writeToCSV(listValuesHere);

ByteDto byteDto = new ByteDto();
byteDto.setFileContent(Base64.getEncoder().withoutPadding().encodeToString(baos.toByteArray()));

return byteDto;
}

private ByteArrayOutputStream writeToCSV(List<Anime> animes) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer writer = new BufferedWriter(new OutputStreamWriter(baos));

for (Anime anime : animes) {
writer.write(anime.getTitle());
writer.write(CSV_DELIMITER);
writer.write(anime.getReleaseDate());
writer.write(CSV_DELIMITER);
writer.write(anime.getRating());

writer.write(System.getProperty("line.separator"));
}

writer.close();

return baos;
}
In this approach, we save the byte array in a DTO instead of writing it in the outputStream. Note that we needed to encode the byte array as Base64, otherwise we will have a problem during deserialization. It also requires some additional work on the Angular side.

exportCSV() {
this.http.get<any>( this.apiUrl + this.RESOURCE_URL + '/exportCSV', { params: this.params } ).subscribe( data => {
console.log( 'downloaded data=' + data.fileContent )
var blob = new Blob( [atob( data.fileContent )], { type: 'text/csv' } )
let filename = 'animes.csv'

if ( window.navigator && window.navigator.msSaveOrOpenBlob ) {
window.navigator.msSaveOrOpenBlob( blob, filename )
} else {
var a = document.createElement( 'a' )
a.href = URL.createObjectURL( blob )
a.download = filename
document.body.appendChild( a )
a.click()
document.body.removeChild( a )
}
} )
}
In this version we needed to decode the byte array from the response and use the msSaveOrOpenBlob method from windows.navigator object. We also need to create a tag on the fly to invoke the download.

Encode image and display in html

To encode an image file(eg. png, jpg, gif) we will need the apache commons-codec library.
To avoid it in a maven project:

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>

Encode a file:

public static String encodeFileToBase64Binary(File file) throws IOException {
String encodedFile = null;
try (FileInputStream fileInputStreamReader = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
fileInputStreamReader.read(bytes);
encodedFile = org.apache.commons.codec.binary.Base64.encodeBase64String(bytes);
}

return encodedFile;
}

Prepare the image:

String strImage = encodeFileToBase64Binary(new File(imagePath));
String imageExt = FilenameUtils.getExtension(imagePath);

imagePath = "data:image/" + imageExt + ";charset=utf-8;base64, " + strImage;

Now you can use imagePath in your HTML.

<img src="#{imagePath}" />

Navigating in Guard in Angular5

There are some cases when we need to navigate in a Guard rather than in component. Why? Well, obviously you didn't have to define a component where you need to navigate. An example use case would be a route where you need to navigate given a certain role.

A sample route:

{
path: 'bride',
canLoad: [AuthGuard],
loadChildren: 'app/module/bride/bride.module#BrideModule'
}
A matching guard that navigates defending on role:

@Injectable()
export class AuthGuard implements CanActivate {

constructor( private router: Router, private route: ActivatedRoute ) {

}

canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | Promise | boolean {
console.log( 'registration.guard' )

if ( !KeycloakService.auth.loggedIn || !KeycloakService.auth.authz.authenticated ) {
return false;
}

//check group
if ( KeycloakService.hasGroup( 'Anime' ) ) {
this.router.navigate( ['/bride/postRegistration'] );

} else if ( KeycloakService.hasGroup( 'Vendor' ) ) {
this.router.navigate( ['/bride/postRegistration'] );
}

return true;
}
}

Multi-layout in Angular5

How to implement a multi-layout feature in Angular5. A common use case for this is when you have a different layout for your public and secured pages. For example, your secured page could have a menu on the left side. Or you have a page that doesn't require a layout.

Let's provide some examples. Let's say you have the following requirements:
  1. Plain pages that don't require any layout.
  2. Public pages.
  3. Secured pages.
The core feature that we need to set is the router configuration. 
  1. First, the app.component content must only be the router-outlet tag.
  2. We need to create a module for the layout components. Why we need a separate module for the layout? It's because it's possible to use the layout on different modules. If we just make it part of a module, say app.module, then we cannot use it inside secret.module. 
The layout module will contain the public and secured layout components. These 2 components are just ordinary component with template defined in its HTML page. The main point here is that inside, HTML  tag must be defined. Remember we have another router in the app.component? The Router class has a way of dealing with this, by using the children property.

In this section, we will provide an example of how a route should be defined in app-routing.

Public pages layout:

{
path: '',
component: PublicPageLayoutComponent,
children: [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent}
]
}

Secured pages layout:

{
path: '',
component: SecuredSiteLayoutComponent,
children: [
{ path: '', component: AdminComponent, pathMatch: 'full' },
{ path: 'admin', component: AdminComponent}
]
}

Now, what if we have a lazy loaded module route? If we defined it like below, it will throw an error.

{
path: 'secret',
component: SecuredSiteLayoutComponent,
loadChildren: 'app/module/secret/secret.module#SecretModule'
}

To fix this, we must define a secret-routing.module and defines some routes similar to app-routing.

const routes: Routes = [
{
path: '',
component: SecuredSiteLayoutComponent,
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent }
]
},
{
path: 'postRegistration',
component: SecuredSiteLayoutComponent2,
children: [
{ path: '', component: PostRegistrationComponent }
]
}
];

Basically, following the same logic of using the children property.

As a bonus, we are adding a guard that navigates depending on the role of a newly registered user. I used this to redirect the user to a configuration page.
@Injectable()
export class RegistrationGuard implements CanActivate {

constructor( private router: Router, private route: ActivatedRoute ) {

}

canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | Promise | boolean {
console.log( 'registration.guard' )

if ( !KeycloakService.auth.loggedIn || !KeycloakService.auth.authz.authenticated ) {
return false;
}

//check group
if ( KeycloakService.hasGroup( 'Bride' ) ) {
this.router.navigate( ['/bride/postRegistration'] );

} else if ( KeycloakService.hasGroup( 'Vendor' ) ) {
this.router.navigate( ['/bride/postRegistration'] );
}

return true;
}
}
Here's a link on how I secured the pages: http://czetsuya-tech.blogspot.com/2017/11/secure-angular4-with-keycloak-role-or.html.

Angular-cli, node, npm most used commands

Below is a summary of the most used commands, specially when starting up or debugging an angular project tested on node server.


Note that to execute the commands below, you need to install node first.
// check node and npm versions
node --version
npm --version

// install the latest angular cli
npm install -g @angular/cli@latest

//install angular cli in dev dependencies
npm install --save-dev @angular/cli@latest

// install dependencies in package.json
npm install
// install node-sass for processing scss
npm install -g node-sass
// install specific version of dependency, for example typescript
npm install --save-dev typescript@2.6.2

// analyze available updated dependencies
ncu

// update package.json (change version in file) and dependencies
ncu -u

// update the local libraries
npm update

// check angular/cli version
ng --version

// clean the cache
npm cache clean --force

// verify the cache
npm cache verify

// remove unused dependencies
npm prune

// uninstall angular cli
npm uninstall -g @angular/cli

// remove node_modules and dist folder
rm -rf node_modules dist

// build the project
ng build --prod --aot=false

Notes:

  • Node and npm can be uninstalled in windows control panel.
  • Some commands are not available anymore in newer version of node like ncu. Replaced by npm update.

Secure Angular4 with Keycloak Role or Group

Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

This article will be an extension of an existing one: http://czetsuya-tech.blogspot.com/2017/10/how-to-secure-angular-app-using.html.

Normally in the front-end side (Angular), we are only concern whether a user is authenticated or not. Authorization whether a user has enough access to a given REST resource is configured in the api side. But what if we want to secure the front-end urls nonetheless? One of the many solution is to create a guard and either add an authorization code in canLoad or canActivate. In this article we will deal with canLoad, as we are lazy-loading our modules (please refer to Angular documentation for more information).

In this particular example, we are checking for the group claim tied to a user. Role would also do, but I normally used that on the REST api side.

Here are the steps:

  1. Create a permission-guard model that we will use when defining the route
    export interface PermissionGuard {
    Only?: Array string>,
    Except?: Array string>,
    RedirectTo?: string | Function
    }
  2. Add the route in your app-routing.module.ts file, and define the Permission model in the data attribute.
    {
    path: 'dashboard/employee',
    canLoad: [AuthGuard],
    loadChildren: 'app/module/dashboard/employee/employee.module#EmployeeModule',
    data: {
    Permission: {
    Only: ['employee'],
    RedirectTo: '403'
    } as PermissionGuard
    }
    }
  3. In our keycloak.service, add the following methods. It checks if a user is a member of a group specified in the PermissionGuard.
    static hasGroup( groupName: string ): boolean {
    return KeycloakService.auth.authz != null &&
    KeycloakService.auth.authz.authenticated
    && KeycloakService.auth.authz.idTokenParsed.groups.indexOf( "/" + groupName ) !== -1 ? true : false;
    }

    static hasGroups( groupNames: Array ): boolean {
    return groupNames.some( v => {
    if ( typeof v === "string" )
    return KeycloakService.hasGroup( v );
    } );
    }
  4. Going back to the auth-guard.service, let's modify the canLoad method to process the authorization. In here we use the previously defined method to check if the current logged user is a member of an specific group.
    canLoad( route: Route ): boolean {
    if ( KeycloakService.auth.loggedIn && KeycloakService.auth.authz.authenticated ) {
    this.logger.info( "user has been successfully authenticated" );

    } else {
    KeycloakService.login();
    return false;
    }

    this.logger.info( "checking authorization" );

    let data = route.data["Permission"] as PermissionGuard;

    if ( Array.isArray( data.Only ) && Array.isArray( data.Except ) ) {
    throw "Can't use both 'Only' and 'Except' in route data.";
    }

    if ( Array.isArray( data.Only ) ) {
    let hasDefined = KeycloakService.hasGroups( data.Only )
    console.log("hasDefined="+hasDefined);
    if ( hasDefined )
    return true;

    if ( data.RedirectTo && data.RedirectTo !== undefined )
    this.router.navigate( [data.RedirectTo] );

    return false;
    }

    if ( Array.isArray( data.Except ) ) {
    let hasDefined = KeycloakService.hasGroups( data.Except )
    if ( !hasDefined )
    return true;

    if ( data.RedirectTo && data.RedirectTo !== undefined )
    this.router.navigate( [data.RedirectTo] );

    return false;
    }
    }
  5. An additional bonus is having a directive that we can use in the UI side to hide / show an element when a user is either a member of a group or not
    import { Directive, OnInit, ElementRef, Input } from '@angular/core';

    import { KeycloakService } from 'app/core/auth/keycloak.service';

    @Directive( {
    selector: '[hasGroup]'
    } )
    export class HasGroupDirective implements OnInit {

    @Input( 'hasGroup' ) group: string;
    @Input( 'hasGroups' ) groups: string;

    constructor( private element: ElementRef, ) { }

    ngOnInit() {
    if ( KeycloakService.hasGroup( this.group ) ) {
    this.element.nativeElement.style.display = '';
    } else {
    this.element.nativeElement.style.display = 'none';
    }
    }
    }


Questions:
  1. What is 403? It's an additional route to redirect when a user is not authorized.
    { path: '403', component: ForbiddenComponent },

Note: This article is inspired by ng2-permission.

Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

How to install ng executable for Angular

I've just finished setting up a new unit and now I'm trying to download some of my old angular projects. To my disappointment, I've encountered a rather similar but different error from before, which unfortunately, I forgot what I did to solve :-(

So I'm blogging it now.

Right after installing node you'll have access to npm binary but not ng. To install the ng binary we run:

npm install -g @angular/cli

And then when I tried to run: ng serve, I got:
npm ERR! Error: EPERM: operation not permitted, scandir

Solution (run the following commands):

npm cache verify
npm install -g @angular/cli --force