作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
萨贾德·侯赛因·萨格尔的头像

Sajjad Hossain Sagor

Sajjad是一个专门开发主题的WordPress开发者, plugins, and WooCommerce add-ons. 他曾为世界各地的公司领导高预算项目,并为一个拥有200多人的WordPress网站建立了后端,000活跃用户和数百万YouTube订阅者. 他也是WordPress社区的核心贡献者.

Previous Role

全栈Web开发人员

Years of Experience

7

Share

在现代软件工程的背景下, 解耦——将应用程序分解成不同的部分——已经成为一种行业标准. 公司和软件工程师都喜欢解耦,因为它允许一个清晰的 separation of concerns 在应用程序的表示层(前端)和数据访问层(后端)之间. 这种方法通过允许多个团队并行开发来提高应用程序的效率,同时也提供了为每一方选择最佳技术的灵活性.

Given its modular nature, 解耦系统的独立组件可以用于缩放, modification, 或者随着系统需求的发展而彻底更换. 这种做法扩展到不同的数字平台, 包括电子商务等领域, online banking, community-driven portals, and social media.

虽然解耦系统提供了许多优点,但它也携带 potential drawbacks. 系统的通信发生在不同的模块或服务之间,可能会带来延迟, 这会降低系统性能. 此外,传统的 browser cookie 为单片应用程序设计的服务器端身份验证方法变得具有挑战性.

为了解决这些问题,开发人员可以利用如下协议 GraphQL, REST, and gRPC 促进良好的组件间通信, prevent delays, 并构建了身份验证的实现结构. 本教程演示了解耦应用可以在wordpress驱动的Angular应用中茁壮成长, 我们将使用GraphQL和 JWT一种流行的基于令牌的身份验证方法.

解耦系统中的有效通信:一个Angular-WordPress的例子

我们将使用headless构建一个博客应用程序 WordPress back end and an Angular front end. WordPress, a widely adopted, 健壮的内容管理系统, 是管理和服务博客内容的理想选择吗. 选择Angular是有策略的, 因为它允许动态内容更新而不需要重新加载整个页面, 这样可以加速用户交互. 这两层之间的通信将由GraphQL管理.

一个简单的、无保护的、解耦的博客应用的带有注解的架构

Initially, 该应用程序将被配置为获取博客帖子内容,并在列表中显示帖子标题给用户. 在它启动并运行之后,您将通过集成一个 JWT基于身份验证特性. 通过这种基于令牌的身份验证,您可以确保只有登录的用户具有访问权限. 未经认证的访问者将看到标题列表,但如果他们试图阅读完整的文章,则会提示登录或注册.

架构与注释的增强,解耦的博客应用程序

On the front end, 路由保护检查用户的权限,决定一条路由是否可以激活, HTTP模块实现HTTP通信. On the back end, GraphQL作为应用程序的通信媒介, 作为HTTP上的API接口实现.

注:复杂的问题 cybersecurity 宽泛的主题是否超出了本文的范围. 本教程的重点是通过有效的跨域解决方案集成不同的前端和后端, 利用GraphQL在Angular-WordPress应用中实现身份验证. This tutorial does not, however, 保证GraphQL的访问只限于已登录的用户, 因为实现这一点需要配置GraphQL来识别访问令牌, a task beyond our scope.

步骤1:设置应用程序的环境

这是这个项目的起点:

  1. Use a fresh or existing WordPress的安装 on your device.
  2. 以管理员身份登录WordPress,从菜单中选择 Settings/General. 在成员部分中,选择旁边的按钮 Anyone can register to enable this option.
  3. 与WordPress一起,您将使用 WPGraphQL plugin. 下载插件 WordPress插件目录 and activate it.
  4. 为了进一步扩展WPGraphQL插件的功能,我们还将使用 JWT身份验证 plugin. 它没有在WordPress的目录中列出,所以根据它的目录添加这个插件 instructions,确保定义一个密钥,详细信息见 readme.md. 没有一个插件将无法工作.
  5. Add 重新安装Angular to your local device. 然后使用该命令创建具有路由和CSS支持的工作空间和应用程序 my-graphql-wp-app -routing -style CSS.
    • 警告:本教程是使用Angular的第16版编写的. 用于Angular的后续版本, 您可能需要调整这些步骤和/或修改此处提供的文件名.

安装好WordPress后,简单博客网站的后端就准备好了.

步骤2:构建应用前端

