Angular and Drupal 8

mine
Share this

Today I’m gonna talk about Angular and Drupal and how they can work together, one with a powerful front end and the other one with an easy and administrable backend. 

So first of everything, let’s put you on context, I am not an expert on angular, but thanks to Stackoverflow and the detailed guide from the Tour of heroes I have made something, there are more things to improve, so you can create a Fork on the git project and add something else. 

Check the GitHub project here

So, all the main guide from the angular configuration comes from the Tutorial: Tour of Heroes, a simple guide where you can find the basic concepts of angular. Also, on the same website, we find extras stuff like the httpClient and some forms. In other words, You can do all this just by following that guide. 

Check the Angular site here

Drupal is more about configurations, in the Github, you can find the feature task, with the content type and the Rest view. In any case, I will talk briefly about how to configure the Drupal site and the most important the services.yml, which has the responsibility to allow access from an external server. 

Check the Drupal site here

What I have done: 

This is an application that gets data from Drupal, render it in a menu and allow you to click on it, so after click on it, you will see the data reflected immediately. After all, that is what angular does that makes it so powerful.  

Also, we have an X icon to delete the rendered content, If I have time, I will set up a button to prevent deleting content. 

Once you click on a content you will see it loaded, and at the same time, you will be able to update that content. 

To finish, under the main menu, we have another form ready to create content. 
 

First: Prepare your army: 

For Drupal I didn't waste time, so Acquia Dev was enough. but wait, this wasn't the first move, the first thing that I did was to create the Angular site and once with it ready, I used Acquia Dev to create a Drupal site inside the Angular site. 

folders

In Drupal, I just download a couple of modules, create a content type (task), a REST view, configure some permissions for add, edit and delete any content, and the most important part was the configuration of CORS, inside the services.yml file, which is a copy of the default.services.yml file. 

Inside it, at the end, you will find the cors.config and this is how I set it up. 

 



  cors.config:
    enabled: true
    # Specify allowed headers, like 'x-allowed-header'.
    allowedHeaders: ['*']
    # Specify allowed request methods, specify ['*'] to allow all possible ones.
    allowedMethods: ['*']
    # Configure requests allowed from specific origins.
    allowedHeaders: ['http://josuevalrob.webfactional.com/']
    # Sets the Access-Control-Expose-Headers header.
    exposedHeaders: false
    # Sets the Access-Control-Max-Age header.
    maxAge: false
    # Sets the Access-Control-Allow-Credentials header.
    supportsCredentials: false

 

Careful, I waste a lot of time for a bad configuration in the services.yml, Something wrong here and you can break your Drupal site. 

Look that my     allowedHeaders: ['http://josuevalrob.webfactional.com/'] is connected to a real server. I tried to use http://localhost:4200/ in the development enviroment but it doesn't work. So for development enviroments, just let allowedHeaders: ['*']

In the Webservices modules section, I installed all the modules, and also downloaded the REST UI, to set up some configurations later. You need to enable these modules before to try the task feature. 

enable modulesrest configuration

Also, we need a content type, you can work with Articles if you want, but in my case, I create a new one, the Task content type and did a rest view to show the content, this will be easily consumed by the GET METHOD later. 

tasks viewtask view

You can check the services here 

So this is basically how everything should work on the Drupal site. Remember, use some  debug tools to see if you can do GET, POST, PATCH or DELETE method, I recommend you a full guide about it, independent from using angular, in here:  

With that guide and any debug tools for REST methods, check if your configuration in the Drupal site is working. 

Step 2: Let's invade Angular. 

In the Tour of heroes, you will find everything, so I will go directly to the different code. For example, in this case, we don't have heroes, we have tasks. And we are using a real HTTP client.

Check out the src/app directory, this is the main things. 

  • app-routing.module.ts:
  • app.component.html    
  • navigation
  • app.component.ts
  • task.service.ts
  • app.module.ts
  • task.ts
  • main-view
  • view-task

In the repository, you will find other components or services that I don't use. 

So now I will explain the different things: 

Theming: Thanks to Start Bootstrap, Simple sidebar, check it
PD: I'm using bootstrap CDN, so the assets are useless.

Do what they want:

Okay, before starting with the REST METHODS, let's see what Drupal wants. If you test your Drupal site with different debugs tools, you already know. For example:

debugging Drupal