在建立应用程序两端之间的通信之前,需要将所有部件准备就绪. In this step, 您将设置必要的元素:创建页面, add and set up routes, 并集成HTTP模块. 有了这些部分,我们就可以获取和显示内容了.

在安装过程中激活的WPGraphQL插件将使WordPress能够通过应用程序的GraphQL API公开数据. 默认情况下,GraphQL端点位于 YOUR-SITE-URL/graphql where YOUR-SITE-URL 替换为与WordPress安装相关的URL. 例如,如果站点URL为 example.com,则应用程序的GraphQL API端点为 example.com/graphql.

Create the App’s Pages

这个简单的应用程序最初只包含两个页面: posts (列出所有职位名称)及 post (显示整篇文章).

使用Angular的CLI方法生成应用的内容页. 使用你喜欢的终端应用,进入Angular的根目录并输入:

Ng生成组件帖子 && Ng生成组件后

但是如果没有渲染容器和路由,这些新页面是不可见的.

Add Routes

路由允许用户通过相应的URL或导航链接直接访问页面. 虽然新安装的Angular包含路由,但默认情况下不支持该特性.

要向应用中添加路由,请替换 src/app/app-routing.module.ts file with:

从“@angular/core”中导入{NgModule};
从“@angular/router”中导入{RouterModule, Routes};
import {PostComponent} from `./post/post.component';
导入{PostsComponent}./posts/posts.component';

const routes: Routes = [
  {path: 'post/:id', component: PostComponent},
  {path: 'posts', component: PostsComponent},
];

@NgModule( {
  imports: [ RouterModule.forRoot( routes ) ],
  exports: [RouterModule]
} )

导出类AppRoutingModule {}

在前面的代码中,我们为应用添加了两条路由:一条路由到 posts page, the other to the post page.

添加Router Outlet组件

要使用路由支持,我们需要 router-outlet 它使Angular能够在用户导航到不同的路由时渲染应用的内容页面.

使用你喜欢的代码编辑器,替换Angular的内容 src/app/app.component.html file with:


现在路由设置完成了. 但是在获取内容之前,我们必须设置HTTP模块中间件.

集成HTTP模块

为了为访问用户获取内容,页面需要向后端发送HTTP请求. 的内容 src/app/app.module.ts file with:

从“@angular/core”中导入{NgModule};
从“@angular/platform-browser”中导入{BrowserModule};
从“@angular/common/http”中导入{HttpClientModule}; 
导入{AppRoutingModule}./app-routing.module';
import {PostComponent} from `./post/post.component';
导入{PostsComponent}./posts/posts.component';
导入{AppComponent}./app.component';

@NgModule( {
  declarations: [
    AppComponent,
    PostComponent,
    PostsComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule, 
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
} )

导出类AppModule {}

With this code, 我们已经集成了Angular的原生HTTP模块, 它使我们能够发送HTTP请求来获取内容.

设置获取和显示内容

现在让我们开始在博客页面上获取和显示内容.

The Posts Page

的内容 src/app/posts/posts.component.ts file with:

从“@angular/core”中导入{Component};
从“@angular/common/http”中导入{HttpClient};

@Component( {
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
} )

导出PostsComponent类
{
  posts = [];

  构造函数(私有http: HttpClient) {}

  Async send_graphql_request(query: string)
  {    
    Const response = await this.http.post( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()

    return response;
  }

  ngOnInit()
  {
    this.send_graphql_request(
      `query GetPostsQuery {
        posts(where: {orderby: {field: DATE, order: DESC}}) {
          nodes {
            databaseId
            featuredImage {
              node {
                sourceUrl
              }
            }
            title
            excerpt
          }
        }
      }`
    )
    .then( response =>
    {
      if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
      {
        this.posts = response.data.posts.nodes;
      }
      else
      {
        console.出了什么问题! Please try again.' );
      }
    } )
  }
}

当用户访问 posts 页面,此代码被触发并向后端发送HTTP请求. 该请求利用GraphQL模式从WordPress数据库中获取最新的帖子.

接下来,要显示获取的帖子,请替换 src/app/posts/posts.component.html file with:

List Of Posts

  • {{post['title']}}

    View Post
  • 将以下CSS添加到 app/src/posts/posts.component.css file to provide the posts 页面与极简主义的外观:

    .content {
      width: 900px;
      margin: 0 auto;
    }
    h2.title {
      text-align: center;
    }
    li.post {
      list-style: none;
      text-align: center;
      flex: 0 0 28.333333%;
      margin-bottom: 15px;
    }
    img {
      max-width: 100%;
    }
    div#data {
      display: flex;
      flex-direction: row;
      justify-content:中心;
      gap: 5%;
      flex-wrap: wrap;
    }
    

    The Post Page

    相同的过程准备 post page. 的内容 src/app/post/post.component.ts file with:

    从“@angular/core”中导入{Component};
    从“@angular/common/http”中导入{HttpClient};
    从“@angular/router”中导入{ActivatedRoute};
    
    @Component( {
      selector: 'app-post',
      templateUrl: './post.component.html',
      styleUrls: ['./post.component.css']
    } )
    
    导出类PostComponent
    {
      post = {
        title : '',
        content : '',
      };
    
      构造函数(私有路由ActivatedRoute,私有http: HttpClient) {}
    
      Async send_graphql_request(query: string)
      { 
        Const response = await this.http.post( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, {} ).toPromise()
    
        return response;
      }
    
      ngOnInit()
      {
        const post_id = this.route.snapshot.paramMap.get( 'id' );
        
        this.send_graphql_request(
          `query GetPostsQuery {
            post(id: "${post_id}", idType: DATABASE_ID) {
              content
              title
            }
          }`
        )
        .then( response =>
        {
          if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
          {
            this.post = response.data.post;
          }
          else
          {
            console.出了什么问题! Please try again.' );
          }
        } )
      }
    }
    

    现在,要显示从 post,替换的内容 src/app/post/post.component.html file with:

    {{post.title}}

    最后,将以下CSS添加到 app/src/post/post.component.css file:

    .content {
      width: 900px;
      margin: 0 auto;
    }
    h2.title {
      text-align: center;
    }
    

    这些CSS规则将给出 post 和它的配偶有着相同的外观和感觉.

    Progress Check

    你已经为应用设置了基本元素,并建立了应用的Angular前端和WordPress后端之间通信所需的核心基础设施. 在浏览器中,测试应用程序示例内容的可见性.

    The posts page.
    一个Posts页面的示例

    The post page.
    一个Post页面的例子

    步骤3:添加身份验证

    添加身份验证允许限制 post 页面只能由授权用户查看. 要实现这一点,请添加 register page and a login page to the app.

    The Registration Page

    Create the Page

    使用终端应用重新访问Angular的根目录,然后输入:

    Ng生成组件寄存器
    

    这将创建一个名为 register.

    要支持HTML表单输入字段作为Angular的输入,请导入Angular的 FormsModule into the src/app/app.module.ts file. 将现有的文件内容替换为:

    从“@angular/core”中导入{NgModule};
    从“@angular/platform-browser”中导入{BrowserModule};
    从“@angular/common/http”中导入{HttpClientModule};
    导入{AppRoutingModule}./app-routing.module';
    import {PostComponent} from `./post/post.component';
    导入{PostsComponent}./posts/posts.component';
    导入{AppComponent}./app.component';
    import {RegisterComponent} from./register/register.component';
    import { FormsModule } from '@angular/forms'; //<----- New line added.
    
    @NgModule( {
      declarations: [
        AppComponent,
        PostComponent,
        PostsComponent,
        RegisterComponent,
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        AppRoutingModule,
        FormsModule //<----- New line added.
      ],
      providers: [],
      bootstrap: [AppComponent]
    } )
    
    导出类AppModule {}
    

    添加了内联注释以查明对代码所做的更改.

    Add a Route

    Now, to create the register 路由,替换 src/app/app-routing.module.ts file with:

    从“@angular/core”中导入{NgModule};
    从“@angular/router”中导入{RouterModule, Routes};
    import {PostComponent} from `./post/post.component';
    导入{PostsComponent}./posts/posts.component';
    import {RegisterComponent} from./register/register.component'; //<----- New line added.
    
    const routes: Routes = [
      {path: 'post/:id', component: PostComponent},
      {path: 'posts', component: PostsComponent},
      {path: 'register', component: RegisterComponent}, //<----- New line added.
    ];
    
    @NgModule( {
      imports: [ RouterModule.forRoot( routes ) ],
      exports: [RouterModule]
    } )
    
    导出类AppRoutingModule {}
    

    With the route added, 现在是时候配置应用程序来验证新用户的凭据并完成他们的注册. 的内容 src / app /注册/登记.component.ts file with:

    从“@angular/core”中导入{Component};
    从“@angular/ Router”中导入{Router};
    从“@angular/common/http”中导入{HttpClient};
    
    @Component( {
      选择器:“app-register”,
      templateUrl: './register.component.html',
      styleUrls: ['./register.component.css']
    } )
    
    导出类RegisterComponent
    {
      构造函数(public router: router, private http: HttpClient) {}
      
      username = '';
      
      email = '';
      
      password = '';
      
      error_message = '';
    
      Async send_graphql_request(query: string)
      {    
        Const response = await this.http.post( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()
    
        return response;
      }
      
      register()
      {
        document.getElementsByTagName('button')[0].setAttribute('disabled', 'disabled');
        
        document.getElementsByTagName('button')[0].innerHTML = 'Loading';
    
        this.send_graphql_request(
          ' RegisterMutation {
            registerUser(输入:{用户名:"${这个.用户名}”,电子邮件:“${this.,密码:“${this .}”.password}"}) {
              user {
                databaseId
              }
            }
          }`
        )
        .then( response =>
        {        
            if( typeof response.errors == 'undefined' && typeof response.data.registerUser.user.databaseId !== 'undefined' )
            {
              this.router.navigate( ['/login'] );
            }
            else
            {
              this.error_message = this.decodeHTMLEntities(响应.errors[0].message );
            }
    
    	  document.getElementsByTagName('button')[0].innerHTML = 'Register';
           document.getElementsByTagName('button')[0].removeAttribute('disabled');
        } )
      }
    
      decodeHTMLEntities(文本:字符串)
      {
        const entities = [
          ['amp', '&'],
          ['apos', '\''],
          ['#x27', '\''],
          ['#x2F', '/'],
          ['#39', '\''],
          ['#47', '/'],
          ['lt', '<'],
          ['gt', '>'],
          ['nbsp', ' '],
          ['quot', '"']
        ];
    
        对于(let I = 0, Max = entities.length; i < max; ++i )
          text = text.replace( new RegExp( '&' + entities[i][0] + ';', 'g'), entities[i][1]);
        
        return text;
      }
    }
    

    The register() 方法将新用户的凭据发送到应用程序的GraphQL API进行验证. 如果注册成功, the new user is created, API返回一个JSON响应,其中包含新创建的用户ID. 否则,将根据需要提示错误消息.

    Add Content

    要向页面添加用户注册表单,请替换 src / app /注册/登记.component.html file with:

    Register

    让我们对登录页面重复这些步骤.

    The Login Page

    Create the Page

    在终端应用中,重新访问Angular的根目录并输入:

    Ng生成组件登录
    

    的内容来创建登录路由 src/app/app-routing.module.ts file with:

    从“@angular/core”中导入{NgModule};
    从“@angular/router”中导入{RouterModule, Routes};
    import {PostComponent} from `./post/post.component';
    导入{PostsComponent}./posts/posts.component';
    import {RegisterComponent} from./register/register.component';
    导入{LoginComponent}./login/login.component'; //<----- New line added.
    
    const routes: Routes = [
      {path: 'post/:id', component: PostComponent},
      {path: 'posts', component: PostsComponent},
      {path: 'register', component: RegisterComponent},
      {path: 'login',组件:LoginComponent}, //<----- New line added.
    ];
    
    @NgModule( {
      imports: [ RouterModule.forRoot( routes ) ],
      exports: [RouterModule]
    } )
    
    导出类AppRoutingModule {}
    

    要设置应用程序来验证用户的凭据,请替换 src/app/login/login.component.ts file with:

    从“@angular/core”中导入{Component};
    从“@angular/ Router”中导入{Router};
    从“@angular/common/http”中导入{HttpClient};
    
    @Component( {
      selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.css']
    } )
    
    导出LoginComponent类
    {
      构造函数(public router: router, private http: HttpClient) {}
      
      username = '';
      
      password = '';
    
      error_message= '';
    
      Async send_graphql_request(query: string)
      {    
        Const response = await this.http.post( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()
    
        return response;
      }
      
      login()
      {
        document.getElementsByTagName('button')[0].setAttribute('disabled', 'disabled');
        
        document.getElementsByTagName('button')[0].innerHTML = 'Loading';
    
        this.send_graphql_request(
          LoginMutation {
            登录(输入:{用户名:"${this.用户名}",密码:"${this.password}"}) {
              authToken
            }
          }`
        )
        .then( response =>
        {        
            if( typeof response.errors == 'undefined' && typeof response.data.login.authToken !== 'undefined' )
            {
              localStorage.settitem ('auth_token', JSON.stringify( response.data.login.authToken ) );
    
              this.router.navigate( ['/posts'] );
            }
            else
            {
              this.error_message = this.decodeHTMLEntities(响应.errors[0].message );
            }
    
    	   document.getElementsByTagName('button')[0].innerHTML = 'Login';
    
            document.getElementsByTagName('button')[0].removeAttribute('disabled');
        } )
      }
    
      decodeHTMLEntities(文本:字符串)
      {
        var entities = [
          ['amp', '&'],
          ['apos', '\''],
          ['#x27', '\''],
          ['#x2F', '/'],
          ['#39', '\''],
          ['#47', '/'],
          ['lt', '<'],
          ['gt', '>'],
          ['nbsp', ' '],
          ['quot', '"']
        ];
    
        对于(var I = 0, Max = entities.length; i < max; ++i )
          text = text.replace( new RegExp( '&' + entities[i][0] + ';', 'g'), entities[i][1]);
        
        return text;
      }
    }
    

    的内容 src/app/login/login.component.html file with:

    Login to your account

    此代码片段将一个登录表单添加到带有用户凭据输入的页面. 类似于应用程序注册页面的设置方式, 这里添加的代码将现有用户的凭据发送到应用程序的GraphQL API进行验证. 如果凭证正确,则API返回JWT,并将其保存在浏览器的JWT中 localStorage for later use. 如果用户的凭据无效或JWT已过期, 必要时,错误消息会引导他们.

    Progress Check

    要测试身份验证,请注册为新用户并登录到应用程序. 然后,要注销,从浏览器中删除令牌 localStorage. 你的结果应该类似于下面的截图:

    The registration page.
    注册页面的一个示例

    The login page.
    登录页面示例

    带有错误消息的登录页面.
    凭据不正确的登录页面示例

    步骤4:实施限制

    启动并运行身份验证功能后,下一个任务是限制对 post 路由,只允许登录用户.

    创建和设置守卫和服务

    在终端应用中,重新访问Angular的根目录并输入:

    ng generate service auth && ng generate guard auth
    

    系统将提示您一个要实现的接口列表. Choose CanActivate 建立一个守卫,通过服务确认用户的身份验证, 也是在此步骤中创建的.

    接下来,设置您的守卫和服务来管理身份验证. 的内容 src/app/auth.service.ts file with:

    从“@angular/core”中导入{Injectable};
    从“@angular/ Router”中导入{Router};
    
    @Injectable( {
      providedIn: 'root'
    } )
    
    导出类AuthService
    {
      router : any;
      
      构造函数(私有路由:Router)
      {
        this.router = route
      }
    
      loggedIn()
      {
        if( localStorage.getItem( 'auth_token' ) != null ) return true;
    
        this.router.navigate( ['/login'] ); return false;
      }
    }
    

    有了这段代码,管理身份验证的服务设置就完成了. 如果存在JWT,服务将向守卫发送肯定响应. Otherwise, it returns a false 响应,指示用户未登录.

    To restrict the post 根据从服务接收到的路由信息,替换 src/app/auth.guard.ts file with:

    从“@angular/router”中导入{CanActivateFn};
    导入{AuthService}./auth.service';
    从“@angular/core”中导入{inject};
    
    export const authGuard: CanActivateFn = ( route, state ) =>
    {
      //使用依赖注入来获取AuthService的实例.
      const authService = inject(authService);
    
      //返回用户是否使用AuthService登录.
      return authService.loggedIn();
    };
    

    Now the post 页面是受限的,只允许登录的用户.

    限制发布页面的路由

    To extend the post 让我们实现一个特定于路由的限制. 的内容 src/app/app-routing.module.ts file with:

    从“@angular/core”中导入{NgModule};
    从“@angular/router”中导入{RouterModule、Routes、CanActivate};
    import {PostComponent} from `./post/post.component';
    导入{PostsComponent}./posts/posts.component';
    导入{LoginComponent}./login/login.component';
    import {RegisterComponent} from./register/register.component';
    导入{authGuard}./auth.guard'; //<----- New line added.
    
    const routes: Routes = [
      { path: 'post/:id', component: PostComponent, canActivate: [ authGuard ] }, //<----- New code added.
      {path: 'posts', component: PostsComponent},
      {path: 'register', component: RegisterComponent},
      {path: 'login',组件:LoginComponent},
    ];
    
    @NgModule( {
      imports: [ RouterModule.forRoot( routes ) ],
      exports: [RouterModule]
    } )
    
    导出类AppRoutingModule {}
    

    更改代码后, post page的路由现在使用Angular的 canActivate 方法将该页仅提供给经过身份验证的用户.

    Verify the JWT

    现在可以验证保存在访问用户浏览器中的JWT了. 具体来说,您将实时检查JWT是否未过期且有效. 的内容 src/app/post/post.component.ts file with:

    从“@angular/core”中导入{Component};
    从“@angular/common/http”中导入{HttpClient};
    从“@angular/router”中导入{ActivatedRoute};
    
    @Component( {
      selector: 'app-post',
      templateUrl: './post.component.html',
      styleUrls: ['./post.component.css']
    } )
    
    导出类PostComponent
    {
      post = {
        title : '',
        content : '',
      };
    
      构造函数(私有路由ActivatedRoute,私有http: HttpClient) {}
    
      Async send_graphql_request(query: string)
      {
        
        let headers = {};
        
        // New code begins here.
        const token = localStorage.getItem('auth_token');
        
        if( token !== null )
        {
          const parsedToken = JSON.parse( token );
    
          if( parsedToken )
          {
            headers = {'Authorization': 'Bearer ' + parsedToken};
          }
        }
        // New code ends here.
        
        Const response = await this.http.post( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { headers } ).toPromise()
    
        return response;
      }
    
      ngOnInit()
      {
        const post_id = this.route.snapshot.paramMap.get( 'id' );
        
        this.send_graphql_request(
          `query GetPostsQuery {
            post(id: "${post_id}", idType: DATABASE_ID) {
              content
              title
            }
          }`
        )
        .then( response =>
        {
          if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
          {
            this.post = response.data.post;
          }
          else
          {
            console.出了什么问题! Please try again.' );
          }
        } )
      }
    }
    

    此代码将保存的JWT注入为 承载授权头 的用户发出的每个HTTP请求中 post page. 为了强调代码先前迭代的变化,新代码由注释来表示.

    最终输出:实现动态和安全的用户体验

    要确认限制是否正常工作,请确保您未登录并访问 posts page. 接下来,尝试访问 post page. 您应该被重定向到登录页面. 中查看获取的内容 post page. 如果应用程序按预期工作, 您已经有效地完成了本教程,并开发了一个解耦的, protected SPA.

    In this digital age, 提供动态和安全的用户体验是一种期望, not an enhancement. 本教程中探讨的概念和方法可以应用到您的下一个解耦项目中,以实现可伸缩性,同时为开发人员提供设计和交付有效网站的灵活性.


    Toptal工程博客的编辑团队向 Branko Radulovic 查看本文中提供的代码示例和其他技术内容.

    了解基本知识

    • 如何在Angular中存储jwt?

      Angular能够访问现代浏览器的localStorage api来存储和检索JSON web令牌.

    • GraphQL和API网关之间的区别是什么?

      GraphQL是一种查询语言和运行时,它提供客户机通过单个端点访问服务器数据. API网关是一个服务器端组件,作为应用程序客户端与各种后端服务或API之间的入口点.

    • 如何在WordPress中使用Angular?

      当WordPress被实现为无头CMS来处理后端功能时, Angular作为它的前端框架,使用WordPress REST和/或GraphQL中的api.

    • GraphQL有什么好处?

      GraphQL提供高效和精确的数据检索, 从单个端点实现客户端对定制数据的请求. GraphQL还减少了网络请求量,并简化了版本控制.

    就这一主题咨询作者或专家.
    Schedule a call
    萨贾德·侯赛因·萨格尔的头像
    Sajjad Hossain Sagor

    Located in 达卡,达卡区,孟加拉国

    Member since October 26, 2022

    About the author

    Sajjad是一个专门开发主题的WordPress开发者, plugins, and WooCommerce add-ons. 他曾为世界各地的公司领导高预算项目,并为一个拥有200多人的WordPress网站建立了后端,000活跃用户和数百万YouTube订阅者. 他也是WordPress社区的核心贡献者.

    Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

    Previous Role

    全栈Web开发人员

    Years of Experience

    7

    世界级的文章,每周发一次.

    订阅意味着同意我们的 privacy policy

    世界级的文章,每周发一次.

    订阅意味着同意我们的 privacy policy

    Toptal Developers

    Join the Toptal® community.