Now see how our POST function works in the angular app, task.service.ts

 


addTask (task: Task): Observable<Task> {

  const url = `${this.mainUrl}/entity/node`;  

  const postReturn = this.http.post(url, task, httpHaljson);

  return postReturn
}

The URL and the httpHaljson are coming from the environment.ts file and they will change depending on your Drupal site. 

export const environment = {
  production: false,
  mainUrl : 'http://drupal.dd:8083',
  httpHaljson : {
		headers: new HttpHeaders({ 
		"X-CSRF-Token": "Qfnczb1SUnvOAsEy0A_xuGp_rkompgO2oTkCBOSEItM",
		"Authorization": "Basic Qfnczb1SUnvOAsEy0A_xuGp_rkompgO2oTkCBOSEItM", // encoded user/pass - this is admin/123qwe
	  // "Content-Type": "application/json"
		"Content-Type": "application/hal+json"
		})
	}
};


For the Authorization, Get the token at https://example.com/rest/session/token

The main part is the task that we send in the function, it has to be like this:

{
   "_links": {
     "type": {
       "href": "http://drupal.dd:8083/rest/type/node/task"
     }
   },
   "title": {
     "value": "I am a new task"
   },
   "type": {
     "target_id": "task"
   } 
}

I will explain how this is created in the navigation/navigation.component.ts

task.ts:

This class is the main one, is our hero. Why do I have extra fields like, type and link?. well because Drupal need that data to create some rest methods. 

export class Task {
  id: number;
  name: string;
  body: string;
  type: string;
  link: string
}

task.service.ts

I will do it by steps, is not so much different from the hero.service.ts, all the imports are the same, also the constructor, there are some different things, but I will explain it in the right time. 

GET Method: In the first try it works with this: 

 

		
  getTasks(): Observable<Task[]> {
          const url = `${this.mainUrl}/tasks`;
        return this.http.get<Task[]>(url)
        .pipe(
        tap(tasks => this.log(`fetched tasks`)),    
          catchError(this.handleError('getTasks', []))
        );        
  }

 

Simple is the same idea as we found in the hero tutorial. Also for printing the elements: 

 

<li *ngFor="let task of tasks" class="d-inline-block col-md-12">
                <a routerLink="/task/{{task.id}}" > {{task.name}}</a>
                <!-- <span class="close big"></span> -->
                <button class="close big" title="delete task"
                (click)="delete(task)">x</button>
</li>

 

This part have an important change, check the problem and the solution in our next blog:

GET task by idNothing new.   

 

getTask(id: number): Observable<Task> {

  const url = `${this.taskUrl}/${id}`;
  const returnGet = this.http.get<Task>(url);
  return returnGet
  .pipe(
        map(tasks => tasks[0]),
        tap(h => {
          const outcome = h ? `fetched` : `did not find`;
          this.log(`${outcome} hero id=${id}`);
        }),    
    catchError(this.handleError<Task>(`getTask id=${id}`))
  );
}

 


The interesting part here is in the view-task.component.ts, cus I needed to change the content, by clicking different tasks, so I needed to subscribe to the params: 

 

    ngOnInit(): void {
      this.route.params.subscribe(
          params => {
              const id = +this.route.snapshot.paramMap.get('id')
                this.taskService.getTask(id)
                  .subscribe(Task => this.task = Task);
          }
      );
    }

 


In this part I needed to ask on StackOverflow, so you can see the question here:

Also, there are some changes in the view-task.component.html cuz now I am working with tabs, and for that, I need materials, so I call them in the app.module.ts: 

 

import {MatTabsModule, MatInputModule, MatIconModule} from '@angular/material';

 

and bring them into the imports:

	
  imports: [

    BrowserModule,

    HttpClientModule,

    AppRoutingModule, 

    FormsModule, 

    BrowserAnimationsModule,

    MatTabsModule, 

    MatInputModule,

    MatIconModule

  ],

The reason is very simple, I want to show in the same component the task and have the possibility to edit it, so is divided into tabs. 

PATCH method:

This happened in the view-task.component.ts and in the view-task.component.html, in one part I created a form and in the other one, I receive the data from that form, all that is different from the hero tutorial. Cuz there I was working with one field. 

 

		<form (ngSubmit)="save(taskName.value, taskBody.value)" #taskForm="ngForm" class="example-form">
  <mat-form-field class="example-full-width">
    <label>Task Name</label>
    <input matInput [(ngModel)]="task.name" #taskName name="name">
  </mat-form-field>


  <mat-form-field class="example-full-width">
    <textarea matInput [(ngModel)]="task.body" #taskBody name="body"></textarea>

  </mat-form-field>
  <button type="submit" class="btn btn-success" >Save</button>
</form>

 

Look, that the name and body, pass through the ngSubmit function in the form, not just in the button, as we found in the hero tutorial. 

And this one is the important part. Remember how we need our task to send and consume by the Drupal site. So here I set up the task that will be sent later in the task.service.ts

 

		   save(name: string, body:string): void {
  let task: any = {
      _links: null,
      nid: null,
      title: null,
      body: null     
   };
   const id = +this.route.snapshot.paramMap.get('id')
   task.nid = {"": id};
   task._links = {type: {"href": "http://drupal.dd:8083/rest/type/node/task"} };
   task.title = {value: name};
   task.body = { "": body};


      this.taskService.updateTask(task, id)
            .subscribe();
   }

 

Look, I am sending the id from the URL. 

Here in, the save() function, I get that data, and at the same time, I give it to the task server as Drupal wanted. In this point a console.log(JSON.stringify(task)); is really useful to debug and see if you are creating exactly what drupal want. 

The result:

		
{

"_links":{"type":{"href":"http://drupal.dd:8083/rest/type/node/task"}},

"nid":{"":12},"title":{"value":"one"},

"body":{"":"<p>thrid</p>"}

}

I am using "Content-Type": "application/hal+json"

DELETE method: 

Was the easiest. It is the same as we found in the tour of heroes. Remember set up permissions on Drupal.

POST method:

This one change in the component that gets the data from the form, cuz like the PATCH method I need to create something specifically for Drupal. 

 

    onSubmit(name: string, body:string): void {
   let task: any = {
       _links: null,
       type: null,
       title: null,
       body: null     
    };
    
    task._links = {type: {"href": "http://drupal.dd:8083/rest/type/node/task"} };
    task.type = {target_id: "task"};
    task.title = {value: name};
    task.body = { "": body};
      
      this.taskService.addTask(task)
        .subscribe(task => {
          this.tasks.push(task);
          // console.log(JSON.stringify(task));

          this.getTasks();
        });
    }

 

At this point we can forget about the Id, Drupal assign an id for all the new data. 

In the service.ts we found or function addTask, and the important part here is our new route, look that the URL now is: https://example.com/entity/node

 

	addTask (task: Task): Observable<Task> {
  const url = `${this.mainUrl}/entity/node`; 
  return this.http.post(url, task, httpHaljson).pipe( 
    tap((task: Task) => this.log(`added task w/ id=${task.id}`)),
    catchError(this.handleError<Task>('addtask'))
  );
}
 

 

Now we are close to the end, and I still have some things to improve in this little application, and they are: 

Conclusions. 

This is a very simple exercise, but behind it, we found a really powerful way to create websites. Imagine a page where all the components are preloaded by Angular, and with the help of Drupal, we send simple data from the database. This means a significant reduction of time and data. 

If you want to improve the actual code, please go to the GitHub and make any change. Or leave a comment to see if I can change something! 

Comments

Submitted by Citiface Thu, 03/15/2018 - 09:54

Hola a todos, porque estoy realmente interesado en leer la publicación de este weblog para actualizarla de manera regular.
Contiene cosas bonitas. ¡Volveré, en tanto que he marcado esta página como preferida y lo he twitteado a mis
seguidores!

Feel free to visit my webpage - Citiface

Submitted by Bbs.inweke.com Sat, 03/17/2018 - 04:35

Ni siquiera sé cómo acabé aquí, mas pensé que esta publicación era buena.
No sé quién eres, pero definitivamente irás a un renombrado blogger si todavía no lo eres;) ¡Salud!
¡Volveré, puesto que he marcado esta página como favorita y lo he twitteado
a mis seguidores!

Here is my blog post - Bbs.inweke.com

Submitted by Generic cialis Mon, 03/26/2018 - 11:44

I’ve read several good stuff here. Definitely worth bookmarking for revisiting. I wonder how so much effort you put to create this kind of great informative site.

Submitted by StevenEscak Wed, 10/31/2018 - 15:35

Hello

Add new comment

The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.